diff --git a/Cargo.lock b/Cargo.lock index 85698d8bb..06256acf2 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -217,6 +217,20 @@ name = "bytemuck" version = "1.21.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ef657dfab802224e671f5818e9a4935f9b1957ed18e58292690cc39e7a4092a3" +dependencies = [ + "bytemuck_derive", +] + +[[package]] +name = "bytemuck_derive" +version = "1.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3fa76293b4f7bb636ab88fd78228235b5248b4d05cc589aed610f954af5d7c7a" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] [[package]] name = "byteorder" @@ -749,6 +763,15 @@ version = "0.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "98de4bbd547a563b716d8dfa9aad1cb19bfab00f4fa09a6a4ed21dbcf44ce9c4" +[[package]] +name = "float-cmp" +version = "0.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b09cf3155332e944990140d967ff5eceb70df778b34f77d8075db46e4704e6d8" +dependencies = [ + "num-traits", +] + [[package]] name = "fnv" version = "1.0.7" @@ -761,6 +784,15 @@ version = "0.1.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a0d2fde1f7b3d48b8395d5f2de76c18a528bd6a9cdde438df747bfcba3e05d6f" +[[package]] +name = "font-types" +version = "0.7.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b3971f9a5ca983419cdc386941ba3b9e1feba01a0ab888adf78739feb2798492" +dependencies = [ + "bytemuck", +] + [[package]] name = "fontconfig-parser" version = "0.5.7" @@ -781,7 +813,33 @@ dependencies = [ "memmap2", "slotmap", "tinyvec", - "ttf-parser", + "ttf-parser 0.24.1", +] + +[[package]] +name = "fontdb" +version = "0.22.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a3a6f9af55fb97ad673fb7a69533eb2f967648a06fa21f8c9bb2cd6d33975716" +dependencies = [ + "fontconfig-parser", + "log", + "memmap2", + "slotmap", + "tinyvec", + "ttf-parser 0.24.1", +] + +[[package]] +name = "fontdb" +version = "0.23.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "457e789b3d1202543297a350643cf459f836cade38934e7a4cf6a39e7cde2905" +dependencies = [ + "log", + "slotmap", + "tinyvec", + "ttf-parser 0.25.1", ] [[package]] @@ -1183,6 +1241,16 @@ dependencies = [ "quick-error", ] +[[package]] +name = "image-webp" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b77d01e822461baa8409e156015a1d91735549f0f2c17691bd2d996bef238f7f" +dependencies = [ + "byteorder-lite", + "quick-error", +] + [[package]] name = "imagesize" version = "0.13.0" @@ -1310,6 +1378,36 @@ dependencies = [ "libc", ] +[[package]] +name = "krilla" +version = "0.3.0" +source = "git+https://github.com/LaurenzV/krilla#478e4639001bc96118a6f651f06d42d37d0be7a8" +dependencies = [ + "base64", + "bumpalo", + "flate2", + "float-cmp 0.10.0", + "fontdb 0.22.0", + "gif", + "image-webp 0.1.3", + "imagesize", + "miniz_oxide", + "once_cell", + "pdf-writer", + "resvg 0.44.0", + "rustybuzz 0.18.0", + "siphasher", + "skrifa", + "subsetter", + "tiny-skia", + "tiny-skia-path", + "usvg 0.44.0", + "xmp-writer", + "yoke", + "zune-jpeg", + "zune-png", +] + [[package]] name = "kurbo" version = "0.11.1" @@ -1738,9 +1836,8 @@ checksum = "df94ce210e5bc13cb6651479fa48d14f601d9858cfe0467f43ae157023b938d3" [[package]] name = "pdf-writer" -version = "0.12.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5df03c7d216de06f93f398ef06f1385a60f2c597bb96f8195c8d98e08a26b1d5" +version = "0.12.0" +source = "git+https://github.com/LaurenzV/pdf-writer?rev=f95a19c#f95a19c07a1b3e3ee021c1199e91f19badb57d46" dependencies = [ "bitflags 2.8.0", "itoa", @@ -1808,7 +1905,7 @@ version = "0.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d15afa937836bf3d876f5a04ce28810c06045857bf46c3d0d31073b8aada5494" dependencies = [ - "ttf-parser", + "ttf-parser 0.24.1", ] [[package]] @@ -1997,6 +2094,16 @@ dependencies = [ "crossbeam-utils", ] +[[package]] +name = "read-fonts" +version = "0.22.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "69aacb76b5c29acfb7f90155d39759a29496aebb49395830e928a9703d2eec2f" +dependencies = [ + "bytemuck", + "font-types", +] + [[package]] name = "redox_syscall" version = "0.5.8" @@ -2048,18 +2155,35 @@ checksum = "2b15c43186be67a4fd63bee50d0303afffcef381492ebe2c5d87f324e1b8815c" [[package]] name = "resvg" -version = "0.43.0" +version = "0.44.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c7314563c59c7ce31c18e23ad3dd092c37b928a0fa4e1c0a1a6504351ab411d1" +checksum = "4a325d5e8d1cebddd070b13f44cec8071594ab67d1012797c121f27a669b7958" dependencies = [ "gif", - "image-webp", + "image-webp 0.1.3", "log", "pico-args", "rgb", "svgtypes", "tiny-skia", - "usvg", + "usvg 0.44.0", + "zune-jpeg", +] + +[[package]] +name = "resvg" +version = "0.45.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dd43d1c474e9dadf09a8fdf22d713ba668b499b5117b9b9079500224e26b5b29" +dependencies = [ + "gif", + "image-webp 0.2.1", + "log", + "pico-args", + "rgb", + "svgtypes", + "tiny-skia", + "usvg 0.45.0", "zune-jpeg", ] @@ -2130,9 +2254,27 @@ dependencies = [ "core_maths", "log", "smallvec", - "ttf-parser", - "unicode-bidi-mirroring", - "unicode-ccc", + "ttf-parser 0.24.1", + "unicode-bidi-mirroring 0.3.0", + "unicode-ccc 0.3.0", + "unicode-properties", + "unicode-script", +] + +[[package]] +name = "rustybuzz" +version = "0.20.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fd3c7c96f8a08ee34eff8857b11b49b07d71d1c3f4e88f8a88d4c9e9f90b1702" +dependencies = [ + "bitflags 2.8.0", + "bytemuck", + "core_maths", + "log", + "smallvec", + "ttf-parser 0.25.1", + "unicode-bidi-mirroring 0.4.0", + "unicode-ccc 0.4.0", "unicode-properties", "unicode-script", ] @@ -2315,6 +2457,16 @@ version = "1.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "56199f7ddabf13fe5074ce809e7d3f42b42ae711800501b5b16ea82ad029c39d" +[[package]] +name = "skrifa" +version = "0.22.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e1c44ad1f6c5bdd4eefed8326711b7dbda9ea45dfd36068c427d332aa382cbe" +dependencies = [ + "bytemuck", + "read-fonts", +] + [[package]] name = "slotmap" version = "1.0.7" @@ -2361,7 +2513,7 @@ version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6637bab7722d379c8b41ba849228d680cc12d0a45ba1fa2b48f2a30577a06731" dependencies = [ - "float-cmp", + "float-cmp 0.9.0", ] [[package]] @@ -2405,28 +2557,7 @@ dependencies = [ [[package]] name = "subsetter" version = "0.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "74f98178f34057d4d4de93d68104007c6dea4dfac930204a69ab4622daefa648" - -[[package]] -name = "svg2pdf" -version = "0.12.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5014c9dadcf318fb7ef8c16438e95abcc9de1ae24d60d5bccc64c55100c50364" -dependencies = [ - "fontdb", - "image", - "log", - "miniz_oxide", - "once_cell", - "pdf-writer", - "resvg", - "siphasher", - "subsetter", - "tiny-skia", - "ttf-parser", - "usvg", -] +source = "git+https://github.com/typst/subsetter?rev=172416a#172416a806246e6e9010d400d5690ca7a026e53d" [[package]] name = "svgtypes" @@ -2716,6 +2847,15 @@ dependencies = [ "core_maths", ] +[[package]] +name = "ttf-parser" +version = "0.25.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d2df906b07856748fa3f6e0ad0cbaa047052d4a7dd609e231c4f72cee8c36f31" +dependencies = [ + "core_maths", +] + [[package]] name = "two-face" version = "0.4.3" @@ -2897,7 +3037,7 @@ dependencies = [ "ecow", "env_proxy", "flate2", - "fontdb", + "fontdb 0.21.0", "native-tls", "once_cell", "openssl", @@ -2927,9 +3067,9 @@ dependencies = [ "icu_provider_blob", "icu_segmenter", "kurbo", - "rustybuzz", + "rustybuzz 0.20.1", "smallvec", - "ttf-parser", + "ttf-parser 0.25.1", "typst-assets", "typst-library", "typst-macros", @@ -2956,7 +3096,7 @@ dependencies = [ "csv", "ecow", "flate2", - "fontdb", + "fontdb 0.21.0", "hayagriva", "icu_properties", "icu_provider", @@ -2976,7 +3116,7 @@ dependencies = [ "regex-syntax", "roxmltree", "rust_decimal", - "rustybuzz", + "rustybuzz 0.20.1", "serde", "serde_json", "serde_yaml 0.9.34+deprecated", @@ -2985,7 +3125,7 @@ dependencies = [ "syntect", "time", "toml", - "ttf-parser", + "ttf-parser 0.25.1", "two-face", "typed-arena", "typst-assets", @@ -2998,7 +3138,7 @@ dependencies = [ "unicode-normalization", "unicode-segmentation", "unscanny", - "usvg", + "usvg 0.45.0", "wasmi", "xmlwriter", ] @@ -3017,26 +3157,17 @@ dependencies = [ name = "typst-pdf" version = "0.13.1" dependencies = [ - "arrayvec", - "base64", "bytemuck", "comemo", "ecow", "image", - "indexmap 2.7.1", - "miniz_oxide", - "pdf-writer", - "serde", - "subsetter", - "svg2pdf", - "ttf-parser", + "krilla", "typst-assets", "typst-library", "typst-macros", "typst-syntax", "typst-timing", "typst-utils", - "xmp-writer", ] [[package]] @@ -3063,9 +3194,9 @@ dependencies = [ "comemo", "image", "pixglyph", - "resvg", + "resvg 0.45.0", "tiny-skia", - "ttf-parser", + "ttf-parser 0.25.1", "typst-library", "typst-macros", "typst-timing", @@ -3080,7 +3211,7 @@ dependencies = [ "ecow", "flate2", "image", - "ttf-parser", + "ttf-parser 0.25.1", "typst-library", "typst-macros", "typst-timing", @@ -3189,12 +3320,24 @@ version = "0.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "64af057ad7466495ca113126be61838d8af947f41d93a949980b2389a118082f" +[[package]] +name = "unicode-bidi-mirroring" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5dfa6e8c60bb66d49db113e0125ee8711b7647b5579dc7f5f19c42357ed039fe" + [[package]] name = "unicode-ccc" version = "0.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "260bc6647b3893a9a90668360803a15f96b85a5257b1c3a0c3daf6ae2496de42" +[[package]] +name = "unicode-ccc" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ce61d488bcdc9bc8b5d1772c404828b17fc481c0a582b5581e95fb233aef503e" + [[package]] name = "unicode-ident" version = "1.0.16" @@ -3288,20 +3431,47 @@ dependencies = [ [[package]] name = "usvg" -version = "0.43.0" +version = "0.44.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6803057b5cbb426e9fb8ce2216f3a9b4ca1dd2c705ba3cbebc13006e437735fd" +checksum = "7447e703d7223b067607655e625e0dbca80822880248937da65966194c4864e6" dependencies = [ "base64", "data-url", "flate2", - "fontdb", + "fontdb 0.22.0", "imagesize", "kurbo", "log", "pico-args", "roxmltree", - "rustybuzz", + "rustybuzz 0.18.0", + "simplecss", + "siphasher", + "strict-num", + "svgtypes", + "tiny-skia-path", + "unicode-bidi", + "unicode-script", + "unicode-vo", + "xmlwriter", +] + +[[package]] +name = "usvg" +version = "0.45.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2ac8e0e3e4696253dc06167990b3fe9a2668ab66270adf949a464db4088cb354" +dependencies = [ + "base64", + "data-url", + "flate2", + "fontdb 0.23.0", + "imagesize", + "kurbo", + "log", + "pico-args", + "roxmltree", + "rustybuzz 0.20.1", "simplecss", "siphasher", "strict-num", @@ -3661,8 +3831,7 @@ checksum = "ec7a2a501ed189703dba8b08142f057e887dfc4b2cc4db2d343ac6376ba3e0b9" [[package]] name = "xmp-writer" version = "0.3.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7eb5954c9ca6dcc869e98d3e42760ed9dab08f3e70212b31d7ab8ae7f3b7a487" +source = "git+https://github.com/LaurenzV/xmp-writer?rev=a1cbb887#a1cbb887a84376fea4d7590d41c194a332840549" [[package]] name = "xz2" @@ -3830,6 +3999,15 @@ version = "0.4.12" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3f423a2c17029964870cfaabb1f13dfab7d092a62a29a89264f4d36990ca414a" +[[package]] +name = "zune-inflate" +version = "0.2.54" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "73ab332fe2f6680068f3582b16a24f90ad7096d5d39b974d1c0aff0125116f02" +dependencies = [ + "simd-adler32", +] + [[package]] name = "zune-jpeg" version = "0.4.14" @@ -3838,3 +4016,13 @@ checksum = "99a5bab8d7dedf81405c4bb1f2b83ea057643d9cb28778cea9eecddeedd2e028" dependencies = [ "zune-core", ] + +[[package]] +name = "zune-png" +version = "0.4.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7d29c085769c6f29effea890f093120ac019375fdc789d2a496ba8ba96c77509" +dependencies = [ + "zune-core", + "zune-inflate", +] diff --git a/Cargo.toml b/Cargo.toml index 0bfd92821..b38f6bc57 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -70,6 +70,7 @@ if_chain = "1" image = { version = "0.25.5", default-features = false, features = ["png", "jpeg", "gif"] } indexmap = { version = "2", features = ["serde"] } kamadak-exif = "0.6" +krilla = { git = "https://github.com/LaurenzV/krilla" } kurbo = "0.11" libfuzzer-sys = "0.4" lipsum = "0.9" @@ -96,10 +97,10 @@ quote = "1" rayon = "1.7.0" regex = "1" regex-syntax = "0.8" -resvg = { version = "0.43", default-features = false, features = ["raster-images"] } +resvg = { version = "0.45.0", default-features = false, features = ["raster-images"] } roxmltree = "0.20" rust_decimal = { version = "1.36.0", default-features = false, features = ["maths"] } -rustybuzz = "0.18" +rustybuzz = "0.20.1" same-file = "1" self-replace = "1.3.7" semver = "1" @@ -122,7 +123,7 @@ time = { version = "0.3.20", features = ["formatting", "macros", "parsing"] } tiny_http = "0.12" tiny-skia = "0.11" toml = { version = "0.8", default-features = false, features = ["parse", "display"] } -ttf-parser = "0.24.1" +ttf-parser = "0.25.1" two-face = { version = "0.4.3", default-features = false, features = ["syntect-fancy"] } typed-arena = "2" unicode-bidi = "0.3.18" @@ -133,7 +134,7 @@ unicode-normalization = "0.1.24" unicode-segmentation = "1" unscanny = "0.1" ureq = { version = "2", default-features = false, features = ["native-tls", "gzip", "json"] } -usvg = { version = "0.43", default-features = false, features = ["text"] } +usvg = { version = "0.45.0", default-features = false, features = ["text"] } walkdir = "2" wasmi = "0.40.0" web-sys = "0.3" diff --git a/crates/typst-library/src/visualize/image/raster.rs b/crates/typst-library/src/visualize/image/raster.rs index 0883fe71d..9b4a310b1 100644 --- a/crates/typst-library/src/visualize/image/raster.rs +++ b/crates/typst-library/src/visualize/image/raster.rs @@ -22,7 +22,7 @@ pub struct RasterImage(Arc); struct Repr { data: Bytes, format: RasterFormat, - dynamic: image::DynamicImage, + dynamic: Arc, icc: Option, dpi: Option, } @@ -136,7 +136,7 @@ impl RasterImage { } }; - Ok(Self(Arc::new(Repr { data, format, dynamic, icc, dpi }))) + Ok(Self(Arc::new(Repr { data, format, dynamic: Arc::new(dynamic), icc, dpi }))) } /// The raw image data. @@ -167,7 +167,7 @@ impl RasterImage { } /// Access the underlying dynamic image. - pub fn dynamic(&self) -> &image::DynamicImage { + pub fn dynamic(&self) -> &Arc { &self.0.dynamic } diff --git a/crates/typst-pdf/src/convert.rs b/crates/typst-pdf/src/convert.rs index eae186931..491c5f221 100644 --- a/crates/typst-pdf/src/convert.rs +++ b/crates/typst-pdf/src/convert.rs @@ -6,9 +6,8 @@ use krilla::error::KrillaError; use krilla::page::PageLabel; use krilla::path::PathBuilder; use krilla::surface::Surface; -use krilla::validation::ValidationError; -use krilla::version::PdfVersion; use krilla::{Document, PageSettings, SerializeSettings}; +use krilla::configure::{Configuration, PdfVersion, ValidationError}; use typst_library::diag::{bail, SourceResult}; use typst_library::foundations::NativeElement; use typst_library::introspection::Location; @@ -34,7 +33,7 @@ pub fn convert( typst_document: &PagedDocument, options: &PdfOptions, ) -> SourceResult> { - let version = get_version(options)?; + let configuration = get_configuration(options)?; let settings = SerializeSettings { compress_content_streams: true, @@ -42,9 +41,8 @@ pub fn convert( ascii_compatible: false, xmp_metadata: true, cmyk_profile: None, - validator: options.validator, + configuration, enable_tagging: false, - pdf_version: version, }; let mut document = Document::new_with(settings); @@ -295,7 +293,7 @@ pub(crate) fn handle_group( fc.state_mut().pre_concat(group.transform); let clip_path = group - .clip_path + .clip .as_ref() .and_then(|p| { let mut builder = PathBuilder::new(); @@ -321,6 +319,8 @@ pub(crate) fn handle_group( /// Finish a krilla document and handle export errors. fn finish(document: Document, gc: GlobalContext) -> SourceResult> { + let validator: krilla::configure::Validator = gc.options.validator.into(); + match document.finish() { Ok(r) => Ok(r), Err(e) => match e { @@ -336,8 +336,9 @@ fn finish(document: Document, gc: GlobalContext) -> SourceResult> { // We can only produce 1 error, so just take the first one. let prefix = format!( "validated export for {} failed:", - gc.options.validator.as_str() + validator.as_str() ); + match &ve[0] { ValidationError::TooLongString => { bail!(Span::detached(), "{prefix} a PDF string longer \ @@ -407,6 +408,16 @@ fn finish(document: Document, gc: GlobalContext) -> SourceResult> { hint: "export using a different standard that supports transparency" ); } + ValidationError::ImageInterpolation => { + bail!(Span::detached(), "{prefix} document contains an image with smooth interpolation"; + hint: "such images are not supported in this export mode" + ); + } + ValidationError::EmbeddedFile(_) => { + bail!(Span::detached(), "{prefix} document contains an embedded file"; + hint: "embedded files are not supported in this export mode" + ); + } // The below errors cannot occur yet, only once Typst supports full PDF/A // and PDF/UA. @@ -423,16 +434,20 @@ fn finish(document: Document, gc: GlobalContext) -> SourceResult> { bail!(Span::detached(), "{prefix} missing document language"; hint: "set the language of the document"); } + // Needs to be set by typst-pdf. ValidationError::MissingHeadingTitle => { bail!(Span::detached(), "{prefix} missing heading title"; hint: "please report this as a bug"); } - // Needs to be set by typst-pdf. ValidationError::MissingDocumentOutline => { bail!(Span::detached(), "{prefix} missing document outline"; hint: "please report this as a bug"); } + ValidationError::MissingTagging => { + bail!(Span::detached(), "{prefix} missing document tags"; + hint: "please report this as a bug"); + } ValidationError::NoDocumentTitle => { bail!(Span::detached(), "{prefix} missing document title"; hint: "set the title of the document"); @@ -494,21 +509,31 @@ fn collect_named_destinations( locs_to_names } -fn get_version(options: &PdfOptions) -> SourceResult { - match options.pdf_version { - None => Ok(options.validator.recommended_version()), - Some(v) => { - if !options.validator.compatible_with_version(v) { - let v_string = v.as_str(); - let s_string = options.validator.as_str(); - let h_message = format!( - "export using {} instead", - options.validator.recommended_version().as_str() - ); - bail!(Span::detached(), "{v_string} is not compatible with standard {s_string}"; hint: "{h_message}"); - } else { - Ok(v) +fn get_configuration(options: &PdfOptions) -> SourceResult { + let config = match (options.pdf_version, options.validator) { + (None, None) => Configuration::new_with_version(PdfVersion::Pdf17), + (Some(pdf), None) => Configuration::new_with_version(pdf.into()), + (None, Some(v)) => Configuration::new_with_validator(v.into()), + (Some(pdf), Some(v)) => { + let pdf = pdf.into(); + let v = v.into(); + + match Configuration::new_with(v, pdf) { + Some(c) => c, + None => { + let pdf_string = pdf.as_str(); + let s_string = v.as_str(); + + let h_message = format!( + "export using {} instead", + v.recommended_version().as_str() + ); + + bail!(Span::detached(), "{pdf_string} is not compatible with standard {s_string}"; hint: "{h_message}"); + } } } - } + }; + + Ok(config) } diff --git a/crates/typst-pdf/src/embed.rs b/crates/typst-pdf/src/embed.rs deleted file mode 100644 index 597638f4b..000000000 --- a/crates/typst-pdf/src/embed.rs +++ /dev/null @@ -1,122 +0,0 @@ -use std::collections::BTreeMap; - -use ecow::EcoString; -use pdf_writer::types::AssociationKind; -use pdf_writer::{Filter, Finish, Name, Ref, Str, TextStr}; -use typst_library::diag::{bail, SourceResult}; -use typst_library::foundations::{NativeElement, Packed, StyleChain}; -use typst_library::pdf::{EmbedElem, EmbeddedFileRelationship}; - -use crate::catalog::{document_date, pdf_date}; -use crate::{deflate, NameExt, PdfChunk, StrExt, WithGlobalRefs}; - -/// 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)> { - 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::().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, -) -> SourceResult { - 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, - 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"); - } - file_spec.description(TextStr(description)); - } - - Ok(file_spec_dict_ref) -} diff --git a/crates/typst-pdf/src/image.rs b/crates/typst-pdf/src/image.rs index 608ac4036..33c456245 100644 --- a/crates/typst-pdf/src/image.rs +++ b/crates/typst-pdf/src/image.rs @@ -1,7 +1,7 @@ use std::hash::{Hash, Hasher}; use std::sync::{Arc, OnceLock}; -use image::{DynamicImage, GenericImageView, Rgba}; +use image::{DynamicImage, EncodableLayout, GenericImageView, Rgba}; use krilla::image::{BitsPerComponent, CustomImage, ImageColorspace}; use krilla::surface::Surface; use krilla::SvgSettings; @@ -137,7 +137,7 @@ impl CustomImage for PdfImage { | DynamicImage::ImageRgb8(_) | DynamicImage::ImageRgba8(_) ) { - self.raster.icc() + self.raster.icc().map(|b| b.as_bytes()) } else { // In all other cases, the dynamic will be converted into RGB8 or LUMA8, so the ICC // profile may become invalid, and thus we don't include it. diff --git a/crates/typst-pdf/src/lib.rs b/crates/typst-pdf/src/lib.rs index 575a8739e..1c1839583 100644 --- a/crates/typst-pdf/src/lib.rs +++ b/crates/typst-pdf/src/lib.rs @@ -15,9 +15,6 @@ use typst_library::diag::SourceResult; use typst_library::foundations::{Datetime, Smart}; use typst_library::layout::{PageRanges, PagedDocument}; -pub use ::krilla::validation::Validator; -pub use ::krilla::version::PdfVersion; - /// Export a document into a PDF file. /// /// Returns the raw bytes making up the PDF file. @@ -26,6 +23,72 @@ pub fn pdf(document: &PagedDocument, options: &PdfOptions) -> SourceResult for krilla::configure::PdfVersion { + fn from(value: PdfVersion) -> Self { + match value { + PdfVersion::Pdf14 => krilla::configure::PdfVersion::Pdf14, + PdfVersion::Pdf15 => krilla::configure::PdfVersion::Pdf15, + PdfVersion::Pdf16 => krilla::configure::PdfVersion::Pdf16, + PdfVersion::Pdf17 => krilla::configure::PdfVersion::Pdf17, + PdfVersion::Pdf20 => krilla::configure::PdfVersion::Pdf20, + } + } +} + +/// A validator for exporting PDF documents to a specific subset of PDF. +#[derive(Copy, Clone, Debug, PartialEq, Eq)] +pub enum Validator { + /// The validator for the PDF/A1-A standard. + A1_A, + /// The validator for the PDF/A1-B standard. + A1_B, + /// The validator for the PDF/A2-B standard. + A2_B, + /// The validator for the PDF/A2-U standard. + A2_U, + /// The validator for the PDF/A3-B standard. + A3_B, + /// The validator for the PDF/A3-U standard. + A3_U, + /// The validator for the PDF/A4 standard. + A4, + /// The validator for the PDF/A4f standard. + A4F, + /// The validator for the PDF/A4e standard. + A4E, +} + +impl From for krilla::configure::Validator { + fn from(value: Validator) -> Self { + match value { + Validator::A1_A => krilla::configure::Validator::A1_A, + Validator::A1_B => krilla::configure::Validator::A1_B, + Validator::A2_B => krilla::configure::Validator::A2_B, + Validator::A2_U => krilla::configure::Validator::A2_U, + Validator::A3_B => krilla::configure::Validator::A3_B, + Validator::A3_U => krilla::configure::Validator::A3_U, + Validator::A4 => krilla::configure::Validator::A4, + Validator::A4F => krilla::configure::Validator::A4F, + Validator::A4E => krilla::configure::Validator::A4E, + } + } +} + /// Settings for PDF export. #[derive(Debug, Default)] pub struct PdfOptions<'a> { @@ -50,7 +113,7 @@ pub struct PdfOptions<'a> { /// The version that should be used to export the PDF. pub pdf_version: Option, /// A standard the PDF should conform to. - pub validator: Validator, + pub validator: Option, } /// A timestamp with timezone information. diff --git a/crates/typst-pdf/src/util.rs b/crates/typst-pdf/src/util.rs index a98227dbd..9e160be7d 100644 --- a/crates/typst-pdf/src/util.rs +++ b/crates/typst-pdf/src/util.rs @@ -6,9 +6,7 @@ use krilla::path as kp; use krilla::path::PathBuilder; use typst_library::layout::{Abs, Point, Size, Transform}; use typst_library::text::Font; -use typst_library::visualize::{ - Color, ColorSpace, FillRule, LineCap, LineJoin, Path, PathItem, -}; +use typst_library::visualize::{Color, ColorSpace, Curve, CurveItem, FillRule, LineCap, LineJoin}; pub(crate) trait SizeExt { fn to_krilla(&self) -> kg::Size; @@ -118,12 +116,12 @@ pub(crate) fn display_font(font: &Font) -> String { } /// Build a typst path using a path builder. -pub(crate) fn convert_path(path: &Path, builder: &mut PathBuilder) { +pub(crate) fn convert_path(path: &Curve, builder: &mut PathBuilder) { for item in &path.0 { match item { - PathItem::MoveTo(p) => builder.move_to(p.x.to_f32(), p.y.to_f32()), - PathItem::LineTo(p) => builder.line_to(p.x.to_f32(), p.y.to_f32()), - PathItem::CubicTo(p1, p2, p3) => builder.cubic_to( + CurveItem::Move(p) => builder.move_to(p.x.to_f32(), p.y.to_f32()), + CurveItem::Line(p) => builder.line_to(p.x.to_f32(), p.y.to_f32()), + CurveItem::Cubic(p1, p2, p3) => builder.cubic_to( p1.x.to_f32(), p1.y.to_f32(), p2.x.to_f32(), @@ -131,7 +129,7 @@ pub(crate) fn convert_path(path: &Path, builder: &mut PathBuilder) { p3.x.to_f32(), p3.y.to_f32(), ), - PathItem::ClosePath => builder.close(), + CurveItem::Close => builder.close(), } } }