Compare commits

...

6 Commits

Author SHA1 Message Date
arielf212
4bdd77db37
Merge e2a022ac7ba5f60cdbcc6f526297ffe2449e9a07 into af2253ba16dfdc731e787e3a43a6f6a63ea65e0a 2025-07-22 19:21:54 +08: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
Ariel Fridman
e2a022ac7b Activate socks-proxy feature in ureq
This closes #3702
2025-06-02 23:53:40 +03:00
Ariel Fridman
3044bb0c76 Remove dependency on env_proxy
ureq 3 supports picking up the proxy env vars by itself, so no need to bring in this dependency with us!
2025-06-02 23:53:40 +03:00
Ariel Fridman
97430912ce Fix breaking changes caused by move to ureq 3
None of the changes cause any logic changes (I think).
They just deal with stuff changed/deprecated in ureq 3.
2025-06-02 23:53:38 +03:00
Ariel Fridman
1197092a03 Move to ureq 3
ureq 2 is getting deprecated, and we have encountered a bug regarding it (#6261).
2025-06-02 23:52:02 +03:00
20 changed files with 713 additions and 114 deletions

335
Cargo.lock generated
View File

@ -136,6 +136,12 @@ version = "0.22.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "72b3254f16251a8381aa12e40e3c4d2f0199f8c6508fbecb9d91f575e0fbb8c6"
[[package]]
name = "base64ct"
version = "1.7.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "89e25b6adfb930f02d1981565a6e5d9c547ac15a96606256d3b59040e5cd4ca3"
[[package]]
name = "biblatex"
version = "0.10.0"
@ -181,9 +187,9 @@ checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a"
[[package]]
name = "bitflags"
version = "2.8.0"
version = "2.9.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8f68f53c83ab957f72c32642f3868eec03eb974d1fb82e453128456482613d36"
checksum = "1b8e56985ec62d17e9c1001dc89c88ecd7dc08e47eba5ec7c29c7b5eeecde967"
dependencies = [
"serde",
]
@ -214,9 +220,9 @@ checksum = "64fa3c856b712db6612c019f14756e64e4bcea13337a6b33b696333a9eaa2d06"
[[package]]
name = "bytemuck"
version = "1.21.0"
version = "1.23.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ef657dfab802224e671f5818e9a4935f9b1957ed18e58292690cc39e7a4092a3"
checksum = "5c76a5792e44e4abe34d3abf15636779261d45a7450612059293d1d2cfc63422"
dependencies = [
"bytemuck_derive",
]
@ -244,6 +250,12 @@ version = "0.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8f1fe948ff07f4bd06c30984e69f5b4899c516a3ef74f34df92a2df2ab535495"
[[package]]
name = "bytes"
version = "1.10.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d71b6127be86fdcfddb610f7182ac57211d4b18a3e9c82eb2d17662f2227ad6a"
[[package]]
name = "cc"
version = "1.2.11"
@ -471,6 +483,35 @@ dependencies = [
"syn",
]
[[package]]
name = "cookie"
version = "0.18.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4ddef33a339a91ea89fb53151bd0a4689cfce27055c291dfa69945475d22c747"
dependencies = [
"percent-encoding",
"time",
"version_check",
]
[[package]]
name = "cookie_store"
version = "0.21.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2eac901828f88a5241ee0600950ab981148a18f2f756900ffba1b125ca6a3ef9"
dependencies = [
"cookie",
"document-features",
"idna",
"indexmap 2.7.1",
"log",
"serde",
"serde_derive",
"serde_json",
"time",
"url",
]
[[package]]
name = "core-foundation"
version = "0.9.4"
@ -572,6 +613,16 @@ version = "0.3.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5c297a1c74b71ae29df00c3e22dd9534821d60eb9af5a0192823fa2acea70c2a"
[[package]]
name = "der"
version = "0.7.10"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e7c1832837b905bbfb5101e07cc24c8deddf52f93225eee6ead5f4d63d53ddcb"
dependencies = [
"pem-rfc7468",
"zeroize",
]
[[package]]
name = "deranged"
version = "0.3.11"
@ -624,6 +675,15 @@ dependencies = [
"syn",
]
[[package]]
name = "document-features"
version = "0.2.11"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "95249b50c6c185bee49034bcb378a49dc2b5dff0be90ff6616d31d64febab05d"
dependencies = [
"litrs",
]
[[package]]
name = "downcast-rs"
version = "1.2.1"
@ -677,16 +737,6 @@ dependencies = [
"syn",
]
[[package]]
name = "env_proxy"
version = "0.4.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3a5019be18538406a43b5419a5501461f0c8b49ea7dfda0cfc32f4e51fc44be1"
dependencies = [
"log",
"url",
]
[[package]]
name = "equivalent"
version = "1.0.1"
@ -964,12 +1014,92 @@ dependencies = [
"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]]
name = "heck"
version = "0.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea"
[[package]]
name = "http"
version = "1.3.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f4a85d31aea989eead29a3aaf9e1115a180df8282431156e533de47660892565"
dependencies = [
"bytes",
"fnv",
"itoa",
]
[[package]]
name = "httparse"
version = "1.10.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6dbf3de79e51f3d586ab4cb9d5c3e2c14aa28ed23d180cf89b4df0454a69cc87"
[[package]]
name = "httpdate"
version = "1.0.3"
@ -1200,9 +1330,9 @@ dependencies = [
[[package]]
name = "image"
version = "0.25.5"
version = "0.25.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "cd6f44aed642f18953a158afeb30206f4d50da59fbc66ecb53c66488de73563b"
checksum = "db35664ce6b9810857a38a906215e75a9c879f0696556a39f59c62829710251a"
dependencies = [
"bytemuck",
"byteorder-lite",
@ -1265,7 +1395,7 @@ version = "0.11.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f37dccff2791ab604f9babef0ba14fbe0be30bd368dc541e2b08d07c8aa908f3"
dependencies = [
"bitflags 2.8.0",
"bitflags 2.9.1",
"inotify-sys",
"libc",
]
@ -1361,7 +1491,7 @@ dependencies = [
[[package]]
name = "krilla"
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 = [
"base64",
"bumpalo",
@ -1370,6 +1500,7 @@ dependencies = [
"float-cmp 0.10.0",
"fxhash",
"gif",
"hayro-write",
"image-webp",
"imagesize",
"once_cell",
@ -1379,6 +1510,7 @@ dependencies = [
"rustybuzz",
"siphasher",
"skrifa",
"smallvec",
"subsetter",
"tiny-skia-path",
"xmp-writer",
@ -1389,7 +1521,7 @@ dependencies = [
[[package]]
name = "krilla-svg"
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 = [
"flate2",
"fontdb",
@ -1402,9 +1534,9 @@ dependencies = [
[[package]]
name = "kurbo"
version = "0.11.1"
version = "0.11.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "89234b2cc610a7dd927ebde6b41dd1a5d4214cffaef4cf1fb2195d592f92518f"
checksum = "1077d333efea6170d9ccb96d3c3026f300ca0773da4938cc4c811daa6df68b0c"
dependencies = [
"arrayvec",
"smallvec",
@ -1456,7 +1588,7 @@ version = "0.1.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c0ff37bd590ca25063e35af745c343cb7a0271906fb7b37e4813e8f79f00268d"
dependencies = [
"bitflags 2.8.0",
"bitflags 2.9.1",
"libc",
"redox_syscall",
]
@ -1501,6 +1633,12 @@ dependencies = [
"serde",
]
[[package]]
name = "litrs"
version = "0.4.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b4ce301924b7887e9d637144fdade93f9dfff9b60981d4ac161db09720d39aa5"
[[package]]
name = "lock_api"
version = "0.4.12"
@ -1622,7 +1760,7 @@ version = "8.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2fee8403b3d66ac7b26aee6e40a897d85dc5ce26f44da36b8b73e987cc52e943"
dependencies = [
"bitflags 2.8.0",
"bitflags 2.9.1",
"filetime",
"fsevent-sys",
"inotify",
@ -1704,7 +1842,7 @@ version = "0.10.72"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "fedfea7d58a1f73118430a55da6a286e7b044961736ce96a16a17068ea25e5da"
dependencies = [
"bitflags 2.8.0",
"bitflags 2.9.1",
"cfg-if",
"foreign-types",
"libc",
@ -1841,12 +1979,21 @@ version = "0.13.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3ea27c5015ab81753fc61e49f8cde74999346605ee148bb20008ef3d3150e0dc"
dependencies = [
"bitflags 2.8.0",
"bitflags 2.9.1",
"itoa",
"memchr",
"ryu",
]
[[package]]
name = "pem-rfc7468"
version = "0.7.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "88b39c9bfcfc231068454382784bb460aae594343fb030d46e9f50a645418412"
dependencies = [
"base64ct",
]
[[package]]
name = "percent-encoding"
version = "2.3.1"
@ -1999,7 +2146,7 @@ version = "0.9.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "57206b407293d2bcd3af849ce869d52068623f19e1b5ff8e8778e3309439682b"
dependencies = [
"bitflags 2.8.0",
"bitflags 2.9.1",
"getopts",
"memchr",
"unicase",
@ -2112,7 +2259,7 @@ version = "0.5.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "03a862b389f93e68874fbf580b9de08dd02facb9a788ebadaf4a3fd33cf58834"
dependencies = [
"bitflags 2.8.0",
"bitflags 2.9.1",
]
[[package]]
@ -2215,13 +2362,31 @@ version = "0.38.44"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "fdb5bc1ae2baa591800df16c9ca78619bf65c0488b41b96ccec5d11220d8c154"
dependencies = [
"bitflags 2.8.0",
"bitflags 2.9.1",
"errno",
"libc",
"linux-raw-sys",
"windows-sys 0.59.0",
]
[[package]]
name = "rustls-pemfile"
version = "2.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "dce314e5fee3f39953d46bb63bb8a46d40c2f8fb7cc5a3b6cab2bde9721d6e50"
dependencies = [
"rustls-pki-types",
]
[[package]]
name = "rustls-pki-types"
version = "1.12.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "229a4a4c221013e7e1f1a043678c5cc39fe5171437c88fb47151a21e6f5b5c79"
dependencies = [
"zeroize",
]
[[package]]
name = "rustversion"
version = "1.0.19"
@ -2234,7 +2399,7 @@ version = "0.20.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "fd3c7c96f8a08ee34eff8857b11b49b07d71d1c3f4e88f8a88d4c9e9f90b1702"
dependencies = [
"bitflags 2.8.0",
"bitflags 2.9.1",
"bytemuck",
"core_maths",
"log",
@ -2282,7 +2447,7 @@ version = "2.11.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "897b2245f0b511c87893af39b033e5ca9cce68824c4d7e7630b5a1d339658d02"
dependencies = [
"bitflags 2.8.0",
"bitflags 2.9.1",
"core-foundation",
"core-foundation-sys",
"libc",
@ -2445,9 +2610,20 @@ dependencies = [
[[package]]
name = "smallvec"
version = "1.13.2"
version = "1.15.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3c5e1a9a646d36c3599cd173a41282daf47c44583ad367b8e6837255952e5c67"
checksum = "67b1b7a3b5fe4f1376887184045fcf45c69e92af734b7aaddc05fb777b6fbd03"
[[package]]
name = "socks"
version = "0.3.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f0c3dbbd9ae980613c6dd8e28a9407b50509d3803b57624d5dfe8315218cd58b"
dependencies = [
"byteorder",
"libc",
"winapi",
]
[[package]]
name = "spin"
@ -2855,7 +3031,7 @@ dependencies = [
[[package]]
name = "typst-assets"
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]]
name = "typst-cli"
@ -2905,7 +3081,7 @@ dependencies = [
[[package]]
name = "typst-dev-assets"
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]]
name = "typst-docs"
@ -3000,7 +3176,6 @@ version = "0.13.1"
dependencies = [
"dirs",
"ecow",
"env_proxy",
"fastrand",
"flate2",
"fontdb",
@ -3055,7 +3230,7 @@ name = "typst-library"
version = "0.13.1"
dependencies = [
"az",
"bitflags 2.8.0",
"bitflags 2.9.1",
"bumpalo",
"chinese-number",
"ciborium",
@ -3067,6 +3242,7 @@ dependencies = [
"fontdb",
"glidesort",
"hayagriva",
"hayro-syntax",
"icu_properties",
"icu_provider",
"icu_provider_blob",
@ -3165,11 +3341,13 @@ version = "0.13.1"
dependencies = [
"bytemuck",
"comemo",
"hayro",
"image",
"pixglyph",
"resvg",
"tiny-skia",
"ttf-parser",
"typst-assets",
"typst-library",
"typst-macros",
"typst-timing",
@ -3183,8 +3361,10 @@ dependencies = [
"comemo",
"ecow",
"flate2",
"hayro",
"image",
"ttf-parser",
"typst-assets",
"typst-library",
"typst-macros",
"typst-timing",
@ -3365,18 +3545,37 @@ checksum = "e9df2af067a7953e9c3831320f35c1cc0600c30d44d9f7a12b01db1cd88d6b47"
[[package]]
name = "ureq"
version = "2.12.1"
version = "3.0.11"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "02d1a66277ed75f640d608235660df48c8e3c19f3b4edb6a263315626cc3c01d"
checksum = "b7a3e9af6113ecd57b8c63d3cd76a385b2e3881365f1f489e54f49801d0c83ea"
dependencies = [
"base64",
"cookie_store",
"der",
"flate2",
"log",
"native-tls",
"once_cell",
"percent-encoding",
"rustls-pemfile",
"rustls-pki-types",
"serde",
"serde_json",
"url",
"socks",
"ureq-proto",
"utf-8",
"webpki-root-certs 0.26.11",
]
[[package]]
name = "ureq-proto"
version = "0.4.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "fadf18427d33828c311234884b7ba2afb57143e6e7e69fda7ee883b624661e36"
dependencies = [
"base64",
"http",
"httparse",
"log",
]
[[package]]
@ -3418,6 +3617,12 @@ dependencies = [
"xmlwriter",
]
[[package]]
name = "utf-8"
version = "0.7.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "09cc8ee72d2a9becf2f2febe0205bbed8fc6615b7cb429ad062dc7b7ddd036a9"
[[package]]
name = "utf16_iter"
version = "1.0.5"
@ -3581,7 +3786,7 @@ version = "0.221.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9845c470a2e10b61dd42c385839cdd6496363ed63b5c9e420b5488b77bd22083"
dependencies = [
"bitflags 2.8.0",
"bitflags 2.9.1",
"indexmap 2.7.1",
]
@ -3595,12 +3800,46 @@ dependencies = [
"wasm-bindgen",
]
[[package]]
name = "webpki-root-certs"
version = "0.26.11"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "75c7f0ef91146ebfb530314f5f1d24528d7f0767efbfd31dce919275413e393e"
dependencies = [
"webpki-root-certs 1.0.0",
]
[[package]]
name = "webpki-root-certs"
version = "1.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "01a83f7e1a9f8712695c03eabe9ed3fbca0feff0152f33f12593e5a6303cb1a4"
dependencies = [
"rustls-pki-types",
]
[[package]]
name = "weezl"
version = "0.1.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "53a85b86a771b1c87058196170769dd264f66c0782acf1ae6cc51bfd64b39082"
[[package]]
name = "winapi"
version = "0.3.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419"
dependencies = [
"winapi-i686-pc-windows-gnu",
"winapi-x86_64-pc-windows-gnu",
]
[[package]]
name = "winapi-i686-pc-windows-gnu"
version = "0.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6"
[[package]]
name = "winapi-util"
version = "0.1.9"
@ -3610,6 +3849,12 @@ dependencies = [
"windows-sys 0.59.0",
]
[[package]]
name = "winapi-x86_64-pc-windows-gnu"
version = "0.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f"
[[package]]
name = "windows-core"
version = "0.52.0"
@ -3716,7 +3961,7 @@ version = "0.33.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3268f3d866458b787f390cf61f4bbb563b922d091359f9608842999eaee3943c"
dependencies = [
"bitflags 2.8.0",
"bitflags 2.9.1",
]
[[package]]
@ -3887,6 +4132,12 @@ dependencies = [
"synstructure",
]
[[package]]
name = "zeroize"
version = "1.8.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ced3678a2879b30306d323f4542626697a464a97c0a07c9aebf7ebca65cd4dde"
[[package]]
name = "zerotrie"
version = "0.1.3"

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-timing = { path = "crates/typst-timing", 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-dev-assets = { git = "https://github.com/typst/typst-dev-assets", rev = "bfa947f" }
typst-assets = { git = "https://github.com/typst/typst-assets", rev = "fbf00f9" }
typst-dev-assets = { git = "https://github.com/typst/typst-dev-assets", rev = "c6c2acf" }
arrayvec = "0.7.4"
az = "1.2"
base64 = "0.22"
@ -54,13 +54,14 @@ csv = "1"
ctrlc = "3.4.1"
dirs = "6"
ecow = { version = "0.2", features = ["serde"] }
env_proxy = "0.4"
fastrand = "2.3"
flate2 = "1"
fontdb = { version = "0.23", default-features = false }
fs_extra = "1.3"
glidesort = "0.1.2"
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"
hypher = "0.1.4"
icu_properties = { version = "1.4", features = ["serde"] }
@ -72,8 +73,8 @@ image = { version = "0.25.5", default-features = false, features = ["png", "jpeg
indexmap = { version = "2", features = ["serde"] }
infer = { version = "0.19.0", default-features = false }
kamadak-exif = "0.6"
krilla = { git = "https://github.com/LaurenzV/krilla", rev = "20c14fe", default-features = false, features = ["raster-images", "comemo", "rayon"] }
krilla-svg = { git = "https://github.com/LaurenzV/krilla", rev = "20c14fe" }
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 = "37b9a00"}
kurbo = "0.11"
libfuzzer-sys = "0.4"
lipsum = "0.9"
@ -132,7 +133,7 @@ unicode-script = "0.5"
unicode-normalization = "0.1.24"
unicode-segmentation = "1"
unscanny = "0.1"
ureq = { version = "2", default-features = false, features = ["native-tls", "gzip", "json"] }
ureq = { version = "3", default-features = false, features = ["native-tls", "gzip", "json", "socks-proxy"] }
usvg = { version = "0.45", default-features = false, features = ["text"] }
utf8_iter = "1.0.4"
walkdir = "2"

View File

@ -145,10 +145,10 @@ impl Release {
};
match downloader.download(&url) {
Ok(response) => response.into_json().map_err(|err| {
Ok(mut response) => response.body_mut().read_json().map_err(|err| {
eco_format!("failed to parse release information ({err})")
}),
Err(ureq::Error::Status(404, _)) => {
Err(ureq::Error::StatusCode(404)) => {
bail!("release not found (searched at {url})")
}
Err(err) => bail!("failed to download release ({err})"),
@ -175,7 +175,7 @@ impl Release {
&mut PrintDownload("release"),
) {
Ok(data) => data,
Err(ureq::Error::Status(404, _)) => {
Err(ureq::Error::StatusCode(404)) => {
bail!("asset not found (searched for {})", asset.name);
}
Err(err) => bail!("failed to download asset ({err})"),

View File

@ -18,7 +18,6 @@ typst-timing = { workspace = true }
typst-utils = { workspace = true }
dirs = { workspace = true, optional = true }
ecow = { workspace = true }
env_proxy = { workspace = true, optional = true }
fastrand = { workspace = true, optional = true }
flate2 = { workspace = true, optional = true }
fontdb = { workspace = true, optional = true }
@ -41,7 +40,7 @@ default = ["fonts", "packages"]
fonts = ["dep:fontdb", "fontdb/memmap", "fontdb/fontconfig"]
# Add generic downloading utilities
downloads = ["dep:env_proxy", "dep:native-tls", "dep:ureq", "dep:openssl"]
downloads = ["dep:native-tls", "dep:ureq", "dep:openssl"]
# Add package downloading utilities, implies `downloads`
packages = ["downloads", "dep:dirs", "dep:flate2", "dep:tar", "dep:fastrand"]

View File

@ -13,9 +13,8 @@ use std::sync::Arc;
use std::time::{Duration, Instant};
use ecow::EcoString;
use native_tls::{Certificate, TlsConnector};
use once_cell::sync::OnceCell;
use ureq::Response;
use ureq::tls::{Certificate, RootCerts, TlsProvider};
/// Manages progress reporting for downloads.
pub trait Progress {
@ -57,7 +56,7 @@ pub struct DownloadState {
pub struct Downloader {
user_agent: EcoString,
cert_path: Option<PathBuf>,
cert: OnceCell<Certificate>,
cert: OnceCell<Certificate<'static>>,
}
impl Downloader {
@ -82,7 +81,10 @@ impl Downloader {
}
/// Crates a new downloader with the given user agent and certificate.
pub fn with_cert(user_agent: impl Into<EcoString>, cert: Certificate) -> Self {
pub fn with_cert(
user_agent: impl Into<EcoString>,
cert: Certificate<'static>,
) -> Self {
Self {
user_agent: user_agent.into(),
cert_path: None,
@ -96,7 +98,7 @@ impl Downloader {
/// - Returns `None` if `--cert` and `TYPST_CERT` are not set.
/// - Returns `Some(Ok(cert))` if the certificate was loaded successfully.
/// - Returns `Some(Err(err))` if an error occurred while loading the certificate.
pub fn cert(&self) -> Option<io::Result<&Certificate>> {
pub fn cert(&self) -> Option<io::Result<&Certificate<'static>>> {
self.cert_path.as_ref().map(|path| {
self.cert.get_or_try_init(|| {
let pem = std::fs::read(path)?;
@ -107,31 +109,34 @@ impl Downloader {
/// Download binary data from the given url.
#[allow(clippy::result_large_err)]
pub fn download(&self, url: &str) -> Result<ureq::Response, ureq::Error> {
let mut builder = ureq::AgentBuilder::new();
let mut tls = TlsConnector::builder();
pub fn download(
&self,
url: &str,
) -> Result<ureq::http::Response<ureq::Body>, ureq::Error> {
let mut builder = ureq::config::Config::builder();
// Set user agent.
builder = builder.user_agent(&self.user_agent);
// Get the network proxy config from the environment and apply it.
if let Some(proxy) = env_proxy::for_url_str(url)
.to_url()
.and_then(|url| ureq::Proxy::new(url).ok())
{
builder = builder.proxy(proxy);
}
// Apply a custom CA certificate if present.
if let Some(cert) = self.cert() {
tls.add_root_certificate(cert?.clone());
}
let maybe_cert = self.cert().transpose()?.cloned().map_or(
RootCerts::PlatformVerifier,
|cert| {
let certs = vec![cert];
RootCerts::Specific(Arc::new(certs))
},
);
// Configure native TLS.
let connector = tls.build().map_err(io::Error::other)?;
builder = builder.tls_connector(Arc::new(connector));
let tls_config = ureq::tls::TlsConfig::builder()
.provider(TlsProvider::NativeTls)
.root_certs(maybe_cert);
builder.build().get(url).call()
builder = builder.tls_config(tls_config.build());
let agent = ureq::Agent::new_with_config(builder.build());
agent.get(url).call()
}
/// Download binary data from the given url and report its progress.
@ -170,7 +175,7 @@ const SAMPLES: usize = 5;
/// over a websocket and reports its progress.
struct RemoteReader<'p> {
/// The reader returned by the ureq::Response.
reader: Box<dyn Read + Send + Sync + 'static>,
reader: ureq::BodyReader<'static>,
/// The download state, holding download metadata for progress reporting.
state: DownloadState,
/// The instant at which progress was last reported.
@ -184,13 +189,18 @@ impl<'p> RemoteReader<'p> {
///
/// The 'Content-Length' header is used as a size hint for read
/// optimization, if present.
fn from_response(response: Response, progress: &'p mut dyn Progress) -> Self {
fn from_response(
response: ureq::http::Response<ureq::Body>,
progress: &'p mut dyn Progress,
) -> Self {
let content_len: Option<usize> = response
.header("Content-Length")
.headers()
.get("Content-Length")
.and_then(|header| header.to_str().ok())
.and_then(|header| header.parse().ok());
Self {
reader: response.into_reader(),
reader: response.into_body().into_reader(),
last_progress: None,
state: DownloadState {
content_len,

View File

@ -150,10 +150,10 @@ impl PackageStorage {
.get_or_try_init(|| {
let url = format!("{DEFAULT_REGISTRY}/{DEFAULT_NAMESPACE}/index.json");
match self.downloader.download(&url) {
Ok(response) => response.into_json().map_err(|err| {
Ok(mut response) => response.body_mut().read_json().map_err(|err| {
eco_format!("failed to parse package index: {err}")
}),
Err(ureq::Error::Status(404, _)) => {
Err(ureq::Error::StatusCode(404)) => {
bail!("failed to fetch package index (not found)")
}
Err(err) => bail!("failed to fetch package index ({err})"),
@ -181,7 +181,7 @@ impl PackageStorage {
let data = match self.downloader.download_with_progress(&url, progress) {
Ok(data) => data,
Err(ureq::Error::Status(404, _)) => {
Err(ureq::Error::StatusCode(404)) => {
if let Ok(version) = self.determine_latest_version(&spec.versionless()) {
return Err(PackageError::VersionNotFound(spec.clone(), version));
} else {

View File

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

View File

@ -1,8 +1,10 @@
//! Image handling.
mod pdf;
mod raster;
mod svg;
pub use self::pdf::PdfImage;
pub use self::raster::{
ExchangeFormat, PixelEncoding, PixelFormat, RasterFormat, RasterImage,
};
@ -10,13 +12,15 @@ pub use self::svg::SvgImage;
use std::ffi::OsStr;
use std::fmt::{self, Debug, Formatter};
use std::num::NonZeroUsize;
use std::sync::Arc;
use ecow::EcoString;
use hayro_syntax::LoadPdfError;
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::foundations::{
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::model::Figurable;
use crate::text::{LocalName, families};
use crate::visualize::image::pdf::PdfDocument;
/// A raster or vector graphic.
///
@ -79,8 +84,7 @@ pub struct ImageElem {
/// format automatically, but that's not always possible).
///
/// Supported formats are `{"png"}`, `{"jpg"}`, `{"gif"}`, `{"svg"}`,
/// `{"webp"}` as well as raw pixel data. Embedding PDFs as images is
/// [not currently supported](https://github.com/typst/typst/issues/145).
/// `{"pdf"}`, `{"webp"}` as well as raw pixel data.
///
/// When providing raw pixel data as the `source`, you must specify a
/// dictionary with the following keys as the `format`:
@ -126,6 +130,11 @@ pub struct ImageElem {
/// A text describing the image.
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
/// 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
@ -261,6 +270,45 @@ impl Packed<ImageElem> {
)
.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)))
@ -286,6 +334,7 @@ impl Packed<ImageElem> {
"jpg" | "jpeg" => return Ok(ExchangeFormat::Jpg.into()),
"gif" => return Ok(ExchangeFormat::Gif.into()),
"svg" | "svgz" => return Ok(VectorFormat::Svg.into()),
"pdf" => return Ok(VectorFormat::Pdf.into()),
"webp" => return Ok(ExchangeFormat::Webp.into()),
_ => {}
}
@ -373,6 +422,7 @@ impl Image {
match &self.0.kind {
ImageKind::Raster(raster) => raster.format().into(),
ImageKind::Svg(_) => VectorFormat::Svg.into(),
ImageKind::Pdf(_) => VectorFormat::Pdf.into(),
}
}
@ -381,6 +431,7 @@ impl Image {
match &self.0.kind {
ImageKind::Raster(raster) => raster.width() as f64,
ImageKind::Svg(svg) => svg.width(),
ImageKind::Pdf(pdf) => pdf.width() as f64,
}
}
@ -389,6 +440,7 @@ impl Image {
match &self.0.kind {
ImageKind::Raster(raster) => raster.height() as f64,
ImageKind::Svg(svg) => svg.height(),
ImageKind::Pdf(pdf) => pdf.height() as f64,
}
}
@ -397,6 +449,7 @@ impl Image {
match &self.0.kind {
ImageKind::Raster(raster) => raster.dpi(),
ImageKind::Svg(_) => Some(Image::USVG_DEFAULT_DPI),
ImageKind::Pdf(_) => Some(Image::DEFAULT_DPI),
}
}
@ -435,6 +488,8 @@ pub enum ImageKind {
Raster(RasterImage),
/// An SVG image.
Svg(SvgImage),
/// A PDF image.
Pdf(PdfImage),
}
impl From<RasterImage> for ImageKind {
@ -469,10 +524,20 @@ impl ImageFormat {
return Some(Self::Vector(VectorFormat::Svg));
}
if is_pdf(data) {
return Some(Self::Vector(VectorFormat::Pdf));
}
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.
fn is_svg(data: &[u8]) -> bool {
// Check for the gzip magic bytes. This check is perhaps a bit too
@ -493,6 +558,9 @@ fn is_svg(data: &[u8]) -> bool {
pub enum VectorFormat {
/// The vector graphics format of the web.
Svg,
/// High-fidelity document and graphics format, with focus on exact
/// reproduction in print.
Pdf,
}
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

@ -9,6 +9,7 @@ use krilla::embed::EmbedError;
use krilla::error::KrillaError;
use krilla::geom::PathBuilder;
use krilla::page::{PageLabel, PageSettings};
use krilla::pdf::PdfError;
use krilla::surface::Surface;
use krilla::{Document, SerializeSettings};
use krilla_svg::render_svg_glyph;
@ -363,6 +364,42 @@ fn finish(
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
// 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(),
"{prefix} missing annotation alt text";
hint: "please report this as a bug"
),
ValidationError::MissingAltText => error!(
ValidationError::MissingAltText(_) => error!(
Span::detached(),
"{prefix} missing alt text";
hint: "make sure your images and equations have alt text"
@ -576,6 +613,13 @@ fn convert_error(
"{prefix} missing document date";
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 krilla::image::{BitsPerComponent, CustomImage, ImageColorspace};
use krilla::pdf::PdfDocument;
use krilla::surface::Surface;
use krilla_svg::{SurfaceExt, SvgSettings};
use typst_library::diag::{SourceResult, bail};
use typst_library::foundations::Smart;
use typst_library::layout::{Abs, Angle, Ratio, Size, Transform};
use typst_library::visualize::{
ExchangeFormat, Image, ImageKind, ImageScaling, RasterFormat, RasterImage,
ExchangeFormat, Image, ImageKind, ImageScaling, PdfImage, RasterFormat, RasterImage,
};
use typst_syntax::Span;
@ -60,6 +61,9 @@ pub(crate) fn handle_image(
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() {
@ -85,9 +89,9 @@ struct Repr {
/// A wrapper around `RasterImage` so that we can implement `CustomImage`.
#[derive(Clone)]
struct PdfImage(Arc<Repr>);
struct PdfRasterImage(Arc<Repr>);
impl PdfImage {
impl PdfRasterImage {
pub fn new(raster: RasterImage) -> Self {
Self(Arc::new(Repr {
raster,
@ -97,7 +101,7 @@ impl PdfImage {
}
}
impl Hash for PdfImage {
impl Hash for PdfRasterImage {
fn hash<H: Hasher>(&self, state: &mut H) {
// `alpha_channel` and `actual_dynamic` are generated from the underlying `RasterImage`,
// 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] {
self.0
.actual_dynamic
@ -196,10 +200,15 @@ fn convert_raster(
interpolate,
)
} 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) {
let base = |hp: bool, vp: bool, mut base_ts: Transform, size: Size| {
if hp {

View File

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

View File

@ -13,11 +13,13 @@ keywords = { workspace = true }
readme = { workspace = true }
[dependencies]
typst-assets = { workspace = true }
typst-library = { workspace = true }
typst-macros = { workspace = true }
typst-timing = { workspace = true }
bytemuck = { workspace = true }
comemo = { workspace = true }
hayro = { workspace = true }
image = { workspace = true }
pixglyph = { 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::{GenericImageView, Rgba};
use std::sync::Arc;
use tiny_skia as sk;
use tiny_skia::IntSize;
use typst_library::foundations::Smart;
use typst_library::layout::Size;
use typst_library::visualize::{Image, ImageKind, ImageScaling};
use typst_library::visualize::{Image, ImageKind, ImageScaling, PdfImage};
use crate::{AbsExt, State};
@ -59,9 +60,9 @@ pub fn render_image(
/// Prepare a texture for an image at a scaled size.
#[comemo::memoize]
fn build_texture(image: &Image, w: u32, h: u32) -> Option<Arc<sk::Pixmap>> {
let mut texture = sk::Pixmap::new(w, h)?;
match image.kind() {
let texture = match image.kind() {
ImageKind::Raster(raster) => {
let mut texture = sk::Pixmap::new(w, h)?;
let w = texture.width();
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;
*dest = sk::ColorU8::from_rgba(r, g, b, a).premultiply();
}
texture
}
ImageKind::Svg(svg) => {
let mut texture = sk::Pixmap::new(w, h)?;
let tree = svg.tree();
let ts = tiny_skia::Transform::from_scale(
w as f32 / tree.size().width(),
h as f32 / tree.size().height(),
);
resvg::render(tree, ts, &mut texture.as_mut());
texture
}
}
ImageKind::Pdf(pdf) => build_pdf_texture(pdf, w, h)?,
};
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 }
[dependencies]
typst-assets = { workspace = true }
typst-library = { workspace = true }
typst-macros = { workspace = true }
typst-timing = { workspace = true }
@ -21,6 +22,7 @@ base64 = { workspace = true }
comemo = { workspace = true }
ecow = { workspace = true }
flate2 = { workspace = true }
hayro = { workspace = true }
image = { workspace = true }
ttf-parser = { workspace = true }
xmlparser = { workspace = true }

View File

@ -1,10 +1,13 @@
use std::sync::Arc;
use base64::Engine;
use ecow::{EcoString, eco_format};
use hayro::{FontData, FontQuery, InterpreterSettings, RenderSettings, StandardFont};
use image::{ImageEncoder, codecs::png::PngEncoder};
use typst_library::foundations::Smart;
use typst_library::layout::{Abs, Axes};
use typst_library::visualize::{
ExchangeFormat, Image, ImageKind, ImageScaling, RasterFormat,
ExchangeFormat, Image, ImageKind, ImageScaling, PdfImage, RasterFormat,
};
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::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,");
@ -73,3 +95,45 @@ pub fn convert_image_to_base64_url(image: &Image) -> EcoString {
url.push_str(&data);
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
paragraphs to stretch into the margins, then reverting to the old margins, you
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.

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

@ -258,7 +258,7 @@ A #box(image("/assets/images/tiger.jpg", height: 1cm, width: 80%)) B
--- image-png-but-pixmap-format ---
#image(
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",
)
@ -289,3 +289,16 @@ A #box(image("/assets/images/tiger.jpg", height: 1cm, width: 80%)) B
..rotations.map(v => raw(str(v), lang: "typc")),
..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)