create_thumbnail: support creating thumbnails of PDF files
- ID
024e4fe- date
2026-09-26 15:10:26+00:00- author
Alex Chan <alex@alexwlchan.net>- parent
46d8c54- message
create_thumbnail: support creating thumbnails of PDF files- changed files
Changed files
Cargo.lock (36741 → 64709)
diff --git a/Cargo.lock b/Cargo.lock
index b68cb00..67fc510 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -9,6 +9,17 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "320119579fcad9c21884f5c4861d16174d0e06250625266f50fe6898340abefa"
[[package]]
+name = "aes"
+version = "0.9.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "35f0f96ce78e38c3dc6d8948aa8163d06385be74000f3c7a95bf1eef35d3ea32"
+dependencies = [
+ "cipher",
+ "cpubits",
+ "cpufeatures",
+]
+
+[[package]]
name = "aho-corasick"
version = "1.1.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -36,6 +47,30 @@ dependencies = [
]
[[package]]
+name = "alloc-no-stdlib"
+version = "3.0.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "c2fb6cfd47bf496ff64095c20eaba0c201404ee38714d4142fcfa1dc334fcc7a"
+
+[[package]]
+name = "alloc-stdlib"
+version = "0.3.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "0b5c1865780388bfa186411ab5f247819487fc4864c6e9c3106611fa347586e1"
+dependencies = [
+ "alloc-no-stdlib",
+]
+
+[[package]]
+name = "android_system_properties"
+version = "0.1.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ae221649c9976a6f6c56ae1facf410f3ddb33cc661c4b7b61020a912d4237fbc"
+dependencies = [
+ "libc",
+]
+
+[[package]]
name = "anstream"
version = "1.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -109,6 +144,12 @@ dependencies = [
]
[[package]]
+name = "arrayref"
+version = "0.3.9"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "76a2e8124351fda1ef8aaaa3bbd7ebbcb486bbcd4225aca0aa0d84bb2db8fecb"
+
+[[package]]
name = "arrayvec"
version = "0.7.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -139,6 +180,16 @@ dependencies = [
]
[[package]]
+name = "atoi_simd"
+version = "0.18.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f3cdb3708a128e559a30fb830e8a77a5022ee6902806925c216658652b452a44"
+dependencies = [
+ "debug_unsafe",
+ "rustversion",
+]
+
+[[package]]
name = "autocfg"
version = "1.5.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -188,6 +239,12 @@ dependencies = [
]
[[package]]
+name = "base64"
+version = "0.23.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ac07cdecf99051d9a5238b80f35af32cdeba5b336e55d957b318b50137e18da5"
+
+[[package]]
name = "bit_field"
version = "0.10.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -195,6 +252,12 @@ checksum = "1e4b40c7323adcfc0a41c4b88143ed58346ff65a288fc144329c5c45e05d70c6"
[[package]]
name = "bitflags"
+version = "1.3.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a"
+
+[[package]]
+name = "bitflags"
version = "2.13.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3ded4057c258ba199e2d26386d3af3780957ecaee6c4ef4041c6b4b8b97c0b06"
@@ -209,6 +272,45 @@ dependencies = [
]
[[package]]
+name = "block-buffer"
+version = "0.12.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d2f6c7dbe95a6ed67ad9f18e57daf93a2f034c524b99fd2b76d18fdfeb6660aa"
+dependencies = [
+ "hybrid-array",
+]
+
+[[package]]
+name = "block-padding"
+version = "0.4.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "710f1dd022ef4e93f8a438b4ba958de7f64308434fa6a87104481645cc30068b"
+dependencies = [
+ "hybrid-array",
+]
+
+[[package]]
+name = "brotli"
+version = "9.0.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f8b851b75c23ca7873623d612fe49bd1989aeb03d08fb9432187eb253d3d4c6b"
+dependencies = [
+ "alloc-no-stdlib",
+ "alloc-stdlib",
+ "brotli-decompressor",
+]
+
+[[package]]
+name = "brotli-decompressor"
+version = "6.0.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "941cd9bd4ddab83cb46fa5a2d428f1c857b24ac78cb876cf7beb710840934bd7"
+dependencies = [
+ "alloc-no-stdlib",
+ "alloc-stdlib",
+]
+
+[[package]]
name = "bstr"
version = "1.13.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -236,6 +338,26 @@ name = "bytemuck"
version = "1.25.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "95832e849adfb21180ccb6826a99da14e5d266ae5c2e668e1602cf234f153797"
+dependencies = [
+ "bytemuck_derive",
+]
+
+[[package]]
+name = "bytemuck_derive"
+version = "1.12.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "6a1f896587b6f2c069c73d2f0913e2d590c3990285cd2f0b6aa02b786b4c679c"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn 3.0.6",
+]
+
+[[package]]
+name = "byteorder"
+version = "1.5.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b"
[[package]]
name = "byteorder-lite"
@@ -244,6 +366,21 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8f1fe948ff07f4bd06c30984e69f5b4899c516a3ef74f34df92a2df2ab535495"
[[package]]
+name = "bytes"
+version = "1.12.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "fc652a48c352aef3ea3aed32080501cf3ef6ed5da78602a020c991775b0aff04"
+
+[[package]]
+name = "cbc"
+version = "0.2.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ce2dc9ee5f88d11e0beb842c88b33c8a5cf0d1329c4b19494af42b07dbfe8896"
+dependencies = [
+ "cipher",
+]
+
+[[package]]
name = "cc"
version = "1.4.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -262,6 +399,29 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4e7648175b45a9a48536d676f68d918270699102aa8dab5496df06904c914600"
[[package]]
+name = "chrono"
+version = "0.4.45"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1aa79e62e7697b8e29b513a68abacf485adcd1fe8284a4316c5ae868e6633327"
+dependencies = [
+ "iana-time-zone",
+ "js-sys",
+ "num-traits",
+ "wasm-bindgen",
+ "windows-link",
+]
+
+[[package]]
+name = "cipher"
+version = "0.5.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e8cf2a2c93cd704877c0858356ed03480ff301ee950b43f1cbe4573b088bfa6c"
+dependencies = [
+ "crypto-common",
+ "inout",
+]
+
+[[package]]
name = "clap"
version = "4.6.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -314,6 +474,39 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1d07550c9036bf2ae0c684c4297d503f838287c83c53686d05370d0e139ae570"
[[package]]
+name = "const-oid"
+version = "0.10.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a6ef517f0926dd24a1582492c791b6a4818a4d94e789a334894aa15b0d12f55c"
+
+[[package]]
+name = "core-foundation-sys"
+version = "0.8.7"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "773648b94d0e5d620f64f280777445740e61fe701025087ec8b57f45c791888b"
+
+[[package]]
+name = "core_detect"
+version = "1.0.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "7f8f80099a98041a3d1622845c271458a2d73e688351bf3cb999266764b81d48"
+
+[[package]]
+name = "cpubits"
+version = "0.1.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "15b85f9c39137c3a891689859392b1bd49812121d0d61c9caf00d46ed5ce06ae"
+
+[[package]]
+name = "cpufeatures"
+version = "0.3.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "5ca28b0ae3115b884660db4118d803791fd6756b6e88f39c0f3f7859060d7566"
+dependencies = [
+ "libc",
+]
+
+[[package]]
name = "crc32fast"
version = "1.5.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -330,6 +523,8 @@ dependencies = [
"assert_cmd",
"clap",
"image",
+ "office_oxide",
+ "pdf_oxide",
"predicates",
"tempfile",
]
@@ -366,18 +561,121 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "460fbee9c2c2f33933d720630a6a0bac33ba7053db5344fac858d4b8952d77d5"
[[package]]
+name = "crypto-common"
+version = "0.2.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ce6e4c961d6cd6c9a86db418387425e8bdeaf05b3c8bc1411e6dca4c252f1453"
+dependencies = [
+ "hybrid-array",
+]
+
+[[package]]
+name = "debug_unsafe"
+version = "0.1.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "7eed2c4702fa172d1ce21078faa7c5203e69f5394d48cc436d25928394a867a2"
+
+[[package]]
+name = "defmt"
+version = "1.1.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e2953bfe4f93bbd20cc71198842756f77d161884c99ebbabc41d80231ded88d1"
+dependencies = [
+ "bitflags 1.3.2",
+ "defmt-macros",
+]
+
+[[package]]
+name = "defmt-macros"
+version = "1.1.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "bad9c72e7ca2137e0dc3813245a0d282fd6daad32fd800af018306a9169b5fe8"
+dependencies = [
+ "defmt-parser",
+ "proc-macro2",
+ "quote",
+ "syn 2.0.119",
+]
+
+[[package]]
+name = "defmt-parser"
+version = "1.0.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "10d60334b3b2e7c9d91ef8150abfb6fa4c1c39ebbcf4a81c2e346aad939fee3e"
+dependencies = [
+ "thiserror",
+]
+
+[[package]]
name = "difflib"
version = "0.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6184e33543162437515c2e2b48714794e37845ec9851711914eec9d308f6ebe8"
[[package]]
+name = "digest"
+version = "0.11.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f1dd6dbb5841937940781866fa1281a1ff7bd3bf827091440879f9994983d5c2"
+dependencies = [
+ "block-buffer",
+ "const-oid",
+ "crypto-common",
+]
+
+[[package]]
+name = "document-features"
+version = "0.2.12"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d4b8a88685455ed29a21542a33abd9cb6510b6b129abadabdcef0f4c55bc8f61"
+dependencies = [
+ "litrs",
+]
+
+[[package]]
name = "either"
version = "1.18.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "252afb9ae5eaa683babdc6a068b3f5726eb19e05070c731f9b2a23a7c3e8ed34"
[[package]]
+name = "encoding_rs"
+version = "0.8.42"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8e985e0451871ad22fb8d2b6b076e2028a502a0d3950998c2c5c0a4f9b5d9679"
+dependencies = [
+ "cfg-if",
+ "core_detect",
+ "multiversion_no_op",
+ "rustversion",
+ "scopeguard",
+ "simdutf8",
+]
+
+[[package]]
+name = "env_filter"
+version = "2.0.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "900d271a03799a1ee8d1ca9b19893b48ca674a9284fefcfb85f05e74ed314217"
+dependencies = [
+ "log",
+ "regex",
+]
+
+[[package]]
+name = "env_logger"
+version = "0.11.11"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "de671bd27a75a797dc9ae289ba1e77276e75e2026408aab65185384e2d5cd3f6"
+dependencies = [
+ "anstream",
+ "anstyle",
+ "env_filter",
+ "jiff",
+ "log",
+]
+
+[[package]]
name = "equator"
version = "0.4.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -398,6 +696,12 @@ dependencies = [
]
[[package]]
+name = "equivalent"
+version = "1.0.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "877a4ace8713b0bcf2a4e7eec82529c029f1d0619886d18145fea96c3ffe5c0f"
+
+[[package]]
name = "errno"
version = "0.3.14"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -408,6 +712,15 @@ dependencies = [
]
[[package]]
+name = "euclid"
+version = "0.22.14"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f1a05365e3b1c6d1650318537c7460c6923f1abdd272ad6842baa2b509957a06"
+dependencies = [
+ "num-traits",
+]
+
+[[package]]
name = "exr"
version = "1.74.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -425,6 +738,24 @@ dependencies = [
]
[[package]]
+name = "fast-float2"
+version = "0.2.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "c6e8948ce679d00a02a94739ea185595dca7118ed04feb991127e443bd3d761f"
+
+[[package]]
+name = "fast_image_resize"
+version = "6.1.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e9c50201dc184ba6553da1695aac20a042efffbe2d84542cee31917c86c3ab1e"
+dependencies = [
+ "cfg-if",
+ "document-features",
+ "num-traits",
+ "thiserror",
+]
+
+[[package]]
name = "fastrand"
version = "2.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -437,6 +768,12 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "caf1079563223d5d59d83c85886a56e586cfd5c1a26292e971a0fa266531ac5a"
[[package]]
+name = "fax"
+version = "0.3.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "70ed8646f3993cad0585c6669ffba6f7e5440a0bfb0a1f16600cae33ce7cf307"
+
+[[package]]
name = "fdeflate"
version = "0.3.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -472,6 +809,70 @@ dependencies = [
]
[[package]]
+name = "font-types"
+version = "0.11.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "5b38ad915f6dadd993ced50848a8291a543bd41ca62bc10740d5e64e2ab4cfd7"
+dependencies = [
+ "bytemuck",
+]
+
+[[package]]
+name = "font-types"
+version = "0.12.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b8eb065f3251655b3c90e22e5e363f310fc5332fb3402e37bbc94752283248f6"
+dependencies = [
+ "bytemuck",
+]
+
+[[package]]
+name = "fontconfig-parser"
+version = "0.5.8"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "bbc773e24e02d4ddd8395fd30dc147524273a83e54e0f312d986ea30de5f5646"
+dependencies = [
+ "roxmltree",
+]
+
+[[package]]
+name = "fontdb"
+version = "0.24.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "2660c5e9157bf76d2db1294e4a9feba604ef610819a3b591088d0d8392a3290f"
+dependencies = [
+ "fontconfig-parser",
+ "log",
+ "memmap2",
+ "slotmap",
+ "tinyvec",
+]
+
+[[package]]
+name = "futures-core"
+version = "0.3.34"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "92d699e522242e69e3003b94ecc1f960f3a5e015aa7c5d7486e65ad01dd94f5e"
+
+[[package]]
+name = "futures-task"
+version = "0.3.34"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "cd417de3d1d015fc3bfd2b1ea46dfc7bab72ef86f1cc7cc9c78e728b34a6d1fd"
+
+[[package]]
+name = "futures-util"
+version = "0.3.34"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "0d50a92467f8ba5dd6e3ee5d4bd04d73ab2e4e1c44474a0674821dfce14b79bc"
+dependencies = [
+ "futures-core",
+ "futures-task",
+ "pin-project-lite",
+ "slab",
+]
+
+[[package]]
name = "getrandom"
version = "0.3.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -490,8 +891,10 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "300e883d756b2e4ec94e02791f39b04b522276138852cfc41d9fb7e904106099"
dependencies = [
"cfg-if",
+ "js-sys",
"libc",
"r-efi 6.0.0",
+ "wasm-bindgen",
]
[[package]]
@@ -501,7 +904,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ee8cfcc411d9adbbaba82fb72661cc1bcca13e8bba98b364e62b2dba8f960159"
dependencies = [
"color_quant",
- "weezl",
+ "weezl 0.1.12",
]
[[package]]
@@ -516,12 +919,85 @@ dependencies = [
]
[[package]]
+name = "harfrust"
+version = "0.13.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "948d0741125ba89cd3e1c23e5642415b6ade7e1d29d67ba25fb925b533e989d6"
+dependencies = [
+ "bitflags 2.13.2",
+ "bytemuck",
+ "once_cell",
+ "read-fonts 0.43.3",
+ "smallvec",
+]
+
+[[package]]
+name = "hashbrown"
+version = "0.17.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ed5909b6e89a2db4456e54cd5f673791d7eca6732202bbf2a9cc504fe2f9b84a"
+
+[[package]]
+name = "hayro-ccitt"
+version = "0.3.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9f4d0e94ddd48749f06bbe4e5389fb9799a0c45bcaf00495042076ef05e3241a"
+
+[[package]]
+name = "hayro-jbig2"
+version = "0.3.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "69374b3668dd45aeb3d3145cda68f2c7b4f223aaa2511e67d076f1c7d741388d"
+dependencies = [
+ "hayro-ccitt",
+]
+
+[[package]]
+name = "hayro-jpeg2000"
+version = "0.4.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "bab0c77d09c0d9b144d429d0bf0fcd4c63c01d5f6c33f9b8ed501283e0f1ef76"
+
+[[package]]
name = "heck"
version = "0.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea"
[[package]]
+name = "hybrid-array"
+version = "0.4.15"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "27f864f10dfb56725ce5ce5472bc52252c8f93a4ab86327122cebf62c5f59a17"
+dependencies = [
+ "typenum",
+]
+
+[[package]]
+name = "iana-time-zone"
+version = "0.1.65"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e31bc9ad994ba00e440a8aa5c9ef0ec67d5cb5e5cb0cc7f8b744a35b389cc470"
+dependencies = [
+ "android_system_properties",
+ "core-foundation-sys",
+ "iana-time-zone-haiku",
+ "js-sys",
+ "log",
+ "wasm-bindgen",
+ "windows-core",
+]
+
+[[package]]
+name = "iana-time-zone-haiku"
+version = "0.1.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f31827a206f56af32e590ba56d5d2d085f558508192593743f16b2306495269f"
+dependencies = [
+ "cc",
+]
+
+[[package]]
name = "image"
version = "0.25.10"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -562,6 +1038,26 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6e44b0a4eaa4c82f441d50a963f2d5f05a787240aeee097597033e72accfd22f"
[[package]]
+name = "indexmap"
+version = "2.14.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "cc4e190f5d26ca7051642629da2c52fc03bde85a03197c99408dcd291734c855"
+dependencies = [
+ "equivalent",
+ "hashbrown",
+]
+
+[[package]]
+name = "inout"
+version = "0.2.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "4250ce6452e92010fdf7268ccc5d14faa80bb12fc741938534c58f16804e03c7"
+dependencies = [
+ "block-padding",
+ "hybrid-array",
+]
+
+[[package]]
name = "interpolate_name"
version = "0.2.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -588,6 +1084,49 @@ dependencies = [
]
[[package]]
+name = "itoa"
+version = "1.0.18"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8f42a60cbdf9a97f5d2305f08a87dc4e09308d1276d28c869c684d7777685682"
+
+[[package]]
+name = "jiff"
+version = "0.2.37"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "0ab1baf72f08796de0260609515130699b890ac25f30e610ad894bc5856cafdb"
+dependencies = [
+ "defmt",
+ "jiff-core",
+ "jiff-static",
+ "log",
+ "portable-atomic",
+ "portable-atomic-util",
+ "serde_core",
+]
+
+[[package]]
+name = "jiff-core"
+version = "0.1.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "5e52fe76043ccecc9005d2305ebaadf7d7fc0cc89ca6baa10a94d6bc68c7128c"
+dependencies = [
+ "defmt",
+ "log",
+]
+
+[[package]]
+name = "jiff-static"
+version = "0.2.37"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "378268a1116ad67ae6228701118ac9f491d78fda38a40a1f1a9e1348de6f7212"
+dependencies = [
+ "jiff-core",
+ "proc-macro2",
+ "quote",
+ "syn 2.0.119",
+]
+
+[[package]]
name = "jobserver"
version = "0.1.35"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -598,6 +1137,38 @@ dependencies = [
]
[[package]]
+name = "jpeg-decoder"
+version = "0.3.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "00810f1d8b74be64b13dbf3db89ac67740615d6c891f0e7b6179326533011a07"
+dependencies = [
+ "rayon",
+]
+
+[[package]]
+name = "js-sys"
+version = "0.3.106"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "7883d941dae510fb2d978fc3fe018c71c9e2892fd38854de3e8b92c2e5ad9cc5"
+dependencies = [
+ "cfg-if",
+ "futures-util",
+ "wasm-bindgen",
+]
+
+[[package]]
+name = "kurbo"
+version = "0.13.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "4b60dfc32f652b926df6192e55525b16d186c69d47876c3ead4da5cc9f8450e2"
+dependencies = [
+ "arrayvec",
+ "euclid",
+ "polycool",
+ "smallvec",
+]
+
+[[package]]
name = "lebe"
version = "0.5.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -632,6 +1203,12 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "32a66949e030da00e8c7d4434b251670a91556f4144941d37452769c25d58a53"
[[package]]
+name = "litrs"
+version = "1.0.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "11d3d7f243d5c5a8b9bb5d6dd2b1602c0cb0b9db1621bafc7ed66e35ff9fe092"
+
+[[package]]
name = "log"
version = "0.4.34"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -657,12 +1234,31 @@ dependencies = [
]
[[package]]
+name = "md-5"
+version = "0.11.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "69b6441f590336821bb897fb28fc622898ccceb1d6cea3fde5ea86b090c4de98"
+dependencies = [
+ "cfg-if",
+ "digest",
+]
+
+[[package]]
name = "memchr"
version = "2.8.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "cf8baf1c55e62ffcace7a9f06f4bd9cd3f0c4beb022d3b367256b91b87513d98"
[[package]]
+name = "memmap2"
+version = "0.9.11"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d1219ed1b7f229ee7104d281dd01d6802fe28bb6e95d292942c4daacdeb798c0"
+dependencies = [
+ "libc",
+]
+
+[[package]]
name = "miniz_oxide"
version = "0.8.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -693,6 +1289,12 @@ dependencies = [
]
[[package]]
+name = "multiversion_no_op"
+version = "1.0.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "743fb55ba31b18fb1ecef6bdc9aa2743314978ac084044301a7eee33fb99a20d"
+
+[[package]]
name = "new_debug_unreachable"
version = "1.0.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -789,6 +1391,23 @@ dependencies = [
]
[[package]]
+name = "office_oxide"
+version = "0.1.11"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "25caa1da7142070699529cfc8f276a739101c22ffccc572644d3c59278f19b56"
+dependencies = [
+ "atoi_simd",
+ "encoding_rs",
+ "fast-float2",
+ "log",
+ "quick-xml 0.41.0",
+ "serde",
+ "serde_json",
+ "thiserror",
+ "zip",
+]
+
+[[package]]
name = "once_cell"
version = "1.21.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -798,19 +1417,121 @@ checksum = "9f7c3e4beb33f85d45ae3e3a1792185706c8e16d043238c593331cc7cd313b50"
name = "once_cell_polyfill"
version = "1.70.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "384b8ab6d37215f3c5301a95a4accb5d64aa607f1fcb26a11b5303878451b4fe"
+checksum = "384b8ab6d37215f3c5301a95a4accb5d64aa607f1fcb26a11b5303878451b4fe"
+
+[[package]]
+name = "paste"
+version = "1.0.15"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "57c0d7b74b563b49d38dae00a0c37d4d6de9b432382b2892f0574ddcae73fd0a"
+
+[[package]]
+name = "pastey"
+version = "0.1.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "35fb2e5f958ec131621fdd531e9fc186ed768cbe395337403ae56c17a74c68ec"
+
+[[package]]
+name = "pdf_oxide"
+version = "0.3.78"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "95b1ecc1a60a314fbd1aea5fe8f700ec32af1efa70fef5c6879162444da3437d"
+dependencies = [
+ "aes",
+ "base64",
+ "bitflags 2.13.2",
+ "brotli",
+ "byteorder",
+ "bytes",
+ "cbc",
+ "chrono",
+ "encoding_rs",
+ "env_logger",
+ "fast_image_resize",
+ "fax 0.3.0",
+ "flate2",
+ "fontdb",
+ "getrandom 0.4.3",
+ "harfrust",
+ "hayro-jbig2",
+ "hayro-jpeg2000",
+ "image",
+ "jpeg-decoder",
+ "libc",
+ "log",
+ "md-5",
+ "memchr",
+ "nom",
+ "office_oxide",
+ "phf",
+ "qcms",
+ "quick-xml 0.42.0",
+ "regex",
+ "serde",
+ "serde_json",
+ "sha2",
+ "smallvec",
+ "stringprep",
+ "subsetter",
+ "taffy",
+ "thiserror",
+ "tiny-skia",
+ "ttf-parser",
+ "unicode-bidi",
+ "unicode-linebreak",
+ "unicode-normalization",
+ "uuid",
+ "weezl 0.2.1",
+]
+
+[[package]]
+name = "phf"
+version = "0.14.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "010378780309880b08997fae13be7834dba947d36393bd372f2b1556deb2a2f6"
+dependencies = [
+ "phf_macros",
+ "phf_shared",
+ "serde",
+]
+
+[[package]]
+name = "phf_generator"
+version = "0.14.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "aeb62e0959d5a1bebc965f4d15d9e2b7cea002b6b0f5ba8cde6cc26738467100"
+dependencies = [
+ "fastrand",
+ "phf_shared",
+]
+
+[[package]]
+name = "phf_macros"
+version = "0.14.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "5fa8d0ca26d424d27630da600c6624696e7dec8bf7b3b492b383c5dc49e5e085"
+dependencies = [
+ "phf_generator",
+ "phf_shared",
+ "proc-macro2",
+ "quote",
+ "syn 2.0.119",
+]
[[package]]
-name = "paste"
-version = "1.0.15"
+name = "phf_shared"
+version = "0.14.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "57c0d7b74b563b49d38dae00a0c37d4d6de9b432382b2892f0574ddcae73fd0a"
+checksum = "c6fd9027e2d9319be6349febd1db4e8d02aa544921200c9b777720ac34a3aa89"
+dependencies = [
+ "siphasher",
+]
[[package]]
-name = "pastey"
-version = "0.1.1"
+name = "pin-project-lite"
+version = "0.2.17"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "35fb2e5f958ec131621fdd531e9fc186ed768cbe395337403ae56c17a74c68ec"
+checksum = "a89322df9ebe1c1578d689c92318e070967d1042b512afbe49518723f4e6d5cd"
[[package]]
name = "png"
@@ -818,7 +1539,7 @@ version = "0.18.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "60769b8b31b2a9f263dae2776c37b1b28ae246943cf719eb6946a1db05128a61"
dependencies = [
- "bitflags",
+ "bitflags 2.13.2",
"crc32fast",
"fdeflate",
"flate2",
@@ -826,6 +1547,30 @@ dependencies = [
]
[[package]]
+name = "polycool"
+version = "0.4.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "50596ddc09eb5ad5f75cacd40209568e66df71baf86e1499a0e99c4cff12a5a6"
+dependencies = [
+ "arrayvec",
+]
+
+[[package]]
+name = "portable-atomic"
+version = "1.15.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "05c8b63e8d9609db387f0324918f81d68fe27748f084ef092fb35954d0539a85"
+
+[[package]]
+name = "portable-atomic-util"
+version = "0.2.8"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "10ab3eb7f3becc3a1cbc4f2c6f20267996cfc1a6467a873763411b136a122715"
+dependencies = [
+ "portable-atomic",
+]
+
+[[package]]
name = "ppv-lite86"
version = "0.2.21"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -922,6 +1667,12 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d55d956fa96f5ec02be2e13af0e20391a5aa83d6a074e3ad368959d0fab299ea"
[[package]]
+name = "qcms"
+version = "0.3.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "edecfcd5d755a5e5d98e24cf43113e7cdaec5a070edd0f6b250c03a573da30fa"
+
+[[package]]
name = "qoi"
version = "0.4.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -937,6 +1688,25 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a993555f31e5a609f617c12db6250dedcac1b0a85076912c436e6fc9b2c8e6a3"
[[package]]
+name = "quick-xml"
+version = "0.41.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e660451e55124f798a69a5af3f49ccfbefbd41910eefd25caf2393e1f3473ec1"
+dependencies = [
+ "memchr",
+ "serde",
+]
+
+[[package]]
+name = "quick-xml"
+version = "0.42.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "41b1177fdf999d2321d3fb46ff47159d9c1fb9ad66a4879f8c50a0b504615e9b"
+dependencies = [
+ "memchr",
+]
+
+[[package]]
name = "quote"
version = "1.0.47"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -1042,7 +1812,7 @@ version = "11.6.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "498cd0dc59d73224351ee52a95fee0f1a617a2eae0e7d9d720cc622c73a54186"
dependencies = [
- "bitflags",
+ "bitflags 2.13.2",
]
[[package]]
@@ -1066,6 +1836,27 @@ dependencies = [
]
[[package]]
+name = "read-fonts"
+version = "0.39.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "c4ed38b89c2c77ff968c524145ad65fb010f38af5c7a224b53b81d47ac2daa81"
+dependencies = [
+ "bytemuck",
+ "font-types 0.11.3",
+]
+
+[[package]]
+name = "read-fonts"
+version = "0.43.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "005c8acf251756c478b0bf402885bfd88a1476020c4c7e6060edc1aa68da38ea"
+dependencies = [
+ "bytemuck",
+ "font-types 0.12.5",
+ "once_cell",
+]
+
+[[package]]
name = "reborrow"
version = "0.5.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -1107,12 +1898,24 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "47b34b781b31e5d73e9fbc8689c70551fd1ade9a19e3e28cfec8580a79290cc4"
[[package]]
+name = "roxmltree"
+version = "0.20.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "6c20b6793b5c2fa6553b250154b78d6d0db37e72700ae35fad9387a46f487c97"
+
+[[package]]
+name = "rustc-hash"
+version = "2.1.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "6b1e7f9a428571be2dc5bc0505c13fb6bf936822b894ec87abf8a08a4e51742d"
+
+[[package]]
name = "rustix"
version = "1.1.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "891efababe418670775f199f0d233d84843c227a0949a883ce15b37c78d6629d"
dependencies = [
- "bitflags",
+ "bitflags 2.13.2",
"errno",
"libc",
"linux-raw-sys",
@@ -1126,6 +1929,22 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "cf54715a573b99ac80df0bc206da022bcd442c974952c7b9720069370852e21f"
[[package]]
+name = "scopeguard"
+version = "1.2.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49"
+
+[[package]]
+name = "serde"
+version = "1.0.229"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "4148590afebada386688f18773da617792bf2ef03ffc1e4cbd2b1d45b023e0ba"
+dependencies = [
+ "serde_core",
+ "serde_derive",
+]
+
+[[package]]
name = "serde_core"
version = "1.0.229"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -1146,6 +1965,30 @@ dependencies = [
]
[[package]]
+name = "serde_json"
+version = "1.0.151"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "c841b55ecdae098c80dcae9cf767f6f8a0c2cdb3416bbef72181df4d0fe73f14"
+dependencies = [
+ "itoa",
+ "memchr",
+ "serde",
+ "serde_core",
+ "zmij",
+]
+
+[[package]]
+name = "sha2"
+version = "0.11.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "446ba717509524cb3f22f17ecc096f10f4822d76ab5c0b9822c5f9c284e825f4"
+dependencies = [
+ "cfg-if",
+ "cpufeatures",
+ "digest",
+]
+
+[[package]]
name = "shlex"
version = "2.0.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -1167,6 +2010,43 @@ dependencies = [
]
[[package]]
+name = "simdutf8"
+version = "0.1.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e3a9fe34e3e7a50316060351f37187a3f546bce95496156754b601a5fa71b76e"
+
+[[package]]
+name = "siphasher"
+version = "1.0.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "33f4fe9184a62d842c9ef383018f3306d8ba224fd9d836f56d7288308847c256"
+
+[[package]]
+name = "skrifa"
+version = "0.42.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "0c34617370ae968efb7161bb2beb517d9084659aae19e24b89e3db25b46e4564"
+dependencies = [
+ "bytemuck",
+ "read-fonts 0.39.2",
+]
+
+[[package]]
+name = "slab"
+version = "0.4.12"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "0c790de23124f9ab44544d7ac05d60440adc586479ce501c1d6d7da3cd8c9cf5"
+
+[[package]]
+name = "slotmap"
+version = "1.1.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "bdd58c3c93c3d278ca835519292445cb4b0d4dc59ccfdf7ceadaab3f8aeb4038"
+dependencies = [
+ "version_check",
+]
+
+[[package]]
name = "smallvec"
version = "1.16.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -1179,12 +2059,41 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6ce2be8dc25455e1f91df71bfa12ad37d7af1092ae736f3a6cd0e37bc7810596"
[[package]]
+name = "strict-num"
+version = "0.1.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "6637bab7722d379c8b41ba849228d680cc12d0a45ba1fa2b48f2a30577a06731"
+
+[[package]]
+name = "stringprep"
+version = "0.1.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "7b4df3d392d81bd458a8a621b8bffbd2302a12ffe288a9d931670948749463b1"
+dependencies = [
+ "unicode-bidi",
+ "unicode-normalization",
+ "unicode-properties",
+]
+
+[[package]]
name = "strsim"
version = "0.11.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f"
[[package]]
+name = "subsetter"
+version = "0.2.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "38803281d1c23166c5ebcb455439a5d2afe711cc909cf88af72448c297756ad6"
+dependencies = [
+ "kurbo",
+ "rustc-hash",
+ "skrifa",
+ "write-fonts",
+]
+
+[[package]]
name = "syn"
version = "2.0.119"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -1207,6 +2116,18 @@ dependencies = [
]
[[package]]
+name = "taffy"
+version = "0.14.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "639627c87f43b9181c811f40a6296409e093a17bc761214cba3c15df74f86b99"
+dependencies = [
+ "arrayvec",
+ "serde",
+ "slotmap",
+ "smallvec",
+]
+
+[[package]]
name = "tempfile"
version = "3.27.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -1251,27 +2172,115 @@ version = "0.11.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b63feaf3343d35b6ca4d50483f94843803b0f51634937cc2ec519fc32232bc52"
dependencies = [
- "fax",
+ "fax 0.2.7",
"flate2",
"half",
"quick-error",
- "weezl",
+ "weezl 0.1.12",
"zune-jpeg",
]
[[package]]
+name = "tiny-skia"
+version = "0.12.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "47ffee5eaaf5527f630fb0e356b90ebdec84d5d18d937c5e440350f88c5a91ea"
+dependencies = [
+ "arrayref",
+ "arrayvec",
+ "bytemuck",
+ "cfg-if",
+ "log",
+ "png",
+ "tiny-skia-path",
+]
+
+[[package]]
+name = "tiny-skia-path"
+version = "0.12.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "edca365c3faccca67d06593c5980fa6c57687de727a03131735bb85f01fdeeb9"
+dependencies = [
+ "arrayref",
+ "bytemuck",
+ "strict-num",
+]
+
+[[package]]
+name = "tinyvec"
+version = "1.13.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "fd3ca314f692efd6c868f8408f53fe444634a845f96c028b97d35f6a1f79f0ee"
+
+[[package]]
+name = "ttf-parser"
+version = "0.25.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d2df906b07856748fa3f6e0ad0cbaa047052d4a7dd609e231c4f72cee8c36f31"
+
+[[package]]
+name = "typed-path"
+version = "0.12.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8e28f89b80c87b8fb0cf04ab448d5dd0dd0ade2f8891bae878de66a75a28600e"
+
+[[package]]
+name = "typenum"
+version = "1.20.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b6f5e870be6c3b371b77fe0ee0bafb859fa4964b4404c27de1d380043c4dda20"
+
+[[package]]
+name = "unicode-bidi"
+version = "0.3.18"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "5c1cb5db39152898a79168971543b1cb5020dff7fe43c8dc468b0885f5e29df5"
+
+[[package]]
name = "unicode-ident"
version = "1.0.26"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d245f478577f809a851594d02313b640fb437e0bb33866753cff937863096954"
[[package]]
+name = "unicode-linebreak"
+version = "0.1.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "3b09c83c3c29d37506a3e260c08c03743a6bb66a9cd432c6934ab501a190571f"
+
+[[package]]
+name = "unicode-normalization"
+version = "0.1.25"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "5fd4f6878c9cb28d874b009da9e8d183b5abc80117c40bbd187a1fde336be6e8"
+dependencies = [
+ "tinyvec",
+]
+
+[[package]]
+name = "unicode-properties"
+version = "0.1.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "7df058c713841ad818f1dc5d3fd88063241cc61f49f5fbea4b951e8cf5a8d71d"
+
+[[package]]
name = "utf8parse"
version = "0.2.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "06abde3611657adf66d383f00b093d7faecc7fa57071cce2578660c9f1010821"
[[package]]
+name = "uuid"
+version = "1.26.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "2ef6dac1e96601b4fb3acccccff2139741fcb757cb9a36089bf5be91cfb285ce"
+dependencies = [
+ "getrandom 0.4.3",
+ "js-sys",
+ "wasm-bindgen",
+]
+
+[[package]]
name = "v_frame"
version = "0.3.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -1308,9 +2317,9 @@ dependencies = [
[[package]]
name = "wasm-bindgen"
-version = "0.2.128"
+version = "0.2.129"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "aecb87a33d3b0c5e3b7aa46336eaf486cffafbd281b195e4c8b80d50df2351bf"
+checksum = "9bb54f33acc68fd454578d9820b0bde1a1a3d17aa17bb7b6595806d02886d409"
dependencies = [
"cfg-if",
"once_cell",
@@ -1321,9 +2330,9 @@ dependencies = [
[[package]]
name = "wasm-bindgen-macro"
-version = "0.2.128"
+version = "0.2.129"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "a690d511e3c1a8b3a55e33511e3c2c00c78415cd23650f32b808627f5696b9ed"
+checksum = "2e29d0c35b16e224a7eeb5cd2d25e3e1968fbd65604117b44d3b789d00ee8535"
dependencies = [
"quote",
"wasm-bindgen-macro-support",
@@ -1331,9 +2340,9 @@ dependencies = [
[[package]]
name = "wasm-bindgen-macro-support"
-version = "0.2.128"
+version = "0.2.129"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "411e4887f0071ef2d2164a9d5fdf2d20efbef78fccd3a78b0c10a1dc5295e48a"
+checksum = "6f501a8bc3719dba86ef8ae4728879c08001bea749eb1333ac5b91e040e2a6b7"
dependencies = [
"bumpalo",
"proc-macro2",
@@ -1344,9 +2353,9 @@ dependencies = [
[[package]]
name = "wasm-bindgen-shared"
-version = "0.2.128"
+version = "0.2.129"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "81941cd78d0c92026c33e5e01312845a4cb1e9af3407f9134b100dd03144103e"
+checksum = "23f0c9c52aa7cd7d77769a4cfe2a9adb1b331f489a41d912ce14513d5ab995c6"
dependencies = [
"unicode-ident",
]
@@ -1358,12 +2367,71 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a28ac98ddc8b9274cb41bb4d9d4d5c425b6020c50c46f25559911905610b4a88"
[[package]]
+name = "weezl"
+version = "0.2.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d4ca08e5ef825b65b056d9efbd95c8750683f0a6d0466d02e96dc2e4e360f3d2"
+
+[[package]]
+name = "windows-core"
+version = "0.62.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b8e83a14d34d0623b51dce9581199302a221863196a1dde71a7663a4c2be9deb"
+dependencies = [
+ "windows-implement",
+ "windows-interface",
+ "windows-link",
+ "windows-result",
+ "windows-strings",
+]
+
+[[package]]
+name = "windows-implement"
+version = "0.60.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "053e2e040ab57b9dc951b72c264860db7eb3b0200ba345b4e4c3b14f67855ddf"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn 2.0.119",
+]
+
+[[package]]
+name = "windows-interface"
+version = "0.59.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "3f316c4a2570ba26bbec722032c4099d8c8bc095efccdc15688708623367e358"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn 2.0.119",
+]
+
+[[package]]
name = "windows-link"
version = "0.2.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f0805222e57f7521d6a62e36fa9163bc891acd422f971defe97d64e70d0a4fe5"
[[package]]
+name = "windows-result"
+version = "0.4.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "7781fa89eaf60850ac3d2da7af8e5242a5ea78d1a11c49bf2910bb5a73853eb5"
+dependencies = [
+ "windows-link",
+]
+
+[[package]]
+name = "windows-strings"
+version = "0.5.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "7837d08f69c77cf6b07689544538e017c1bfcf57e34b4c0ff58e6c2cd3b37091"
+dependencies = [
+ "windows-link",
+]
+
+[[package]]
name = "windows-sys"
version = "0.61.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -1379,6 +2447,19 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1ebf944e87a7c253233ad6766e082e3cd714b5d03812acc24c318f549614536e"
[[package]]
+name = "write-fonts"
+version = "0.48.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "cb731d4c4d93eacc69a1ad2f270f905788a98e4a3438267bcafbe08d3431c8d8"
+dependencies = [
+ "font-types 0.11.3",
+ "indexmap",
+ "kurbo",
+ "log",
+ "read-fonts 0.39.2",
+]
+
+[[package]]
name = "y4m"
version = "0.8.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -1405,12 +2486,44 @@ dependencies = [
]
[[package]]
+name = "zip"
+version = "8.6.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "2d04a6b5381502aa6087c94c669499eb1602eb9c5e8198e534de571f7154809b"
+dependencies = [
+ "crc32fast",
+ "flate2",
+ "indexmap",
+ "memchr",
+ "typed-path",
+ "zopfli",
+]
+
+[[package]]
name = "zlib-rs"
version = "0.6.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b268e58e7c693d7c271f93ffc4ba3b380412554231c85bf61ca7af91042a4112"
[[package]]
+name = "zmij"
+version = "1.0.23"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "29666d0abbfad1e3dc4dcf6144730dd3a3ab225bbbdac83319345b1b44ccfc1b"
+
+[[package]]
+name = "zopfli"
+version = "0.8.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f05cd8797d63865425ff89b5c4a48804f35ba0ce8d125800027ad6017d2b5249"
+dependencies = [
+ "bumpalo",
+ "crc32fast",
+ "log",
+ "simd-adler32",
+]
+
+[[package]]
name = "zune-core"
version = "0.5.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
Cargo.toml (238 → 527)
diff --git a/Cargo.toml b/Cargo.toml
index afab24e..750bc31 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -6,9 +6,15 @@ edition = "2021"
[dependencies]
clap = { version = "4", features = ["derive"] }
image = "0.25"
+pdf_oxide = { version ="0.3.78", features=["rendering"] }
+tempfile = "3"
+
+# This is a temporary pin because of a compatibility issue.
+# Remove it when upgrading to pdf_oxide 0.3.79.
+# See https://github.com/yfedoseev/pdf_oxide/issues/1546
+office_oxide = { version = "=0.1.11", default-features = false }
[dev-dependencies]
anyhow = "1.0.104"
assert_cmd = "2"
predicates = "3"
-tempfile = "3"
src/create_parent_directory.rs (1248 → 0)
diff --git a/src/create_parent_directory.rs b/src/create_parent_directory.rs
deleted file mode 100644
index e028978..0000000
--- a/src/create_parent_directory.rs
+++ /dev/null
@@ -1,46 +0,0 @@
-use std::fs;
-use std::io;
-use std::path::Path;
-
-/// Create the parent directory of a given path.
-///
-/// Example:
-///
-/// create_parent_directory("path/to/images/index.html")
-/// ~> creates "path/to/images/"
-///
-pub fn create_parent_directory(path: impl AsRef<Path>) -> io::Result<()> {
- let path = path.as_ref();
-
- // Quoting from the Rust docs for PathBuf.parent() [1]:
- //
- // Returns None if the path terminates in a root or prefix,
- // or if it’s the empty string.
- //
- // This function should only ever be called on paths to files, so
- // .parent() will never return None.
- //
- // [1]: https://doc.rust-lang.org/std/path/struct.PathBuf.html#method.parent
- let parent_dir = path.parent().unwrap();
-
- fs::create_dir_all(&parent_dir)
-}
-
-#[cfg(test)]
-mod test_create_parent_directory {
- use super::*;
- use crate::test_utils::test_dir;
-
- #[test]
- fn it_creates_a_directory() {
- let t = test_dir();
- assert!(!t.exists());
-
- let path = t.join("path/to/images").join("index.html");
- assert!(create_parent_directory(&path).is_ok());
-
- assert!(t.exists());
- assert!(t.join("path/to/images").exists());
- assert!(!path.exists());
- }
-}
src/create_thumbnail.rs (7645 → 16108)
diff --git a/src/create_thumbnail.rs b/src/create_thumbnail.rs
index ed107e0..cd5c3bc 100644
--- a/src/create_thumbnail.rs
+++ b/src/create_thumbnail.rs
@@ -1,39 +1,110 @@
+use std::fs;
use std::path::{Path, PathBuf};
use std::process::Command;
use std::str;
use image::imageops::FilterType;
use image::{DynamicImage, ImageDecoder, ImageReader};
+use pdf_oxide::rendering::{render_page, RenderOptions};
+use pdf_oxide::PdfDocument;
-use crate::create_parent_directory::create_parent_directory;
use crate::dimensions::{thumbnail_dimensions, TargetDimension};
use crate::errors::ThumbnailError;
use crate::is_animated_gif::is_animated_gif;
-/// Create a thumbnail for the image, and return the relative path of
-/// the thumbnail within the collection folder.
+/// Create a thumbnail for a path, and return the relative path of
+/// the thumbnail within the output directory.
pub fn create_thumbnail(
path: impl AsRef<Path>,
out_dir: &PathBuf,
+ page: Option<usize>,
target: TargetDimension,
) -> Result<PathBuf, ThumbnailError> {
- let path = path.as_ref();
- let file_name = path.file_name().ok_or(ThumbnailError::MissingFileName)?;
- let thumbnail_path = out_dir.join(file_name);
- create_parent_directory(&thumbnail_path)?;
+ let ext = path
+ .as_ref()
+ .extension()
+ .and_then(|e| e.to_str())
+ .map(|e| e.to_ascii_lowercase());
- // Make sure we don't overwrite the original image with a thumbnail
- if *path == thumbnail_path {
- return Err(ThumbnailError::SameInputOutputPath);
+ fs::create_dir_all(out_dir)?;
+
+ match (ext.as_deref(), page) {
+ (Some("pdf"), page_num) => {
+ let page = page_num.unwrap_or(1);
+ create_pdf_thumbnail(path, out_dir, page, target)
+ }
+
+ // Passing a page number for non-PDF files is an error.
+ (_, Some(_)) => Err(ThumbnailError::UnsupportedPageOption(
+ path.as_ref().to_path_buf(),
+ )),
+
+ (Some("gif"), None) if is_animated_gif(&path)? => {
+ create_animated_gif_thumbnail(path, out_dir, target)
+ }
+ (_, None) => create_static_thumbnail(path, out_dir, target),
}
+}
- let (new_width, new_height) = thumbnail_dimensions(&path, target)?;
+/// Create a thumbnail for a PDF.
+fn create_pdf_thumbnail(
+ pdf_path: impl AsRef<Path>,
+ out_dir: &PathBuf,
+ page: usize,
+ target: TargetDimension,
+) -> Result<PathBuf, ThumbnailError> {
+ let pdf_path = pdf_path.as_ref();
+ let file_stem = pdf_path
+ .file_stem()
+ .and_then(|s| s.to_str())
+ .ok_or(ThumbnailError::MissingFileName)?;
- if is_animated_gif(path)? {
- create_animated_gif_thumbnail(path, out_dir, new_width, new_height)
- } else {
- create_static_thumbnail(path, out_dir, new_width, new_height)
+ // Create a temporary directory to save candidate raster images.
+ let tmp_dir = tempfile::tempdir()?;
+
+ let mut doc = PdfDocument::open(pdf_path).map_err(ThumbnailError::PdfOpenError)?;
+
+ // Pages are 1-indexed for the user. Verify page is in bounds.
+ let total_pages = doc
+ .page_count()
+ .map_err(|e| ThumbnailError::PdfOpenError(e))?;
+ if page == 0 || page > total_pages {
+ return Err(ThumbnailError::InvalidPdfPage { page, total_pages });
}
+
+ // People use 1-indexing for pages, but render_page() is 0-indexed.
+ // 300 DPI is larger than thumbnails need, but will give us enough
+ // data to render the thumbnail properly.
+ let page_index = page.saturating_sub(1);
+
+ // Render the page as both PNG and JPEG.
+ let png_options = RenderOptions::with_dpi(300);
+ let png_image = render_page(&mut doc, page_index, &png_options)
+ .map_err(ThumbnailError::RenderPdfPageError)?;
+ let png_path = tmp_dir.path().join(format!("{}.png", file_stem));
+ png_image
+ .save(&png_path)
+ .map_err(ThumbnailError::CandidateImageSaveError)?;
+
+ let jpg_options = RenderOptions::with_dpi(300).as_jpeg(85);
+ let jpg_image = render_page(&mut doc, page_index, &jpg_options)
+ .map_err(ThumbnailError::RenderPdfPageError)?;
+ let jpg_path = tmp_dir.path().join(format!("{}.jpg", file_stem));
+ jpg_image
+ .save(&jpg_path)
+ .map_err(ThumbnailError::CandidateImageSaveError)?;
+
+ // Check file sizes and pick the smaller candidate file
+ let png_size = fs::metadata(&png_path)?.len();
+ let jpg_size = fs::metadata(&jpg_path)?.len();
+
+ let chosen_path = if png_size <= jpg_size {
+ png_path
+ } else {
+ jpg_path
+ };
+
+ create_static_thumbnail(&chosen_path, out_dir, target)
}
/// Create a thumbnail for an animated GIF.
@@ -48,11 +119,12 @@ pub fn create_thumbnail(
/// installed, but I'm not sure how to simulate that.
///
fn create_animated_gif_thumbnail(
- gif_path: &Path,
+ path: impl AsRef<Path>,
out_dir: &PathBuf,
- width: u32,
- height: u32,
+ target: TargetDimension,
) -> Result<PathBuf, ThumbnailError> {
+ let (width, height) = thumbnail_dimensions(&path, target)?;
+ let gif_path = path.as_ref();
let file_name = gif_path
.file_name()
.ok_or(ThumbnailError::MissingFileName)?;
@@ -113,17 +185,24 @@ fn ensure_even(x: u32) -> u32 {
/// This function assumes that the original image file definitely exists.
///
fn create_static_thumbnail(
- image_path: &Path,
+ image_path: impl AsRef<Path>,
out_dir: &PathBuf,
- width: u32,
- height: u32,
+ target: TargetDimension,
) -> Result<PathBuf, ThumbnailError> {
+ let image_path = image_path.as_ref();
let file_name = image_path
.file_name()
.ok_or(ThumbnailError::MissingFileName)?;
let thumbnail_path = out_dir.join(file_name);
+ // Make sure we don't overwrite the original image with a thumbnail
+ if *image_path == thumbnail_path {
+ return Err(ThumbnailError::SameInputOutputPath);
+ }
+
+ let (width, height) = thumbnail_dimensions(image_path, target)?;
+
let mut decoder = ImageReader::open(image_path)?.into_decoder()?;
let orientation = decoder.orientation()?;
let mut img = DynamicImage::from_decoder(decoder)?;
@@ -137,7 +216,7 @@ fn create_static_thumbnail(
}
#[cfg(test)]
-mod test_create_thumbnail {
+mod test_create_image_thumbnail {
use super::create_thumbnail;
use crate::dimensions::TargetDimension;
use crate::test_utils::{get_dimensions, test_dir};
@@ -155,7 +234,7 @@ mod test_create_thumbnail {
#[test]
fn $name() {
let out_dir = test_dir();
- let thumbnail_path = create_thumbnail($img_path, &out_dir, $target).unwrap();
+ let thumbnail_path = create_thumbnail($img_path, &out_dir, None, $target).unwrap();
assert_eq!(thumbnail_path, out_dir.join($expected_filename));
assert!(thumbnail_path.exists());
@@ -167,64 +246,234 @@ mod test_create_thumbnail {
image_thumbnail_tests! {
animated_gif: {
- img_path: "src/tests/animated_squares.gif",
- target: TargetDimension::MaxWidth(16),
+ img_path: "src/tests/animated_squares.gif",
+ target: TargetDimension::MaxWidth(16),
expected_filename: "animated_squares.mp4",
- expected_dims: (16, 16),
+ expected_dims: (16, 16),
},
// Regression test for "height not divisible by 2" error.
animated_gif_with_odd_width: {
- img_path: "src/tests/animated_squares.gif",
- target: TargetDimension::MaxWidth(15),
+ img_path: "src/tests/animated_squares.gif",
+ target: TargetDimension::MaxWidth(15),
expected_filename: "animated_squares.mp4",
- expected_dims: (16, 16),
+ expected_dims: (16, 16),
},
static_gif: {
- img_path: "src/tests/yellow.gif",
- target: TargetDimension::MaxWidth(16),
+ img_path: "src/tests/yellow.gif",
+ target: TargetDimension::MaxWidth(16),
expected_filename: "yellow.gif",
- expected_dims: (16, 8),
+ expected_dims: (16, 8),
},
png: {
- img_path: "src/tests/red.png",
- target: TargetDimension::MaxWidth(16),
+ img_path: "src/tests/red.png",
+ target: TargetDimension::MaxWidth(16),
expected_filename: "red.png",
- expected_dims: (16, 32),
+ expected_dims: (16, 32),
},
jpeg: {
- img_path: "src/tests/noise.jpg",
- target: TargetDimension::MaxWidth(16),
+ img_path: "src/tests/noise.jpg",
+ target: TargetDimension::MaxWidth(16),
expected_filename: "noise.jpg",
- expected_dims: (16, 32),
+ expected_dims: (16, 32),
},
tiff: {
- img_path: "src/tests/green.tiff",
- target: TargetDimension::MaxWidth(16),
+ img_path: "src/tests/green.tiff",
+ target: TargetDimension::MaxWidth(16),
expected_filename: "green.tiff",
- expected_dims: (16, 16),
+ expected_dims: (16, 16),
},
webp: {
- img_path: "src/tests/purple.webp",
- target: TargetDimension::MaxWidth(16),
+ img_path: "src/tests/purple.webp",
+ target: TargetDimension::MaxWidth(16),
expected_filename: "purple.webp",
- expected_dims: (16, 16),
+ expected_dims: (16, 16),
},
// If the target dimension is larger than the original image,
// the thumbnail is created at original size.
clamps_to_original_size: {
- img_path: "src/tests/noise.jpg",
- target: TargetDimension::MaxWidth(500),
+ img_path: "src/tests/noise.jpg",
+ target: TargetDimension::MaxWidth(500),
expected_filename: "noise.jpg",
- expected_dims: (128, 256),
+ expected_dims: (128, 256),
},
// This source image comes from Dave Perrett's exif-orientation-examples
// repo, and is used under MIT.
// See https://github.com/recurser/exif-orientation-examples
exif_orientation: {
- img_path: "src/tests/Landscape_5.jpg",
- target: TargetDimension::MaxWidth(180),
+ img_path: "src/tests/Landscape_5.jpg",
+ target: TargetDimension::MaxWidth(180),
expected_filename: "Landscape_5.jpg",
- expected_dims: (180, 120),
+ expected_dims: (180, 120),
+ },
+ }
+
+ // Passing a page number is an error if the file isn't a PDF.
+ #[test]
+ fn errors_if_page_for_images() {
+ let out_dir = test_dir();
+ let page = Some(1);
+ let target = TargetDimension::MaxWidth(50);
+
+ let result = create_thumbnail("src/tests/noise.jpg", &out_dir, page, target);
+ assert!(result.is_err());
+ }
+}
+
+#[cfg(test)]
+mod test_create_pdf_thumbnail {
+ use anyhow::Result;
+ use image::ImageReader;
+
+ use super::create_thumbnail;
+ use crate::dimensions::TargetDimension;
+ use crate::errors::ThumbnailError;
+ use crate::test_utils::{get_dimensions, test_dir};
+
+ macro_rules! pdf_thumbnail_tests {
+ ($($name: ident: {
+ pdf_path: $pdf_path: expr,
+ target: $target:expr,
+ page: $page:expr,
+ expected_filename: $expected_filename:expr,
+ expected_dims: $expected_dims:expr,
+ },)*)
+ =>
+ {
+ $(
+ #[test]
+ fn $name() {
+ let out_dir = test_dir();
+ let thumbnail_path = create_thumbnail($pdf_path, &out_dir, Some($page), $target).unwrap();
+
+ assert_eq!(thumbnail_path, out_dir.join($expected_filename));
+ assert!(thumbnail_path.exists());
+ assert_eq!(get_dimensions(&thumbnail_path), $expected_dims);
+
+ // Open the created thumbnail with ImageReader.
+ // If the image format doesn't match the extension,
+ // (for example, if this is a PNG saved with a .jpeg
+ // suffix) this will error.
+ ImageReader::open(&thumbnail_path).unwrap();
+ }
+ )*
+ }
+ }
+
+ pdf_thumbnail_tests! {
+ portrait: {
+ pdf_path: "src/tests/rgb.pdf",
+ target: TargetDimension::MaxWidth(16),
+ page: 1,
+ expected_filename: "rgb.png",
+ expected_dims: (16, 23),
+ },
+ landscape: {
+ pdf_path: "src/tests/rgb.pdf",
+ target: TargetDimension::MaxWidth(16),
+ page: 2,
+ expected_filename: "rgb.png",
+ expected_dims: (16, 11),
+ },
+ square: {
+ pdf_path: "src/tests/rgb.pdf",
+ target: TargetDimension::MaxWidth(16),
+ page: 3,
+ expected_filename: "rgb.png",
+ expected_dims: (16, 16),
+ },
+ gradient: {
+ pdf_path: "src/tests/gradient.pdf",
+ target: TargetDimension::MaxWidth(16),
+ page: 1,
+ expected_filename: "gradient.jpg",
+ expected_dims: (16, 16),
+ },
+ uppercase: {
+ pdf_path: "src/tests/RGB_UPPER.PDF",
+ target: TargetDimension::MaxWidth(16),
+ page: 1,
+ expected_filename: "RGB_UPPER.png",
+ expected_dims: (16, 23),
},
}
+
+ #[test]
+ fn errors_if_nonexistent_pdf() {
+ let out_dir = test_dir();
+ let target = TargetDimension::MaxWidth(50);
+
+ let result = create_thumbnail("does_not_exist.pdf", &out_dir, None, target);
+ assert!(matches!(result, Err(ThumbnailError::PdfOpenError(_))));
+ }
+
+ #[test]
+ fn errors_if_invalid_pdf_file() -> Result<()> {
+ let pdf_file = tempfile::Builder::new()
+ .suffix(".pdf")
+ .rand_bytes(5)
+ .tempfile()?;
+
+ let out_dir = test_dir();
+ let target = TargetDimension::MaxWidth(50);
+
+ let result = create_thumbnail(pdf_file.path(), &out_dir, None, target);
+ assert!(matches!(result, Err(ThumbnailError::PdfOpenError(_))));
+
+ Ok(())
+ }
+
+ #[test]
+ fn errors_if_png_disguised_as_pdf() -> Result<()> {
+ let temp_dir = tempfile::tempdir()?;
+ let pdf_path = temp_dir.path().join("actually_a_png.pdf");
+
+ // Write actual PNG magic bytes (\x89PNG\r\n\x1a\n) followed
+ // by random binary data
+ let png_header = [
+ 0x89, 0x50, 0x4E, 0x47, 0x0D, 0x0A, 0x1A, 0x0A, 0xFF, 0xD8, 0xFF,
+ ];
+ std::fs::write(&pdf_path, png_header).unwrap();
+
+ let out_dir = test_dir();
+ let target = TargetDimension::MaxWidth(50);
+
+ let result = create_thumbnail(&pdf_path, &out_dir, None, target);
+ assert!(matches!(result, Err(ThumbnailError::PdfOpenError(_))));
+
+ Ok(())
+ }
+
+ #[test]
+ fn errors_if_zero_pdf_page() {
+ let page = Some(0);
+
+ let out_dir = test_dir();
+ let target = TargetDimension::MaxWidth(50);
+
+ let result = create_thumbnail("src/tests/rgb.pdf", &out_dir, page, target);
+ assert!(matches!(
+ result,
+ Err(ThumbnailError::InvalidPdfPage {
+ page: 0,
+ total_pages: 3
+ })
+ ));
+ }
+
+ #[test]
+ fn errors_if_too_large_pdf_page() {
+ let page = Some(5);
+
+ let out_dir = test_dir();
+ let target = TargetDimension::MaxWidth(50);
+
+ let result = create_thumbnail("src/tests/rgb.pdf", &out_dir, page, target);
+ assert!(matches!(
+ result,
+ Err(ThumbnailError::InvalidPdfPage {
+ page: 5,
+ total_pages: 3
+ })
+ ));
+ }
}
src/errors.rs (1737 → 2699)
diff --git a/src/errors.rs b/src/errors.rs
index f01db6c..1597055 100644
--- a/src/errors.rs
+++ b/src/errors.rs
@@ -1,5 +1,6 @@
use std::fmt;
use std::io;
+use std::path::PathBuf;
use image::ImageError;
@@ -13,6 +14,11 @@ pub enum ThumbnailError {
PathConversionError,
SameInputOutputPath,
IoError(std::io::Error),
+ UnsupportedPageOption(PathBuf),
+ PdfOpenError(pdf_oxide::Error),
+ RenderPdfPageError(pdf_oxide::Error),
+ CandidateImageSaveError(pdf_oxide::Error),
+ InvalidPdfPage { page: usize, total_pages: usize },
}
impl fmt::Display for ThumbnailError {
@@ -29,6 +35,18 @@ impl fmt::Display for ThumbnailError {
"Cannot write thumbnail to the same path as the original image"
),
ThumbnailError::IoError(e) => write!(f, "I/O error: {}", e),
+ ThumbnailError::UnsupportedPageOption(path) => write!(
+ f,
+ "Page option is not supported for file: {}",
+ path.display()
+ ),
+ ThumbnailError::PdfOpenError(e) => write!(f, "Failed to open PDF: {}", e),
+ ThumbnailError::RenderPdfPageError(e) => write!(f, "Failed to render PDF page: {}", e),
+ ThumbnailError::CandidateImageSaveError(e) => write!(f, "Failed to save image: {}", e),
+ ThumbnailError::InvalidPdfPage { page, total_pages } => write!(
+ f,
+ "Invalid PDF page requested: page {page} is out of bounds for a {total_pages}-page document (pages are 1-indexed)"
+ ),
}
}
}
src/main.rs (9277 → 11697)
diff --git a/src/main.rs b/src/main.rs
index c10765c..4ed99d2 100644
--- a/src/main.rs
+++ b/src/main.rs
@@ -3,9 +3,9 @@
use std::env;
use std::path::PathBuf;
-use clap::Parser;
+use clap::error::ErrorKind;
+use clap::{CommandFactory, Parser};
-mod create_parent_directory;
mod create_thumbnail;
mod dimensions;
mod errors;
@@ -13,11 +13,12 @@ mod is_animated_gif;
use crate::create_thumbnail::create_thumbnail;
use crate::dimensions::TargetDimension;
+use crate::errors::ThumbnailError;
#[derive(Debug, Parser)]
#[clap(version, about)]
struct Cli {
- /// Path to the image to be thumbnailed
+ /// Path to the file to be thumbnailed
path: PathBuf,
/// Path to the directory to save the thumbnail in. Defaults to
@@ -35,6 +36,10 @@ struct Cli {
/// Width of the thumbnail to create
#[arg(long)]
width: Option<u32>,
+
+ /// Page number to create a thumbnail of (PDF-only) [default: 1]
+ #[arg(long)]
+ page: Option<usize>,
}
fn main() {
@@ -45,15 +50,36 @@ fn main() {
(Some(w), None) => TargetDimension::MaxWidth(w),
(None, Some(h)) => TargetDimension::MaxHeight(h),
_ => {
- eprintln!(
- "Failed to create thumbnail: you must pass at least one of --width or --height"
- );
- std::process::exit(1);
+ let mut cmd = Cli::command();
+ cmd.error(
+ ErrorKind::ArgumentConflict,
+ "you must pass at least one of --width or --height",
+ )
+ .exit();
}
};
- match create_thumbnail(&cli.path, &cli.out_dir, target) {
+ match create_thumbnail(&cli.path, &cli.out_dir, cli.page, target) {
Ok(thumbnail_path) => print!("{}", thumbnail_path.display()),
+ Err(ThumbnailError::UnsupportedPageOption(path)) => {
+ let mut cmd = Cli::command();
+ cmd.error(
+ ErrorKind::ArgumentConflict,
+ format!(
+ "the `--page` option is not supported for file: {}",
+ path.display()
+ ),
+ )
+ .exit();
+ }
+ Err(ThumbnailError::InvalidPdfPage { page, total_pages }) => {
+ let mut cmd = Cli::command();
+ cmd.error(
+ ErrorKind::ArgumentConflict,
+ format!("invalid `--page`: page {page} is out of bounds for a {total_pages}-page document"),
+ )
+ .exit();
+ }
Err(e) => {
eprintln!("{}", e);
std::process::exit(1);
@@ -118,6 +144,16 @@ mod test_cli {
expected_filename: "noise.jpg",
expected_dims: (32, 64),
},
+ pdf_defaults_to_page_1: {
+ args: &["src/tests/rgb.pdf", "--width=64"],
+ expected_filename: "rgb.png",
+ expected_dims: (64, 91),
+ },
+ pdf_with_page: {
+ args: &["src/tests/rgb.pdf", "--width=64", "--page=3"],
+ expected_filename: "rgb.png",
+ expected_dims: (64, 64),
+ },
}
// Omitting `--out-dir` creates the thumbnail in CWD.
@@ -154,6 +190,7 @@ mod test_cli {
macro_rules! cli_error_tests {
($($name:ident: {
args: $args:expr,
+ expect_code: $expect_code:expr,
expect_error: $expect_error:expr,
},)*) => {
$(
@@ -164,7 +201,7 @@ mod test_cli {
.args($args)
.assert()
.failure()
- .code(1)
+ .code($expect_code)
.stdout("")
.stderr($expect_error);
}
@@ -175,14 +212,17 @@ mod test_cli {
cli_error_tests! {
no_width_nor_height: {
args: &["src/tests/red.png"],
- expect_error: "Failed to create thumbnail: you must pass at least one of --width or --height\n",
+ expect_code: 2,
+ expect_error: predicate::str::contains("you must pass at least one of --width or --height"),
},
nonexistent_file: {
args: &["doesnotexist.png", "--width=50"],
+ expect_code: 1,
expect_error: "Failed to open image: No such file or directory (os error 2)\n",
},
non_image: {
args: &["Cargo.toml", "--width=50"],
+ expect_code: 1,
expect_error: "Failed to open image: The file extension `.\"toml\"` was not recognized as an image format\n",
},
// TODO: Improve this error message.
@@ -190,13 +230,32 @@ mod test_cli {
// It's good to know the tool won't completely break, but it'd
// be nice to return a more meaningful error message in this case.
out_dir_is_file: {
- args: &["src/images/nose.jpg", "--width=50", "--out-dir=README.md"],
+ args: &["src/tests/nose.jpg", "--width=50", "--out-dir=README.md"],
+ expect_code: 1,
expect_error: "I/O error: File exists (os error 17)\n",
},
+ // TODO: don't make this an error; instead, write to the same
+ // directory but with a thumbnail suffix.
overwrite_original_file: {
- args: &["src/images/noise.jpg", "--width=50", "--out-dir=src/images"],
+ args: &["src/tests/noise.jpg", "--width=50", "--out-dir=src/tests"],
+ expect_code: 1,
expect_error: "Cannot write thumbnail to the same path as the original image\n",
},
+ page_with_non_pdf: {
+ args: &["src/tests/noise.jpg", "--width=50", "--page=1"],
+ expect_code: 2,
+ expect_error: predicate::str::contains("the `--page` option is not supported for file"),
+ },
+ pdf_with_zero_page: {
+ args: &["src/tests/rgb.pdf", "--width=50", "--page=0"],
+ expect_code: 2,
+ expect_error: predicate::str::contains("invalid `--page`: page 0 is out of bounds for a 3-page document"),
+ },
+ pdf_with_out_of_bounds_page: {
+ args: &["src/tests/rgb.pdf", "--width=50", "--page=5"],
+ expect_code: 2,
+ expect_error: predicate::str::contains("invalid `--page`: page 5 is out of bounds for a 3-page document"),
+ },
}
#[test]
src/tests/RGB_UPPER.PDF (0 → 17783)
diff --git a/src/tests/RGB_UPPER.PDF b/src/tests/RGB_UPPER.PDF
new file mode 100644
index 0000000..19dae81
Binary files /dev/null and b/src/tests/RGB_UPPER.PDF differ
src/tests/gradient.pdf (0 → 449299)
diff --git a/src/tests/gradient.pdf b/src/tests/gradient.pdf
new file mode 100644
index 0000000..b12a4b6
Binary files /dev/null and b/src/tests/gradient.pdf differ
src/tests/rgb.pdf (0 → 17783)
diff --git a/src/tests/rgb.pdf b/src/tests/rgb.pdf
new file mode 100644
index 0000000..19dae81
Binary files /dev/null and b/src/tests/rgb.pdf differ