Compare commits

...

8 Commits

Author SHA1 Message Date
tinger
1a8797e6cc Use by-value builder API for Deprecation
This makes the API for `Binding` more consistent without requiring any
intermediate bindings for the `Deprecation`.
2025-07-23 20:12:36 +02:00
tinger
6eced92e7c Bundle deprecation message and version in DeprecationInfo
Because the increase in size of `Binding` seemed to create a performance
regression we'll try to reduce the size by putting the rarely used
deprecation on the heap. In fact, the struct is even smaller than before
now, because `deprecation` was previously a wide pointer.
2025-07-23 20:02:28 +02:00
tinger
c854ef8bdc Add 0.15.0 as removal version to deprecated bindings
Note that `path` was left out because it may not be removed, but
replaced by a path type constructor right away.
2025-07-23 20:02:28 +02:00
tinger
0511bef66b Add support for until to the deprecated macro
This allows easily annotating the `until` version in impl blocks
using the `#[scope]` macro.
2025-07-23 20:02:28 +02:00
tinger
00f979b351 Add version to Binding deprecation warnings
This allows displaying a hint in which the version a binding may be
removed in. It helps to signal how urgent a package update may be to
package authors. The version is also sent to the proprietary doc
generator.
2025-07-23 20:02:28 +02:00
Tobias Schmitz
7278d887cf
Fix bounding box computation for lines in curves (#6647)
Co-authored-by: Laurenz <laurmaedje@gmail.com>
2025-07-23 14:17:03 +00:00
Laurenz
78355421ad
Add pdf extension to image autocompletions (#6643) 2025-07-22 12:07:29 +00:00
Laurenz Stampfl
af2253ba16
Add support for PDF embedding (#6623)
Co-authored-by: Laurenz <laurmaedje@gmail.com>
2025-07-22 11:06:44 +00:00
42 changed files with 653 additions and 124 deletions

124
Cargo.lock generated
View File

@ -181,9 +181,9 @@ checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a"
[[package]] [[package]]
name = "bitflags" name = "bitflags"
version = "2.8.0" version = "2.9.1"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8f68f53c83ab957f72c32642f3868eec03eb974d1fb82e453128456482613d36" checksum = "1b8e56985ec62d17e9c1001dc89c88ecd7dc08e47eba5ec7c29c7b5eeecde967"
dependencies = [ dependencies = [
"serde", "serde",
] ]
@ -214,9 +214,9 @@ checksum = "64fa3c856b712db6612c019f14756e64e4bcea13337a6b33b696333a9eaa2d06"
[[package]] [[package]]
name = "bytemuck" name = "bytemuck"
version = "1.21.0" version = "1.23.1"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ef657dfab802224e671f5818e9a4935f9b1957ed18e58292690cc39e7a4092a3" checksum = "5c76a5792e44e4abe34d3abf15636779261d45a7450612059293d1d2cfc63422"
dependencies = [ dependencies = [
"bytemuck_derive", "bytemuck_derive",
] ]
@ -964,6 +964,69 @@ dependencies = [
"url", "url",
] ]
[[package]]
name = "hayro"
version = "0.1.0"
source = "git+https://github.com/LaurenzV/hayro?rev=e701f95#e701f9569157a2fe4ade68930dc9e9283782dcca"
dependencies = [
"bytemuck",
"hayro-interpret",
"image",
"kurbo",
"rustc-hash",
"smallvec",
]
[[package]]
name = "hayro-font"
version = "0.1.0"
source = "git+https://github.com/LaurenzV/hayro?rev=e701f95#e701f9569157a2fe4ade68930dc9e9283782dcca"
dependencies = [
"log",
"phf",
]
[[package]]
name = "hayro-interpret"
version = "0.1.0"
source = "git+https://github.com/LaurenzV/hayro?rev=e701f95#e701f9569157a2fe4ade68930dc9e9283782dcca"
dependencies = [
"bitflags 2.9.1",
"hayro-font",
"hayro-syntax",
"kurbo",
"log",
"phf",
"qcms",
"skrifa",
"smallvec",
"yoke 0.8.0",
]
[[package]]
name = "hayro-syntax"
version = "0.0.1"
source = "git+https://github.com/LaurenzV/hayro?rev=e701f95#e701f9569157a2fe4ade68930dc9e9283782dcca"
dependencies = [
"flate2",
"kurbo",
"log",
"rustc-hash",
"smallvec",
"zune-jpeg",
]
[[package]]
name = "hayro-write"
version = "0.1.0"
source = "git+https://github.com/LaurenzV/hayro?rev=e701f95#e701f9569157a2fe4ade68930dc9e9283782dcca"
dependencies = [
"flate2",
"hayro-syntax",
"log",
"pdf-writer",
]
[[package]] [[package]]
name = "heck" name = "heck"
version = "0.5.0" version = "0.5.0"
@ -1200,9 +1263,9 @@ dependencies = [
[[package]] [[package]]
name = "image" name = "image"
version = "0.25.5" version = "0.25.6"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "cd6f44aed642f18953a158afeb30206f4d50da59fbc66ecb53c66488de73563b" checksum = "db35664ce6b9810857a38a906215e75a9c879f0696556a39f59c62829710251a"
dependencies = [ dependencies = [
"bytemuck", "bytemuck",
"byteorder-lite", "byteorder-lite",
@ -1265,7 +1328,7 @@ version = "0.11.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f37dccff2791ab604f9babef0ba14fbe0be30bd368dc541e2b08d07c8aa908f3" checksum = "f37dccff2791ab604f9babef0ba14fbe0be30bd368dc541e2b08d07c8aa908f3"
dependencies = [ dependencies = [
"bitflags 2.8.0", "bitflags 2.9.1",
"inotify-sys", "inotify-sys",
"libc", "libc",
] ]
@ -1361,7 +1424,7 @@ dependencies = [
[[package]] [[package]]
name = "krilla" name = "krilla"
version = "0.4.0" version = "0.4.0"
source = "git+https://github.com/LaurenzV/krilla?rev=20c14fe#20c14fefee5002566b3d6668b338bbe2168784e7" source = "git+https://github.com/LaurenzV/krilla?rev=37b9a00#37b9a00bfac87ed0b347b7cf8e9d37a6f68fcccd"
dependencies = [ dependencies = [
"base64", "base64",
"bumpalo", "bumpalo",
@ -1370,6 +1433,7 @@ dependencies = [
"float-cmp 0.10.0", "float-cmp 0.10.0",
"fxhash", "fxhash",
"gif", "gif",
"hayro-write",
"image-webp", "image-webp",
"imagesize", "imagesize",
"once_cell", "once_cell",
@ -1379,6 +1443,7 @@ dependencies = [
"rustybuzz", "rustybuzz",
"siphasher", "siphasher",
"skrifa", "skrifa",
"smallvec",
"subsetter", "subsetter",
"tiny-skia-path", "tiny-skia-path",
"xmp-writer", "xmp-writer",
@ -1389,7 +1454,7 @@ dependencies = [
[[package]] [[package]]
name = "krilla-svg" name = "krilla-svg"
version = "0.1.0" version = "0.1.0"
source = "git+https://github.com/LaurenzV/krilla?rev=20c14fe#20c14fefee5002566b3d6668b338bbe2168784e7" source = "git+https://github.com/LaurenzV/krilla?rev=37b9a00#37b9a00bfac87ed0b347b7cf8e9d37a6f68fcccd"
dependencies = [ dependencies = [
"flate2", "flate2",
"fontdb", "fontdb",
@ -1402,9 +1467,9 @@ dependencies = [
[[package]] [[package]]
name = "kurbo" name = "kurbo"
version = "0.11.1" version = "0.11.2"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "89234b2cc610a7dd927ebde6b41dd1a5d4214cffaef4cf1fb2195d592f92518f" checksum = "1077d333efea6170d9ccb96d3c3026f300ca0773da4938cc4c811daa6df68b0c"
dependencies = [ dependencies = [
"arrayvec", "arrayvec",
"smallvec", "smallvec",
@ -1456,7 +1521,7 @@ version = "0.1.3"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c0ff37bd590ca25063e35af745c343cb7a0271906fb7b37e4813e8f79f00268d" checksum = "c0ff37bd590ca25063e35af745c343cb7a0271906fb7b37e4813e8f79f00268d"
dependencies = [ dependencies = [
"bitflags 2.8.0", "bitflags 2.9.1",
"libc", "libc",
"redox_syscall", "redox_syscall",
] ]
@ -1622,7 +1687,7 @@ version = "8.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2fee8403b3d66ac7b26aee6e40a897d85dc5ce26f44da36b8b73e987cc52e943" checksum = "2fee8403b3d66ac7b26aee6e40a897d85dc5ce26f44da36b8b73e987cc52e943"
dependencies = [ dependencies = [
"bitflags 2.8.0", "bitflags 2.9.1",
"filetime", "filetime",
"fsevent-sys", "fsevent-sys",
"inotify", "inotify",
@ -1704,7 +1769,7 @@ version = "0.10.72"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "fedfea7d58a1f73118430a55da6a286e7b044961736ce96a16a17068ea25e5da" checksum = "fedfea7d58a1f73118430a55da6a286e7b044961736ce96a16a17068ea25e5da"
dependencies = [ dependencies = [
"bitflags 2.8.0", "bitflags 2.9.1",
"cfg-if", "cfg-if",
"foreign-types", "foreign-types",
"libc", "libc",
@ -1841,7 +1906,7 @@ version = "0.13.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3ea27c5015ab81753fc61e49f8cde74999346605ee148bb20008ef3d3150e0dc" checksum = "3ea27c5015ab81753fc61e49f8cde74999346605ee148bb20008ef3d3150e0dc"
dependencies = [ dependencies = [
"bitflags 2.8.0", "bitflags 2.9.1",
"itoa", "itoa",
"memchr", "memchr",
"ryu", "ryu",
@ -1999,7 +2064,7 @@ version = "0.9.6"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "57206b407293d2bcd3af849ce869d52068623f19e1b5ff8e8778e3309439682b" checksum = "57206b407293d2bcd3af849ce869d52068623f19e1b5ff8e8778e3309439682b"
dependencies = [ dependencies = [
"bitflags 2.8.0", "bitflags 2.9.1",
"getopts", "getopts",
"memchr", "memchr",
"unicase", "unicase",
@ -2112,7 +2177,7 @@ version = "0.5.8"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "03a862b389f93e68874fbf580b9de08dd02facb9a788ebadaf4a3fd33cf58834" checksum = "03a862b389f93e68874fbf580b9de08dd02facb9a788ebadaf4a3fd33cf58834"
dependencies = [ dependencies = [
"bitflags 2.8.0", "bitflags 2.9.1",
] ]
[[package]] [[package]]
@ -2215,7 +2280,7 @@ version = "0.38.44"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "fdb5bc1ae2baa591800df16c9ca78619bf65c0488b41b96ccec5d11220d8c154" checksum = "fdb5bc1ae2baa591800df16c9ca78619bf65c0488b41b96ccec5d11220d8c154"
dependencies = [ dependencies = [
"bitflags 2.8.0", "bitflags 2.9.1",
"errno", "errno",
"libc", "libc",
"linux-raw-sys", "linux-raw-sys",
@ -2234,7 +2299,7 @@ version = "0.20.1"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "fd3c7c96f8a08ee34eff8857b11b49b07d71d1c3f4e88f8a88d4c9e9f90b1702" checksum = "fd3c7c96f8a08ee34eff8857b11b49b07d71d1c3f4e88f8a88d4c9e9f90b1702"
dependencies = [ dependencies = [
"bitflags 2.8.0", "bitflags 2.9.1",
"bytemuck", "bytemuck",
"core_maths", "core_maths",
"log", "log",
@ -2282,7 +2347,7 @@ version = "2.11.1"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "897b2245f0b511c87893af39b033e5ca9cce68824c4d7e7630b5a1d339658d02" checksum = "897b2245f0b511c87893af39b033e5ca9cce68824c4d7e7630b5a1d339658d02"
dependencies = [ dependencies = [
"bitflags 2.8.0", "bitflags 2.9.1",
"core-foundation", "core-foundation",
"core-foundation-sys", "core-foundation-sys",
"libc", "libc",
@ -2445,9 +2510,9 @@ dependencies = [
[[package]] [[package]]
name = "smallvec" name = "smallvec"
version = "1.13.2" version = "1.15.1"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3c5e1a9a646d36c3599cd173a41282daf47c44583ad367b8e6837255952e5c67" checksum = "67b1b7a3b5fe4f1376887184045fcf45c69e92af734b7aaddc05fb777b6fbd03"
[[package]] [[package]]
name = "spin" name = "spin"
@ -2855,7 +2920,7 @@ dependencies = [
[[package]] [[package]]
name = "typst-assets" name = "typst-assets"
version = "0.13.1" version = "0.13.1"
source = "git+https://github.com/typst/typst-assets?rev=edf0d64#edf0d648376e29738a05a933af9ea99bb81557b1" source = "git+https://github.com/typst/typst-assets?rev=fbf00f9#fbf00f9539fdb0825bef4d39fb57d5986c51b756"
[[package]] [[package]]
name = "typst-cli" name = "typst-cli"
@ -2905,7 +2970,7 @@ dependencies = [
[[package]] [[package]]
name = "typst-dev-assets" name = "typst-dev-assets"
version = "0.13.1" version = "0.13.1"
source = "git+https://github.com/typst/typst-dev-assets?rev=bfa947f#bfa947f3433d7d13a995168c40ae788a2ebfe648" source = "git+https://github.com/typst/typst-dev-assets?rev=c6c2acf#c6c2acf6cdc31f99a23a478d3d614f8bf806a4f5"
[[package]] [[package]]
name = "typst-docs" name = "typst-docs"
@ -3055,7 +3120,7 @@ name = "typst-library"
version = "0.13.1" version = "0.13.1"
dependencies = [ dependencies = [
"az", "az",
"bitflags 2.8.0", "bitflags 2.9.1",
"bumpalo", "bumpalo",
"chinese-number", "chinese-number",
"ciborium", "ciborium",
@ -3067,6 +3132,7 @@ dependencies = [
"fontdb", "fontdb",
"glidesort", "glidesort",
"hayagriva", "hayagriva",
"hayro-syntax",
"icu_properties", "icu_properties",
"icu_provider", "icu_provider",
"icu_provider_blob", "icu_provider_blob",
@ -3165,11 +3231,13 @@ version = "0.13.1"
dependencies = [ dependencies = [
"bytemuck", "bytemuck",
"comemo", "comemo",
"hayro",
"image", "image",
"pixglyph", "pixglyph",
"resvg", "resvg",
"tiny-skia", "tiny-skia",
"ttf-parser", "ttf-parser",
"typst-assets",
"typst-library", "typst-library",
"typst-macros", "typst-macros",
"typst-timing", "typst-timing",
@ -3183,8 +3251,10 @@ dependencies = [
"comemo", "comemo",
"ecow", "ecow",
"flate2", "flate2",
"hayro",
"image", "image",
"ttf-parser", "ttf-parser",
"typst-assets",
"typst-library", "typst-library",
"typst-macros", "typst-macros",
"typst-timing", "typst-timing",
@ -3581,7 +3651,7 @@ version = "0.221.2"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9845c470a2e10b61dd42c385839cdd6496363ed63b5c9e420b5488b77bd22083" checksum = "9845c470a2e10b61dd42c385839cdd6496363ed63b5c9e420b5488b77bd22083"
dependencies = [ dependencies = [
"bitflags 2.8.0", "bitflags 2.9.1",
"indexmap 2.7.1", "indexmap 2.7.1",
] ]
@ -3716,7 +3786,7 @@ version = "0.33.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3268f3d866458b787f390cf61f4bbb563b922d091359f9608842999eaee3943c" checksum = "3268f3d866458b787f390cf61f4bbb563b922d091359f9608842999eaee3943c"
dependencies = [ dependencies = [
"bitflags 2.8.0", "bitflags 2.9.1",
] ]
[[package]] [[package]]

View File

@ -32,8 +32,8 @@ typst-svg = { path = "crates/typst-svg", version = "0.13.1" }
typst-syntax = { path = "crates/typst-syntax", version = "0.13.1" } typst-syntax = { path = "crates/typst-syntax", version = "0.13.1" }
typst-timing = { path = "crates/typst-timing", version = "0.13.1" } typst-timing = { path = "crates/typst-timing", version = "0.13.1" }
typst-utils = { path = "crates/typst-utils", version = "0.13.1" } typst-utils = { path = "crates/typst-utils", version = "0.13.1" }
typst-assets = { git = "https://github.com/typst/typst-assets", rev = "edf0d64" } typst-assets = { git = "https://github.com/typst/typst-assets", rev = "fbf00f9" }
typst-dev-assets = { git = "https://github.com/typst/typst-dev-assets", rev = "bfa947f" } typst-dev-assets = { git = "https://github.com/typst/typst-dev-assets", rev = "c6c2acf" }
arrayvec = "0.7.4" arrayvec = "0.7.4"
az = "1.2" az = "1.2"
base64 = "0.22" base64 = "0.22"
@ -61,6 +61,8 @@ fontdb = { version = "0.23", default-features = false }
fs_extra = "1.3" fs_extra = "1.3"
glidesort = "0.1.2" glidesort = "0.1.2"
hayagriva = "0.8.1" hayagriva = "0.8.1"
hayro-syntax = { git = "https://github.com/LaurenzV/hayro", rev = "e701f95" }
hayro = { git = "https://github.com/LaurenzV/hayro", rev = "e701f95" }
heck = "0.5" heck = "0.5"
hypher = "0.1.4" hypher = "0.1.4"
icu_properties = { version = "1.4", features = ["serde"] } icu_properties = { version = "1.4", features = ["serde"] }
@ -72,8 +74,8 @@ image = { version = "0.25.5", default-features = false, features = ["png", "jpeg
indexmap = { version = "2", features = ["serde"] } indexmap = { version = "2", features = ["serde"] }
infer = { version = "0.19.0", default-features = false } infer = { version = "0.19.0", default-features = false }
kamadak-exif = "0.6" kamadak-exif = "0.6"
krilla = { git = "https://github.com/LaurenzV/krilla", rev = "20c14fe", default-features = false, features = ["raster-images", "comemo", "rayon"] } krilla = { git = "https://github.com/LaurenzV/krilla", rev = "37b9a00", default-features = false, features = ["raster-images", "comemo", "rayon", "pdf"] }
krilla-svg = { git = "https://github.com/LaurenzV/krilla", rev = "20c14fe" } krilla-svg = { git = "https://github.com/LaurenzV/krilla", rev = "37b9a00"}
kurbo = "0.11" kurbo = "0.11"
libfuzzer-sys = "0.4" libfuzzer-sys = "0.4"
lipsum = "0.9" lipsum = "0.9"

View File

@ -834,7 +834,7 @@ fn param_value_completions<'a>(
fn path_completion(func: &Func, param: &ParamInfo) -> Option<&'static [&'static str]> { fn path_completion(func: &Func, param: &ParamInfo) -> Option<&'static [&'static str]> {
Some(match (func.name(), param.name) { Some(match (func.name(), param.name) {
(Some("image"), "source") => { (Some("image"), "source") => {
&["png", "jpg", "jpeg", "gif", "svg", "svgz", "webp"] &["png", "jpg", "jpeg", "gif", "svg", "svgz", "webp", "pdf"]
} }
(Some("csv"), "source") => &["csv"], (Some("csv"), "source") => &["csv"],
(Some("plugin"), "source") => &["wasm"], (Some("plugin"), "source") => &["wasm"],

View File

@ -31,6 +31,7 @@ flate2 = { workspace = true }
fontdb = { workspace = true } fontdb = { workspace = true }
glidesort = { workspace = true } glidesort = { workspace = true }
hayagriva = { workspace = true } hayagriva = { workspace = true }
hayro-syntax = { workspace = true }
icu_properties = { workspace = true } icu_properties = { workspace = true }
icu_provider = { workspace = true } icu_provider = { workspace = true }
icu_provider_blob = { workspace = true } icu_provider_blob = { workspace = true }

View File

@ -234,18 +234,23 @@ impl From<SyntaxError> for SourceDiagnostic {
/// Destination for a deprecation message when accessing a deprecated value. /// Destination for a deprecation message when accessing a deprecated value.
pub trait DeprecationSink { pub trait DeprecationSink {
/// Emits the given deprecation message into this sink. /// Emits the given deprecation message into this sink alongside a version
fn emit(self, message: &str); /// in which the deprecated item is planned to be removed.
fn emit(self, message: &str, until: Option<&str>);
} }
impl DeprecationSink for () { impl DeprecationSink for () {
fn emit(self, _: &str) {} fn emit(self, _: &str, _: Option<&str>) {}
} }
impl DeprecationSink for (&mut Engine<'_>, Span) { impl DeprecationSink for (&mut Engine<'_>, Span) {
/// Emits the deprecation message as a warning. /// Emits the deprecation message as a warning.
fn emit(self, message: &str) { fn emit(self, message: &str, version: Option<&str>) {
self.0.sink.warn(SourceDiagnostic::warning(self.1, message)); self.0.sink.warn(
SourceDiagnostic::warning(self.1, message).with_hints(
version.map(|v| eco_format!("this will be removed in {}", v)),
),
);
} }
} }

View File

@ -253,8 +253,8 @@ pub struct Binding {
span: Span, span: Span,
/// The category of the binding. /// The category of the binding.
category: Option<Category>, category: Option<Category>,
/// A deprecation message for the definition. /// The deprecation information if this item is deprecated.
deprecation: Option<&'static str>, deprecation: Option<Box<Deprecation>>,
} }
/// The different kinds of slots. /// The different kinds of slots.
@ -284,8 +284,8 @@ impl Binding {
} }
/// Marks this binding as deprecated, with the given `message`. /// Marks this binding as deprecated, with the given `message`.
pub fn deprecated(&mut self, message: &'static str) -> &mut Self { pub fn deprecated(&mut self, deprecation: Deprecation) -> &mut Self {
self.deprecation = Some(message); self.deprecation = Some(Box::new(deprecation));
self self
} }
@ -300,8 +300,8 @@ impl Binding {
/// - pass `()` to ignore the message. /// - pass `()` to ignore the message.
/// - pass `(&mut engine, span)` to emit a warning into the engine. /// - pass `(&mut engine, span)` to emit a warning into the engine.
pub fn read_checked(&self, sink: impl DeprecationSink) -> &Value { pub fn read_checked(&self, sink: impl DeprecationSink) -> &Value {
if let Some(message) = self.deprecation { if let Some(info) = &self.deprecation {
sink.emit(message); sink.emit(info.message, info.until);
} }
&self.value &self.value
} }
@ -337,8 +337,8 @@ impl Binding {
} }
/// A deprecation message for the value, if any. /// A deprecation message for the value, if any.
pub fn deprecation(&self) -> Option<&'static str> { pub fn deprecation(&self) -> Option<&Deprecation> {
self.deprecation self.deprecation.as_deref()
} }
/// The category of the value, if any. /// The category of the value, if any.
@ -356,6 +356,51 @@ pub enum Capturer {
Context, Context,
} }
/// Information about a deprecated binding.
#[derive(Debug, Copy, Clone, Eq, PartialEq, Hash)]
pub struct Deprecation {
/// A deprecation message for the definition.
message: &'static str,
/// A version in which the deprecated binding is planned to be removed.
until: Option<&'static str>,
}
impl Deprecation {
/// Creates new deprecation info with a default message to display when
/// emitting the deprecation warning.
pub fn new() -> Self {
Self { message: "item is deprecated", until: None }
}
/// Set the message to display when emitting the deprecation warning.
pub fn with_message(mut self, message: &'static str) -> Self {
self.message = message;
self
}
/// Set the version in which the binding is planned to be removed.
pub fn with_until(mut self, version: &'static str) -> Self {
self.until = Some(version);
self
}
/// The message to display when emitting the deprecation warning.
pub fn message(&self) -> &'static str {
self.message
}
/// The version in which the binding is planned to be removed.
pub fn until(&self) -> Option<&'static str> {
self.until
}
}
impl Default for Deprecation {
fn default() -> Self {
Self::new()
}
}
/// The error message when trying to mutate a variable from the standard /// The error message when trying to mutate a variable from the standard
/// library. /// library.
#[cold] #[cold]

View File

@ -151,7 +151,7 @@ impl Symbol {
modifiers.best_match_in(list.variants().map(|(m, _, d)| (m, d))) modifiers.best_match_in(list.variants().map(|(m, _, d)| (m, d)))
{ {
if let Some(message) = deprecation { if let Some(message) = deprecation {
sink.emit(message) sink.emit(message, None)
} }
return Ok(self); return Ok(self);
} }

View File

@ -33,7 +33,10 @@ pub fn cbor(
impl cbor { impl cbor {
/// Reads structured data from CBOR bytes. /// Reads structured data from CBOR bytes.
#[func(title = "Decode CBOR")] #[func(title = "Decode CBOR")]
#[deprecated = "`cbor.decode` is deprecated, directly pass bytes to `cbor` instead"] #[deprecated(
message = "`cbor.decode` is deprecated, directly pass bytes to `cbor` instead",
until = "0.15.0"
)]
pub fn decode( pub fn decode(
engine: &mut Engine, engine: &mut Engine,
/// CBOR data. /// CBOR data.

View File

@ -95,7 +95,10 @@ pub fn csv(
impl csv { impl csv {
/// Reads structured data from a CSV string/bytes. /// Reads structured data from a CSV string/bytes.
#[func(title = "Decode CSV")] #[func(title = "Decode CSV")]
#[deprecated = "`csv.decode` is deprecated, directly pass bytes to `csv` instead"] #[deprecated(
message = "`csv.decode` is deprecated, directly pass bytes to `csv` instead",
until = "0.15.0"
)]
pub fn decode( pub fn decode(
engine: &mut Engine, engine: &mut Engine,
/// CSV data. /// CSV data.

View File

@ -67,7 +67,10 @@ pub fn json(
impl json { impl json {
/// Reads structured data from a JSON string/bytes. /// Reads structured data from a JSON string/bytes.
#[func(title = "Decode JSON")] #[func(title = "Decode JSON")]
#[deprecated = "`json.decode` is deprecated, directly pass bytes to `json` instead"] #[deprecated(
message = "`json.decode` is deprecated, directly pass bytes to `json` instead",
until = "0.15.0"
)]
pub fn decode( pub fn decode(
engine: &mut Engine, engine: &mut Engine,
/// JSON data. /// JSON data.

View File

@ -41,7 +41,10 @@ pub fn toml(
impl toml { impl toml {
/// Reads structured data from a TOML string/bytes. /// Reads structured data from a TOML string/bytes.
#[func(title = "Decode TOML")] #[func(title = "Decode TOML")]
#[deprecated = "`toml.decode` is deprecated, directly pass bytes to `toml` instead"] #[deprecated(
message = "`toml.decode` is deprecated, directly pass bytes to `toml` instead",
until = "0.15.0"
)]
pub fn decode( pub fn decode(
engine: &mut Engine, engine: &mut Engine,
/// TOML data. /// TOML data.

View File

@ -75,7 +75,10 @@ pub fn xml(
impl xml { impl xml {
/// Reads structured data from an XML string/bytes. /// Reads structured data from an XML string/bytes.
#[func(title = "Decode XML")] #[func(title = "Decode XML")]
#[deprecated = "`xml.decode` is deprecated, directly pass bytes to `xml` instead"] #[deprecated(
message = "`xml.decode` is deprecated, directly pass bytes to `xml` instead",
until = "0.15.0"
)]
pub fn decode( pub fn decode(
engine: &mut Engine, engine: &mut Engine,
/// XML data. /// XML data.

View File

@ -54,7 +54,10 @@ pub fn yaml(
impl yaml { impl yaml {
/// Reads structured data from a YAML string/bytes. /// Reads structured data from a YAML string/bytes.
#[func(title = "Decode YAML")] #[func(title = "Decode YAML")]
#[deprecated = "`yaml.decode` is deprecated, directly pass bytes to `yaml` instead"] #[deprecated(
message = "`yaml.decode` is deprecated, directly pass bytes to `yaml` instead",
until = "0.15.0"
)]
pub fn decode( pub fn decode(
engine: &mut Engine, engine: &mut Engine,
/// YAML data. /// YAML data.

View File

@ -1,6 +1,6 @@
//! Modifiable symbols. //! Modifiable symbols.
use crate::foundations::{Module, Scope, Symbol, Value}; use crate::foundations::{Deprecation, Module, Scope, Symbol, Value};
/// Hook up all `symbol` definitions. /// Hook up all `symbol` definitions.
pub(super) fn define(global: &mut Scope) { pub(super) fn define(global: &mut Scope) {
@ -23,7 +23,7 @@ fn extend_scope_from_codex_module(scope: &mut Scope, module: codex::Module) {
let scope_binding = scope.define(name, value); let scope_binding = scope.define(name, value);
if let Some(message) = binding.deprecation { if let Some(message) = binding.deprecation {
scope_binding.deprecated(message); scope_binding.deprecated(Deprecation::new().with_message(message));
} }
} }
} }

View File

@ -476,26 +476,18 @@ impl Curve {
/// Computes the size of the bounding box of this curve. /// Computes the size of the bounding box of this curve.
pub fn bbox_size(&self) -> Size { pub fn bbox_size(&self) -> Size {
let mut min_x = Abs::inf(); let mut min = Point::splat(Abs::inf());
let mut min_y = Abs::inf(); let mut max = Point::splat(-Abs::inf());
let mut max_x = -Abs::inf();
let mut max_y = -Abs::inf();
let mut cursor = Point::zero(); let mut cursor = Point::zero();
for item in self.0.iter() { for item in self.0.iter() {
match item { match item {
CurveItem::Move(to) => { CurveItem::Move(to) => {
min_x = min_x.min(cursor.x);
min_y = min_y.min(cursor.y);
max_x = max_x.max(cursor.x);
max_y = max_y.max(cursor.y);
cursor = *to; cursor = *to;
} }
CurveItem::Line(to) => { CurveItem::Line(to) => {
min_x = min_x.min(cursor.x); min = min.min(cursor).min(*to);
min_y = min_y.min(cursor.y); max = max.max(cursor).max(*to);
max_x = max_x.max(cursor.x);
max_y = max_y.max(cursor.y);
cursor = *to; cursor = *to;
} }
CurveItem::Cubic(c0, c1, end) => { CurveItem::Cubic(c0, c1, end) => {
@ -507,17 +499,17 @@ impl Curve {
); );
let bbox = cubic.bounding_box(); let bbox = cubic.bounding_box();
min_x = min_x.min(Abs::pt(bbox.x0)).min(Abs::pt(bbox.x1)); min.x = min.x.min(Abs::pt(bbox.x0)).min(Abs::pt(bbox.x1));
min_y = min_y.min(Abs::pt(bbox.y0)).min(Abs::pt(bbox.y1)); min.y = min.y.min(Abs::pt(bbox.y0)).min(Abs::pt(bbox.y1));
max_x = max_x.max(Abs::pt(bbox.x0)).max(Abs::pt(bbox.x1)); max.x = max.x.max(Abs::pt(bbox.x0)).max(Abs::pt(bbox.x1));
max_y = max_y.max(Abs::pt(bbox.y0)).max(Abs::pt(bbox.y1)); max.y = max.y.max(Abs::pt(bbox.y0)).max(Abs::pt(bbox.y1));
cursor = *end; cursor = *end;
} }
CurveItem::Close => (), CurveItem::Close => (),
} }
} }
Size::new(max_x - min_x, max_y - min_y) Size::new(max.x - min.x, max.y - min.y)
} }
} }

View File

@ -1,8 +1,10 @@
//! Image handling. //! Image handling.
mod pdf;
mod raster; mod raster;
mod svg; mod svg;
pub use self::pdf::PdfImage;
pub use self::raster::{ pub use self::raster::{
ExchangeFormat, PixelEncoding, PixelFormat, RasterFormat, RasterImage, ExchangeFormat, PixelEncoding, PixelFormat, RasterFormat, RasterImage,
}; };
@ -10,13 +12,15 @@ pub use self::svg::SvgImage;
use std::ffi::OsStr; use std::ffi::OsStr;
use std::fmt::{self, Debug, Formatter}; use std::fmt::{self, Debug, Formatter};
use std::num::NonZeroUsize;
use std::sync::Arc; use std::sync::Arc;
use ecow::EcoString; use ecow::EcoString;
use hayro_syntax::LoadPdfError;
use typst_syntax::{Span, Spanned}; use typst_syntax::{Span, Spanned};
use typst_utils::LazyHash; use typst_utils::{LazyHash, NonZeroExt};
use crate::diag::{At, LoadedWithin, SourceResult, StrResult, warning}; use crate::diag::{At, LoadedWithin, SourceResult, StrResult, bail, warning};
use crate::engine::Engine; use crate::engine::Engine;
use crate::foundations::{ use crate::foundations::{
Bytes, Cast, Content, Derived, NativeElement, Packed, Smart, StyleChain, cast, elem, Bytes, Cast, Content, Derived, NativeElement, Packed, Smart, StyleChain, cast, elem,
@ -26,6 +30,7 @@ use crate::layout::{Length, Rel, Sizing};
use crate::loading::{DataSource, Load, LoadSource, Loaded, Readable}; use crate::loading::{DataSource, Load, LoadSource, Loaded, Readable};
use crate::model::Figurable; use crate::model::Figurable;
use crate::text::{LocalName, families}; use crate::text::{LocalName, families};
use crate::visualize::image::pdf::PdfDocument;
/// A raster or vector graphic. /// A raster or vector graphic.
/// ///
@ -79,8 +84,7 @@ pub struct ImageElem {
/// format automatically, but that's not always possible). /// format automatically, but that's not always possible).
/// ///
/// Supported formats are `{"png"}`, `{"jpg"}`, `{"gif"}`, `{"svg"}`, /// Supported formats are `{"png"}`, `{"jpg"}`, `{"gif"}`, `{"svg"}`,
/// `{"webp"}` as well as raw pixel data. Embedding PDFs as images is /// `{"pdf"}`, `{"webp"}` as well as raw pixel data.
/// [not currently supported](https://github.com/typst/typst/issues/145).
/// ///
/// When providing raw pixel data as the `source`, you must specify a /// When providing raw pixel data as the `source`, you must specify a
/// dictionary with the following keys as the `format`: /// dictionary with the following keys as the `format`:
@ -126,6 +130,11 @@ pub struct ImageElem {
/// A text describing the image. /// A text describing the image.
pub alt: Option<EcoString>, pub alt: Option<EcoString>,
/// The page number that should be embedded as an image. This attribute only
/// has an effect for PDF files.
#[default(NonZeroUsize::ONE)]
pub page: NonZeroUsize,
/// How the image should adjust itself to a given area (the area is defined /// How the image should adjust itself to a given area (the area is defined
/// by the `width` and `height` fields). Note that `fit` doesn't visually /// by the `width` and `height` fields). Note that `fit` doesn't visually
/// change anything if the area's aspect ratio is the same as the image's /// change anything if the area's aspect ratio is the same as the image's
@ -169,7 +178,10 @@ pub struct ImageElem {
impl ImageElem { impl ImageElem {
/// Decode a raster or vector graphic from bytes or a string. /// Decode a raster or vector graphic from bytes or a string.
#[func(title = "Decode Image")] #[func(title = "Decode Image")]
#[deprecated = "`image.decode` is deprecated, directly pass bytes to `image` instead"] #[deprecated(
message = "`image.decode` is deprecated, directly pass bytes to `image` instead",
until = "0.15.0"
)]
pub fn decode( pub fn decode(
span: Span, span: Span,
/// The data to decode as an image. Can be a string for SVGs. /// The data to decode as an image. Can be a string for SVGs.
@ -261,6 +273,45 @@ impl Packed<ImageElem> {
) )
.within(loaded)?, .within(loaded)?,
), ),
ImageFormat::Vector(VectorFormat::Pdf) => {
let document = match PdfDocument::new(loaded.data.clone()) {
Ok(doc) => doc,
Err(e) => match e {
LoadPdfError::Encryption => {
bail!(
span,
"the PDF is encrypted or password-protected";
hint: "such PDFs are currently not supported";
hint: "preprocess the PDF to remove the encryption"
);
}
LoadPdfError::Invalid => {
bail!(
span,
"the PDF could not be loaded";
hint: "perhaps the PDF file is malformed"
);
}
},
};
// The user provides the page number start from 1, but further
// down the pipeline, page numbers are 0-based.
let page_num = self.page.get(styles).get();
let page_idx = page_num - 1;
let num_pages = document.num_pages();
let Some(pdf_image) = PdfImage::new(document, page_idx) else {
let s = if num_pages == 1 { "" } else { "s" };
bail!(
span,
"page {page_num} does not exist";
hint: "the document only has {num_pages} page{s}"
);
};
ImageKind::Pdf(pdf_image)
}
}; };
Ok(Image::new(kind, self.alt.get_cloned(styles), self.scaling.get(styles))) Ok(Image::new(kind, self.alt.get_cloned(styles), self.scaling.get(styles)))
@ -286,6 +337,7 @@ impl Packed<ImageElem> {
"jpg" | "jpeg" => return Ok(ExchangeFormat::Jpg.into()), "jpg" | "jpeg" => return Ok(ExchangeFormat::Jpg.into()),
"gif" => return Ok(ExchangeFormat::Gif.into()), "gif" => return Ok(ExchangeFormat::Gif.into()),
"svg" | "svgz" => return Ok(VectorFormat::Svg.into()), "svg" | "svgz" => return Ok(VectorFormat::Svg.into()),
"pdf" => return Ok(VectorFormat::Pdf.into()),
"webp" => return Ok(ExchangeFormat::Webp.into()), "webp" => return Ok(ExchangeFormat::Webp.into()),
_ => {} _ => {}
} }
@ -373,6 +425,7 @@ impl Image {
match &self.0.kind { match &self.0.kind {
ImageKind::Raster(raster) => raster.format().into(), ImageKind::Raster(raster) => raster.format().into(),
ImageKind::Svg(_) => VectorFormat::Svg.into(), ImageKind::Svg(_) => VectorFormat::Svg.into(),
ImageKind::Pdf(_) => VectorFormat::Pdf.into(),
} }
} }
@ -381,6 +434,7 @@ impl Image {
match &self.0.kind { match &self.0.kind {
ImageKind::Raster(raster) => raster.width() as f64, ImageKind::Raster(raster) => raster.width() as f64,
ImageKind::Svg(svg) => svg.width(), ImageKind::Svg(svg) => svg.width(),
ImageKind::Pdf(pdf) => pdf.width() as f64,
} }
} }
@ -389,6 +443,7 @@ impl Image {
match &self.0.kind { match &self.0.kind {
ImageKind::Raster(raster) => raster.height() as f64, ImageKind::Raster(raster) => raster.height() as f64,
ImageKind::Svg(svg) => svg.height(), ImageKind::Svg(svg) => svg.height(),
ImageKind::Pdf(pdf) => pdf.height() as f64,
} }
} }
@ -397,6 +452,7 @@ impl Image {
match &self.0.kind { match &self.0.kind {
ImageKind::Raster(raster) => raster.dpi(), ImageKind::Raster(raster) => raster.dpi(),
ImageKind::Svg(_) => Some(Image::USVG_DEFAULT_DPI), ImageKind::Svg(_) => Some(Image::USVG_DEFAULT_DPI),
ImageKind::Pdf(_) => Some(Image::DEFAULT_DPI),
} }
} }
@ -435,6 +491,8 @@ pub enum ImageKind {
Raster(RasterImage), Raster(RasterImage),
/// An SVG image. /// An SVG image.
Svg(SvgImage), Svg(SvgImage),
/// A PDF image.
Pdf(PdfImage),
} }
impl From<RasterImage> for ImageKind { impl From<RasterImage> for ImageKind {
@ -469,10 +527,20 @@ impl ImageFormat {
return Some(Self::Vector(VectorFormat::Svg)); return Some(Self::Vector(VectorFormat::Svg));
} }
if is_pdf(data) {
return Some(Self::Vector(VectorFormat::Pdf));
}
None None
} }
} }
/// Checks whether the data looks like a PDF file.
fn is_pdf(data: &[u8]) -> bool {
let head = &data[..data.len().min(2048)];
memchr::memmem::find(head, b"%PDF-").is_some()
}
/// Checks whether the data looks like an SVG or a compressed SVG. /// Checks whether the data looks like an SVG or a compressed SVG.
fn is_svg(data: &[u8]) -> bool { fn is_svg(data: &[u8]) -> bool {
// Check for the gzip magic bytes. This check is perhaps a bit too // Check for the gzip magic bytes. This check is perhaps a bit too
@ -493,6 +561,9 @@ fn is_svg(data: &[u8]) -> bool {
pub enum VectorFormat { pub enum VectorFormat {
/// The vector graphics format of the web. /// The vector graphics format of the web.
Svg, Svg,
/// High-fidelity document and graphics format, with focus on exact
/// reproduction in print.
Pdf,
} }
impl<R> From<R> for ImageFormat impl<R> From<R> for ImageFormat

View File

@ -0,0 +1,98 @@
use std::hash::{Hash, Hasher};
use std::sync::Arc;
use hayro_syntax::page::Page;
use hayro_syntax::{LoadPdfError, Pdf};
use crate::foundations::Bytes;
/// A PDF document.
#[derive(Clone, Hash)]
pub struct PdfDocument(Arc<DocumentRepr>);
/// The internal representation of a `PdfDocument`.
struct DocumentRepr {
pdf: Arc<Pdf>,
data: Bytes,
}
impl PdfDocument {
/// Loads a PDF document.
#[comemo::memoize]
#[typst_macros::time(name = "load pdf document")]
pub fn new(data: Bytes) -> Result<PdfDocument, LoadPdfError> {
let pdf = Arc::new(Pdf::new(Arc::new(data.clone()))?);
Ok(Self(Arc::new(DocumentRepr { data, pdf })))
}
/// Returns the underlying PDF document.
pub fn pdf(&self) -> &Arc<Pdf> {
&self.0.pdf
}
/// Return the number of pages in the PDF.
pub fn num_pages(&self) -> usize {
self.0.pdf.pages().len()
}
}
impl Hash for DocumentRepr {
fn hash<H: Hasher>(&self, state: &mut H) {
self.data.hash(state);
}
}
/// A specific page of a PDF acting as an image.
#[derive(Clone, Hash)]
pub struct PdfImage(Arc<ImageRepr>);
/// The internal representation of a `PdfImage`.
struct ImageRepr {
document: PdfDocument,
page_index: usize,
width: f32,
height: f32,
}
impl PdfImage {
/// Creates a new PDF image.
///
/// Returns `None` if the page index is not valid.
#[comemo::memoize]
pub fn new(document: PdfDocument, page_index: usize) -> Option<PdfImage> {
let (width, height) = document.0.pdf.pages().get(page_index)?.render_dimensions();
Some(Self(Arc::new(ImageRepr { document, page_index, width, height })))
}
/// Returns the underlying Typst PDF document.
pub fn document(&self) -> &PdfDocument {
&self.0.document
}
/// Returns the PDF page of the image.
pub fn page(&self) -> &Page {
&self.document().pdf().pages()[self.0.page_index]
}
/// Returns the width of the image.
pub fn width(&self) -> f32 {
self.0.width
}
/// Returns the height of the image.
pub fn height(&self) -> f32 {
self.0.height
}
/// Returns the page index of the image.
pub fn page_index(&self) -> usize {
self.0.page_index
}
}
impl Hash for ImageRepr {
fn hash<H: Hasher>(&self, state: &mut H) {
self.document.hash(state);
self.page_index.hash(state);
}
}

View File

@ -24,6 +24,7 @@ pub use self::shape::*;
pub use self::stroke::*; pub use self::stroke::*;
pub use self::tiling::*; pub use self::tiling::*;
use crate::foundations::Deprecation;
use crate::foundations::{Element, Scope, Type}; use crate::foundations::{Element, Scope, Type};
/// Hook up all visualize definitions. /// Hook up all visualize definitions.
@ -41,11 +42,14 @@ pub(super) fn define(global: &mut Scope) {
global.define_elem::<CircleElem>(); global.define_elem::<CircleElem>();
global.define_elem::<PolygonElem>(); global.define_elem::<PolygonElem>();
global.define_elem::<CurveElem>(); global.define_elem::<CurveElem>();
global global.define("path", Element::of::<PathElem>()).deprecated(
.define("path", Element::of::<PathElem>()) Deprecation::new()
.deprecated("the `path` function is deprecated, use `curve` instead"); .with_message("the `path` function is deprecated, use `curve` instead"),
global );
.define("pattern", Type::of::<Tiling>()) global.define("pattern", Type::of::<Tiling>()).deprecated(
.deprecated("the name `pattern` is deprecated, use `tiling` instead"); Deprecation::new()
.with_message("the name `pattern` is deprecated, use `tiling` instead")
.with_until("0.15.0"),
);
global.reset_category(); global.reset_category();
} }

View File

@ -1,7 +1,8 @@
use heck::ToKebabCase; use heck::ToKebabCase;
use proc_macro2::TokenStream; use proc_macro2::TokenStream;
use quote::quote; use quote::quote;
use syn::{Result, parse_quote}; use syn::punctuated::Punctuated;
use syn::{MetaNameValue, Result, Token, parse_quote};
use crate::util::{BareType, foundations}; use crate::util::{BareType, foundations};
@ -52,14 +53,37 @@ pub fn scope(_: TokenStream, item: syn::Item) -> Result<TokenStream> {
_ => bail!(child, "unexpected item in scope"), _ => bail!(child, "unexpected item in scope"),
}; };
if let Some(message) = attrs.iter().find_map(|attr| match &attr.meta { if let Some(attr) = attrs.iter().find(|attr| attr.path().is_ident("deprecated")) {
match &attr.meta {
syn::Meta::NameValue(pair) if pair.path.is_ident("deprecated") => { syn::Meta::NameValue(pair) if pair.path.is_ident("deprecated") => {
Some(&pair.value) let message = &pair.value;
}
_ => None,
}) {
def = quote! { #def.deprecated(#message) } def = quote! { #def.deprecated(#message) }
} }
syn::Meta::List(list) if list.path.is_ident("deprecated") => {
let args = list.parse_args_with(
Punctuated::<MetaNameValue, Token![,]>::parse_separated_nonempty,
)?;
let mut deprecation =
quote! { crate::foundations::Deprecation::new() };
if let Some(message) = args.iter().find_map(|pair| {
pair.path.is_ident("message").then_some(&pair.value)
}) {
deprecation = quote! { #deprecation.with_message(#message) }
}
if let Some(version) = args.iter().find_map(|pair| {
pair.path.is_ident("until").then_some(&pair.value)
}) {
deprecation = quote! { #deprecation.with_until(#version) }
}
def = quote! { #def.deprecated(#deprecation) }
}
_ => {}
}
}
definitions.push(def); definitions.push(def);
} }

View File

@ -9,6 +9,7 @@ use krilla::embed::EmbedError;
use krilla::error::KrillaError; use krilla::error::KrillaError;
use krilla::geom::PathBuilder; use krilla::geom::PathBuilder;
use krilla::page::{PageLabel, PageSettings}; use krilla::page::{PageLabel, PageSettings};
use krilla::pdf::PdfError;
use krilla::surface::Surface; use krilla::surface::Surface;
use krilla::{Document, SerializeSettings}; use krilla::{Document, SerializeSettings};
use krilla_svg::render_svg_glyph; use krilla_svg::render_svg_glyph;
@ -363,6 +364,42 @@ fn finish(
hint: "convert the image to 8 bit instead" hint: "convert the image to 8 bit instead"
) )
} }
KrillaError::Pdf(_, e, loc) => {
let span = to_span(loc);
match e {
// We already validated in `typst-library` that the page index is valid.
PdfError::InvalidPage(_) => bail!(
span,
"invalid page number for PDF file";
hint: "please report this as a bug"
),
PdfError::VersionMismatch(v) => {
let pdf_ver = v.as_str();
let config_ver = configuration.version();
let cur_ver = config_ver.as_str();
bail!(span,
"the version of the PDF is too high";
hint: "the current export target is {cur_ver}, while the PDF has version {pdf_ver}";
hint: "raise the export target to {pdf_ver} or higher";
hint: "or preprocess the PDF to convert it to a lower version"
);
}
}
}
KrillaError::DuplicateTagId(_, loc) => {
let span = to_span(loc);
bail!(span,
"duplicate tag id";
hint: "please report this as a bug"
);
}
KrillaError::UnknownTagId(_, loc) => {
let span = to_span(loc);
bail!(span,
"unknown tag id";
hint: "please report this as a bug"
);
}
}, },
} }
} }
@ -535,12 +572,12 @@ fn convert_error(
} }
// The below errors cannot occur yet, only once Typst supports full PDF/A // The below errors cannot occur yet, only once Typst supports full PDF/A
// and PDF/UA. But let's still add a message just to be on the safe side. // and PDF/UA. But let's still add a message just to be on the safe side.
ValidationError::MissingAnnotationAltText => error!( ValidationError::MissingAnnotationAltText(_) => error!(
Span::detached(), Span::detached(),
"{prefix} missing annotation alt text"; "{prefix} missing annotation alt text";
hint: "please report this as a bug" hint: "please report this as a bug"
), ),
ValidationError::MissingAltText => error!( ValidationError::MissingAltText(_) => error!(
Span::detached(), Span::detached(),
"{prefix} missing alt text"; "{prefix} missing alt text";
hint: "make sure your images and equations have alt text" hint: "make sure your images and equations have alt text"
@ -576,6 +613,13 @@ fn convert_error(
"{prefix} missing document date"; "{prefix} missing document date";
hint: "set the date of the document" hint: "set the date of the document"
), ),
ValidationError::EmbeddedPDF(loc) => {
error!(
to_span(*loc),
"embedding PDFs is currently not supported in this export mode";
hint: "try converting the PDF to an SVG before embedding it"
)
}
} }
} }

View File

@ -3,13 +3,14 @@ use std::sync::{Arc, OnceLock};
use image::{DynamicImage, EncodableLayout, GenericImageView, Rgba}; use image::{DynamicImage, EncodableLayout, GenericImageView, Rgba};
use krilla::image::{BitsPerComponent, CustomImage, ImageColorspace}; use krilla::image::{BitsPerComponent, CustomImage, ImageColorspace};
use krilla::pdf::PdfDocument;
use krilla::surface::Surface; use krilla::surface::Surface;
use krilla_svg::{SurfaceExt, SvgSettings}; use krilla_svg::{SurfaceExt, SvgSettings};
use typst_library::diag::{SourceResult, bail}; use typst_library::diag::{SourceResult, bail};
use typst_library::foundations::Smart; use typst_library::foundations::Smart;
use typst_library::layout::{Abs, Angle, Ratio, Size, Transform}; use typst_library::layout::{Abs, Angle, Ratio, Size, Transform};
use typst_library::visualize::{ use typst_library::visualize::{
ExchangeFormat, Image, ImageKind, ImageScaling, RasterFormat, RasterImage, ExchangeFormat, Image, ImageKind, ImageScaling, PdfImage, RasterFormat, RasterImage,
}; };
use typst_syntax::Span; use typst_syntax::Span;
@ -60,6 +61,9 @@ pub(crate) fn handle_image(
SvgSettings { embed_text: true, ..Default::default() }, SvgSettings { embed_text: true, ..Default::default() },
); );
} }
ImageKind::Pdf(pdf) => {
surface.draw_pdf_page(&convert_pdf(pdf), size.to_krilla(), pdf.page_index())
}
} }
if image.alt().is_some() { if image.alt().is_some() {
@ -85,9 +89,9 @@ struct Repr {
/// A wrapper around `RasterImage` so that we can implement `CustomImage`. /// A wrapper around `RasterImage` so that we can implement `CustomImage`.
#[derive(Clone)] #[derive(Clone)]
struct PdfImage(Arc<Repr>); struct PdfRasterImage(Arc<Repr>);
impl PdfImage { impl PdfRasterImage {
pub fn new(raster: RasterImage) -> Self { pub fn new(raster: RasterImage) -> Self {
Self(Arc::new(Repr { Self(Arc::new(Repr {
raster, raster,
@ -97,7 +101,7 @@ impl PdfImage {
} }
} }
impl Hash for PdfImage { impl Hash for PdfRasterImage {
fn hash<H: Hasher>(&self, state: &mut H) { fn hash<H: Hasher>(&self, state: &mut H) {
// `alpha_channel` and `actual_dynamic` are generated from the underlying `RasterImage`, // `alpha_channel` and `actual_dynamic` are generated from the underlying `RasterImage`,
// so this is enough. Since `raster` is prehashed, this is also very cheap. // so this is enough. Since `raster` is prehashed, this is also very cheap.
@ -105,7 +109,7 @@ impl Hash for PdfImage {
} }
} }
impl CustomImage for PdfImage { impl CustomImage for PdfRasterImage {
fn color_channel(&self) -> &[u8] { fn color_channel(&self) -> &[u8] {
self.0 self.0
.actual_dynamic .actual_dynamic
@ -196,10 +200,15 @@ fn convert_raster(
interpolate, interpolate,
) )
} else { } else {
krilla::image::Image::from_custom(PdfImage::new(raster), interpolate) krilla::image::Image::from_custom(PdfRasterImage::new(raster), interpolate)
} }
} }
#[comemo::memoize]
fn convert_pdf(pdf: &PdfImage) -> PdfDocument {
PdfDocument::new(pdf.document().pdf().clone())
}
fn exif_transform(image: &RasterImage, size: Size) -> (Transform, Size) { fn exif_transform(image: &RasterImage, size: Size) -> (Transform, Size) {
let base = |hp: bool, vp: bool, mut base_ts: Transform, size: Size| { let base = |hp: bool, vp: bool, mut base_ts: Transform, size: Size| {
if hp { if hp {

View File

@ -49,7 +49,6 @@ pub(crate) fn handle_link(
fc.push_annotation( fc.push_annotation(
LinkAnnotation::new( LinkAnnotation::new(
rect, rect,
None,
Target::Action(Action::Link(LinkAction::new(u.to_string()))), Target::Action(Action::Link(LinkAction::new(u.to_string()))),
) )
.into(), .into(),
@ -64,7 +63,6 @@ pub(crate) fn handle_link(
fc.push_annotation( fc.push_annotation(
LinkAnnotation::new( LinkAnnotation::new(
rect, rect,
None,
Target::Destination(krilla::destination::Destination::Named( Target::Destination(krilla::destination::Destination::Named(
nd.clone(), nd.clone(),
)), )),
@ -83,7 +81,6 @@ pub(crate) fn handle_link(
fc.push_annotation( fc.push_annotation(
LinkAnnotation::new( LinkAnnotation::new(
rect, rect,
None,
Target::Destination(krilla::destination::Destination::Xyz( Target::Destination(krilla::destination::Destination::Xyz(
XyzDestination::new(index, pos.point.to_krilla()), XyzDestination::new(index, pos.point.to_krilla()),
)), )),

View File

@ -13,11 +13,13 @@ keywords = { workspace = true }
readme = { workspace = true } readme = { workspace = true }
[dependencies] [dependencies]
typst-assets = { workspace = true }
typst-library = { workspace = true } typst-library = { workspace = true }
typst-macros = { workspace = true } typst-macros = { workspace = true }
typst-timing = { workspace = true } typst-timing = { workspace = true }
bytemuck = { workspace = true } bytemuck = { workspace = true }
comemo = { workspace = true } comemo = { workspace = true }
hayro = { workspace = true }
image = { workspace = true } image = { workspace = true }
pixglyph = { workspace = true } pixglyph = { workspace = true }
resvg = { workspace = true } resvg = { workspace = true }

View File

@ -1,11 +1,12 @@
use std::sync::Arc; use hayro::{FontData, FontQuery, InterpreterSettings, RenderSettings, StandardFont};
use image::imageops::FilterType; use image::imageops::FilterType;
use image::{GenericImageView, Rgba}; use image::{GenericImageView, Rgba};
use std::sync::Arc;
use tiny_skia as sk; use tiny_skia as sk;
use tiny_skia::IntSize;
use typst_library::foundations::Smart; use typst_library::foundations::Smart;
use typst_library::layout::Size; use typst_library::layout::Size;
use typst_library::visualize::{Image, ImageKind, ImageScaling}; use typst_library::visualize::{Image, ImageKind, ImageScaling, PdfImage};
use crate::{AbsExt, State}; use crate::{AbsExt, State};
@ -59,9 +60,9 @@ pub fn render_image(
/// Prepare a texture for an image at a scaled size. /// Prepare a texture for an image at a scaled size.
#[comemo::memoize] #[comemo::memoize]
fn build_texture(image: &Image, w: u32, h: u32) -> Option<Arc<sk::Pixmap>> { fn build_texture(image: &Image, w: u32, h: u32) -> Option<Arc<sk::Pixmap>> {
let mut texture = sk::Pixmap::new(w, h)?; let texture = match image.kind() {
match image.kind() {
ImageKind::Raster(raster) => { ImageKind::Raster(raster) => {
let mut texture = sk::Pixmap::new(w, h)?;
let w = texture.width(); let w = texture.width();
let h = texture.height(); let h = texture.height();
@ -85,15 +86,63 @@ fn build_texture(image: &Image, w: u32, h: u32) -> Option<Arc<sk::Pixmap>> {
let Rgba([r, g, b, a]) = src; let Rgba([r, g, b, a]) = src;
*dest = sk::ColorU8::from_rgba(r, g, b, a).premultiply(); *dest = sk::ColorU8::from_rgba(r, g, b, a).premultiply();
} }
texture
} }
ImageKind::Svg(svg) => { ImageKind::Svg(svg) => {
let mut texture = sk::Pixmap::new(w, h)?;
let tree = svg.tree(); let tree = svg.tree();
let ts = tiny_skia::Transform::from_scale( let ts = tiny_skia::Transform::from_scale(
w as f32 / tree.size().width(), w as f32 / tree.size().width(),
h as f32 / tree.size().height(), h as f32 / tree.size().height(),
); );
resvg::render(tree, ts, &mut texture.as_mut()); resvg::render(tree, ts, &mut texture.as_mut());
texture
} }
} ImageKind::Pdf(pdf) => build_pdf_texture(pdf, w, h)?,
};
Some(Arc::new(texture)) Some(Arc::new(texture))
} }
// Keep this in sync with `typst-svg`!
fn build_pdf_texture(pdf: &PdfImage, w: u32, h: u32) -> Option<sk::Pixmap> {
let select_standard_font = move |font: StandardFont| -> Option<(FontData, u32)> {
let bytes = match font {
StandardFont::Helvetica => typst_assets::pdf::SANS,
StandardFont::HelveticaBold => typst_assets::pdf::SANS_BOLD,
StandardFont::HelveticaOblique => typst_assets::pdf::SANS_ITALIC,
StandardFont::HelveticaBoldOblique => typst_assets::pdf::SANS_BOLD_ITALIC,
StandardFont::Courier => typst_assets::pdf::FIXED,
StandardFont::CourierBold => typst_assets::pdf::FIXED_BOLD,
StandardFont::CourierOblique => typst_assets::pdf::FIXED_ITALIC,
StandardFont::CourierBoldOblique => typst_assets::pdf::FIXED_BOLD_ITALIC,
StandardFont::TimesRoman => typst_assets::pdf::SERIF,
StandardFont::TimesBold => typst_assets::pdf::SERIF_BOLD,
StandardFont::TimesItalic => typst_assets::pdf::SERIF_ITALIC,
StandardFont::TimesBoldItalic => typst_assets::pdf::SERIF_BOLD_ITALIC,
StandardFont::ZapfDingBats => typst_assets::pdf::DING_BATS,
StandardFont::Symbol => typst_assets::pdf::SYMBOL,
};
Some((Arc::new(bytes), 0))
};
let interpreter_settings = InterpreterSettings {
font_resolver: Arc::new(move |query| match query {
FontQuery::Standard(s) => select_standard_font(*s),
FontQuery::Fallback(f) => select_standard_font(f.pick_standard_font()),
}),
warning_sink: Arc::new(|_| {}),
};
let render_settings = RenderSettings {
x_scale: w as f32 / pdf.width(),
y_scale: h as f32 / pdf.height(),
width: Some(w as u16),
height: Some(h as u16),
};
let hayro_pix = hayro::render(pdf.page(), &interpreter_settings, &render_settings);
sk::Pixmap::from_vec(hayro_pix.take_u8(), IntSize::from_wh(w, h)?)
}

View File

@ -13,6 +13,7 @@ keywords = { workspace = true }
readme = { workspace = true } readme = { workspace = true }
[dependencies] [dependencies]
typst-assets = { workspace = true }
typst-library = { workspace = true } typst-library = { workspace = true }
typst-macros = { workspace = true } typst-macros = { workspace = true }
typst-timing = { workspace = true } typst-timing = { workspace = true }
@ -21,6 +22,7 @@ base64 = { workspace = true }
comemo = { workspace = true } comemo = { workspace = true }
ecow = { workspace = true } ecow = { workspace = true }
flate2 = { workspace = true } flate2 = { workspace = true }
hayro = { workspace = true }
image = { workspace = true } image = { workspace = true }
ttf-parser = { workspace = true } ttf-parser = { workspace = true }
xmlparser = { workspace = true } xmlparser = { workspace = true }

View File

@ -1,10 +1,13 @@
use std::sync::Arc;
use base64::Engine; use base64::Engine;
use ecow::{EcoString, eco_format}; use ecow::{EcoString, eco_format};
use hayro::{FontData, FontQuery, InterpreterSettings, RenderSettings, StandardFont};
use image::{ImageEncoder, codecs::png::PngEncoder}; use image::{ImageEncoder, codecs::png::PngEncoder};
use typst_library::foundations::Smart; use typst_library::foundations::Smart;
use typst_library::layout::{Abs, Axes}; use typst_library::layout::{Abs, Axes};
use typst_library::visualize::{ use typst_library::visualize::{
ExchangeFormat, Image, ImageKind, ImageScaling, RasterFormat, ExchangeFormat, Image, ImageKind, ImageScaling, PdfImage, RasterFormat,
}; };
use crate::SVGRenderer; use crate::SVGRenderer;
@ -66,6 +69,25 @@ pub fn convert_image_to_base64_url(image: &Image) -> EcoString {
}), }),
}, },
ImageKind::Svg(svg) => ("svg+xml", svg.data()), ImageKind::Svg(svg) => ("svg+xml", svg.data()),
ImageKind::Pdf(pdf) => {
// To make sure the image isn't pixelated, we always scale up so the
// lowest dimension has at least 1000 pixels. However, we only scale
// up as much so that the largest dimension doesn't exceed 3000
// pixels.
const MIN_RES: f32 = 1000.0;
const MAX_RES: f32 = 3000.0;
let base_width = pdf.width();
let w_scale = (MIN_RES / base_width).max(MAX_RES / base_width);
let base_height = pdf.height();
let h_scale = (MIN_RES / base_height).min(MAX_RES / base_height);
let total_scale = w_scale.min(h_scale);
let width = (base_width * total_scale).ceil() as u32;
let height = (base_height * total_scale).ceil() as u32;
buf = pdf_to_png(pdf, width, height);
("png", buf.as_slice())
}
}; };
let mut url = eco_format!("data:image/{format};base64,"); let mut url = eco_format!("data:image/{format};base64,");
@ -73,3 +95,45 @@ pub fn convert_image_to_base64_url(image: &Image) -> EcoString {
url.push_str(&data); url.push_str(&data);
url url
} }
// Keep this in sync with `typst-png`!
fn pdf_to_png(pdf: &PdfImage, w: u32, h: u32) -> Vec<u8> {
let select_standard_font = move |font: StandardFont| -> Option<(FontData, u32)> {
let bytes = match font {
StandardFont::Helvetica => typst_assets::pdf::SANS,
StandardFont::HelveticaBold => typst_assets::pdf::SANS_BOLD,
StandardFont::HelveticaOblique => typst_assets::pdf::SANS_ITALIC,
StandardFont::HelveticaBoldOblique => typst_assets::pdf::SANS_BOLD_ITALIC,
StandardFont::Courier => typst_assets::pdf::FIXED,
StandardFont::CourierBold => typst_assets::pdf::FIXED_BOLD,
StandardFont::CourierOblique => typst_assets::pdf::FIXED_ITALIC,
StandardFont::CourierBoldOblique => typst_assets::pdf::FIXED_BOLD_ITALIC,
StandardFont::TimesRoman => typst_assets::pdf::SERIF,
StandardFont::TimesBold => typst_assets::pdf::SERIF_BOLD,
StandardFont::TimesItalic => typst_assets::pdf::SERIF_ITALIC,
StandardFont::TimesBoldItalic => typst_assets::pdf::SERIF_BOLD_ITALIC,
StandardFont::ZapfDingBats => typst_assets::pdf::DING_BATS,
StandardFont::Symbol => typst_assets::pdf::SYMBOL,
};
Some((Arc::new(bytes), 0))
};
let interpreter_settings = InterpreterSettings {
font_resolver: Arc::new(move |query| match query {
FontQuery::Standard(s) => select_standard_font(*s),
FontQuery::Fallback(f) => select_standard_font(f.pick_standard_font()),
}),
warning_sink: Arc::new(|_| {}),
};
let render_settings = RenderSettings {
x_scale: w as f32 / pdf.width(),
y_scale: h as f32 / pdf.height(),
width: Some(w as u16),
height: Some(h as u16),
};
let hayro_pix = hayro::render(pdf.page(), &interpreter_settings, &render_settings);
hayro_pix.take_png()
}

View File

@ -665,12 +665,3 @@ applicable, contains possible workarounds.
[`page` function]($page) which will force a page break. If you just want a few [`page` function]($page) which will force a page break. If you just want a few
paragraphs to stretch into the margins, then reverting to the old margins, you paragraphs to stretch into the margins, then reverting to the old margins, you
can use the [`pad` function]($pad) with negative padding. can use the [`pad` function]($pad) with negative padding.
- **Include PDFs as images.** In LaTeX, it has become customary to insert vector
graphics as PDF or EPS files. Typst supports neither format as an image
format, but you can easily convert both into SVG files with [online
tools](https://cloudconvert.com/pdf-to-svg) or
[Inkscape](https://inkscape.org/). The web app will automatically convert PDF
files to SVG files upon uploading them. You can also use the
community-provided [`muchpdf` package](https://typst.app/universe/package/muchpdf)
to embed PDFs. It internally converts PDFs to SVGs on-the-fly.

View File

@ -17,6 +17,7 @@ use serde::Deserialize;
use serde_yaml as yaml; use serde_yaml as yaml;
use std::sync::LazyLock; use std::sync::LazyLock;
use typst::diag::{StrResult, bail}; use typst::diag::{StrResult, bail};
use typst::foundations::Deprecation;
use typst::foundations::{ use typst::foundations::{
AutoValue, Binding, Bytes, CastInfo, Func, Module, NoneValue, ParamInfo, Repr, Scope, AutoValue, Binding, Bytes, CastInfo, Func, Module, NoneValue, ParamInfo, Repr, Scope,
Smart, Type, Value, Smart, Type, Value,
@ -381,7 +382,7 @@ fn func_page(
parent: &str, parent: &str,
func: &Func, func: &Func,
path: &[&str], path: &[&str],
deprecation: Option<&'static str>, deprecation: Option<&Deprecation>,
) -> PageModel { ) -> PageModel {
let model = func_model(resolver, func, path, false, deprecation); let model = func_model(resolver, func, path, false, deprecation);
let name = func.name().unwrap(); let name = func.name().unwrap();
@ -402,7 +403,7 @@ fn func_model(
func: &Func, func: &Func,
path: &[&str], path: &[&str],
nested: bool, nested: bool,
deprecation: Option<&'static str>, deprecation: Option<&Deprecation>,
) -> FuncModel { ) -> FuncModel {
let name = func.name().unwrap(); let name = func.name().unwrap();
let scope = func.scope().unwrap(); let scope = func.scope().unwrap();
@ -438,7 +439,8 @@ fn func_model(
oneliner: oneliner(details), oneliner: oneliner(details),
element: func.element().is_some(), element: func.element().is_some(),
contextual: func.contextual().unwrap_or(false), contextual: func.contextual().unwrap_or(false),
deprecation, deprecation_message: deprecation.map(Deprecation::message),
deprecation_until: deprecation.and_then(Deprecation::until),
details: Html::markdown(resolver, details, nesting), details: Html::markdown(resolver, details, nesting),
example: example.map(|md| Html::markdown(resolver, md, None)), example: example.map(|md| Html::markdown(resolver, md, None)),
self_, self_,
@ -718,7 +720,7 @@ fn symbols_model(resolver: &dyn Resolver, group: &GroupData) -> SymbolsModel {
} }
}; };
for (variant, c, deprecation) in symbol.variants() { for (variant, c, deprecation_message) in symbol.variants() {
let shorthand = |list: &[(&'static str, char)]| { let shorthand = |list: &[(&'static str, char)]| {
list.iter().copied().find(|&(_, x)| x == c).map(|(s, _)| s) list.iter().copied().find(|&(_, x)| x == c).map(|(s, _)| s)
}; };
@ -737,7 +739,9 @@ fn symbols_model(resolver: &dyn Resolver, group: &GroupData) -> SymbolsModel {
.filter(|(other, _, _)| other != &variant) .filter(|(other, _, _)| other != &variant)
.map(|(other, _, _)| complete(other)) .map(|(other, _, _)| complete(other))
.collect(), .collect(),
deprecation: deprecation.or_else(|| binding.deprecation()), deprecation_message: deprecation_message
.or_else(|| binding.deprecation().map(Deprecation::message)),
deprecation_until: binding.deprecation().and_then(Deprecation::until),
}); });
} }
} }

View File

@ -89,7 +89,8 @@ pub struct FuncModel {
pub oneliner: EcoString, pub oneliner: EcoString,
pub element: bool, pub element: bool,
pub contextual: bool, pub contextual: bool,
pub deprecation: Option<&'static str>, pub deprecation_message: Option<&'static str>,
pub deprecation_until: Option<&'static str>,
pub details: Html, pub details: Html,
/// This example is only for nested function models. Others can have /// This example is only for nested function models. Others can have
/// their example directly in their details. /// their example directly in their details.
@ -165,7 +166,8 @@ pub struct SymbolModel {
pub markup_shorthand: Option<&'static str>, pub markup_shorthand: Option<&'static str>,
pub math_shorthand: Option<&'static str>, pub math_shorthand: Option<&'static str>,
pub math_class: Option<&'static str>, pub math_class: Option<&'static str>,
pub deprecation: Option<&'static str>, pub deprecation_message: Option<&'static str>,
pub deprecation_until: Option<&'static str>,
} }
/// Shorthands listed on a category page. /// Shorthands listed on a category page.

Binary file not shown.

After

Width:  |  Height:  |  Size: 399 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.1 KiB

After

Width:  |  Height:  |  Size: 2.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.4 KiB

BIN
tests/ref/image-pdf.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.9 KiB

View File

@ -1,3 +1,4 @@
--- cbor-decode-deprecated --- --- cbor-decode-deprecated ---
// Warning: 15-21 `cbor.decode` is deprecated, directly pass bytes to `cbor` instead // Warning: 15-21 `cbor.decode` is deprecated, directly pass bytes to `cbor` instead
// Hint: 15-21 this will be removed in 0.15.0
#let _ = cbor.decode #let _ = cbor.decode

View File

@ -32,4 +32,5 @@
--- csv-decode-deprecated --- --- csv-decode-deprecated ---
// Warning: 14-20 `csv.decode` is deprecated, directly pass bytes to `csv` instead // Warning: 14-20 `csv.decode` is deprecated, directly pass bytes to `csv` instead
// Hint: 14-20 this will be removed in 0.15.0
#let _ = csv.decode #let _ = csv.decode

View File

@ -11,6 +11,7 @@
--- json-decode-deprecated --- --- json-decode-deprecated ---
// Warning: 15-21 `json.decode` is deprecated, directly pass bytes to `json` instead // Warning: 15-21 `json.decode` is deprecated, directly pass bytes to `json` instead
// Hint: 15-21 this will be removed in 0.15.0
#let _ = json.decode #let _ = json.decode
--- issue-3363-json-large-number --- --- issue-3363-json-large-number ---

View File

@ -42,4 +42,5 @@
--- toml-decode-deprecated --- --- toml-decode-deprecated ---
// Warning: 15-21 `toml.decode` is deprecated, directly pass bytes to `toml` instead // Warning: 15-21 `toml.decode` is deprecated, directly pass bytes to `toml` instead
// Hint: 15-21 this will be removed in 0.15.0
#let _ = toml.decode #let _ = toml.decode

View File

@ -29,4 +29,5 @@
--- xml-decode-deprecated --- --- xml-decode-deprecated ---
// Warning: 14-20 `xml.decode` is deprecated, directly pass bytes to `xml` instead // Warning: 14-20 `xml.decode` is deprecated, directly pass bytes to `xml` instead
// Hint: 14-20 this will be removed in 0.15.0
#let _ = xml.decode #let _ = xml.decode

View File

@ -18,4 +18,5 @@
--- yaml-decode-deprecated --- --- yaml-decode-deprecated ---
// Warning: 15-21 `yaml.decode` is deprecated, directly pass bytes to `yaml` instead // Warning: 15-21 `yaml.decode` is deprecated, directly pass bytes to `yaml` instead
// Hint: 15-21 this will be removed in 0.15.0
#let _ = yaml.decode #let _ = yaml.decode

View File

@ -130,6 +130,16 @@
down, up, down, up, down, down, up, down, up, down,
) )
--- curve-stroke-gradient-sharp ---
#set page(width: auto)
#let down = curve.line((40pt, 40pt), relative: true)
#let up = curve.line((40pt, -40pt), relative: true)
#curve(
stroke: 4pt + gradient.linear(red, blue).sharp(3),
down, up, down, up, down,
)
--- curve-fill-rule --- --- curve-fill-rule ---
#stack( #stack(
dir: ltr, dir: ltr,

View File

@ -188,26 +188,31 @@ A #box(image("/assets/images/tiger.jpg", height: 1cm, width: 80%)) B
--- image-decode-svg --- --- image-decode-svg ---
// Test parsing from svg data // Test parsing from svg data
// Warning: 8-14 `image.decode` is deprecated, directly pass bytes to `image` instead // Warning: 8-14 `image.decode` is deprecated, directly pass bytes to `image` instead
// Hint: 8-14 this will be removed in 0.15.0
#image.decode(`<svg xmlns="http://www.w3.org/2000/svg" height="140" width="500"><ellipse cx="200" cy="80" rx="100" ry="50" style="fill:yellow;stroke:purple;stroke-width:2" /></svg>`.text, format: "svg") #image.decode(`<svg xmlns="http://www.w3.org/2000/svg" height="140" width="500"><ellipse cx="200" cy="80" rx="100" ry="50" style="fill:yellow;stroke:purple;stroke-width:2" /></svg>`.text, format: "svg")
--- image-decode-bad-svg --- --- image-decode-bad-svg ---
// Error: 15-152 failed to parse SVG (missing root node at 1:1) // Error: 15-152 failed to parse SVG (missing root node at 1:1)
// Warning: 8-14 `image.decode` is deprecated, directly pass bytes to `image` instead // Warning: 8-14 `image.decode` is deprecated, directly pass bytes to `image` instead
// Hint: 8-14 this will be removed in 0.15.0
#image.decode(`<svg height="140" width="500"><ellipse cx="200" cy="80" rx="100" ry="50" style="fill:yellow;stroke:purple;stroke-width:2" /></svg>`.text, format: "svg") #image.decode(`<svg height="140" width="500"><ellipse cx="200" cy="80" rx="100" ry="50" style="fill:yellow;stroke:purple;stroke-width:2" /></svg>`.text, format: "svg")
--- image-decode-detect-format --- --- image-decode-detect-format ---
// Test format auto detect // Test format auto detect
// Warning: 8-14 `image.decode` is deprecated, directly pass bytes to `image` instead // Warning: 8-14 `image.decode` is deprecated, directly pass bytes to `image` instead
// Hint: 8-14 this will be removed in 0.15.0
#image.decode(read("/assets/images/tiger.jpg", encoding: none), width: 80%) #image.decode(read("/assets/images/tiger.jpg", encoding: none), width: 80%)
--- image-decode-specify-format --- --- image-decode-specify-format ---
// Test format manual // Test format manual
// Warning: 8-14 `image.decode` is deprecated, directly pass bytes to `image` instead // Warning: 8-14 `image.decode` is deprecated, directly pass bytes to `image` instead
// Hint: 8-14 this will be removed in 0.15.0
#image.decode(read("/assets/images/tiger.jpg", encoding: none), format: "jpg", width: 80%) #image.decode(read("/assets/images/tiger.jpg", encoding: none), format: "jpg", width: 80%)
--- image-decode-specify-wrong-format --- --- image-decode-specify-wrong-format ---
// Error: 2-91 failed to decode image (Format error decoding Png: Invalid PNG signature.) // Error: 2-91 failed to decode image (Format error decoding Png: Invalid PNG signature.)
// Warning: 8-14 `image.decode` is deprecated, directly pass bytes to `image` instead // Warning: 8-14 `image.decode` is deprecated, directly pass bytes to `image` instead
// Hint: 8-14 this will be removed in 0.15.0
#image.decode(read("/assets/images/tiger.jpg", encoding: none), format: "png", width: 80%) #image.decode(read("/assets/images/tiger.jpg", encoding: none), format: "png", width: 80%)
--- image-pixmap-empty --- --- image-pixmap-empty ---
@ -258,7 +263,7 @@ A #box(image("/assets/images/tiger.jpg", height: 1cm, width: 80%)) B
--- image-png-but-pixmap-format --- --- image-png-but-pixmap-format ---
#image( #image(
read("/assets/images/tiger.jpg", encoding: none), read("/assets/images/tiger.jpg", encoding: none),
// Error: 11-18 expected "png", "jpg", "gif", "webp", dictionary, "svg", or auto // Error: 11-18 expected "png", "jpg", "gif", "webp", dictionary, "svg", "pdf", or auto
format: "rgba8", format: "rgba8",
) )
@ -289,3 +294,16 @@ A #box(image("/assets/images/tiger.jpg", height: 1cm, width: 80%)) B
..rotations.map(v => raw(str(v), lang: "typc")), ..rotations.map(v => raw(str(v), lang: "typc")),
..rotations.map(rotated) ..rotations.map(rotated)
) )
--- image-pdf ---
#image("/assets/images/matplotlib.pdf")
--- image-pdf-multiple-pages ---
#image("/assets/images/diagrams.pdf", page: 1)
#image("/assets/images/diagrams.pdf", page: 3)
#image("/assets/images/diagrams.pdf", page: 2)
--- image-pdf-invalid-page ---
// Error: 2-49 page 2 does not exist
// Hint: 2-49 the document only has 1 page
#image("/assets/images/matplotlib.pdf", page: 2)

View File

@ -161,5 +161,6 @@
#set page(width: auto, height: auto, margin: 0pt) #set page(width: auto, height: auto, margin: 0pt)
// Warning: 10-17 the name `pattern` is deprecated, use `tiling` instead // Warning: 10-17 the name `pattern` is deprecated, use `tiling` instead
// Hint: 10-17 this will be removed in 0.15.0
#let t = pattern(size: (10pt, 10pt), line(stroke: 4pt, start: (0%, 0%), end: (100%, 100%))) #let t = pattern(size: (10pt, 10pt), line(stroke: 4pt, start: (0%, 0%), end: (100%, 100%)))
#rect(width: 50pt, height: 50pt, fill: t) #rect(width: 50pt, height: 50pt, fill: t)