diff --git a/Cargo.lock b/Cargo.lock index cf82c8ef1e..169fa63031 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -37,9 +37,9 @@ checksum = "4aa90d7ce82d4be67b64039a3d588d38dbcc6736577de4a847025ce5b0c468d1" [[package]] name = "allocator-api2" -version = "0.2.21" +version = "0.2.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "683d7910e743518b0e34f1186f92494becacb047c7b6bf616c96772180fef923" +checksum = "5c6cb57a04249c6480766f7f7cef5467412af1490f8d1e243141daddada3264f" [[package]] name = "alsa" @@ -48,7 +48,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ed7572b7ba83a31e20d1b48970ee402d2e3e0537dcfe0a3ff4d6eb7508617d43" dependencies = [ "alsa-sys", - "bitflags 2.8.0", + "bitflags 2.9.0", "cfg-if", "libc", ] @@ -80,9 +80,9 @@ dependencies = [ [[package]] name = "anstream" -version = "0.6.18" +version = "0.6.17" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8acc5369981196006228e28809f761875c0327210a891e941f4c683b3a99529b" +checksum = "23a1e53f0f5d86382dafe1cf314783b2044280f406e7e1506368220ad11b1338" dependencies = [ "anstyle", "anstyle-parse", @@ -119,20 +119,19 @@ dependencies = [ [[package]] name = "anstyle-wincon" -version = "3.0.7" +version = "3.0.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ca3534e77181a9cc07539ad51f2141fe32f6c3ffd4df76db8ad92346b003ae4e" +checksum = "2109dbce0e72be3ec00bed26e6a7479ca384ad226efdd66db8fa2e3a38c83125" dependencies = [ "anstyle", - "once_cell", "windows-sys 0.59.0", ] [[package]] name = "anyhow" -version = "1.0.96" +version = "1.0.92" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6b964d184e89d9b6b67dd2715bc8e74cf3107fb2b529990c90cf517326150bf4" +checksum = "74f37166d7d48a0284b99dd824694c26119c700b53bf0d1540cdb147dbdaaf13" [[package]] name = "approx" @@ -154,9 +153,9 @@ dependencies = [ [[package]] name = "arbitrary" -version = "1.4.1" +version = "1.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dde20b3d026af13f561bdd0f15edf01fc734f0dafcedbaf42bba506a9517f223" +checksum = "7d5a26814d8dcb93b0e5a0ff3c6d80a8843bafb21b39e8e18a6f05471870e110" [[package]] name = "arg_enum_proc_macro" @@ -166,7 +165,7 @@ checksum = "0ae92a5119aa49cdbcf6b9f893fe4e1d98b04ccbf82ee0584ad948a44a734dea" dependencies = [ "proc-macro2", "quote", - "syn 2.0.98", + "syn 2.0.87", ] [[package]] @@ -222,9 +221,9 @@ dependencies = [ [[package]] name = "async-io" -version = "2.4.0" +version = "2.3.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "43a2b323ccce0a1d90b449fd71f2a06ca7faa7c54c2751f06c9bd851fc061059" +checksum = "444b0228950ee6501b3568d3c93bf1176a1fdbc3b758dcd9475046d30f4dc7e8" dependencies = [ "async-lock", "cfg-if", @@ -351,7 +350,7 @@ version = "0.70.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f49d8fed880d473ea71efb9bf597651e77201bdd4893efe54c9e5d65ae04ce6f" dependencies = [ - "bitflags 2.8.0", + "bitflags 2.9.0", "cexpr", "clang-sys", "itertools 0.13.0", @@ -360,7 +359,7 @@ dependencies = [ "regex", "rustc-hash 1.1.0", "shlex", - "syn 2.0.98", + "syn 2.0.87", ] [[package]] @@ -369,6 +368,30 @@ version = "0.10.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "dc827186963e592360843fb5ba4b973e145841266c1357f7180c43526f2e5b61" +[[package]] +name = "bitcode" +version = "0.6.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ee1bce7608560cd4bf0296a4262d0dbf13e6bcec5ff2105724c8ab88cc7fc784" +dependencies = [ + "arrayvec", + "bitcode_derive", + "bytemuck", + "glam", + "serde", +] + +[[package]] +name = "bitcode_derive" +version = "0.6.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a539389a13af092cd345a2b47ae7dec12deb306d660b2223d25cd3419b253ebe" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.87", +] + [[package]] name = "bitflags" version = "1.3.2" @@ -377,18 +400,18 @@ checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" [[package]] name = "bitflags" -version = "2.8.0" +version = "2.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8f68f53c83ab957f72c32642f3868eec03eb974d1fb82e453128456482613d36" +checksum = "5c8214115b7bf84099f1309324e63141d4c5d7cc26862f97a0a857dbefe165bd" dependencies = [ "serde", ] [[package]] name = "bitstream-io" -version = "2.6.0" +version = "2.5.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6099cdc01846bc367c4e7dd630dc5966dccf36b652fae7a74e17b640411a91b2" +checksum = "b81e1519b0d82120d2fd469d5bfb2919a9361c48b02d82d04befc1cdd2002452" [[package]] name = "block" @@ -411,15 +434,15 @@ dependencies = [ [[package]] name = "built" -version = "0.7.7" +version = "0.7.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "56ed6191a7e78c36abdb16ab65341eefd73d64d303fffccdbb00d51e4205967b" +checksum = "c360505aed52b7ec96a3636c3f039d99103c37d1d9b4f7a8c743d3ea9ffcd03b" [[package]] name = "bumpalo" -version = "3.17.0" +version = "3.16.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1628fb46dfa0b37568d12e5edd512553eccf6a22a78e8bde00bb4aed84d5bdbf" +checksum = "79296716171880943b8470b5f8d03aa55eb2e645a4874bdbb28adb49162e012c" [[package]] name = "by_address" @@ -429,9 +452,9 @@ checksum = "64fa3c856b712db6612c019f14756e64e4bcea13337a6b33b696333a9eaa2d06" [[package]] name = "bytemuck" -version = "1.21.0" +version = "1.19.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ef657dfab802224e671f5818e9a4935f9b1957ed18e58292690cc39e7a4092a3" +checksum = "8334215b81e418a0a7bdb8ef0849474f40bb10c8b71f1c4ed315cff49f32494d" [[package]] name = "byteorder" @@ -447,17 +470,17 @@ checksum = "8f1fe948ff07f4bd06c30984e69f5b4899c516a3ef74f34df92a2df2ab535495" [[package]] name = "bytes" -version = "1.10.0" +version = "1.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f61dac84819c6588b558454b194026eb1f09c293b9036ae9b159e74e73ab6cf9" +checksum = "9ac0150caa2ae65ca5bd83f25c7de183dea78d4d366469f148435e2acfbad0da" [[package]] name = "cairo-rs" -version = "0.20.7" +version = "0.20.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ae50b5510d86cf96ac2370e66d8dc960882f3df179d6a5a1e52bd94a1416c0f7" +checksum = "d7fa699e1d7ae691001a811dda5ef0e3e42e1d4119b26426352989df9e94e3e6" dependencies = [ - "bitflags 2.8.0", + "bitflags 2.9.0", "cairo-sys-rs", "glib", "libc", @@ -465,9 +488,9 @@ dependencies = [ [[package]] name = "cairo-sys-rs" -version = "0.20.7" +version = "0.20.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f18b6bb8e43c7eb0f2aac7976afe0c61b6f5fc2ab7bc4c139537ea56c92290df" +checksum = "428290f914b9b86089f60f5d8a9f6e440508e1bcff23b25afd51502b0a2da88f" dependencies = [ "glib-sys", "libc", @@ -482,9 +505,9 @@ checksum = "37b2a672a2cb129a2e41c10b1224bb368f9f37a2b16b612598138befd7b37eb5" [[package]] name = "cc" -version = "1.2.14" +version = "1.1.34" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0c3d1b2e905a3a7b00a6141adb0e4c0bb941d11caf55349d863942a1cc44e3c9" +checksum = "67b9470d453346108f93a59222a9a1a5724db32d0a4727b7ab7ace4b4d822dc9" dependencies = [ "jobserver", "libc", @@ -518,9 +541,9 @@ dependencies = [ [[package]] name = "cfg-expr" -version = "0.17.2" +version = "0.17.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8d4ba6e40bd1184518716a6e1a781bf9160e286d219ccdb8ab2612e74cfe4789" +checksum = "d0890061c4d3223e7267f3bad2ec40b997d64faac1c2815a4a9d95018e2b9e9c" dependencies = [ "smallvec", "target-lexicon", @@ -544,9 +567,9 @@ dependencies = [ [[package]] name = "chrono" -version = "0.4.39" +version = "0.4.38" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7e36cc9d416881d2e24f9a963be5fb1cd90966419ac844274161d10488b3e825" +checksum = "a21f936df1771bf62b77f047b726c4625ff2e8aa607c01ec06e5a05bd8463401" dependencies = [ "android-tzdata", "iana-time-zone", @@ -569,9 +592,9 @@ dependencies = [ [[package]] name = "clap" -version = "4.5.30" +version = "4.5.20" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "92b7b18d71fad5313a1e320fa9897994228ce274b60faa4d694fe0ea89cd9e6d" +checksum = "b97f376d85a664d5837dbae44bf546e6477a679ff6610010f17276f686d867e8" dependencies = [ "clap_builder", "clap_derive", @@ -579,9 +602,9 @@ dependencies = [ [[package]] name = "clap_builder" -version = "4.5.30" +version = "4.5.20" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a35db2071778a7344791a4fb4f95308b5673d219dee3ae348b86642574ecc90c" +checksum = "19bc80abd44e4bed93ca373a0704ccbd1b710dc5749406201bb018272808dc54" dependencies = [ "anstream", "anstyle", @@ -591,21 +614,21 @@ dependencies = [ [[package]] name = "clap_derive" -version = "4.5.28" +version = "4.5.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bf4ced95c6f4a675af3da73304b9ac4ed991640c36374e4b46795c49e17cf1ed" +checksum = "4ac6a0c7b1a9e9a5186361f67dfa1b88213572f427fb9ab038efb2bd8c582dab" dependencies = [ "heck", "proc-macro2", "quote", - "syn 2.0.98", + "syn 2.0.87", ] [[package]] name = "clap_lex" -version = "0.7.4" +version = "0.7.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f46ad14479a25103f283c0f10005961cf086d8dc42205bb44c46ac563475dca6" +checksum = "1462739cb27611015575c0c11df5df7601141071f07518d56fcc1be504cbec97" [[package]] name = "color_quant" @@ -640,15 +663,15 @@ dependencies = [ [[package]] name = "console" -version = "0.15.10" +version = "0.15.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ea3c6ecd8059b57859df5c69830340ed3c41d30e3da0c1cbed90a96ac853041b" +checksum = "0e1f83fc076bd6dd27517eacdf25fef6c4dfe5f1d7448bafaaf3a26f13b5e4eb" dependencies = [ "encode_unicode", + "lazy_static", "libc", - "once_cell", "unicode-width", - "windows-sys 0.59.0", + "windows-sys 0.52.0", ] [[package]] @@ -659,9 +682,9 @@ checksum = "773648b94d0e5d620f64f280777445740e61fe701025087ec8b57f45c791888b" [[package]] name = "core_maths" -version = "0.1.1" +version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "77745e017f5edba1a9c1d854f6f3a52dac8a12dd5af5d2f54aecf61e43d80d30" +checksum = "e3b02505ccb8c50b0aa21ace0fc08c3e53adebd4e58caa18a36152803c7709a3" dependencies = [ "libm", ] @@ -720,9 +743,9 @@ dependencies = [ [[package]] name = "crossbeam-deque" -version = "0.8.6" +version = "0.8.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9dd111b7b7f7d55b72c0a6ae361660ee5853c9af73f70c3c2ef6858b950e2e51" +checksum = "613f8cc01fe9cf1a3eb3d7f488fd2fa8388403e97039e2f73692932e291a770d" dependencies = [ "crossbeam-epoch", "crossbeam-utils", @@ -739,15 +762,15 @@ dependencies = [ [[package]] name = "crossbeam-utils" -version = "0.8.21" +version = "0.8.20" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d0a5c400df2834b80a4c3327b3aad3a4c4cd4de0629063962b03235697506a28" +checksum = "22ec99545bb0ed0ea7bb9b8e1e9122ea386ff8a48c0922e43f36d45ab09e0e80" [[package]] name = "crunchy" -version = "0.2.3" +version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "43da5946c66ffcc7745f48db692ffbb10a83bfe0afd96235c5c2a4fb23994929" +checksum = "7a81dae078cea95a014a339291cec439d2f232ebe854a9d672b796c6afafa9b7" [[package]] name = "cssparser" @@ -758,7 +781,7 @@ dependencies = [ "cssparser-macros", "dtoa-short", "itoa", - "phf 0.11.3", + "phf 0.11.2", "smallvec", ] @@ -769,7 +792,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "13b588ba4ac1a99f7f2964d24b3d896ddc6bf847ee3855dbd4366f058cfcd331" dependencies = [ "quote", - "syn 2.0.98", + "syn 2.0.87", ] [[package]] @@ -809,15 +832,12 @@ dependencies = [ [[package]] name = "dashmap" -version = "5.5.3" +version = "4.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "978747c1d849a7d2ee5e8adc0159961c48fb7e5db2f06af6723b80123bb53856" +checksum = "e77a43b28d0668df09411cb0bc9a8c2adc40f9a048afe863e05fd43251e8e39c" dependencies = [ "cfg-if", - "hashbrown 0.14.5", - "lock_api", - "once_cell", - "parking_lot_core", + "num_cpus", ] [[package]] @@ -865,13 +885,13 @@ dependencies = [ [[package]] name = "derive_more" -version = "0.99.19" +version = "0.99.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3da29a38df43d6f156149c9b43ded5e018ddff2a855cf2cfd62e8cd7d079c69f" +checksum = "5f33878137e4dafd7fa914ad4e259e18a4e8e532b9617a2d0150262bf53abfce" dependencies = [ "proc-macro2", "quote", - "syn 2.0.98", + "syn 2.0.87", ] [[package]] @@ -883,21 +903,10 @@ dependencies = [ "console", "shell-words", "tempfile", - "thiserror 1.0.69", + "thiserror 1.0.66", "zeroize", ] -[[package]] -name = "displaydoc" -version = "0.2.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "97369cbbc041bc366949bc74d34658d6cda5621039731c6310521892a3a20ae0" -dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.98", -] - [[package]] name = "downcast-rs" version = "1.2.1" @@ -946,9 +955,9 @@ dependencies = [ [[package]] name = "encode_unicode" -version = "1.0.0" +version = "0.3.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "34aa73646ffb006b8f5147f3dc182bd4bcb190227ce861fc4a4844bf8e3cb2c0" +checksum = "a357d28ed41a50f9c765dbfe56cbc04a64e53e5fc58ba79fbc34c10ef3df831f" [[package]] name = "encoding_rs" @@ -961,18 +970,18 @@ dependencies = [ [[package]] name = "equivalent" -version = "1.0.2" +version = "1.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "877a4ace8713b0bcf2a4e7eec82529c029f1d0619886d18145fea96c3ffe5c0f" +checksum = "5443807d6dff69373d433ab9ef5378ad8df50ca6298caf15de6e52e24aaf54d5" [[package]] name = "errno" -version = "0.3.10" +version = "0.3.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "33d852cb9b869c2a9b3df2f71a3074817f01e1844f839a144f5fcef059a4eb5d" +checksum = "534c5cf6194dfab3db3242765c03bbe257cf92f22b38f6bc0c58d59108a820ba" dependencies = [ "libc", - "windows-sys 0.59.0", + "windows-sys 0.52.0", ] [[package]] @@ -986,9 +995,9 @@ dependencies = [ [[package]] name = "event-listener" -version = "5.4.0" +version = "5.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3492acde4c3fc54c845eaab3eed8bd00c7a7d881f78bfc801e43a93dec1331ae" +checksum = "6032be9bd27023a771701cc49f9f053c751055f71efb2e0ae5c15809093675ba" dependencies = [ "concurrent-queue", "parking", @@ -997,9 +1006,9 @@ dependencies = [ [[package]] name = "event-listener-strategy" -version = "0.5.3" +version = "0.5.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3c3e4e0dd3673c1139bf041f3008816d9cf2946bbfac2945c09e523b8d7b05b2" +checksum = "0f214dc438f977e6d4e3500aaa277f5ad94ca83fbbd9b1a15713ce2344ccc5a1" dependencies = [ "event-listener", "pin-project-lite", @@ -1034,15 +1043,15 @@ checksum = "dd2e7510819d6fbf51a5545c8f922716ecfb14df168a3242f7d33e0239efe6a1" [[package]] name = "fastrand" -version = "2.3.0" +version = "2.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "37909eebbb50d72f9059c3b6d82c0463f2ff062c9e95845c43a6c9c0355411be" +checksum = "e8c02a5121d4ea3eb16a80748c74f5549a5665e4c21333c6098f283870fbdea6" [[package]] name = "fdeflate" -version = "0.3.7" +version = "0.3.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1e6853b52649d4ac5c0bd02320cddc5ba956bdb407c4b75a2c6b75bf51500f8c" +checksum = "07c6f4c64c1d33a3111c4466f7365ebdcc37c5bd1ea0d62aae2e3d722aacbedb" dependencies = [ "simd-adler32", ] @@ -1080,9 +1089,9 @@ dependencies = [ [[package]] name = "flate2" -version = "1.0.35" +version = "1.0.34" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c936bfdafb507ebbf50b8074c54fa31c5be9a1e7e5f467dd659697041407d07c" +checksum = "a1b589b4dc103969ad3cf85c950899926ec64300a1a46d76c03a6072957036f0" dependencies = [ "crc32fast", "miniz_oxide", @@ -1216,9 +1225,9 @@ checksum = "9e5c1b78ca4aae1ac06c48a526a655760685149f0d465d21f37abfe57ce075c6" [[package]] name = "futures-lite" -version = "2.6.0" +version = "2.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f5edaec856126859abb19ed65f39e90fea3a9574b9707f13539acf4abf7eb532" +checksum = "3f1fa2f9765705486b33fd2acf1577f8ec449c2ba1f318ae5447697b7c08d210" dependencies = [ "fastrand", "futures-core", @@ -1235,7 +1244,7 @@ checksum = "162ee34ebcb7c64a8abebc059ce0fee27c2262618d7b60ed8faf72fef13c3650" dependencies = [ "proc-macro2", "quote", - "syn 2.0.98", + "syn 2.0.87", ] [[package]] @@ -1279,9 +1288,9 @@ dependencies = [ [[package]] name = "gdk-pixbuf" -version = "0.20.9" +version = "0.20.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7563afd6ff0a221edfbb70a78add5075b8d9cb48e637a40a24c3ece3fea414d0" +checksum = "c4c29071a9e92337d8270a85cb0510cda4ac478be26d09ad027cc1d081911b19" dependencies = [ "gdk-pixbuf-sys", "gio", @@ -1291,9 +1300,9 @@ dependencies = [ [[package]] name = "gdk-pixbuf-sys" -version = "0.20.7" +version = "0.20.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "67f2587c9202bf997476bbba6aaed4f78a11538a2567df002a5f57f5331d0b5c" +checksum = "687343b059b91df5f3fbd87b4307038fa9e647fcc0461d0d3f93e94fee20bf3d" dependencies = [ "gio-sys", "glib-sys", @@ -1304,9 +1313,9 @@ dependencies = [ [[package]] name = "gdk4" -version = "0.9.6" +version = "0.9.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4850c9d9c1aecd1a3eb14fadc1cdb0ac0a2298037e116264c7473e1740a32d60" +checksum = "d3fb4af2d606b0ac4e81f982f0d064bcb71ca73859ce5f30475f7eb2e2be4bc3" dependencies = [ "cairo-rs", "gdk-pixbuf", @@ -1319,9 +1328,9 @@ dependencies = [ [[package]] name = "gdk4-sys" -version = "0.9.6" +version = "0.9.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6f6eb95798e2b46f279cf59005daf297d5b69555428f185650d71974a910473a" +checksum = "3642625b384ad43c006871462e6c498488b503dbf219542591f4ddf0f5f2032b" dependencies = [ "cairo-sys-rs", "gdk-pixbuf-sys", @@ -1336,9 +1345,9 @@ dependencies = [ [[package]] name = "geo" -version = "0.29.3" +version = "0.29.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "34f0e6e028c581e82e6822a68869514e94c25e7f8ea669a2d8595bdf7461ccc5" +checksum = "8798f09c0fb3625cf216569408e151a1884c3a028a0b533b7c223ae8f695c89a" dependencies = [ "earcutr", "float_next_after", @@ -1354,13 +1363,12 @@ dependencies = [ [[package]] name = "geo-types" -version = "0.7.15" +version = "0.7.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3bd1157f0f936bf0cd68dec91e8f7c311afe60295574d62b70d4861a1bfdf2d9" +checksum = "9ff16065e5720f376fbced200a5ae0f47ace85fd70b7e54269790281353b6d61" dependencies = [ "approx 0.5.1", "num-traits", - "rayon", "rstar", "serde", ] @@ -1429,9 +1437,9 @@ dependencies = [ [[package]] name = "gio" -version = "0.20.9" +version = "0.20.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a4f00c70f8029d84ea7572dd0e1aaa79e5329667b4c17f329d79ffb1e6277487" +checksum = "d8569975884fdfdbed536b682448fbd8c70bafbd69cac2d45eb1a7a372702241" dependencies = [ "futures-channel", "futures-core", @@ -1446,24 +1454,30 @@ dependencies = [ [[package]] name = "gio-sys" -version = "0.20.9" +version = "0.20.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "160eb5250a26998c3e1b54e6a3d4ea15c6c7762a6062a19a7b63eff6e2b33f9e" +checksum = "217f464cad5946ae4369c355155e2d16b488c08920601083cb4891e352ae777b" dependencies = [ "glib-sys", "gobject-sys", "libc", "system-deps 7.0.3", - "windows-sys 0.59.0", + "windows-sys 0.52.0", ] +[[package]] +name = "glam" +version = "0.28.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "779ae4bf7e8421cf91c0b3b64e7e8b40b862fba4d393f59150042de7c4965a94" + [[package]] name = "glib" -version = "0.20.9" +version = "0.20.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "707b819af8059ee5395a2de9f2317d87a53dbad8846a2f089f0bb44703f37686" +checksum = "358431b0e0eb15b9d02db52e1f19c805b953c5c168099deb3de88beab761768c" dependencies = [ - "bitflags 2.8.0", + "bitflags 2.9.0", "futures-channel", "futures-core", "futures-executor", @@ -1489,22 +1503,22 @@ dependencies = [ [[package]] name = "glib-macros" -version = "0.20.7" +version = "0.20.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "715601f8f02e71baef9c1f94a657a9a77c192aea6097cf9ae7e5e177cd8cde68" +checksum = "e7d21ca27acfc3e91da70456edde144b4ac7c36f78ee77b10189b3eb4901c156" dependencies = [ "heck", "proc-macro-crate", "proc-macro2", "quote", - "syn 2.0.98", + "syn 2.0.87", ] [[package]] name = "glib-sys" -version = "0.20.9" +version = "0.20.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a8928869a44cfdd1fccb17d6746e4ff82c8f82e41ce705aa026a52ca8dc3aefb" +checksum = "8a5911863ab7ecd4a6f8d5976f12eeba076b23669c49b066d877e742544aa389" dependencies = [ "libc", "system-deps 7.0.3", @@ -1512,15 +1526,15 @@ dependencies = [ [[package]] name = "glob" -version = "0.3.2" +version = "0.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a8d1add55171497b4705a648c6b583acafb01d58050a51727785f0b2c8e0a2b2" +checksum = "d2fabcfbdc87f4758337ca535fb41a6d701b65693ce38287d856d1674551ec9b" [[package]] name = "gobject-sys" -version = "0.20.9" +version = "0.20.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c773a3cb38a419ad9c26c81d177d96b4b08980e8bdbbf32dace883e96e96e7e3" +checksum = "a4c674d2ff8478cf0ec29d2be730ed779fef54415a2fb4b565c52def62696462" dependencies = [ "glib-sys", "libc", @@ -1529,9 +1543,9 @@ dependencies = [ [[package]] name = "graphene-rs" -version = "0.20.9" +version = "0.20.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3cbc5911bfb32d68dcfa92c9510c462696c2f715548fcd7f3f1be424c739de19" +checksum = "1f53144c7fe78292705ff23935f1477d511366fb2f73c43d63b37be89076d2fe" dependencies = [ "glib", "graphene-sys", @@ -1540,9 +1554,9 @@ dependencies = [ [[package]] name = "graphene-sys" -version = "0.20.7" +version = "0.20.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "11a68d39515bf340e879b72cecd4a25c1332557757ada6e8aba8654b4b81d23a" +checksum = "e741797dc5081e59877a4d72c442c72d61efdd99161a0b1c1b29b6b988934b99" dependencies = [ "glib-sys", "libc", @@ -1552,9 +1566,9 @@ dependencies = [ [[package]] name = "gsk4" -version = "0.9.6" +version = "0.9.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "61f5e72f931c8c9f65fbfc89fe0ddc7746f147f822f127a53a9854666ac1f855" +checksum = "3deb9ae5a7fb759b2405e248d52850d9ef733079b90af2d6b01638f5b9eeafe1" dependencies = [ "cairo-rs", "gdk4", @@ -1567,9 +1581,9 @@ dependencies = [ [[package]] name = "gsk4-sys" -version = "0.9.6" +version = "0.9.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "755059de55fa6f85a46bde8caf03e2184c96bfda1f6206163c72fb0ea12436dc" +checksum = "2226662e97948f3f241c9a6c432cd95eeca662daf4a327e201458bb540ad9590" dependencies = [ "cairo-sys-rs", "gdk4-sys", @@ -1583,9 +1597,9 @@ dependencies = [ [[package]] name = "gtk4" -version = "0.9.6" +version = "0.9.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "af1c491051f030994fd0cde6f3c44f3f5640210308cff1298c7673c47408091d" +checksum = "d34465497f5a4c182c9c94a582a187db7d6af0863f28e87ccf4379f21f0e2a22" dependencies = [ "cairo-rs", "field-offset", @@ -1604,21 +1618,21 @@ dependencies = [ [[package]] name = "gtk4-macros" -version = "0.9.5" +version = "0.9.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0ed1786c4703dd196baf7e103525ce0cf579b3a63a0570fe653b7ee6bac33999" +checksum = "a7c518d5dd41c57385c7cd30af52e261820c897fc1144e558bb88c303d048ae2" dependencies = [ "proc-macro-crate", "proc-macro2", "quote", - "syn 2.0.98", + "syn 2.0.87", ] [[package]] name = "gtk4-sys" -version = "0.9.6" +version = "0.9.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "41e03b01e54d77c310e1d98647d73f996d04b2f29b9121fe493ea525a7ec03d6" +checksum = "f11c7812e28542edb4d0495a2fde1f4588899e2accfcebaa80115f27dc7358a3" dependencies = [ "cairo-sys-rs", "gdk-pixbuf-sys", @@ -1664,9 +1678,9 @@ dependencies = [ [[package]] name = "hashbrown" -version = "0.15.2" +version = "0.15.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bf151400ff0baff5465007dd2f3e717f3fe502074ca563069ce3a6629d07b289" +checksum = "1e087f84d4f86bf4b218b927129862374b72199ae7d8657835f1e89000eea4fb" [[package]] name = "heapless" @@ -1684,6 +1698,12 @@ version = "0.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea" +[[package]] +name = "hermit-abi" +version = "0.3.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d231dfb89cfffdbc30e7fc41579ed6066ad03abda9e567ccafae602b97ec5024" + [[package]] name = "hermit-abi" version = "0.4.0" @@ -1692,9 +1712,9 @@ checksum = "fbf6a919d6cf397374f7dfeeea91d974c7c0a7221d0d0f4f20d859d329e53fcc" [[package]] name = "i_float" -version = "1.6.0" +version = "1.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "775f9961a8d2f879725da8aff789bb20a3ddf297473e0c90af75e69313919490" +checksum = "f5fe043aae28ce70bd2f78b2f5f82a3654d63607c82594da4dabb8b6cb81f2b2" dependencies = [ "serde", ] @@ -1707,9 +1727,9 @@ checksum = "347c253b4748a1a28baf94c9ce133b6b166f08573157e05afe718812bc599fcd" [[package]] name = "i_overlay" -version = "1.9.4" +version = "1.7.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "01882ce5ed786bf6e8f5167f171a4026cd129ce17d9ff5cbf1e6749b98628ece" +checksum = "a469f68cb8a7cef375b2b0f581faf5859b4b50600438c00d46b71acc25ebbd0c" dependencies = [ "i_float", "i_key_sort", @@ -1720,9 +1740,9 @@ dependencies = [ [[package]] name = "i_shape" -version = "1.6.0" +version = "1.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "27dbe9e5238d6b9c694c08415bf00fb370b089949bd818ab01f41f8927b8774c" +checksum = "1b44852d57a991c7dedaf76c55bc44f677f547ff899a430d29e13efd6133d7d8" dependencies = [ "i_float", "serde", @@ -1757,124 +1777,6 @@ dependencies = [ "cc", ] -[[package]] -name = "icu_collections" -version = "1.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "db2fa452206ebee18c4b5c2274dbf1de17008e874b4dc4f0aea9d01ca79e4526" -dependencies = [ - "displaydoc", - "yoke", - "zerofrom", - "zerovec", -] - -[[package]] -name = "icu_locid" -version = "1.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "13acbb8371917fc971be86fc8057c41a64b521c184808a698c02acc242dbf637" -dependencies = [ - "displaydoc", - "litemap", - "tinystr", - "writeable", - "zerovec", -] - -[[package]] -name = "icu_locid_transform" -version = "1.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "01d11ac35de8e40fdeda00d9e1e9d92525f3f9d887cdd7aa81d727596788b54e" -dependencies = [ - "displaydoc", - "icu_locid", - "icu_locid_transform_data", - "icu_provider", - "tinystr", - "zerovec", -] - -[[package]] -name = "icu_locid_transform_data" -version = "1.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fdc8ff3388f852bede6b579ad4e978ab004f139284d7b28715f773507b946f6e" - -[[package]] -name = "icu_normalizer" -version = "1.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "19ce3e0da2ec68599d193c93d088142efd7f9c5d6fc9b803774855747dc6a84f" -dependencies = [ - "displaydoc", - "icu_collections", - "icu_normalizer_data", - "icu_properties", - "icu_provider", - "smallvec", - "utf16_iter", - "utf8_iter", - "write16", - "zerovec", -] - -[[package]] -name = "icu_normalizer_data" -version = "1.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f8cafbf7aa791e9b22bec55a167906f9e1215fd475cd22adfcf660e03e989516" - -[[package]] -name = "icu_properties" -version = "1.5.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "93d6020766cfc6302c15dbbc9c8778c37e62c14427cb7f6e601d849e092aeef5" -dependencies = [ - "displaydoc", - "icu_collections", - "icu_locid_transform", - "icu_properties_data", - "icu_provider", - "tinystr", - "zerovec", -] - -[[package]] -name = "icu_properties_data" -version = "1.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "67a8effbc3dd3e4ba1afa8ad918d5684b8868b3b26500753effea8d2eed19569" - -[[package]] -name = "icu_provider" -version = "1.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6ed421c8a8ef78d3e2dbc98a973be2f3770cb42b606e3ab18d6237c4dfde68d9" -dependencies = [ - "displaydoc", - "icu_locid", - "icu_provider_macros", - "stable_deref_trait", - "tinystr", - "writeable", - "yoke", - "zerofrom", - "zerovec", -] - -[[package]] -name = "icu_provider_macros" -version = "1.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1ec89e9337638ecdc08744df490b221a7399bf8d164eb52a665454e60e075ad6" -dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.98", -] - [[package]] name = "ident_case" version = "1.0.1" @@ -1883,30 +1785,19 @@ checksum = "b9e0384b61958566e926dc50660321d12159025e767c18e043daf26b70104c39" [[package]] name = "idna" -version = "1.0.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "686f825264d630750a544639377bae737628043f20d38bbc029e8f29ea968a7e" -dependencies = [ - "idna_adapter", - "smallvec", - "utf8_iter", -] - -[[package]] -name = "idna_adapter" -version = "1.2.0" +version = "0.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "daca1df1c957320b2cf139ac61e7bd64fed304c5040df000a745aa1de3b4ef71" +checksum = "634d9b1461af396cad843f47fdba5597a4f9e6ddd4bfb6ff5d85028c25cb12f6" dependencies = [ - "icu_normalizer", - "icu_properties", + "unicode-bidi", + "unicode-normalization", ] [[package]] name = "ijson" -version = "0.1.4" +version = "0.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2ce2bfde9cd56af9f5d4b8de37d2d203050cfa1f68cb28d5bb163e32389e810e" +checksum = "b96214564d1f12875bd9661b183d8494dd10e373cb693629536fe2f3125e254b" dependencies = [ "dashmap", "lazy_static", @@ -1916,9 +1807,9 @@ dependencies = [ [[package]] name = "image" -version = "0.25.5" +version = "0.25.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cd6f44aed642f18953a158afeb30206f4d50da59fbc66ecb53c66488de73563b" +checksum = "bc144d44a31d753b02ce64093d532f55ff8dc4ebf2ffb8a63c0dda691385acae" dependencies = [ "bytemuck", "byteorder-lite", @@ -1939,9 +1830,9 @@ dependencies = [ [[package]] name = "image-webp" -version = "0.2.1" +version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b77d01e822461baa8409e156015a1d91735549f0f2c17691bd2d996bef238f7f" +checksum = "e031e8e3d94711a9ccb5d6ea357439ef3dcbed361798bd4071dc4d9793fbe22f" dependencies = [ "byteorder-lite", "quick-error", @@ -1961,25 +1852,25 @@ checksum = "d0263a3d970d5c054ed9312c0057b4f3bde9c0b33836d3637361d4a9e6e7a408" [[package]] name = "indexmap" -version = "2.7.1" +version = "2.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8c9c992b02b5b4c94ea26e32fe5bccb7aa7d9f390ab5c1221ff895bc7ea8b652" +checksum = "707907fe3c25f5424cce2cb7e1cbcafee6bdbe735ca90ef77c29e84591e5b9da" dependencies = [ "equivalent", - "hashbrown 0.15.2", + "hashbrown 0.15.0", ] [[package]] name = "indicatif" -version = "0.17.11" +version = "0.17.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "183b3088984b400f4cfac3620d5e076c84da5364016b4f49473de574b2586235" +checksum = "763a5a8f45087d6bcea4222e7b72c291a054edf80e4ef6efd2a4979878c7bea3" dependencies = [ "console", + "instant", "number_prefix", "portable-atomic", "unicode-width", - "web-time", ] [[package]] @@ -1987,7 +1878,7 @@ name = "ink-stroke-modeler-rs" version = "0.1.0" source = "git+https://github.com/flxzt/ink-stroke-modeler-rs?rev=84d311e9b0d034dcd955a1f353d37f54b2bda70f#84d311e9b0d034dcd955a1f353d37f54b2bda70f" dependencies = [ - "thiserror 1.0.69", + "thiserror 1.0.66", ] [[package]] @@ -1996,7 +1887,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.0", "inotify-sys", "libc", ] @@ -2010,6 +1901,15 @@ dependencies = [ "libc", ] +[[package]] +name = "instant" +version = "0.1.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e0242819d153cba4b4b05a5a8f2a7e9bbf97b6055b2a002b395c96b5ff3c0222" +dependencies = [ + "cfg-if", +] + [[package]] name = "interpolate_name" version = "0.2.4" @@ -2018,7 +1918,7 @@ checksum = "c34819042dc3d3971c46c2190835914dfbe0c3c13f61449b2997f4e9722dfa60" dependencies = [ "proc-macro2", "quote", - "syn 2.0.98", + "syn 2.0.87", ] [[package]] @@ -2084,9 +1984,9 @@ dependencies = [ [[package]] name = "itoa" -version = "1.0.14" +version = "1.0.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d75a2a4b1b190afb6f5425f10f6a8f959d2ea0b9c2b1d79553551850539e4674" +checksum = "49f1f14873335454500d59611f1cf4a4b0f786f9ac11f4312a78e4cf2566695b" [[package]] name = "jni" @@ -2099,7 +1999,7 @@ dependencies = [ "combine", "jni-sys", "log", - "thiserror 1.0.69", + "thiserror 1.0.66", "walkdir", "windows-sys 0.45.0", ] @@ -2127,11 +2027,10 @@ checksum = "f5d4a7da358eff58addd2877a45865158f0d78c911d43a5784ceb7bbf52833b0" [[package]] name = "js-sys" -version = "0.3.77" +version = "0.3.72" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1cfaf33c695fc6e08064efbc1f72ec937429614f25eef83af942d0e227c3a28f" +checksum = "6a88f1bda2bd75b0452a14784937d796722fdebfe50df998aeb3f0b7603019a9" dependencies = [ - "once_cell", "wasm-bindgen", ] @@ -2225,25 +2124,26 @@ dependencies = [ [[package]] name = "libc" -version = "0.2.169" +version = "0.2.161" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b5aba8db14291edd000dfcc4d620c7ebfb122c613afb886ca8803fa4e128a20a" +checksum = "8e9489c2807c139ffd9c1794f4af0ebe86a828db53ecdc7fea2111d0fed085d1" [[package]] name = "libfuzzer-sys" -version = "0.4.9" +version = "0.4.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cf78f52d400cf2d84a3a973a78a592b4adc535739e0a5597a0da6f0c357adc75" +checksum = "a96cfd5557eb82f2b83fed4955246c988d331975a002961b07c81584d107e7f7" dependencies = [ "arbitrary", "cc", + "once_cell", ] [[package]] name = "libloading" -version = "0.8.6" +version = "0.8.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fc2f4eb4bc735547cfed7c0a4922cbd04a4655978c09b54f1f7b228750664c34" +checksum = "4979f22fdb869068da03c9f7528f8297c6fd2606bc3a4affe42e6a823fdb8da4" dependencies = [ "cfg-if", "windows-targets 0.52.6", @@ -2261,7 +2161,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.0", "libc", "redox_syscall", ] @@ -2304,15 +2204,9 @@ dependencies = [ [[package]] name = "linux-raw-sys" -version = "0.4.15" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d26c52dbd32dccf2d10cac7725f8eae5296885fb5703b261f7d0a0739ec807ab" - -[[package]] -name = "litemap" -version = "0.7.4" +version = "0.4.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4ee93343901ab17bd981295f2cf0026d4ad018c7c31ba84549a4ddbb47a45104" +checksum = "78b3ae25bc7c8c38cec158d1f2757ee79e9b3740fbc7ccf0e59e4b08d793fa89" [[package]] name = "locale_config" @@ -2339,9 +2233,9 @@ dependencies = [ [[package]] name = "log" -version = "0.4.25" +version = "0.4.22" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "04cbf5b083de1c7e0222a7a51dbfdba1cbe1c6ab0b15e29fff3f6c077fd9cd9f" +checksum = "a7a70ba024b9dc04c27ea2f0c0548feb474ec5c54bba33a7f72f873a39d07b24" [[package]] name = "loop9" @@ -2383,8 +2277,8 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "16ce3abbeba692c8b8441d036ef91aea6df8da2c6b6e21c7e14d3c18e526be45" dependencies = [ "log", - "phf 0.11.3", - "phf_codegen 0.11.3", + "phf 0.11.2", + "phf_codegen 0.11.2", "string_cache", "string_cache_codegen", "tendril", @@ -2422,7 +2316,6 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8ea1f30cedd69f0a2954655f7188c6a834246d2bcf1e315e2ac40c4b24dc9519" dependencies = [ "cfg-if", - "rayon", ] [[package]] @@ -2457,9 +2350,9 @@ checksum = "68354c5c6bd36d73ff3feceb05efa59b6acb7626617f4962be322a825e61f79a" [[package]] name = "miniz_oxide" -version = "0.8.4" +version = "0.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b3b1c9bd4fe1f0f8b387f6eb9eb3b4a1aa26185e5750efb9140301703f62cd1b" +checksum = "e2d80299ef12ff69b16a84bb182e3b9df68b5a91574d3d4fa6e41b65deec4df1" dependencies = [ "adler2", "simd-adler32", @@ -2467,10 +2360,11 @@ dependencies = [ [[package]] name = "mio" -version = "1.0.3" +version = "1.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2886843bf800fba2e3377cff24abf6379b4c4d5c6681eaf9ea5b0d15090450bd" +checksum = "80e04d1dcff3aae0704555fe5fee3bcfaf3d1fdf8a7e521d5b9d2b42acb52cec" dependencies = [ + "hermit-abi 0.3.9", "libc", "log", "wasi 0.11.0+wasi-snapshot-preview1", @@ -2502,7 +2396,7 @@ checksum = "254a5372af8fc138e36684761d3c0cdb758a4410e938babcff1c860ce14ddbfc" dependencies = [ "proc-macro2", "quote", - "syn 2.0.98", + "syn 2.0.87", ] [[package]] @@ -2511,12 +2405,12 @@ version = "0.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2076a31b7010b17a38c01907c45b945e8f11495ee4dd588309718901b1f7a5b7" dependencies = [ - "bitflags 2.8.0", + "bitflags 2.9.0", "jni-sys", "log", "ndk-sys", "num_enum", - "thiserror 1.0.69", + "thiserror 1.0.66", ] [[package]] @@ -2562,7 +2456,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.0", "filetime", "fsevent-sys", "inotify", @@ -2632,7 +2526,7 @@ checksum = "ed3955f1a9c7c0c15e092f9c887db08b1fc683305fdf6eb6684f22555355e202" dependencies = [ "proc-macro2", "quote", - "syn 2.0.98", + "syn 2.0.87", ] [[package]] @@ -2665,6 +2559,16 @@ dependencies = [ "libm", ] +[[package]] +name = "num_cpus" +version = "1.16.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4161fcb6d602d4d2081af7c3a45852d875a03dd337a6bfdd6e06407b61342a43" +dependencies = [ + "hermit-abi 0.3.9", + "libc", +] + [[package]] name = "num_enum" version = "0.7.3" @@ -2683,7 +2587,7 @@ dependencies = [ "proc-macro-crate", "proc-macro2", "quote", - "syn 2.0.98", + "syn 2.0.87", ] [[package]] @@ -2694,9 +2598,9 @@ checksum = "830b246a0e5f20af87141b25c173cd1b609bd7779a4617d6ec582abaf90870f3" [[package]] name = "numeric-sort" -version = "0.1.4" +version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1f218700a47ff14f06bb676b97e63a6fb2fd25db3bf05d6f369971188e303f7f" +checksum = "5180ed575d2ae6100dc63813e3810aba273c291608cf019aa58f76784ecc331b" [[package]] name = "objc" @@ -2752,15 +2656,15 @@ dependencies = [ [[package]] name = "once_cell" -version = "1.20.3" +version = "1.20.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "945462a4b81e43c4e3ba96bd7b49d834c6f61198356aa858733bc4acf3cbe62e" +checksum = "1261fe7e33c73b354eab43b1273a57c8f967d0391e80353e51f764ac02cf6775" [[package]] name = "open" -version = "5.3.2" +version = "5.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e2483562e62ea94312f3576a7aca397306df7990b8d89033e18766744377ef95" +checksum = "61a877bf6abd716642a53ef1b89fb498923a4afca5c754f9050b4d081c05c4b3" dependencies = [ "is-wsl", "libc", @@ -2769,9 +2673,9 @@ dependencies = [ [[package]] name = "ordered-float" -version = "4.6.0" +version = "4.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7bb71e1b3fa6ca1c61f383464aaf2bb0e2f8e772a1f01d486832464de363b951" +checksum = "83e7ccb95e240b7c9506a3d544f10d935e142cc90b0a1d56954fb44d89ad6b97" dependencies = [ "num-traits", ] @@ -2791,7 +2695,7 @@ dependencies = [ "approx 0.5.1", "fast-srgb8", "palette_derive", - "phf 0.11.3", + "phf 0.11.2", ] [[package]] @@ -2803,14 +2707,14 @@ dependencies = [ "by_address", "proc-macro2", "quote", - "syn 2.0.98", + "syn 2.0.87", ] [[package]] name = "pango" -version = "0.20.9" +version = "0.20.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6b1f5dc1b8cf9bc08bfc0843a04ee0fa2e78f1e1fa4b126844a383af4f25f0ec" +checksum = "aa26aa54b11094d72141a754901cd71d9356432bb8147f9cace8d9c7ba95f356" dependencies = [ "gio", "glib", @@ -2820,9 +2724,9 @@ dependencies = [ [[package]] name = "pango-sys" -version = "0.20.9" +version = "0.20.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0dbb9b751673bd8fe49eb78620547973a1e719ed431372122b20abd12445bab5" +checksum = "84fd65917bf12f06544ae2bbc200abf9fc0a513a5a88a0fa81013893aef2b838" dependencies = [ "glib-sys", "gobject-sys", @@ -2832,9 +2736,9 @@ dependencies = [ [[package]] name = "pangocairo" -version = "0.20.7" +version = "0.20.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4690509a2fea2a6552a0ef8aa3e5f790c1365365ee0712afa1aedb39af3997b6" +checksum = "4291ca8cdd05e4330752bf8a450d3a4e701ca48fd9aad2b3566e92849ee4055e" dependencies = [ "cairo-rs", "glib", @@ -2845,9 +2749,9 @@ dependencies = [ [[package]] name = "pangocairo-sys" -version = "0.20.7" +version = "0.20.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5be6ac24147911a6a46783922fc288cf02f67570bc0d360e563b5b26aead6767" +checksum = "be0ed959258ea648a49bde7dfdbaa98310717cb15159b421fa76510c45ec306e" dependencies = [ "cairo-sys-rs", "glib-sys", @@ -2893,7 +2797,7 @@ checksum = "6cb6ad7b4b94d153e501bea7bee863fc83015db2f936a0a0c6745021853f1043" dependencies = [ "approx 0.5.1", "arrayvec", - "bitflags 2.8.0", + "bitflags 2.9.0", "downcast-rs", "either", "ena", @@ -2902,13 +2806,13 @@ dependencies = [ "num-derive", "num-traits", "ordered-float", - "rustc-hash 2.1.1", + "rustc-hash 2.0.0", "serde", "simba", "slab", "smallvec", "spade", - "thiserror 1.0.69", + "thiserror 1.0.66", ] [[package]] @@ -2937,9 +2841,9 @@ dependencies = [ [[package]] name = "pathdiff" -version = "0.2.3" +version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "df94ce210e5bc13cb6651479fa48d14f601d9858cfe0467f43ae157023b938d3" +checksum = "d61c5ce1153ab5b689d0c074c4e7fc613e942dfb7dd9eea5ab202d2ad91fe361" [[package]] name = "percent-encoding" @@ -2958,12 +2862,12 @@ dependencies = [ [[package]] name = "phf" -version = "0.11.3" +version = "0.11.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1fd6780a80ae0c52cc120a26a1a42c1ae51b247a253e4e06113d23d2c2edd078" +checksum = "ade2d8b8f33c7333b51bcf0428d37e217e9f32192ae4772156f65063b8ce03dc" dependencies = [ "phf_macros", - "phf_shared 0.11.3", + "phf_shared 0.11.2", ] [[package]] @@ -2978,12 +2882,12 @@ dependencies = [ [[package]] name = "phf_codegen" -version = "0.11.3" +version = "0.11.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "aef8048c789fa5e851558d709946d6d79a8ff88c0440c587967f8e94bfb1216a" +checksum = "e8d39688d359e6b34654d328e262234662d16cc0f60ec8dcbe5e718709342a5a" dependencies = [ - "phf_generator 0.11.3", - "phf_shared 0.11.3", + "phf_generator 0.11.2", + "phf_shared 0.11.2", ] [[package]] @@ -2998,25 +2902,25 @@ dependencies = [ [[package]] name = "phf_generator" -version = "0.11.3" +version = "0.11.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3c80231409c20246a13fddb31776fb942c38553c51e871f8cbd687a4cfb5843d" +checksum = "48e4cc64c2ad9ebe670cb8fd69dd50ae301650392e81c05f9bfcb2d5bdbc24b0" dependencies = [ - "phf_shared 0.11.3", + "phf_shared 0.11.2", "rand 0.8.5", ] [[package]] name = "phf_macros" -version = "0.11.3" +version = "0.11.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f84ac04429c13a7ff43785d75ad27569f2951ce0ffd30a3321230db2fc727216" +checksum = "3444646e286606587e49f3bcf1679b8cef1dc2c5ecc29ddacaffc305180d464b" dependencies = [ - "phf_generator 0.11.3", - "phf_shared 0.11.3", + "phf_generator 0.11.2", + "phf_shared 0.11.2", "proc-macro2", "quote", - "syn 2.0.98", + "syn 2.0.87", ] [[package]] @@ -3030,11 +2934,11 @@ dependencies = [ [[package]] name = "phf_shared" -version = "0.11.3" +version = "0.11.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "67eabc2ef2a60eb7faa00097bd1ffdb5bd28e62bf39990626a582201b7a754e5" +checksum = "90fcb95eef784c2ac79119d1dd819e162b5da872ce6f3c3abe1e8ca1c082f72b" dependencies = [ - "siphasher 1.0.1", + "siphasher 0.3.11", ] [[package]] @@ -3069,9 +2973,9 @@ dependencies = [ [[package]] name = "pin-project-lite" -version = "0.2.16" +version = "0.2.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3b3cff922bd51709b605d9ead9aa71031d81447142d828eb4a6eba76fe619f9b" +checksum = "915a1e146535de9163f3987b8944ed8cf49a18bb0056bcebcdcece385cece4ff" [[package]] name = "pin-utils" @@ -3098,9 +3002,9 @@ checksum = "953ec861398dccce10c670dfeaf3ec4911ca479e9c02154b3a215178c5f566f2" [[package]] name = "png" -version = "0.17.16" +version = "0.17.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "82151a2fc869e011c153adc57cf2789ccb8d9906ce52c0b39a6b5697749d7526" +checksum = "52f9d46a34a05a6a57566bc2bfae066ef07585a6e3fa30fbbdff5936380623f0" dependencies = [ "bitflags 1.3.2", "crc32fast", @@ -3121,13 +3025,13 @@ dependencies = [ [[package]] name = "polling" -version = "3.7.4" +version = "3.7.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a604568c3202727d1507653cb121dbd627a58684eb09a820fd746bee38b4442f" +checksum = "cc2790cd301dec6cd3b7a025e4815cf825724a51c98dccfe6a3e55f05ffb6511" dependencies = [ "cfg-if", "concurrent-queue", - "hermit-abi", + "hermit-abi 0.4.0", "pin-project-lite", "rustix", "tracing", @@ -3162,9 +3066,9 @@ dependencies = [ [[package]] name = "portable-atomic" -version = "1.10.0" +version = "1.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "280dc24453071f1b63954171985a0b0d30058d287960968b9b2aca264c8d4ee6" +checksum = "cc9c68a3f6da06753e9335d63e27f6b9754dd1920d941135b7ea8224f141adb2" [[package]] name = "ppv-lite86" @@ -3187,14 +3091,14 @@ version = "3.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8ecf48c7ca261d60b74ab1a7b20da18bede46776b2e55535cb958eb595c5fa7b" dependencies = [ - "toml_edit", + "toml_edit 0.22.22", ] [[package]] name = "proc-macro2" -version = "1.0.93" +version = "1.0.89" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "60946a68e5f9d28b0dc1c21bb8a97ee7d018a8b322fa57838ba31cc878e22d99" +checksum = "f139b0662de085916d1fb67d2b4169d1addddda1919e696f3252b740b629986e" dependencies = [ "unicode-ident", ] @@ -3215,7 +3119,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a65f2e60fbf1063868558d69c6beacf412dc755f9fc020f514b7955fc914fe30" dependencies = [ "quote", - "syn 2.0.98", + "syn 2.0.87", ] [[package]] @@ -3235,9 +3139,9 @@ checksum = "a993555f31e5a609f617c12db6250dedcac1b0a85076912c436e6fc9b2c8e6a3" [[package]] name = "quote" -version = "1.0.38" +version = "1.0.37" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0e4dccaaaf89514f546c693ddc140f729f958c247918a13380cccc6078391acc" +checksum = "b5b9d34b8991d19d98081b46eacdd8eb58c6f2b201139f7c5f643cc155a633af" dependencies = [ "proc-macro2", ] @@ -3260,8 +3164,8 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3779b94aeb87e8bd4e834cee3650289ee9e0d5677f976ecdb6d219e5f4f6cd94" dependencies = [ "rand_chacha 0.9.0", - "rand_core 0.9.1", - "zerocopy 0.8.20", + "rand_core 0.9.3", + "zerocopy 0.8.23", ] [[package]] @@ -3281,7 +3185,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d3022b5f1df60f26e1ffddd6c66e8aa15de382ae63b3a0c1bfc0e4d3e3f325cb" dependencies = [ "ppv-lite86", - "rand_core 0.9.1", + "rand_core 0.9.3", ] [[package]] @@ -3295,12 +3199,11 @@ dependencies = [ [[package]] name = "rand_core" -version = "0.9.1" +version = "0.9.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a88e0da7a2c97baa202165137c158d0a2e824ac465d13d81046727b34cb247d3" +checksum = "99d9a13982dcf210057a8a78572b2217b667c3beacbf3a0d8b454f6f82837d38" dependencies = [ "getrandom 0.3.1", - "zerocopy 0.8.20", ] [[package]] @@ -3319,7 +3222,7 @@ version = "0.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b48ac3f7ffaab7fac4d2376632268aa5f89abdb55f7ebf8f4d11fffccb2320f7" dependencies = [ - "rand_core 0.9.1", + "rand_core 0.9.3", ] [[package]] @@ -3352,7 +3255,7 @@ dependencies = [ "rand_chacha 0.3.1", "simd_helpers", "system-deps 6.2.2", - "thiserror 1.0.69", + "thiserror 1.0.66", "v_frame", "wasm-bindgen", ] @@ -3368,7 +3271,6 @@ dependencies = [ "loop9", "quick-error", "rav1e", - "rayon", "rgb", ] @@ -3406,11 +3308,11 @@ checksum = "e03e7866abec1101869ffa8e2c8355c4c2419d0214ece0cc3e428e5b94dea6e9" [[package]] name = "redox_syscall" -version = "0.5.8" +version = "0.5.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "03a862b389f93e68874fbf580b9de08dd02facb9a788ebadaf4a3fd33cf58834" +checksum = "9b6dfecf2c74bce2466cabf93f6664d6998a69eb21e39f4207930065b27b771f" dependencies = [ - "bitflags 2.8.0", + "bitflags 2.9.0", ] [[package]] @@ -3421,7 +3323,7 @@ checksum = "b544ef1b4eac5dc2db33ea63606ae9ffcfac26c1416a2806ae0bf5f56b201191" dependencies = [ "aho-corasick", "memchr", - "regex-automata 0.4.9", + "regex-automata 0.4.8", "regex-syntax 0.8.5", ] @@ -3436,9 +3338,9 @@ dependencies = [ [[package]] name = "regex-automata" -version = "0.4.9" +version = "0.4.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "809e8dc61f6de73b46c85f4c96486310fe304c434cfa43669d7b40f711150908" +checksum = "368758f23274712b504848e9d5a6f010445cc8b87a7cdb4d7cbee666c1288da3" dependencies = [ "aho-corasick", "memchr", @@ -3510,7 +3412,7 @@ dependencies = [ "serde", "serde_json", "svg", - "thiserror 2.0.11", + "thiserror 2.0.9", "tracing", "tracing-subscriber", "unicode-segmentation", @@ -3572,10 +3474,13 @@ version = "0.11.0" dependencies = [ "anyhow", "approx 0.5.1", + "async-fs", "base64", + "bitcode", "cairo-rs", "chrono", "clap", + "crc32fast", "flate2", "futures", "geo", @@ -3611,11 +3516,12 @@ dependencies = [ "serde_json", "slotmap", "svg", - "thiserror 2.0.11", + "thiserror 2.0.9", "tracing", "unicode-segmentation", "usvg", "xmlwriter", + "zstd", ] [[package]] @@ -3671,9 +3577,9 @@ checksum = "6c20b6793b5c2fa6553b250154b78d6d0db37e72700ae35fad9387a46f487c97" [[package]] name = "rstar" -version = "0.12.2" +version = "0.12.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "421400d13ccfd26dfa5858199c30a5d76f9c54e0dba7575273025b43c5175dbb" +checksum = "133315eb94c7b1e8d0cb097e5a710d850263372fd028fff18969de708afc7008" dependencies = [ "heapless", "num-traits", @@ -3688,9 +3594,9 @@ checksum = "08d43f7aa6b08d49f382cde6a7982047c3426db949b1424bc4b7ec9ae12c6ce2" [[package]] name = "rustc-hash" -version = "2.1.1" +version = "2.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "357703d41365b4b27c590e3ed91eabb1b663f07c4c084095e60cbed4362dff0d" +checksum = "583034fd73374156e66797ed8e5b0d5690409c9226b22d87cb7f19821c05d152" [[package]] name = "rustc_version" @@ -3703,30 +3609,24 @@ dependencies = [ [[package]] name = "rustix" -version = "0.38.44" +version = "0.38.38" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fdb5bc1ae2baa591800df16c9ca78619bf65c0488b41b96ccec5d11220d8c154" +checksum = "aa260229e6538e52293eeb577aabd09945a09d6d9cc0fc550ed7529056c2e32a" dependencies = [ - "bitflags 2.8.0", + "bitflags 2.9.0", "errno", "libc", "linux-raw-sys", - "windows-sys 0.59.0", + "windows-sys 0.52.0", ] -[[package]] -name = "rustversion" -version = "1.0.19" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f7c45b9784283f1b2e7fb61b42047c2fd678ef0960d4f6f1eba131594cc369d4" - [[package]] name = "rustybuzz" version = "0.18.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c85d1ccd519e61834798eb52c4e886e8c2d7d698dd3d6ce0b1b47eb8557f1181" dependencies = [ - "bitflags 2.8.0", + "bitflags 2.9.0", "bytemuck", "core_maths", "log", @@ -3740,15 +3640,15 @@ dependencies = [ [[package]] name = "ryu" -version = "1.0.19" +version = "1.0.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6ea1a2d0a644769cc99faa24c3ad26b379b786fe7c36fd3c546254801650e6dd" +checksum = "f3cb5ba0dc43242ce17de99c180e96db90b235b8a9fdc9543c96d2209116bd9f" [[package]] name = "safe_arch" -version = "0.7.4" +version = "0.7.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "96b02de82ddbe1b636e6170c21be622223aea188ef2e139be0a5b219ec215323" +checksum = "c3460605018fdc9612bce72735cba0d27efbcd9904780d44c7e3a9948f96148a" dependencies = [ "bytemuck", ] @@ -3774,7 +3674,7 @@ version = "0.25.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4eb30575f3638fc8f6815f448d50cb1a2e255b0897985c8c59f4d37b72a07b06" dependencies = [ - "bitflags 2.8.0", + "bitflags 2.9.0", "cssparser", "derive_more", "fxhash", @@ -3789,38 +3689,38 @@ dependencies = [ [[package]] name = "semver" -version = "1.0.25" +version = "1.0.23" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f79dfe2d285b0488816f30e700a7438c5a73d816b5b7d3ac72fbc48b0d185e03" +checksum = "61697e0a1c7e512e84a621326239844a24d8207b4669b41bc18b32ea5cbf988b" dependencies = [ "serde", ] [[package]] name = "serde" -version = "1.0.218" +version = "1.0.214" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e8dfc9d19bdbf6d17e22319da49161d5d0108e4188e8b680aef6299eed22df60" +checksum = "f55c3193aca71c12ad7890f1785d2b73e1b9f63a0bbc353c08ef26fe03fc56b5" dependencies = [ "serde_derive", ] [[package]] name = "serde_derive" -version = "1.0.218" +version = "1.0.214" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f09503e191f4e797cb8aac08e9a4a4695c5edf6a2e70e376d961ddd5c969f82b" +checksum = "de523f781f095e28fa605cdce0f8307e451cc0fd14e2eb4cd2e98a355b147766" dependencies = [ "proc-macro2", "quote", - "syn 2.0.98", + "syn 2.0.87", ] [[package]] name = "serde_json" -version = "1.0.139" +version = "1.0.132" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "44f86c3acccc9c65b153fe1b85a3be07fe5515274ec9f0653b4a0875731c72a6" +checksum = "d726bfaff4b320266d395898905d0eba0345aae23b54aee3a737e260fd46db03" dependencies = [ "itoa", "memchr", @@ -3906,9 +3806,9 @@ dependencies = [ [[package]] name = "simplecss" -version = "0.2.2" +version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7a9c6883ca9c3c7c90e888de77b7a5c849c779d25d74a1269b0218b14e8b136c" +checksum = "a11be7c62927d9427e9f40f3444d5499d868648e2edbc4e2116de69e7ec0e89d" dependencies = [ "log", ] @@ -3946,9 +3846,9 @@ dependencies = [ [[package]] name = "smallvec" -version = "1.14.0" +version = "1.13.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7fcf8323ef1faaee30a44a340193b1ac6814fd9b7b4e88e9d4519a3e4abe1cfd" +checksum = "3c5e1a9a646d36c3599cd173a41282daf47c44583ad367b8e6837255952e5c67" [[package]] name = "smol" @@ -3996,25 +3896,26 @@ dependencies = [ [[package]] name = "string_cache" -version = "0.8.8" +version = "0.8.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "938d512196766101d333398efde81bc1f37b00cb42c2f8350e5df639f040bbbe" +checksum = "f91138e76242f575eb1d3b38b4f1362f10d3a43f47d182a5b359af488a02293b" dependencies = [ "new_debug_unreachable", + "once_cell", "parking_lot", - "phf_shared 0.11.3", + "phf_shared 0.10.0", "precomputed-hash", "serde", ] [[package]] name = "string_cache_codegen" -version = "0.5.4" +version = "0.5.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c711928715f1fe0fe509c53b43e993a9a557babc2d0a3567d0a3006f1ac931a0" +checksum = "6bb30289b722be4ff74a408c3cc27edeaad656e06cb1fe8fa9231fa59c728988" dependencies = [ - "phf_generator 0.11.3", - "phf_shared 0.11.3", + "phf_generator 0.10.0", + "phf_shared 0.10.0", "proc-macro2", "quote", ] @@ -4059,9 +3960,9 @@ dependencies = [ [[package]] name = "svgtypes" -version = "0.15.3" +version = "0.15.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "68c7541fff44b35860c1a7a47a7cadf3e4a304c457b58f9870d9706ece028afc" +checksum = "794de53cc48eaabeed0ab6a3404a65f40b3e38c067e4435883a65d2aa4ca000e" dependencies = [ "kurbo 0.11.1", "siphasher 1.0.1", @@ -4151,26 +4052,15 @@ dependencies = [ [[package]] name = "syn" -version = "2.0.98" +version = "2.0.87" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "36147f1a48ae0ec2b5b3bc5b537d267457555a10dc06f3dbc8cb11ba3006d3b1" +checksum = "25aa4ce346d03a6dcd68dd8b4010bcb74e54e62c90c573f394c46eae99aba32d" dependencies = [ "proc-macro2", "quote", "unicode-ident", ] -[[package]] -name = "synstructure" -version = "0.13.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c8af7666ab7b6390ab78131fb5b0fce11d6b7a6951602017c35fa82800708971" -dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.98", -] - [[package]] name = "system-deps" version = "6.2.2" @@ -4180,7 +4070,7 @@ dependencies = [ "cfg-expr 0.15.8", "heck", "pkg-config", - "toml", + "toml 0.8.19", "version-compare", ] @@ -4190,10 +4080,10 @@ version = "7.0.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "66d23aaf9f331227789a99e8de4c91bf46703add012bdfd45fdecdfb2975a005" dependencies = [ - "cfg-expr 0.17.2", + "cfg-expr 0.17.0", "heck", "pkg-config", - "toml", + "toml 0.8.19", "version-compare", ] @@ -4211,13 +4101,12 @@ checksum = "bc1ee6eef34f12f765cb94725905c6312b6610ab2b0940889cfe58dae7bc3c72" [[package]] name = "tempfile" -version = "3.17.1" +version = "3.13.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "22e5a0acb1f3f55f65cc4a866c361b2fb2a0ff6366785ae6fbb5f85df07ba230" +checksum = "f0f2c9fc62d0beef6951ccffd757e241266a2c833136efbe35af6cd2567dca5b" dependencies = [ "cfg-if", "fastrand", - "getrandom 0.3.1", "once_cell", "rustix", "windows-sys 0.59.0", @@ -4236,42 +4125,42 @@ dependencies = [ [[package]] name = "thiserror" -version = "1.0.69" +version = "1.0.66" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b6aaf5339b578ea85b50e080feb250a3e8ae8cfcdff9a461c9ec2904bc923f52" +checksum = "5d171f59dbaa811dbbb1aee1e73db92ec2b122911a48e1390dfe327a821ddede" dependencies = [ - "thiserror-impl 1.0.69", + "thiserror-impl 1.0.66", ] [[package]] name = "thiserror" -version = "2.0.11" +version = "2.0.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d452f284b73e6d76dd36758a0c8684b1d5be31f92b89d07fd5822175732206fc" +checksum = "f072643fd0190df67a8bab670c20ef5d8737177d6ac6b2e9a236cb096206b2cc" dependencies = [ - "thiserror-impl 2.0.11", + "thiserror-impl 2.0.9", ] [[package]] name = "thiserror-impl" -version = "1.0.69" +version = "1.0.66" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4fee6c4efc90059e10f81e6d42c60a18f76588c3d74cb83a0b242a2b6c7504c1" +checksum = "b08be0f17bd307950653ce45db00cd31200d82b624b36e181337d9c7d92765b5" dependencies = [ "proc-macro2", "quote", - "syn 2.0.98", + "syn 2.0.87", ] [[package]] name = "thiserror-impl" -version = "2.0.11" +version = "2.0.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "26afc1baea8a989337eeb52b6e72a039780ce45c3edfcc9c5b9d112feeb173c2" +checksum = "7b50fa271071aae2e6ee85f842e2e28ba8cd2c5fb67f11fcb1fd70b276f9e7d4" dependencies = [ "proc-macro2", "quote", - "syn 2.0.98", + "syn 2.0.87", ] [[package]] @@ -4306,21 +4195,11 @@ dependencies = [ "strict-num", ] -[[package]] -name = "tinystr" -version = "0.7.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9117f5d4db391c1cf6927e7bea3db74b9a1c1add8f7eda9ffd5364f40f57b82f" -dependencies = [ - "displaydoc", - "zerovec", -] - [[package]] name = "tinyvec" -version = "1.8.1" +version = "1.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "022db8904dfa342efe721985167e9fcd16c29b226db4397ed752a761cfce81e8" +checksum = "445e881f4f6d382d5f27c034e25eb92edd7c784ceab92a0937db7f2e9471b938" dependencies = [ "tinyvec_macros", ] @@ -4333,14 +4212,26 @@ checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20" [[package]] name = "toml" -version = "0.8.20" +version = "0.7.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cd87a5cdd6ffab733b2f74bc4fd7ee5fff6634124999ac278c35fc78c6120148" +checksum = "dd79e69d3b627db300ff956027cc6c3798cef26d22526befdfcd12feeb6d2257" dependencies = [ "serde", "serde_spanned", "toml_datetime", - "toml_edit", + "toml_edit 0.19.15", +] + +[[package]] +name = "toml" +version = "0.8.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a1ed1f98e3fdc28d6d910e6737ae6ab1a93bf1985935a1193e68f93eeb68d24e" +dependencies = [ + "serde", + "serde_spanned", + "toml_datetime", + "toml_edit 0.22.22", ] [[package]] @@ -4354,22 +4245,35 @@ dependencies = [ [[package]] name = "toml_edit" -version = "0.22.24" +version = "0.19.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1b5bb770da30e5cbfde35a2d7b9b8a2c4b8ef89548a7a6aeab5c9a576e3e7421" +dependencies = [ + "indexmap", + "serde", + "serde_spanned", + "toml_datetime", + "winnow 0.5.40", +] + +[[package]] +name = "toml_edit" +version = "0.22.22" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "17b4795ff5edd201c7cd6dca065ae59972ce77d1b80fa0a84d94950ece7d1474" +checksum = "4ae48d6208a266e853d946088ed816055e556cc6028c5e8e2b84d9fa5dd7c7f5" dependencies = [ "indexmap", "serde", "serde_spanned", "toml_datetime", - "winnow", + "winnow 0.6.20", ] [[package]] name = "tracing" -version = "0.1.41" +version = "0.1.40" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "784e0ac535deb450455cbfa28a6f0df145ea1bb7ae51b821cf5e7927fdcfbdd0" +checksum = "c3523ab5a71916ccf420eebdf5521fcef02141234bbc0b8a49f2fdc4544364ef" dependencies = [ "pin-project-lite", "tracing-attributes", @@ -4378,20 +4282,20 @@ dependencies = [ [[package]] name = "tracing-attributes" -version = "0.1.28" +version = "0.1.27" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "395ae124c09f9e6918a2310af6038fba074bcf474ac352496d5910dd59a2226d" +checksum = "34704c8d6ebcbc939824180af020566b01a7c01f80641264eba0999f6c2b6be7" dependencies = [ "proc-macro2", "quote", - "syn 2.0.98", + "syn 2.0.87", ] [[package]] name = "tracing-core" -version = "0.1.33" +version = "0.1.32" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e672c95779cf947c5311f83787af4fa8fffd12fb27e4993211a84bdfd9610f9c" +checksum = "c06d3da6113f116aaee68e4d601191614c9053067f9ab7f6edbcb161237daa54" dependencies = [ "once_cell", "valuable", @@ -4410,9 +4314,9 @@ dependencies = [ [[package]] name = "tracing-subscriber" -version = "0.3.19" +version = "0.3.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e8189decb5ac0fa7bc8b96b7cb9b2701d60d48805aca84a238004d665fcc4008" +checksum = "ad0f048c97dbd9faa9b7df56362b8ebcaa52adb06b498c050d2f4e32f90a7a8b" dependencies = [ "matchers", "nu-ansi-term", @@ -4437,9 +4341,9 @@ dependencies = [ [[package]] name = "typenum" -version = "1.18.0" +version = "1.17.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1dccffe3ce07af9386bfd29e80c0ab1a8205a2fc34e4bcd40364df902cfa8f3f" +checksum = "42ff0bf0c66b8238c6f3b578df37d0b7848e55df8577b3f74f92a69acceeb825" [[package]] name = "unic-bidi" @@ -4494,9 +4398,9 @@ dependencies = [ [[package]] name = "unicode-bidi" -version = "0.3.18" +version = "0.3.17" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5c1cb5db39152898a79168971543b1cb5020dff7fe43c8dc468b0885f5e29df5" +checksum = "5ab17db44d7388991a428b2ee655ce0c212e862eff1768a455c58f9aad6e7893" [[package]] name = "unicode-bidi-mirroring" @@ -4512,9 +4416,18 @@ checksum = "260bc6647b3893a9a90668360803a15f96b85a5257b1c3a0c3daf6ae2496de42" [[package]] name = "unicode-ident" -version = "1.0.17" +version = "1.0.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "00e2473a93778eb0bad35909dff6a10d28e63f792f16ed15e404fca9d5eeedbe" +checksum = "e91b56cd4cadaeb79bbf1a5645f6b4f8dc5bde8834ad5894a8db35fda9efa1fe" + +[[package]] +name = "unicode-normalization" +version = "0.1.24" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5033c97c4262335cded6d6fc3e5c18ab755e1a3dc96376350f3d8e9f009ad956" +dependencies = [ + "tinyvec", +] [[package]] name = "unicode-properties" @@ -4542,15 +4455,15 @@ checksum = "b1d386ff53b415b7fe27b50bb44679e2cc4660272694b7b6f3326d8480823a94" [[package]] name = "unicode-width" -version = "0.2.0" +version = "0.1.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1fc81956842c57dac11422a97c3b8195a1ff727f06e85c84ed2e8aa277c9a0fd" +checksum = "7dd6e30e90baa6f72411720665d41d89b9a3d039dc45b8faea1ddd07f617f6af" [[package]] name = "url" -version = "2.5.4" +version = "2.5.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "32f8b686cadd1473f4bd0117a5d28d36b1ade384ea9b5069a1c40aefed7fda60" +checksum = "22784dbdf76fdde8af1aeda5622b546b422b6fc585325248a2bf9f5e41e94d6c" dependencies = [ "form_urlencoded", "idna", @@ -4576,7 +4489,7 @@ dependencies = [ "simplecss", "siphasher 1.0.1", "strict-num", - "svgtypes 0.15.3", + "svgtypes 0.15.2", "tiny-skia-path", "unicode-bidi", "unicode-script", @@ -4590,18 +4503,6 @@ version = "0.7.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "09cc8ee72d2a9becf2f2febe0205bbed8fc6615b7cb429ad062dc7b7ddd036a9" -[[package]] -name = "utf16_iter" -version = "1.0.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c8232dd3cdaed5356e0f716d285e4b40b932ac434100fe9b7e0e8e935b9e6246" - -[[package]] -name = "utf8_iter" -version = "1.0.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b6c140620e7ffbb22c2dee59cafe6084a59b5ffc27a8859a5f0d494b5d52b6be" - [[package]] name = "utf8parse" version = "0.2.2" @@ -4621,9 +4522,9 @@ dependencies = [ [[package]] name = "valuable" -version = "0.1.1" +version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ba73ea9cf16a25df0c8caa16c51acb937d5712a8429db78a3ee29d5dcacd3a65" +checksum = "830b7e5d4d90034032940e4ace0d9a9a057e7a45cd94e6c007832e39edb82f6d" [[package]] name = "version-compare" @@ -4664,48 +4565,47 @@ dependencies = [ [[package]] name = "wasm-bindgen" -version = "0.2.100" +version = "0.2.95" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1edc8929d7499fc4e8f0be2262a241556cfc54a0bea223790e71446f2aab1ef5" +checksum = "128d1e363af62632b8eb57219c8fd7877144af57558fb2ef0368d0087bddeb2e" dependencies = [ "cfg-if", "once_cell", - "rustversion", "wasm-bindgen-macro", ] [[package]] name = "wasm-bindgen-backend" -version = "0.2.100" +version = "0.2.95" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2f0a0651a5c2bc21487bde11ee802ccaf4c51935d0d3d42a6101f98161700bc6" +checksum = "cb6dd4d3ca0ddffd1dd1c9c04f94b868c37ff5fac97c30b97cff2d74fce3a358" dependencies = [ "bumpalo", "log", + "once_cell", "proc-macro2", "quote", - "syn 2.0.98", + "syn 2.0.87", "wasm-bindgen-shared", ] [[package]] name = "wasm-bindgen-futures" -version = "0.4.50" +version = "0.4.45" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "555d470ec0bc3bb57890405e5d4322cc9ea83cebb085523ced7be4144dac1e61" +checksum = "cc7ec4f8827a71586374db3e87abdb5a2bb3a15afed140221307c3ec06b1f63b" dependencies = [ "cfg-if", "js-sys", - "once_cell", "wasm-bindgen", "web-sys", ] [[package]] name = "wasm-bindgen-macro" -version = "0.2.100" +version = "0.2.95" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7fe63fc6d09ed3792bd0897b314f53de8e16568c2b3f7982f468c0bf9bd0b407" +checksum = "e79384be7f8f5a9dd5d7167216f022090cf1f9ec128e6e6a482a2cb5c5422c56" dependencies = [ "quote", "wasm-bindgen-macro-support", @@ -4713,41 +4613,28 @@ dependencies = [ [[package]] name = "wasm-bindgen-macro-support" -version = "0.2.100" +version = "0.2.95" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8ae87ea40c9f689fc23f209965b6fb8a99ad69aeeb0231408be24920604395de" +checksum = "26c6ab57572f7a24a4985830b120de1594465e5d500f24afe89e16b4e833ef68" dependencies = [ "proc-macro2", "quote", - "syn 2.0.98", + "syn 2.0.87", "wasm-bindgen-backend", "wasm-bindgen-shared", ] [[package]] name = "wasm-bindgen-shared" -version = "0.2.100" +version = "0.2.95" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1a05d73b933a847d6cccdda8f838a22ff101ad9bf93e33684f39c1f5f0eece3d" -dependencies = [ - "unicode-ident", -] +checksum = "65fc09f10666a9f147042251e0dda9c18f166ff7de300607007e96bdebc1068d" [[package]] name = "web-sys" -version = "0.3.77" +version = "0.3.72" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "33b6dd2ef9186f1f2072e409e99cd22a975331a6b3591b12c764e0e55c60d5d2" -dependencies = [ - "js-sys", - "wasm-bindgen", -] - -[[package]] -name = "web-time" -version = "1.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5a6580f308b1fad9207618087a65c04e7a10bc77e02c8e84e9b00dd4b12fa0bb" +checksum = "f6488b90108c040df0fe62fa815cbdee25124641df01814dd7282749234c6112" dependencies = [ "js-sys", "wasm-bindgen", @@ -4761,9 +4648,9 @@ checksum = "53a85b86a771b1c87058196170769dd264f66c0782acf1ae6cc51bfd64b39082" [[package]] name = "wide" -version = "0.7.32" +version = "0.7.28" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "41b5576b9a81633f3e8df296ce0063042a73507636cbe956c61133dd7034ab22" +checksum = "b828f995bf1e9622031f8009f8481a85406ce1f4d4588ff746d872043e855690" dependencies = [ "bytemuck", "safe_arch", @@ -4988,20 +4875,29 @@ checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec" [[package]] name = "winnow" -version = "0.7.3" +version = "0.5.40" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f593a95398737aeed53e489c785df13f3618e41dbcd6718c6addbf1395aa6876" +dependencies = [ + "memchr", +] + +[[package]] +name = "winnow" +version = "0.6.20" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0e7f4ea97f6f78012141bcdb6a216b2609f0979ada50b20ca5b52dde2eac2bb1" +checksum = "36c1fec1a2bb5866f07c25f68c26e565c4c200aebb96d7e55710c19d3e8ac49b" dependencies = [ "memchr", ] [[package]] name = "winresource" -version = "0.1.19" +version = "0.1.17" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7276691b353ad4547af8c3268488d1311f4be791ffdc0c65b8cfa8f41eed693b" +checksum = "77e2aaaf8cfa92078c0c0375423d631f82f2f57979c2884fdd5f604a11e45329" dependencies = [ - "toml", + "toml 0.7.8", "version_check", ] @@ -5011,21 +4907,9 @@ version = "0.33.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3268f3d866458b787f390cf61f4bbb563b922d091359f9608842999eaee3943c" dependencies = [ - "bitflags 2.8.0", + "bitflags 2.9.0", ] -[[package]] -name = "write16" -version = "1.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d1890f4022759daae28ed4fe62859b1236caebfc61ede2f63ed4e695f3f6d936" - -[[package]] -name = "writeable" -version = "0.5.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1e9df38ee2d2c3c5948ea468a8406ff0db0b29ae1ffde1bcf20ef305bcc95c51" - [[package]] name = "xi-unicode" version = "0.3.0" @@ -5049,30 +4933,6 @@ version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ec7a2a501ed189703dba8b08142f057e887dfc4b2cc4db2d343ac6376ba3e0b9" -[[package]] -name = "yoke" -version = "0.7.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "120e6aef9aa629e3d4f52dc8cc43a015c7724194c97dfaf45180d2daf2b77f40" -dependencies = [ - "serde", - "stable_deref_trait", - "yoke-derive", - "zerofrom", -] - -[[package]] -name = "yoke-derive" -version = "0.7.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2380878cad4ac9aac1e2435f3eb4020e8374b5f13c296cb75b4620ff8e229154" -dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.98", - "synstructure", -] - [[package]] name = "zerocopy" version = "0.7.35" @@ -5085,11 +4945,11 @@ dependencies = [ [[package]] name = "zerocopy" -version = "0.8.20" +version = "0.8.23" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dde3bb8c68a8f3f1ed4ac9221aad6b10cece3e60a8e2ea54a6a2dec806d0084c" +checksum = "fd97444d05a4328b90e75e503a34bad781f14e28a823ad3557f0750df1ebcbc6" dependencies = [ - "zerocopy-derive 0.8.20", + "zerocopy-derive 0.8.23", ] [[package]] @@ -5100,67 +4960,52 @@ checksum = "fa4f8080344d4671fb4e831a13ad1e68092748387dfc4f55e356242fae12ce3e" dependencies = [ "proc-macro2", "quote", - "syn 2.0.98", + "syn 2.0.87", ] [[package]] name = "zerocopy-derive" -version = "0.8.20" +version = "0.8.23" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "eea57037071898bf96a6da35fd626f4f27e9cee3ead2a6c703cf09d472b2e700" +checksum = "6352c01d0edd5db859a63e2605f4ea3183ddbd15e2c4a9e7d32184df75e4f154" dependencies = [ "proc-macro2", "quote", - "syn 2.0.98", + "syn 2.0.87", ] [[package]] -name = "zerofrom" -version = "0.1.5" +name = "zeroize" +version = "1.8.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cff3ee08c995dee1859d998dea82f7374f2826091dd9cd47def953cae446cd2e" -dependencies = [ - "zerofrom-derive", -] +checksum = "ced3678a2879b30306d323f4542626697a464a97c0a07c9aebf7ebca65cd4dde" [[package]] -name = "zerofrom-derive" -version = "0.1.5" +name = "zstd" +version = "0.13.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "595eed982f7d355beb85837f651fa22e90b3c044842dc7f2c2842c086f295808" +checksum = "fcf2b778a664581e31e389454a7072dab1647606d44f7feea22cd5abb9c9f3f9" dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.98", - "synstructure", + "zstd-safe", ] [[package]] -name = "zeroize" -version = "1.8.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ced3678a2879b30306d323f4542626697a464a97c0a07c9aebf7ebca65cd4dde" - -[[package]] -name = "zerovec" -version = "0.10.4" +name = "zstd-safe" +version = "7.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "aa2b893d79df23bfb12d5461018d408ea19dfafe76c2c7ef6d4eba614f8ff079" +checksum = "54a3ab4db68cea366acc5c897c7b4d4d1b8994a9cd6e6f841f8964566a419059" dependencies = [ - "yoke", - "zerofrom", - "zerovec-derive", + "zstd-sys", ] [[package]] -name = "zerovec-derive" -version = "0.10.3" +name = "zstd-sys" +version = "2.0.13+zstd.1.5.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6eafa6dfb17584ea3e2bd6e76e0cc15ad7af12b09abdd1ca55961bed9b1063c6" +checksum = "38ff0f21cfee8f97d94cef41359e0c89aa6113028ab0291aa8ca0038995a95aa" dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.98", + "cc", + "pkg-config", ] [[package]] @@ -5180,9 +5025,9 @@ dependencies = [ [[package]] name = "zune-jpeg" -version = "0.4.14" +version = "0.4.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "99a5bab8d7dedf81405c4bb1f2b83ea057643d9cb28778cea9eecddeedd2e028" +checksum = "16099418600b4d8f028622f73ff6e3deaabdff330fb9a2a131dea781ee8b0768" dependencies = [ "zune-core", ] diff --git a/Cargo.toml b/Cargo.toml index e9ee826d8d..4985371635 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -25,9 +25,11 @@ anyhow = "1.0" approx = "0.5.1" async-fs = "2.1" base64 = "0.22.1" +bitcode = { version = "0.6", features = ["serde"] } cairo-rs = { version = "0.20.1", features = ["v1_18", "png", "svg", "pdf"] } chrono = "0.4.38" clap = { version = "4.5", features = ["derive"] } +crc32fast = { version = "1.4" } dialoguer = "0.11.0" flate2 = "1.0" fs_extra = "1.3" @@ -83,7 +85,8 @@ url = "2.5" usvg = "0.44.0" winresource = "0.1.17" xmlwriter = "0.1.0" -# Enabling feature > v20_9 causes linker errors on mingw +zstd = { version = "0.13", features = ["zstdmt"] } + # using a custom patched version to work on windows # as long as https://gitlab.gnome.org/World/Rust/poppler-rs/-/issues/1 # is not fixed diff --git a/crates/rnote-cli/src/cli.rs b/crates/rnote-cli/src/cli.rs index 0261ad2c8f..6bf60cf0b1 100644 --- a/crates/rnote-cli/src/cli.rs +++ b/crates/rnote-cli/src/cli.rs @@ -1,7 +1,9 @@ // Imports -use crate::{export, import, test, thumbnail}; +use crate::{export, import, mutate, test, thumbnail}; + use anyhow::Context; use clap::Parser; +use clap::builder::PossibleValuesParser; use rnote_compose::SplitOrder; use rnote_engine::SelectionCollision; use rnote_engine::engine::export::{ @@ -9,6 +11,7 @@ use rnote_engine::engine::export::{ SelectionExportPrefs, }; use rnote_engine::engine::import::XoppImportPrefs; +use rnote_engine::fileformats::rnoteformat; use smol::fs::File; use smol::io::{AsyncReadExt, AsyncWriteExt}; use std::path::{Path, PathBuf}; @@ -69,6 +72,27 @@ pub(crate) enum Command { #[arg(long, action = clap::ArgAction::SetTrue, global = true)] open: bool, }, + /// Mutates one or more of the following for the specified Rnote file(s):{n} + /// compression method, compression level, serialization method, method lock + Mutate { + /// The rnote save file(s) to mutate + rnote_files: Vec, + /// Keep the original rnote save file(s) + #[arg(long = "not-in-place", alias = "nip", action = clap::ArgAction::SetTrue)] + not_in_place: bool, + /// Sets method_lock to true, allowing a rnote save file to keep using non-default methods to serialize and compress itself + #[arg(short = 'l', long, action = clap::ArgAction::SetTrue, conflicts_with = "unlock")] + lock: bool, + /// Sets method_lock to false, coercing the file to use default methods on the next save + #[arg(short = 'u', long, action = clap::ArgAction::SetTrue, conflicts_with = "lock")] + unlock: bool, + #[arg(short = 's', long, action = clap::ArgAction::Set, value_parser = PossibleValuesParser::new(rnoteformat::SerializationMethod::VALID_STR_ARRAY))] + serialization_method: Option, + #[arg(short = 'c', long, action = clap::ArgAction::Set, value_parser = PossibleValuesParser::new(rnoteformat::CompressionMethod::VALID_STR_ARRAY))] + compression_method: Option, + #[arg(short = 'v', long, action = clap::ArgAction::Set)] + compression_level: Option, + }, /// Generate rnote thumbail from a given file Thumbnail { /// Input rnote file @@ -252,6 +276,28 @@ pub(crate) async fn run() -> anyhow::Result<()> { .await?; println!("Export finished!"); } + Command::Mutate { + rnote_files, + not_in_place, + lock, + unlock, + serialization_method, + compression_method, + compression_level, + } => { + println!("Mutating..\n"); + mutate::run_mutate( + rnote_files, + not_in_place, + lock, + unlock, + serialization_method, + compression_method, + compression_level, + ) + .await?; + println!("Mutate finished!"); + } Command::Thumbnail { rnote_file, size, diff --git a/crates/rnote-cli/src/main.rs b/crates/rnote-cli/src/main.rs index eb2c13feaa..17be955c09 100644 --- a/crates/rnote-cli/src/main.rs +++ b/crates/rnote-cli/src/main.rs @@ -6,6 +6,7 @@ pub(crate) mod cli; pub(crate) mod export; pub(crate) mod import; +pub(crate) mod mutate; pub(crate) mod test; pub(crate) mod thumbnail; pub(crate) mod validators; diff --git a/crates/rnote-cli/src/meson.build b/crates/rnote-cli/src/meson.build index 3be7f85868..56edf425ff 100644 --- a/crates/rnote-cli/src/meson.build +++ b/crates/rnote-cli/src/meson.build @@ -4,6 +4,7 @@ rnote_cli_sources = files( 'export.rs', 'import.rs', 'main.rs', + 'mutate.rs', 'test.rs', 'validators.rs', ) diff --git a/crates/rnote-cli/src/mutate.rs b/crates/rnote-cli/src/mutate.rs new file mode 100644 index 0000000000..029510f089 --- /dev/null +++ b/crates/rnote-cli/src/mutate.rs @@ -0,0 +1,94 @@ +use rnote_engine::engine::EngineSnapshot; +use rnote_engine::fileformats::FileFormatSaver; +use rnote_engine::fileformats::rnoteformat::RnoteHeader; +use rnote_engine::fileformats::{FileFormatLoader, rnoteformat::RnoteFile}; +use smol::{fs::OpenOptions, io::AsyncReadExt}; +use std::path::PathBuf; +use std::str::FromStr; + +pub(crate) async fn run_mutate( + rnote_files: Vec, + not_in_place: bool, + lock: bool, + unlock: bool, + serialization_method: Option, + compression_method: Option, + compression_level: Option, +) -> anyhow::Result<()> { + let total_len = rnote_files.len(); + let mut total_delta: f64 = 0.0; + for (idx, mut filepath) in rnote_files.into_iter().enumerate() { + println!("Working on file {} out of {}", idx + 1, total_len); + + let file_read_operation = async { + let mut read_file = OpenOptions::new().read(true).open(&filepath).await?; + + let mut bytes: Vec = { + match read_file.metadata().await { + Ok(metadata) => { + Vec::with_capacity(usize::try_from(metadata.len()).unwrap_or(usize::MAX)) + } + Err(err) => { + eprintln!("Failed to read file metadata, '{err}'"); + Vec::new() + } + } + }; + + read_file.read_to_end(&mut bytes).await?; + Ok::, anyhow::Error>(bytes) + }; + let bytes = file_read_operation.await?; + let old_size_mb = bytes.len() as f64 / 1e6; + let rnote_file = RnoteFile::load_from_bytes(&bytes)?; + + let serialization = if let Some(ref str) = serialization_method { + rnote_engine::fileformats::rnoteformat::SerializationMethod::from_str(str).unwrap() + } else { + rnote_file.header.serialization + }; + + let mut compression = if let Some(ref str) = compression_method { + rnote_engine::fileformats::rnoteformat::CompressionMethod::from_str(str).unwrap() + } else { + rnote_file.header.compression + }; + + if let Some(lvl) = compression_level { + compression.update_compression_integer(lvl)?; + } + + let method_lock = (rnote_file.header.method_lock | lock) && !unlock; + let uc_data = serialization.serialize(&EngineSnapshot::try_from(rnote_file)?)?; + let uc_size = uc_data.len() as u64; + let data = compression.compress(uc_data)?; + + let rnote_file = RnoteFile { + header: RnoteHeader { + serialization, + compression, + uc_size, + method_lock, + }, + body: data, + }; + + if not_in_place { + let file_stem = filepath + .file_stem() + .ok_or_else(|| anyhow::anyhow!("File does not contain a valid file stem"))? + .to_str() + .ok_or_else(|| anyhow::anyhow!("File does not contain a valid file stem"))?; + filepath.set_file_name(format!("{}_mut.rnote", file_stem)); + } + + let data = rnote_file.save_as_bytes("")?; + let new_size_mb = data.len() as f64 / 1e6; + rnote_engine::utils::atomic_save_to_file(&filepath, &data).await?; + + println!("{:.2} MB → {:.2} MB", old_size_mb, new_size_mb,); + total_delta += new_size_mb - old_size_mb; + } + println!("\n⇒ ∆ = {:.2} MB", total_delta); + Ok(()) +} diff --git a/crates/rnote-engine/Cargo.toml b/crates/rnote-engine/Cargo.toml index c8add5331c..aea64a62a6 100644 --- a/crates/rnote-engine/Cargo.toml +++ b/crates/rnote-engine/Cargo.toml @@ -13,10 +13,13 @@ rnote-compose = { workspace = true } anyhow = { workspace = true } approx = { workspace = true } +async-fs = { workspace = true } base64 = { workspace = true } +bitcode = { workspace = true, features = ["serde"] } cairo-rs = { workspace = true } chrono = { workspace = true } clap = { workspace = true, optional = true } +crc32fast = { workspace = true } flate2 = { workspace = true } futures = { workspace = true } geo = { workspace = true } @@ -55,6 +58,7 @@ tracing = { workspace = true } unicode-segmentation = { workspace = true } usvg = { workspace = true } xmlwriter = { workspace = true } +zstd = { workspace = true, features = ["zstdmt"] } # the long-term plan is to remove the gtk4 dependency entirely after switching to another renderer. gtk4 = { workspace = true, optional = true } diff --git a/crates/rnote-engine/src/engine/export.rs b/crates/rnote-engine/src/engine/export.rs index 106ae22ae6..961fffd64a 100644 --- a/crates/rnote-engine/src/engine/export.rs +++ b/crates/rnote-engine/src/engine/export.rs @@ -331,9 +331,7 @@ impl Engine { let engine_snapshot = self.take_snapshot(); rayon::spawn(move || { let result = || -> anyhow::Result> { - let rnote_file = RnoteFile { - engine_snapshot: ijson::to_value(&engine_snapshot)?, - }; + let rnote_file = RnoteFile::try_from(&engine_snapshot)?; rnote_file.save_as_bytes(&file_name) }; if oneshot_sender.send(result()).is_err() { @@ -353,6 +351,7 @@ impl Engine { penholder: self.penholder.clone_config(), import_prefs: self.import_prefs.clone_config(), export_prefs: self.export_prefs.clone_config(), + save_prefs: self.save_prefs.clone_from_engine_to_engineconfig(), pen_sounds: self.pen_sounds(), optimize_epd: self.optimize_epd(), } diff --git a/crates/rnote-engine/src/engine/import.rs b/crates/rnote-engine/src/engine/import.rs index e208153452..e2b42444c2 100644 --- a/crates/rnote-engine/src/engine/import.rs +++ b/crates/rnote-engine/src/engine/import.rs @@ -162,6 +162,7 @@ impl Engine { self.penholder = engine_config.penholder; self.import_prefs = engine_config.import_prefs; self.export_prefs = engine_config.export_prefs; + self.save_prefs = engine_config.save_prefs.clone_from_engineconfig_to_engine(); // Set the pen sounds to update the audioplayer self.set_pen_sounds(engine_config.pen_sounds, data_dir); diff --git a/crates/rnote-engine/src/engine/mod.rs b/crates/rnote-engine/src/engine/mod.rs index bb7a92bdfc..8ca95ebb40 100644 --- a/crates/rnote-engine/src/engine/mod.rs +++ b/crates/rnote-engine/src/engine/mod.rs @@ -2,6 +2,7 @@ pub mod export; pub mod import; pub mod rendering; +pub mod save; pub mod snapshot; pub mod strokecontent; pub mod visual_debug; @@ -11,6 +12,7 @@ pub use export::ExportPrefs; use futures::StreamExt; use futures::channel::mpsc::UnboundedReceiver; pub use import::ImportPrefs; +pub use save::SavePrefs; pub use snapshot::EngineSnapshot; pub use strokecontent::StrokeContent; @@ -154,6 +156,8 @@ pub struct EngineConfig { import_prefs: ImportPrefs, #[serde(rename = "export_prefs")] export_prefs: ExportPrefs, + #[serde(rename = "save_prefs")] + save_prefs: SavePrefs, #[serde(rename = "pen_sounds")] pen_sounds: bool, #[serde(rename = "optimize_epd")] @@ -235,6 +239,8 @@ pub struct Engine { pub import_prefs: ImportPrefs, #[serde(rename = "export_prefs")] pub export_prefs: ExportPrefs, + #[serde(rename = "save_prefs")] + pub save_prefs: SavePrefs, #[serde(rename = "pen_sounds")] pen_sounds: bool, #[serde(rename = "optimize_epd")] @@ -277,6 +283,7 @@ impl Default for Engine { penholder: PenHolder::default(), import_prefs: ImportPrefs::default(), export_prefs: ExportPrefs::default(), + save_prefs: SavePrefs::default(), pen_sounds: false, optimize_epd: false, @@ -377,6 +384,7 @@ impl Engine { stroke_components: Arc::clone(&store_history_entry.stroke_components), chrono_components: Arc::clone(&store_history_entry.chrono_components), chrono_counter: store_history_entry.chrono_counter, + save_prefs: self.save_prefs.clone_from_engine_to_enginesnapshot(), } } @@ -384,6 +392,8 @@ impl Engine { pub fn load_snapshot(&mut self, snapshot: EngineSnapshot) -> WidgetFlags { self.document = snapshot.document.clone_config(); self.camera = snapshot.camera.clone_config(); + self.save_prefs.merge(&snapshot.save_prefs); + let mut widget_flags = self.store.import_from_snapshot(&snapshot) | self.doc_resize_autoexpand() | self.current_pen_update_state() diff --git a/crates/rnote-engine/src/engine/save.rs b/crates/rnote-engine/src/engine/save.rs new file mode 100644 index 0000000000..4a1eef2968 --- /dev/null +++ b/crates/rnote-engine/src/engine/save.rs @@ -0,0 +1,136 @@ +// Imports +use crate::fileformats::rnoteformat::{ + RnoteHeader, compression::CompressionMethod, serialization::SerializationMethod, +}; +use serde::{Deserialize, Serialize}; + +// Re-exports +pub use crate::fileformats::rnoteformat::compression::CompressionLevel; + +/// The SavePrefs struct is similar to RnoteHeader, it is used by Engine, EngineSnapshot, and EngineConfig. +#[derive(Debug, Clone, Copy, Default, Serialize, Deserialize)] +#[serde(default, rename = "save_prefs")] +pub struct SavePrefs { + #[serde(rename = "serialization")] + pub serialization: SerializationMethod, + #[serde(rename = "compression")] + pub compression: CompressionMethod, + #[serde(skip)] + pub method_lock: bool, + #[serde(skip)] + pub on_next_save: Option<(SerializationMethod, CompressionMethod)>, +} + +impl SavePrefs { + pub(crate) fn new( + serialization: SerializationMethod, + compression: CompressionMethod, + method_lock: bool, + on_next_save: Option<(SerializationMethod, CompressionMethod)>, + ) -> Self { + Self { + serialization, + compression, + method_lock, + on_next_save, + } + } + + pub(crate) fn new_simple( + serialization: SerializationMethod, + compression: CompressionMethod, + ) -> Self { + Self { + serialization, + compression, + method_lock: false, + on_next_save: None, + } + } + + /// Checks that serialization and compression are default methods. + #[rustfmt::skip] + pub(crate) fn uses_default_methods(&self) -> bool { + self.serialization.is_similar_to(&SerializationMethod::default()) + && self.compression.is_similar_to(&CompressionMethod::default()) + } + + /// Used to export the SavePrefs of the Engine to an EngineSnapshot. + pub(crate) fn clone_from_engine_to_enginesnapshot(&self) -> Self { + match self.on_next_save { + Some((serialization, compression)) => { + // Necessary as the RnoteFile ignores the `on_next_save` component + Self::new(serialization, compression, self.method_lock, None) + } + None => *self, + } + } + + /// Used to load the SavePrefs of the EngineConfig into the Engine. + pub(crate) fn clone_from_engineconfig_to_engine(&self) -> Self { + // SavePrefs can only be retrieved from the EngineConfig if they conform to the default methods. + if self.uses_default_methods() { + Self::new_simple(self.serialization, self.compression) + } else { + Self::default() + } + } + + /// Used to export the SavePrefs of the Engine to EngineConfig. + pub(crate) fn clone_from_engine_to_engineconfig(&self) -> Self { + match (self.uses_default_methods(), self.on_next_save) { + (true | false, Some((serialization, compression))) => { + Self::new_simple(serialization, compression) + } + (true, None) => *self, + (false, None) => Self::default(), + } + } + + /// Used by engine to merge the incoming SavePrefs originating from a Rnote file into its current SavePrefs. + pub(crate) fn merge(&mut self, new: &Self) { + let on_next_save = match (new.uses_default_methods(), new.method_lock) { + (true, true) | (true, false) | (false, true) => None, + (false, false) => Some((self.serialization, self.compression)), + }; + self.serialization = new.serialization; + self.compression = new.compression; + self.method_lock = new.method_lock; + self.on_next_save = on_next_save; + } + + pub fn finished_saving(&mut self) { + if let Some((serialization, compression)) = self.on_next_save { + self.serialization = serialization; + self.compression = compression; + self.on_next_save = None; + } + } + + #[rustfmt::skip] + pub fn update_compression_level(&mut self, new_level: CompressionLevel) { + let self_level = self.compression.get_compression_level(); + if self_level.ne(&new_level) { + match self.on_next_save { + Some((_, ref mut compression)) => compression.set_compression_level(&new_level), + None => self.on_next_save = Some((self.serialization, self.compression.clone_with_new_compression_level(&new_level))) + } + } + else if let Some((serialization, compression)) = self.on_next_save { + if self.serialization.is_similar_to(&serialization) && self.compression.is_similar_to(&compression) { + self.on_next_save = None + } + } + } +} + +impl From for SavePrefs { + fn from(value: RnoteHeader) -> Self { + Self { + serialization: value.serialization, + compression: value.compression, + method_lock: value.method_lock, + on_next_save: None, + } + } +} diff --git a/crates/rnote-engine/src/engine/snapshot.rs b/crates/rnote-engine/src/engine/snapshot.rs index 6ed09a51f3..305d46a2c5 100644 --- a/crates/rnote-engine/src/engine/snapshot.rs +++ b/crates/rnote-engine/src/engine/snapshot.rs @@ -1,6 +1,7 @@ // Imports use crate::document::background; use crate::engine::import::XoppImportPrefs; +use crate::fileformats::rnoteformat::SerializationMethod; use crate::fileformats::{FileFormatLoader, rnoteformat, xoppformat}; use crate::store::{ChronoComponent, StrokeKey}; use crate::strokes::Stroke; @@ -12,6 +13,8 @@ use slotmap::{HopSlotMap, SecondaryMap}; use std::sync::Arc; use tracing::error; +use super::save::SavePrefs; + // An engine snapshot, used when loading/saving the current document from/into a file. #[derive(Debug, Clone, Serialize, Deserialize)] #[serde(default, rename = "engine_snapshot")] @@ -26,6 +29,9 @@ pub struct EngineSnapshot { pub chrono_components: Arc>>, #[serde(rename = "chrono_counter")] pub chrono_counter: u32, + // save_prefs is skipped as it is extracted and incorporated into the header when saving + #[serde(skip, default)] + pub save_prefs: SavePrefs, } impl Default for EngineSnapshot { @@ -36,12 +42,17 @@ impl Default for EngineSnapshot { stroke_components: Arc::new(HopSlotMap::with_key()), chrono_components: Arc::new(SecondaryMap::new()), chrono_counter: 0, + save_prefs: SavePrefs::default(), } } } impl EngineSnapshot { - /// Loads a snapshot from the bytes of a .rnote file. + pub fn with_save_prefs(mut self, save_prefs: SavePrefs) -> Self { + self.save_prefs = save_prefs; + self + } + /// Loads a snapshot from the bytes of a Rnote file. /// /// To import this snapshot into the current engine, use [`Engine::load_snapshot()`]. pub async fn load_from_rnote_bytes(bytes: Vec) -> anyhow::Result { @@ -49,9 +60,23 @@ impl EngineSnapshot { rayon::spawn(move || { let result = || -> anyhow::Result { + // Efficient support for legacy Rnote files, by checking the existence of the gzip magic number at the start of the file, avoids the costly try_from conversion. + if bytes + .get(..2) + .ok_or_else(|| anyhow::anyhow!("File is empty"))? + == [0x1f, 0x8b] + { + let legacy = rnoteformat::legacy::LegacyRnoteFile::load_from_bytes(&bytes)?; + return Ok(ijson::from_value::(&legacy.engine_snapshot)? + .with_save_prefs(SavePrefs::new_simple( + SerializationMethod::Json, + rnoteformat::CompressionMethod::Gzip(5), + ))); + } + let rnote_file = rnoteformat::RnoteFile::load_from_bytes(&bytes) - .context("loading RnoteFile from bytes failed.")?; - Ok(ijson::from_value(&rnote_file.engine_snapshot)?) + .context("Loading RnoteFile from bytes failed.")?; + Self::try_from(rnote_file) }; if let Err(_data) = snapshot_sender.send(result()) { diff --git a/crates/rnote-engine/src/engine/visual_debug.rs b/crates/rnote-engine/src/engine/visual_debug.rs index f50342d38c..c023dfb7c4 100644 --- a/crates/rnote-engine/src/engine/visual_debug.rs +++ b/crates/rnote-engine/src/engine/visual_debug.rs @@ -147,7 +147,7 @@ pub(crate) fn draw_statistics_to_gtk_snapshot( ], na::point![ surface_bounds.maxs[0] - 20.0, - surface_bounds.mins[1] + 120.0 + surface_bounds.mins[1] + 420.0 ], ); let cairo_cx = snapshot.append_cairo(&graphene::Rect::from_p2d_aabb(text_bounds)); @@ -166,12 +166,13 @@ pub(crate) fn draw_statistics_to_gtk_snapshot( .count(); let statistics_text_string = format!( - "strokes in store: {}\nstrokes in current viewport: {}\nstrokes selected: {}\nstroke trashed: {}\nstrokes holding images: {}", + "strokes in store: {}\nstrokes in current viewport: {}\nstrokes selected: {}\nstroke trashed: {}\nstrokes holding images: {}\n\n {:?}", strokes_total.len(), strokes_in_viewport.len(), selected_strokes.len(), trashed_strokes.len(), strokes_hold_image, + engine.save_prefs, ); let text_layout = piet_cx .text() diff --git a/crates/rnote-engine/src/fileformats/rnoteformat/compression.rs b/crates/rnote-engine/src/fileformats/rnoteformat/compression.rs new file mode 100644 index 0000000000..b4a901f7ce --- /dev/null +++ b/crates/rnote-engine/src/fileformats/rnoteformat/compression.rs @@ -0,0 +1,211 @@ +// Imports +use serde::{Deserialize, Serialize}; +use std::{ + io::{Read, Write}, + str::FromStr, +}; + +/// Compression methods that can be applied to the serialized engine snapshot +#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)] +pub enum CompressionMethod { + #[serde(rename = "none")] + None, + #[serde(rename = "gzip")] + Gzip(u8), + #[serde(rename = "zstd")] + Zstd(u8), +} + +impl Default for CompressionMethod { + fn default() -> Self { + Self::Zstd(12) + } +} + +impl CompressionMethod { + pub const VALID_STR_ARRAY: [&'static str; 6] = ["None", "none", "Gzip", "gzip", "Zstd", "zstd"]; + + pub fn is_similar_to(&self, other: &Self) -> bool { + std::mem::discriminant(self) == std::mem::discriminant(other) + } + + pub fn compress(&self, data: Vec) -> anyhow::Result> { + match self { + Self::None => Ok(data), + Self::Gzip(compression_level) => { + let mut encoder = flate2::write::GzEncoder::new( + Vec::new(), + flate2::Compression::new(u32::from(*compression_level)), + ); + encoder.write_all(&data)?; + Ok(encoder.finish()?) + } + Self::Zstd(compression_level) => { + let mut encoder = + zstd::Encoder::new(Vec::::new(), i32::from(*compression_level))?; + let _ = encoder.set_parameter( + zstd::zstd_safe::CParameter::EnableLongDistanceMatching(true), + ); + if let Ok(num_workers) = std::thread::available_parallelism() { + encoder.multithread(num_workers.get() as u32)?; + } + encoder.write_all(&data)?; + Ok(encoder.finish()?) + } + } + } + + pub fn decompress(&self, uc_size: usize, data: Vec) -> anyhow::Result> { + match self { + Self::None => Ok(data), + Self::Gzip { .. } => { + let mut bytes: Vec = Vec::with_capacity(uc_size); + let mut decoder = flate2::read::MultiGzDecoder::new(&data[..]); + decoder.read_to_end(&mut bytes)?; + Ok(bytes) + } + Self::Zstd { .. } => { + let mut bytes: Vec = Vec::with_capacity(uc_size); + let mut decoder = zstd::Decoder::new(&data[..])?; + decoder.read_to_end(&mut bytes)?; + Ok(bytes) + } + } + } + + pub fn update_compression_integer(&mut self, new: u8) -> anyhow::Result<()> { + match self { + Self::None => { + tracing::warn!("Cannot update the compression level of 'None'"); + Ok(()) + } + Self::Gzip(curr) => { + if !(0..=9).contains(&new) { + Err(anyhow::anyhow!( + "Invalid compression level for Gzip, expected a value between 0 and 9" + )) + } else { + *curr = new; + Ok(()) + } + } + Self::Zstd(curr) => { + if !zstd::compression_level_range().contains(&i32::from(new)) { + Err(anyhow::anyhow!( + "Invalid compression level for Zstd, expected a value between 1 and 22" + )) + } else { + *curr = new; + Ok(()) + } + } + } + } + + pub fn get_compression_level(&self) -> CompressionLevel { + match self { + Self::None => CompressionLevel::None, + Self::Gzip(val) => match *val { + 0..=1 => CompressionLevel::VeryLow, + 2..=3 => CompressionLevel::Low, + 4..=5 => CompressionLevel::Medium, + 6..=7 => CompressionLevel::High, + 8..=9 => CompressionLevel::VeryHigh, + 10.. => { + tracing::warn!("Compression integer of {self:?} is greater than expected"); + CompressionLevel::VeryHigh + } + }, + Self::Zstd(val) => match *val { + 1..=5 => CompressionLevel::VeryLow, + 6..=9 => CompressionLevel::Low, + 10..=13 => CompressionLevel::Medium, + 14..=17 => CompressionLevel::High, + 18..=22 => CompressionLevel::VeryHigh, + 0 => { + tracing::warn!("Compression integer of {self:?} is lower than expected"); + CompressionLevel::VeryLow + } + 23.. => { + tracing::warn!("Compression integer of {self:?} is greater than expected"); + CompressionLevel::VeryHigh + } + }, + } + } + + fn get_compression_integer_from_compression_level(&self, level: &CompressionLevel) -> u8 { + match self { + Self::None => 0, + Self::Gzip(..) => match level { + CompressionLevel::VeryHigh => 8, + CompressionLevel::High => 6, + CompressionLevel::Medium => 5, + CompressionLevel::Low => 3, + CompressionLevel::VeryLow => 1, + CompressionLevel::None => 0, + }, + Self::Zstd(..) => match level { + CompressionLevel::VeryHigh => 20, + CompressionLevel::High => 16, + CompressionLevel::Medium => 12, + CompressionLevel::Low => 8, + CompressionLevel::VeryLow => 3, + CompressionLevel::None => 0, + }, + } + } + pub fn set_compression_level(&mut self, level: &CompressionLevel) { + let new_integer = self.get_compression_integer_from_compression_level(level); + match self { + Self::None => tracing::warn!( + "Attempting to set the compression level for `CompressionMethod::None` " + ), + Self::Gzip(integer) | Self::Zstd(integer) => *integer = new_integer, + } + } + + pub fn clone_with_new_compression_level(&self, level: &CompressionLevel) -> Self { + let new_integer = self.get_compression_integer_from_compression_level(level); + match self { + Self::None => Self::None, + Self::Gzip(..) => Self::Gzip(new_integer), + Self::Zstd(..) => Self::Zstd(new_integer), + } + } +} + +impl FromStr for CompressionMethod { + type Err = &'static str; + fn from_str(s: &str) -> Result { + match s { + "None" | "none" => Ok(Self::None), + "Gzip" | "gzip" => Ok(Self::Gzip(5)), + "Zstd" | "zstd" => Ok(Self::Zstd(12)), + _ => Err("Unknown compression method"), + } + } +} + +#[derive(Debug, Clone, Copy, PartialEq, Eq, num_derive::FromPrimitive, num_derive::ToPrimitive)] +pub enum CompressionLevel { + VeryHigh, + High, + Medium, + Low, + VeryLow, + None, +} + +impl TryFrom for CompressionLevel { + type Error = anyhow::Error; + + fn try_from(value: u32) -> Result { + num_traits::FromPrimitive::from_u32(value).ok_or_else(|| { + anyhow::anyhow!( + "CompressionLevel try_from::() for value {} failed", + value + ) + }) + } +} diff --git a/crates/rnote-engine/src/fileformats/rnoteformat/maj0min5patch8.rs b/crates/rnote-engine/src/fileformats/rnoteformat/legacy/maj0min5patch8.rs similarity index 100% rename from crates/rnote-engine/src/fileformats/rnoteformat/maj0min5patch8.rs rename to crates/rnote-engine/src/fileformats/rnoteformat/legacy/maj0min5patch8.rs diff --git a/crates/rnote-engine/src/fileformats/rnoteformat/maj0min5patch9.rs b/crates/rnote-engine/src/fileformats/rnoteformat/legacy/maj0min5patch9.rs similarity index 100% rename from crates/rnote-engine/src/fileformats/rnoteformat/maj0min5patch9.rs rename to crates/rnote-engine/src/fileformats/rnoteformat/legacy/maj0min5patch9.rs diff --git a/crates/rnote-engine/src/fileformats/rnoteformat/maj0min6.rs b/crates/rnote-engine/src/fileformats/rnoteformat/legacy/maj0min6.rs similarity index 100% rename from crates/rnote-engine/src/fileformats/rnoteformat/maj0min6.rs rename to crates/rnote-engine/src/fileformats/rnoteformat/legacy/maj0min6.rs diff --git a/crates/rnote-engine/src/fileformats/rnoteformat/maj0min9.rs b/crates/rnote-engine/src/fileformats/rnoteformat/legacy/maj0min9.rs similarity index 100% rename from crates/rnote-engine/src/fileformats/rnoteformat/maj0min9.rs rename to crates/rnote-engine/src/fileformats/rnoteformat/legacy/maj0min9.rs diff --git a/crates/rnote-engine/src/fileformats/rnoteformat/legacy/mod.rs b/crates/rnote-engine/src/fileformats/rnoteformat/legacy/mod.rs new file mode 100644 index 0000000000..cd96d92143 --- /dev/null +++ b/crates/rnote-engine/src/fileformats/rnoteformat/legacy/mod.rs @@ -0,0 +1,107 @@ +// Modules +mod maj0min5patch8; +mod maj0min5patch9; +mod maj0min6; +pub mod maj0min9; + +// Imports +use super::FileFormatLoader; +use anyhow::Context; +use maj0min5patch8::RnoteFileMaj0Min5Patch8; +use maj0min5patch9::RnoteFileMaj0Min5Patch9; +use maj0min6::RnoteFileMaj0Min6; +use maj0min9::RnoteFileMaj0Min9; +use serde::{Deserialize, Serialize}; +use std::io::Read; + +/// Decompress from gzip. +fn decompress_from_gzip(compressed: &[u8]) -> Result, anyhow::Error> { + // Optimization for the gzip format, defined by RFC 1952 + // capacity of the vector defined by the size of the uncompressed data + // given in little endian format, by the last 4 bytes of "compressed" + // + // ISIZE (Input SIZE) + // This contains the size of the original (uncompressed) input data modulo 2^32. + let mut bytes: Vec = { + let mut decompressed_size: [u8; 4] = [0; 4]; + let idx_start = compressed + .len() + .checked_sub(4) + // only happens if the file has less than 4 bytes + .ok_or_else(|| { + anyhow::anyhow!("Not a valid gzip-compressed file") + .context("Failed to get the size of the decompressed data") + })?; + decompressed_size.copy_from_slice(&compressed[idx_start..]); + // u32 -> usize to avoid issues on 32-bit architectures + // also more reasonable since the uncompressed size is given by 4 bytes + Vec::with_capacity(u32::from_le_bytes(decompressed_size) as usize) + }; + + let mut decoder = flate2::read::MultiGzDecoder::new(compressed); + decoder.read_to_end(&mut bytes)?; + Ok(bytes) +} + +/// The rnote file wrapper. +/// +/// Used to extract and match the version up front, before deserializing the data. +#[derive(Debug, Clone, Serialize, Deserialize)] +#[serde(rename = "rnotefile_wrapper")] +struct LegacyRnoteFileWrapper { + #[serde(rename = "version")] + version: semver::Version, + #[serde(rename = "data")] + data: ijson::IValue, +} + +pub type LegacyRnoteFile = RnoteFileMaj0Min9; + +impl FileFormatLoader for LegacyRnoteFile { + fn load_from_bytes(bytes: &[u8]) -> anyhow::Result { + let wrapper = + serde_json::from_slice::(&decompress_from_gzip(bytes)?) + .context("Deserializing RnotefileWrapper from bytes failed")?; + + // Conversions for older file format versions happen here + if semver::VersionReq::parse(">=0.9.0") + .unwrap() + .matches(&wrapper.version) + { + ijson::from_value::(&wrapper.data) + .context("Deserializing RnoteFileMaj0Min9 failed") + } else if semver::VersionReq::parse(">=0.5.10") + .unwrap() + .matches(&wrapper.version) + { + ijson::from_value::(&wrapper.data) + .context("Deserializing RnoteFileMaj0Min6 failed") + .and_then(RnoteFileMaj0Min9::try_from) + .context("Converting RnoteFileMaj0Min6 to newest file version failed") + } else if semver::VersionReq::parse(">=0.5.9") + .unwrap() + .matches(&wrapper.version) + { + ijson::from_value::(&wrapper.data) + .context("Deserializing RnoteFileMaj0Min5Patch9 failed") + .and_then(RnoteFileMaj0Min6::try_from) + .and_then(RnoteFileMaj0Min9::try_from) + .context("Converting RnoteFileMaj0Min5Patch9 to newest file version failed") + } else if semver::VersionReq::parse(">=0.5.0") + .unwrap() + .matches(&wrapper.version) + { + ijson::from_value::(&wrapper.data) + .context("Deserializing RnoteFileMaj0Min5Patch8 failed") + .and_then(RnoteFileMaj0Min5Patch9::try_from) + .and_then(RnoteFileMaj0Min6::try_from) + .and_then(RnoteFileMaj0Min9::try_from) + .context("Converting RnoteFileMaj0Min5Patch8 to newest file version failed") + } else { + Err(anyhow::anyhow!( + "Failed to load rnote file from bytes, unsupported version: '{}'", + wrapper.version + )) + } + } +} diff --git a/crates/rnote-engine/src/fileformats/rnoteformat/maj0min12.rs b/crates/rnote-engine/src/fileformats/rnoteformat/maj0min12.rs new file mode 100644 index 0000000000..77ab9d1630 --- /dev/null +++ b/crates/rnote-engine/src/fileformats/rnoteformat/maj0min12.rs @@ -0,0 +1,63 @@ +use super::{ + compression::CompressionMethod, legacy::maj0min9::RnoteFileMaj0Min9, + serialization::SerializationMethod, +}; +use serde::{Deserialize, Serialize}; + +/// # Rnote File Format Specifications +/// +/// ## Prelude (not included in this struct, u16,u32,... are represented using little endian) +/// * magic number: [u8; 9] = [0x52, 0x4e, 0x4f, 0x54, 0x45, 0xce, 0xa6, 0xce, 0x9b], "RNOTEϕλ" +/// * version: [u64, u64, u64, u16, str, u16, str] (almost one-to-one representation of semver::Version) +/// [major, minor, patch, Prerelease size, Prerelease, BuildMetadata size, Buildmetadata] +/// * header size: u32 +/// +/// ## Header +/// a forward-compatible json-encoded struct +/// * serialization: method used to serialize/deserialize the engine snapshot +/// * compression: method used to compress/decompress the serialized engine snapshot +/// * uncompressed size: size of the uncompressed and serialized engine snapshot +/// * method lock: if set to true, the file can keep using non-standard methods and will not be forced back into using defaults +/// +/// ## Body +/// the body contains the serialized and (potentially) compressed engine snapshot +#[derive(Debug, Clone)] +pub struct RnoteFileMaj0Min12 { + /// The file's head is composed of the prelude plus the header (below). + /// Contains the necessary information to efficiently compress/decompress, serialize/deserialize the rnote file. + pub header: RnoteHeaderMaj0Min12, + /// The serialized and (potentially) compressed engine snapshot. + pub body: Vec, +} + +#[derive(Debug, Clone, Serialize, Deserialize)] +#[serde(rename = "header")] +pub struct RnoteHeaderMaj0Min12 { + /// method used to serialize/deserialize the engine snapshot + #[serde(rename = "serialization")] + pub serialization: SerializationMethod, + /// method used to compress/decompress the serialized engine snapshot + #[serde(rename = "compression")] + pub compression: CompressionMethod, + /// size of the uncompressed and serialized engine snapshot + #[serde(rename = "uncompressed_size")] + pub uc_size: u64, + #[serde(rename = "method_lock")] + pub method_lock: bool, +} + +impl TryFrom for RnoteFileMaj0Min12 { + type Error = anyhow::Error; + /// Inefficient conversion, as the legacy struct stores the ijson EngineSnapshot and not the compressed and serialized bytes, thankfully bypassed in EngineSnapshot::load_from_rnote_bytes. Therefore in general this would only be used by rnote-cli mutate. + fn try_from(value: RnoteFileMaj0Min9) -> Result { + Ok(Self { + header: RnoteHeaderMaj0Min12 { + serialization: SerializationMethod::Json, + compression: CompressionMethod::None, + uc_size: 0, + method_lock: false, + }, + body: serde_json::to_vec(&value.engine_snapshot)?, + }) + } +} diff --git a/crates/rnote-engine/src/fileformats/rnoteformat/mod.rs b/crates/rnote-engine/src/fileformats/rnoteformat/mod.rs index 9826a02bc4..1979128784 100644 --- a/crates/rnote-engine/src/fileformats/rnoteformat/mod.rs +++ b/crates/rnote-engine/src/fileformats/rnoteformat/mod.rs @@ -6,139 +6,123 @@ //! Then [TryFrom] can be implemented to allow conversions and chaining from older to newer versions. // Modules -pub(crate) mod maj0min5patch8; -pub(crate) mod maj0min5patch9; -pub(crate) mod maj0min6; -pub(crate) mod maj0min9; +pub(crate) mod compression; +pub(crate) mod legacy; +pub(crate) mod maj0min12; +pub(crate) mod prelude; +pub(crate) mod serialization; + +// Re-exports +pub use compression::CompressionMethod; +pub use serialization::SerializationMethod; // Imports -use self::maj0min5patch8::RnoteFileMaj0Min5Patch8; -use self::maj0min5patch9::RnoteFileMaj0Min5Patch9; -use self::maj0min6::RnoteFileMaj0Min6; -use self::maj0min9::RnoteFileMaj0Min9; use super::{FileFormatLoader, FileFormatSaver}; -use anyhow::Context; -use serde::{Deserialize, Serialize}; -use std::io::{Read, Write}; - -/// Compress bytes with gzip. -fn compress_to_gzip(to_compress: &[u8]) -> Result, anyhow::Error> { - let mut encoder = flate2::write::GzEncoder::new(Vec::::new(), flate2::Compression::new(5)); - encoder.write_all(to_compress)?; - Ok(encoder.finish()?) -} +use crate::engine::{EngineSnapshot, save::SavePrefs}; +use legacy::LegacyRnoteFile; +use maj0min12::RnoteFileMaj0Min12; +use prelude::{Prelude, PreludeError}; -/// Decompress from gzip. -fn decompress_from_gzip(compressed: &[u8]) -> Result, anyhow::Error> { - // Optimization for the gzip format, defined by RFC 1952 - // capacity of the vector defined by the size of the uncompressed data - // given in little endian format, by the last 4 bytes of "compressed" - // - // ISIZE (Input SIZE) - // This contains the size of the original (uncompressed) input data modulo 2^32. - let mut bytes: Vec = { - let mut decompressed_size: [u8; 4] = [0; 4]; - let idx_start = compressed - .len() - .checked_sub(4) - // only happens if the file has less than 4 bytes - .ok_or_else(|| { - anyhow::anyhow!("Invalid file") - .context("Failed to get the size of the decompressed data") - })?; - decompressed_size.copy_from_slice(&compressed[idx_start..]); - // u32 -> usize to avoid issues on 32-bit architectures - // also more reasonable since the uncompressed size is given by 4 bytes - Vec::with_capacity(u32::from_le_bytes(decompressed_size) as usize) - }; - - let mut decoder = flate2::read::MultiGzDecoder::new(compressed); - decoder.read_to_end(&mut bytes)?; - Ok(bytes) -} +pub type RnoteFile = maj0min12::RnoteFileMaj0Min12; +pub type RnoteHeader = maj0min12::RnoteHeaderMaj0Min12; -/// The rnote file wrapper. -/// -/// Used to extract and match the version up front, before deserializing the data. -#[derive(Debug, Clone, Serialize, Deserialize)] -#[serde(rename = "rnotefile_wrapper")] -struct RnotefileWrapper { - #[serde(rename = "version")] - version: semver::Version, - #[serde(rename = "data")] - data: ijson::IValue, +impl RnoteFileMaj0Min12 { + pub const SEMVER: &'static str = crate::utils::crate_version(); } -/// The Rnote file in the newest format version. -/// -/// This struct exists to allow for upgrading older versions before loading the file in. -pub type RnoteFile = RnoteFileMaj0Min9; +impl FileFormatSaver for RnoteFile { + fn save_as_bytes(&self, _file_name: &str) -> anyhow::Result> { + let version = semver::Version::parse(Self::SEMVER)?; + let header = serde_json::to_vec(&ijson::to_value(&self.header)?)?; + let prelude = Prelude::new(version, header.len()).try_to_bytes()?; -impl RnoteFile { - pub const SEMVER: &'static str = crate::utils::crate_version(); + // From running simple tests, using concat seems to be the best choice, it's much faster than either append extend. + Ok([prelude.as_slice(), header.as_slice(), self.body.as_slice()].concat()) + } } impl FileFormatLoader for RnoteFile { - fn load_from_bytes(bytes: &[u8]) -> anyhow::Result { - let wrapper = serde_json::from_slice::( - &decompress_from_gzip(bytes).context("decompressing bytes failed.")?, - ) - .context("deserializing RnotefileWrapper from bytes failed.")?; - - // Conversions for older file format versions happen here - if semver::VersionReq::parse(">=0.9.0") - .unwrap() - .matches(&wrapper.version) - { - ijson::from_value::(&wrapper.data) - .context("deserializing RnoteFileMaj0Min9 failed.") - } else if semver::VersionReq::parse(">=0.5.10") - .unwrap() - .matches(&wrapper.version) - { - ijson::from_value::(&wrapper.data) - .context("deserializing RnoteFileMaj0Min6 failed.") - .and_then(RnoteFileMaj0Min9::try_from) - .context("converting RnoteFileMaj0Min6 to newest file version failed.") - } else if semver::VersionReq::parse(">=0.5.9") - .unwrap() - .matches(&wrapper.version) - { - ijson::from_value::(&wrapper.data) - .context("deserializing RnoteFileMaj0Min5Patch9 failed.") - .and_then(RnoteFileMaj0Min6::try_from) - .and_then(RnoteFileMaj0Min9::try_from) - .context("converting RnoteFileMaj0Min5Patch9 to newest file version failed.") - } else if semver::VersionReq::parse(">=0.5.0") + fn load_from_bytes(bytes: &[u8]) -> anyhow::Result + where + Self: Sized, + { + match Prelude::try_from_bytes(bytes) { + Ok((prelude, mut cursor)) => { + let header_slice = + bytes + .get(cursor..cursor + prelude.header_size) + .ok_or_else(|| { + anyhow::anyhow!("Failed to get RnoteHeader, insufficient bytes") + })?; + cursor += prelude.header_size; + + let body_slice = bytes + .get(cursor..) + .ok_or_else(|| anyhow::anyhow!("Failed to get body, insufficient bytes"))?; + + Ok(Self { + header: RnoteHeader::load_from_slice(header_slice, &prelude.version)?, + body: body_slice.to_vec(), + }) + } + Err(error) => match error.downcast_ref::() { + Some(PreludeError::LegacyRnoteFile) => { + RnoteFile::try_from(LegacyRnoteFile::load_from_bytes(bytes)?) + } + None => Err(error), + }, + } + } +} + +impl RnoteHeader { + fn load_from_slice(slice: &[u8], version: &semver::Version) -> anyhow::Result { + if semver::VersionReq::parse(">=0.11.0") .unwrap() - .matches(&wrapper.version) + .matches(version) { - ijson::from_value::(&wrapper.data) - .context("deserializing RnoteFileMaj0Min5Patch8 failed") - .and_then(RnoteFileMaj0Min5Patch9::try_from) - .and_then(RnoteFileMaj0Min6::try_from) - .and_then(RnoteFileMaj0Min9::try_from) - .context("converting RnoteFileMaj0Min5Patch8 to newest file version failed.") + Ok(ijson::from_value(&serde_json::from_slice(slice)?)?) } else { - Err(anyhow::anyhow!( - "failed to load rnote file from bytes, unsupported version: {}.", - wrapper.version - )) + Err(anyhow::anyhow!("Unsupported version: '{}'", version)) } } } -impl FileFormatSaver for RnoteFile { - fn save_as_bytes(&self, _file_name: &str) -> anyhow::Result> { - let wrapper = RnotefileWrapper { - version: semver::Version::parse(Self::SEMVER).unwrap(), - data: ijson::to_value(self).context("converting RnoteFile to JSON value failed.")?, - }; - let compressed = compress_to_gzip( - &serde_json::to_vec(&wrapper).context("Serializing RnoteFileWrapper failed.")?, - ) - .context("compressing bytes failed.")?; - - Ok(compressed) +impl TryFrom for EngineSnapshot { + type Error = anyhow::Error; + + fn try_from(value: RnoteFile) -> Result { + let uc_size = usize::try_from(value.header.uc_size).unwrap_or(usize::MAX); + let uc_body = value.header.compression.decompress(uc_size, value.body)?; + let mut engine_snapshot = value.header.serialization.deserialize(&uc_body)?; + engine_snapshot.save_prefs = SavePrefs::from(value.header); + + Ok(engine_snapshot) + } +} + +impl TryFrom<&EngineSnapshot> for RnoteFile { + type Error = anyhow::Error; + + fn try_from(value: &EngineSnapshot) -> Result { + let save_prefs = value.save_prefs; + + let compression = save_prefs.compression; + let serialization = save_prefs.serialization; + + let uc_data = serialization.serialize(value)?; + let uc_size = uc_data.len() as u64; + let data = compression.compress(uc_data)?; + let method_lock = save_prefs.method_lock; + + Ok(Self { + header: RnoteHeader { + compression, + serialization, + uc_size, + method_lock, + }, + body: data, + }) } } diff --git a/crates/rnote-engine/src/fileformats/rnoteformat/prelude.rs b/crates/rnote-engine/src/fileformats/rnoteformat/prelude.rs new file mode 100644 index 0000000000..ecd9f33988 --- /dev/null +++ b/crates/rnote-engine/src/fileformats/rnoteformat/prelude.rs @@ -0,0 +1,163 @@ +// Imports +use anyhow::{Context, anyhow, bail}; +use thiserror::Error; + +/// # Prelude +/// * magic number: [u8; 9] = [0x52, 0x4e, 0x4f, 0x54, 0x45, 0xce, 0xa6, 0xce, 0x9b], "RNOTEϕλ" +/// * version: [u64, u64, u64, u16, str, u16, str] (almost one-to-one representation of semver::Version) +/// [major, minor, patch, Prerelease size, Prerelease, BuildMetadata size, Buildmetadata] +/// * header size: u32 +#[derive(Debug, Clone)] +pub struct Prelude { + pub version: semver::Version, + pub header_size: usize, +} + +impl Prelude { + /// The magic number used to identify rnote files, do not modify. + pub const MAGIC_NUMBER: [u8; 9] = [0x52, 0x4e, 0x4f, 0x54, 0x45, 0xce, 0xa6, 0xce, 0x9b]; + + /// Creates a new prelude. + pub fn new(version: semver::Version, header_size: usize) -> Self { + Self { + version, + header_size, + } + } + + /// Returns the byte representation of the prelude + pub fn try_to_bytes(self) -> anyhow::Result> { + let pre_release: &str = self.version.pre.as_str(); + let build_metadata: &str = self.version.build.as_str(); + + Ok([ + &Self::MAGIC_NUMBER[..], + &self.version.major.to_le_bytes(), + &self.version.minor.to_le_bytes(), + &self.version.patch.to_le_bytes(), + &u16::try_from(pre_release.len()) + .context("Prerelease exceeds maximum size (u16::MAX)")? + .to_le_bytes(), + pre_release.as_bytes(), + &u16::try_from(build_metadata.len()) + .context("BuildMetadata exceeds maximum size (u16::MAX)")? + .to_le_bytes(), + build_metadata.as_bytes(), + &u32::try_from(self.header_size) + .context("Serialized RnoteHeader exceeds maximum size (u32::MAX)")? + .to_le_bytes(), + ] + .concat()) + } + + /// Returns the prelude alongside the cursor which is the index at which it left off. + pub fn try_from_bytes(bytes: &[u8]) -> anyhow::Result<(Self, usize)> { + let mut cursor: usize = 0; + + let magic_number = bytes + .get(cursor..9) + .ok_or_else(|| anyhow!("Failed to get magic number"))?; + cursor += 9; + + if magic_number != Self::MAGIC_NUMBER { + // Checks for legacy files using the gzip magic number. + if magic_number[..2] == [0x1f, 0x8b] { + return Err(anyhow::Error::new(PreludeError::LegacyRnoteFile)); + } else { + bail!("Unrecognized file format"); + } + } + + let mut major: [u8; 8] = [0; 8]; + major.copy_from_slice( + bytes + .get(cursor..cursor + 8) + .ok_or_else(|| anyhow!("Failed to get version.major, insufficient bytes"))?, + ); + cursor += 8; + let major = u64::from_le_bytes(major); + + let mut minor: [u8; 8] = [0; 8]; + minor.copy_from_slice( + bytes + .get(cursor..cursor + 8) + .ok_or_else(|| anyhow!("Failed to get version.minor, insufficient bytes"))?, + ); + cursor += 8; + let minor = u64::from_le_bytes(minor); + + let mut patch: [u8; 8] = [0; 8]; + patch.copy_from_slice( + bytes + .get(cursor..cursor + 8) + .ok_or_else(|| anyhow!("Failed to get version.patch, insufficient bytes"))?, + ); + cursor += 8; + let patch = u64::from_le_bytes(patch); + + let mut pre_release_size: [u8; 2] = [0; 2]; + pre_release_size.copy_from_slice( + bytes + .get(cursor..cursor + 2) + .ok_or_else(|| anyhow!("Failed to get size of version.pre, insufficient bytes"))?, + ); + cursor += 2; + let pre_release = match usize::from(u16::from_le_bytes(pre_release_size)) { + 0 => semver::Prerelease::EMPTY, + len => { + let text = + core::str::from_utf8(bytes.get(cursor..cursor + len).ok_or_else(|| { + anyhow!("Failed to get version.pre, insufficient bytes") + })?)?; + cursor += len; + semver::Prerelease::new(text)? + } + }; + + let mut build_metadata_size: [u8; 2] = [0; 2]; + build_metadata_size.copy_from_slice( + bytes.get(cursor..cursor + 2).ok_or_else(|| { + anyhow!("Failed to get size of version.build, insufficient bytes") + })?, + ); + cursor += 2; + let build_metadata = match usize::from(u16::from_le_bytes(build_metadata_size)) { + 0 => semver::BuildMetadata::EMPTY, + len => { + let text = + core::str::from_utf8(bytes.get(cursor..cursor + len).ok_or_else(|| { + anyhow!("Failed to get version.build, insufficient bytes") + })?)?; + cursor += len; + semver::BuildMetadata::new(text)? + } + }; + + let version = semver::Version { + major, + minor, + patch, + pre: pre_release, + build: build_metadata, + }; + + let mut header_size: [u8; 4] = [0; 4]; + header_size.copy_from_slice( + bytes + .get(cursor..cursor + 4) + .ok_or_else(|| anyhow!("Failed to get header size, insufficient bytes"))?, + ); + cursor += 4; + let header_size = usize::try_from(u32::from_le_bytes(header_size)) + .context("Serialized RnoteHeader exceeds maximum size (usize::MAX)")?; + + Ok((Self::new(version, header_size), cursor)) + } +} + +/// Custom error used to handle legacy rnote files. +#[derive(Debug, Error)] +pub enum PreludeError { + #[error("")] + LegacyRnoteFile, +} diff --git a/crates/rnote-engine/src/fileformats/rnoteformat/serialization.rs b/crates/rnote-engine/src/fileformats/rnoteformat/serialization.rs new file mode 100644 index 0000000000..7032725926 --- /dev/null +++ b/crates/rnote-engine/src/fileformats/rnoteformat/serialization.rs @@ -0,0 +1,51 @@ +// Imports +use crate::engine::EngineSnapshot; +use serde::{Deserialize, Serialize}; +use std::str::FromStr; + +/// Serialization methods that can be applied to a snapshot of the engine +#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)] +pub enum SerializationMethod { + #[serde(rename = "bitcode")] + Bitcode, + #[serde(rename = "json")] + Json, +} + +impl Default for SerializationMethod { + fn default() -> Self { + Self::Json + } +} + +impl SerializationMethod { + pub const VALID_STR_ARRAY: [&'static str; 5] = ["Bitcode", "bitcode", "Json", "JSON", "json"]; + + /// Keeping this function to mimic the behaviour of CompressionMethod and forward-comptability + pub fn is_similar_to(&self, other: &Self) -> bool { + std::mem::discriminant(self) == std::mem::discriminant(other) + } + pub fn serialize(&self, engine_snapshot: &EngineSnapshot) -> anyhow::Result> { + match self { + Self::Bitcode => Ok(bitcode::serialize(engine_snapshot)?), + Self::Json => Ok(serde_json::to_vec(&ijson::to_value(engine_snapshot)?)?), + } + } + pub fn deserialize(&self, data: &[u8]) -> anyhow::Result { + match self { + Self::Bitcode => Ok(bitcode::deserialize(data)?), + Self::Json => Ok(ijson::from_value(&serde_json::from_slice(data)?)?), + } + } +} + +impl FromStr for SerializationMethod { + type Err = &'static str; + fn from_str(s: &str) -> Result { + match s { + "Bitcode" | "bitcode" => Ok(Self::Bitcode), + "Json" | "JSON" | "json" => Ok(Self::Json), + _ => Err("Unknown serialization method"), + } + } +} diff --git a/crates/rnote-engine/src/meson.build b/crates/rnote-engine/src/meson.build index 1f20fa5f30..036719dc02 100644 --- a/crates/rnote-engine/src/meson.build +++ b/crates/rnote-engine/src/meson.build @@ -11,16 +11,22 @@ rnote_engine_sources = files( 'engine/import.rs', 'engine/mod.rs', 'engine/rendering.rs', + 'engine/save.rs', 'engine/snapshot.rs', 'engine/strokecontent.rs', 'engine/visual_debug.rs', 'ext.rs', 'fileformats/mod.rs', - 'fileformats/rnoteformat/maj0min5patch8.rs', - 'fileformats/rnoteformat/maj0min5patch9.rs', - 'fileformats/rnoteformat/maj0min6.rs', - 'fileformats/rnoteformat/maj0min9.rs', + 'fileformats/rnoteformat/legacy/mod.rs', + 'fileformats/rnoteformat/legacy/maj0min5patch8.rs', + 'fileformats/rnoteformat/legacy/maj0min5patch9.rs', + 'fileformats/rnoteformat/legacy/maj0min6.rs', + 'fileformats/rnoteformat/legacy/maj0min9.rs', 'fileformats/rnoteformat/mod.rs', + 'fileformats/rnoteformat/compression.rs', + 'fileformats/rnoteformat/maj0min12.rs', + 'fileformats/rnoteformat/prelude.rs', + 'fileformats/rnoteformat/serialization.rs', 'fileformats/xoppformat.rs', 'lib.rs', 'pens/brush.rs', diff --git a/crates/rnote-engine/src/utils.rs b/crates/rnote-engine/src/utils.rs index 1f3fa341d2..4ffc5d4d10 100644 --- a/crates/rnote-engine/src/utils.rs +++ b/crates/rnote-engine/src/utils.rs @@ -1,5 +1,7 @@ // Imports use crate::fileformats::xoppformat; +use anyhow::Context; +use futures::{AsyncReadExt, AsyncWriteExt}; use geo::line_string; use p2d::bounding_volume::Aabb; use rnote_compose::Color; @@ -96,3 +98,91 @@ pub mod glib_bytes_base64 { rnote_compose::serialize::sliceu8_base64::deserialize(d).map(glib::Bytes::from_owned) } } + +pub async fn atomic_save_to_file(filepath: Q, bytes: &[u8]) -> anyhow::Result<()> +where + Q: AsRef, +{ + let filepath = filepath.as_ref().to_owned(); + + // checks that the extension is not already 'tmp' + if filepath + .extension() + .ok_or_else(|| anyhow::anyhow!("Specified filepath does not have an extension"))? + .to_str() + .ok_or_else(|| anyhow::anyhow!("The extension of the specified filepath is invalid"))? + == "tmp" + { + Err(anyhow::anyhow!("The extension of the file cannot be 'tmp'"))?; + } + + let tmp_filepath = filepath.with_extension("tmp"); + + let file_write_operation = async { + let mut write_file = async_fs::OpenOptions::new() + .create(true) + .write(true) + .truncate(true) + .open(&tmp_filepath) + .await + .with_context(|| { + format!( + "Failed to create/open/truncate tmp file with path '{}'", + tmp_filepath.display() + ) + })?; + write_file.write_all(bytes).await.with_context(|| { + format!( + "Failed to write to tmp file with path '{}'", + tmp_filepath.display() + ) + })?; + write_file.sync_all().await.with_context(|| { + format!( + "Failed to sync tmp file with path '{}'", + tmp_filepath.display() + ) + })?; + + Ok::<(), anyhow::Error>(()) + }; + file_write_operation.await?; + + let file_check_operation = async { + let internal_checksum = crc32fast::hash(bytes); + + let mut read_file = async_fs::OpenOptions::new() + .read(true) + .open(&tmp_filepath) + .await + .with_context(|| { + format!( + "Failed to open/read tmp file with path '{}'", + &tmp_filepath.display() + ) + })?; + let mut data: Vec = Vec::with_capacity(bytes.len()); + read_file.read_to_end(&mut data).await?; + let external_checksum = crc32fast::hash(&data); + + if internal_checksum != external_checksum { + return Err(anyhow::anyhow!( + "Mismatch between the internal and external checksums, temporary file most likely corrupted" + )); + } + + Ok::<(), anyhow::Error>(()) + }; + file_check_operation.await?; + + let file_swap_operation = async { + async_fs::rename(&tmp_filepath, &filepath) + .await + .context("Failed to rename the temporary file into the original one")?; + + Ok::<(), anyhow::Error>(()) + }; + file_swap_operation.await?; + + Ok(()) +} diff --git a/crates/rnote-ui/data/ui/settingspanel.ui b/crates/rnote-ui/data/ui/settingspanel.ui index e1c205a8e7..73e90901f6 100644 --- a/crates/rnote-ui/data/ui/settingspanel.ui +++ b/crates/rnote-ui/data/ui/settingspanel.ui @@ -383,11 +383,11 @@ gets disabled. - + Invert Color Brightness Invert the brightness of all background pattern colors - + center Invert @@ -396,6 +396,28 @@ gets disabled. + + + + File + + + Compression Level + + + + Very High + High + Medium + Low + Very Low + + + + + + + diff --git a/crates/rnote-ui/src/appwindow/actions.rs b/crates/rnote-ui/src/appwindow/actions.rs index 26ccca5690..35f35c2867 100644 --- a/crates/rnote-ui/src/appwindow/actions.rs +++ b/crates/rnote-ui/src/appwindow/actions.rs @@ -844,6 +844,11 @@ impl RnAppWindow { appwindow.overlays().progressbar_abort(); } else { appwindow.overlays().progressbar_finish(); + let save_prefs = canvas.engine_ref().save_prefs; + appwindow + .sidebar() + .settings_panel() + .refresh_file_compression_level_row(&save_prefs); // No success toast on saving without dialog, success is already indicated in the header title } } else { diff --git a/crates/rnote-ui/src/appwindow/imp.rs b/crates/rnote-ui/src/appwindow/imp.rs index 581a26986e..8ea1f85be5 100644 --- a/crates/rnote-ui/src/appwindow/imp.rs +++ b/crates/rnote-ui/src/appwindow/imp.rs @@ -317,19 +317,29 @@ impl RnAppWindow { trace!( "there are unsaved changes on the tab {:?} with a file on disk, saving",i ); - glib::spawn_future_local(clone!(#[weak] canvas, #[weak] appwindow ,async move { - if let Err(e) = canvas.save_document_to_file(&output_file).await { - error!("Saving document failed, Err: `{e:?}`"); - canvas.set_output_file(None); - appwindow - .overlays() - .dispatch_toast_error(&gettext("Saving document failed")); - }; + glib::spawn_future_local(clone!(#[weak] canvas, #[weak] appwindow, async move { + match canvas.save_document_to_file(&output_file).await { + Ok(sucess) => { + if sucess { + let save_prefs = canvas.engine_ref().save_prefs; + appwindow + .sidebar() + .settings_panel() + .refresh_file_compression_level_row(&save_prefs); + } + }, + Err(e) => { + error!("Saving document failed, Err: `{e:?}`"); + canvas.set_output_file(None); + appwindow + .overlays() + .dispatch_toast_error(&gettext("Saving document failed")); + } + } })); } } } - glib::ControlFlow::Continue }), ), diff --git a/crates/rnote-ui/src/canvas/imexport.rs b/crates/rnote-ui/src/canvas/imexport.rs index fa4e7e30ce..67e430f316 100644 --- a/crates/rnote-ui/src/canvas/imexport.rs +++ b/crates/rnote-ui/src/canvas/imexport.rs @@ -1,7 +1,5 @@ // Imports use super::RnCanvas; -use anyhow::Context; -use futures::AsyncWriteExt; use futures::channel::oneshot; use gtk4::{gio, prelude::*}; use rnote_compose::ext::Vector2Ext; @@ -212,7 +210,7 @@ impl RnCanvas { self.set_save_in_progress(true); debug!("Saving file is now in progress"); - let file_path = file + let filepath = file .path() .ok_or_else(|| anyhow::anyhow!("Could not get a path for file: `{file:?}`."))?; let basename = file @@ -221,43 +219,11 @@ impl RnCanvas { let rnote_bytes_receiver = self .engine_ref() .save_as_rnote_bytes(basename.to_string_lossy().to_string()); - let mut skip_set_output_file = false; - if let Some(output_file_path) = self.output_file().and_then(|f| f.path()) { - if crate::utils::paths_abs_eq(output_file_path, &file_path).unwrap_or(false) { - skip_set_output_file = true; - } - } self.dismiss_output_file_modified_toast(); - let file_write_operation = async move { - let bytes = rnote_bytes_receiver.await??; - self.set_output_file_expect_write(true); - let mut write_file = async_fs::OpenOptions::new() - .create(true) - .truncate(true) - .write(true) - .open(&file_path) - .await - .context(format!( - "Failed to create/open/truncate file for path '{}'", - file_path.display() - ))?; - if !skip_set_output_file { - // this installs the file watcher. - self.set_output_file(Some(file.to_owned())); - } - write_file.write_all(&bytes).await.context(format!( - "Failed to write bytes to file with path '{}'", - file_path.display() - ))?; - write_file.sync_all().await.context(format!( - "Failed to sync file after writing with path '{}'", - file_path.display() - ))?; - Ok(()) - }; - - if let Err(e) = file_write_operation.await { + let bytes = rnote_bytes_receiver.await??; + self.set_output_file_expect_write(true); + if let Err(e) = rnote_engine::utils::atomic_save_to_file(&filepath, &bytes).await { self.set_save_in_progress(false); // If the file operations failed in any way, we make sure to clear the expect_write flag // because we can't know for sure if the output-file watcher will be able to. @@ -265,7 +231,10 @@ impl RnCanvas { return Err(e); } + // required by atomic file saving + self.set_output_file(Some(gio::File::for_path(&filepath))); debug!("Saving file has finished successfully"); + self.engine_mut().save_prefs.finished_saving(); self.set_unsaved_changes(false); self.set_save_in_progress(false); diff --git a/crates/rnote-ui/src/dialogs/export.rs b/crates/rnote-ui/src/dialogs/export.rs index 8290d9ff81..c2507f4667 100644 --- a/crates/rnote-ui/src/dialogs/export.rs +++ b/crates/rnote-ui/src/dialogs/export.rs @@ -68,6 +68,11 @@ pub(crate) async fn dialog_save_doc_as(appwindow: &RnAppWindow, canvas: &RnCanva crate::overlays::TEXT_TOAST_TIMEOUT_DEFAULT, ); appwindow.overlays().progressbar_finish(); + let save_prefs = canvas.engine_ref().save_prefs; + appwindow + .sidebar() + .settings_panel() + .refresh_file_compression_level_row(&save_prefs); } Ok(false) => { // Saving was already in progress diff --git a/crates/rnote-ui/src/settingspanel/mod.rs b/crates/rnote-ui/src/settingspanel/mod.rs index 6013caaa7c..0eb806a913 100644 --- a/crates/rnote-ui/src/settingspanel/mod.rs +++ b/crates/rnote-ui/src/settingspanel/mod.rs @@ -5,6 +5,8 @@ mod penshortcutrow; // Re-exports pub(crate) use penshortcutrow::RnPenShortcutRow; use rnote_compose::ext::Vector2Ext; +use rnote_engine::engine::SavePrefs; +use rnote_engine::fileformats::rnoteformat::CompressionMethod; // Imports use crate::{RnAppWindow, RnCanvasWrapper, RnIconPicker, RnUnitEntry}; @@ -19,6 +21,7 @@ use rnote_compose::penevent::ShortcutKey; use rnote_engine::document::Layout; use rnote_engine::document::background::PatternStyle; use rnote_engine::document::format::{self, Format, PredefinedFormat}; +use rnote_engine::engine::save::CompressionLevel; use rnote_engine::ext::GdkRGBAExt; use std::cell::RefCell; @@ -94,7 +97,9 @@ mod imp { #[template_child] pub(crate) doc_background_pattern_height_unitentry: TemplateChild, #[template_child] - pub(crate) background_pattern_invert_color_button: TemplateChild