diff --git a/Cargo.lock b/Cargo.lock index bf3ac0b..d24a92d 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2,6 +2,21 @@ # It is not intended for manual editing. version = 3 +[[package]] +name = "addr2line" +version = "0.19.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a76fd60b23679b7d19bd066031410fb7e458ccc5e958eb5c325888ce4baedc97" +dependencies = [ + "gimli", +] + +[[package]] +name = "adler" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f26201604c87b1e01bd3d98f8d5d9a8fcbb815e8cedb41ffccbeb4bf593a35fe" + [[package]] name = "aho-corasick" version = "0.7.20" @@ -11,11 +26,38 @@ dependencies = [ "memchr", ] +[[package]] +name = "android_system_properties" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "819e7219dbd41043ac279b19830f2efc897156490d7fd6ea916720117ee66311" +dependencies = [ + "libc", +] + [[package]] name = "anyhow" version = "1.0.69" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "224afbd727c3d6e4b90103ece64b8d1b67fbb1973b1046c2281eed3f3803f800" +dependencies = [ + "backtrace", +] + +[[package]] +name = "approx" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cab112f0a86d568ea0e627cc1d6be74a1e9cd55214684db5561995f6dad897c6" +dependencies = [ + "num-traits", +] + +[[package]] +name = "arc-swap" +version = "1.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bddcadddf5e9015d310179a59bb28c4d4b9920ad0f11e8e14dbadf654890c9a6" [[package]] name = "ashpd" @@ -31,7 +73,7 @@ dependencies = [ "gdk4-x11", "gtk4", "once_cell", - "rand", + "rand 0.8.5", "serde", "serde_repr", "url", @@ -40,13 +82,12 @@ dependencies = [ [[package]] name = "async-broadcast" -version = "0.5.0" +version = "0.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1b19760fa2b7301cf235360ffd6d3558b1ed4249edd16d6cca8d690cee265b95" +checksum = "7c48ccdbf6ca6b121e0f586cbc0e73ae440e56c67c30fa0873b4e110d9c26d2b" dependencies = [ "event-listener", "futures-core", - "parking_lot", ] [[package]] @@ -111,12 +152,11 @@ dependencies = [ [[package]] name = "async-lock" -version = "2.6.0" +version = "2.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c8101efe8695a6c17e02911402145357e718ac92d3ff88ae8419e84b1707b685" +checksum = "fa24f727524730b077666307f2734b4a1a1c57acb79193127dcc8914d5242dd7" dependencies = [ "event-listener", - "futures-lite", ] [[package]] @@ -164,9 +204,9 @@ checksum = "7a40729d2133846d9ed0ea60a8b9541bccddab49cd30f0715a1da672fe9a2524" [[package]] name = "async-trait" -version = "0.1.64" +version = "0.1.65" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1cd7fce9ba8c3c042128ce72d8b2ddbf3a05747efb67ea0313c635e10bda47a2" +checksum = "095183a3539c7c7649b2beb87c2d3f0591f3a7fed07761cc546d244e27e0238c" dependencies = [ "proc-macro2", "quote", @@ -185,7 +225,7 @@ version = "0.2.14" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d9b39be18770d11421cdb1b9947a45dd3f37e93092cbf377614828a319d5fee8" dependencies = [ - "hermit-abi", + "hermit-abi 0.1.19", "libc", "winapi", ] @@ -196,6 +236,27 @@ version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa" +[[package]] +name = "backtrace" +version = "0.3.67" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "233d376d6d185f2a3093e58f283f60f880315b6c60075b01f36b3b85154564ca" +dependencies = [ + "addr2line", + "cc", + "cfg-if", + "libc", + "miniz_oxide", + "object", + "rustc-demangle", +] + +[[package]] +name = "bit_field" +version = "0.10.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dc827186963e592360843fb5ba4b973e145841266c1357f7180c43526f2e5b61" + [[package]] name = "bitflags" version = "1.3.2" @@ -243,6 +304,12 @@ version = "0.6.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2c676a478f63e9fa2dd5368a42f28bba0d6c560b775f38583c8bbaa7fcd67c9c" +[[package]] +name = "bytemuck" +version = "1.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "17febce684fd15d89027105661fec94afb475cb995fbc59d2865198446ba2eea" + [[package]] name = "byteorder" version = "1.4.3" @@ -257,7 +324,7 @@ checksum = "a8af54f5d48af1226928adc1f57edd22f5df1349e7da1fc96ae15cf43db0e871" dependencies = [ "bitflags", "cairo-sys-rs", - "glib 0.17.1", + "glib 0.17.2", "libc", "once_cell", "thiserror", @@ -269,16 +336,16 @@ version = "0.17.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f55382a01d30e5e53f185eee269124f5e21ab526595b872751278dfbb463594e" dependencies = [ - "glib-sys 0.17.0", + "glib-sys 0.17.2", "libc", "system-deps", ] [[package]] name = "camino" -version = "1.1.2" +version = "1.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c77df041dc383319cc661b428b6961a005db4d6808d5e12536931b1ca9556055" +checksum = "6031a462f977dd38968b6f23378356512feeace69cef817e1a4475108093cec3" dependencies = [ "serde", ] @@ -300,11 +367,17 @@ checksum = "4acbb09d9ee8e23699b9634375c72795d095bf268439da88562cf9b501f181fa" dependencies = [ "camino", "cargo-platform", - "semver 1.0.16", + "semver", "serde", "serde_json", ] +[[package]] +name = "cast" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "37b2a672a2cb129a2e41c10b1224bb368f9f37a2b16b612598138befd7b37eb5" + [[package]] name = "cc" version = "1.0.79" @@ -326,6 +399,83 @@ version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" +[[package]] +name = "chrono" +version = "0.4.23" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "16b0a3d9ed01224b22057780a37bb8c5dbfe1be8ba48678e7bf57ec4b385411f" +dependencies = [ + "iana-time-zone", + "js-sys", + "num-integer", + "num-traits", + "time", + "wasm-bindgen", + "winapi", +] + +[[package]] +name = "clap" +version = "4.1.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c3d7ae14b20b94cb02149ed21a86c423859cbe18dc7ed69845cace50e52b40a5" +dependencies = [ + "bitflags", + "clap_derive", + "clap_lex", + "is-terminal", + "once_cell", + "strsim", + "termcolor", +] + +[[package]] +name = "clap_complete" +version = "4.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "501ff0a401473ea1d4c3b125ff95506b62c5bc5768d818634195fbb7c4ad5ff4" +dependencies = [ + "clap", +] + +[[package]] +name = "clap_derive" +version = "4.1.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "44bec8e5c9d09e439c4335b1af0abaab56dcf3b94999a936e1bb47b9134288f0" +dependencies = [ + "heck", + "proc-macro-error", + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "clap_lex" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "350b9cf31731f9957399229e9b2adc51eeabdfbe9d71d9a0552275fd12710d09" +dependencies = [ + "os_str_bytes", +] + +[[package]] +name = "codespan-reporting" +version = "0.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3538270d33cc669650c4b093848450d380def10c331d38c768e34cac80576e6e" +dependencies = [ + "termcolor", + "unicode-width", +] + +[[package]] +name = "color_quant" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3d7b894f5411737b7867f4827955924d7c254fc9f4d91a6aad6b097804b1018b" + [[package]] name = "concurrent-queue" version = "2.1.0" @@ -335,6 +485,18 @@ dependencies = [ "crossbeam-utils", ] +[[package]] +name = "convert_case" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6245d59a3e82a7fc217c5828a6692dbc6dfb63a0c8c90495621f7b9d79704a0e" + +[[package]] +name = "core-foundation-sys" +version = "0.8.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5827cebf4670468b8772dd191856768aedcb1b0278a04f989f7766351917b9dc" + [[package]] name = "cpufeatures" version = "0.2.5" @@ -345,14 +507,63 @@ dependencies = [ ] [[package]] -name = "crossbeam-utils" -version = "0.8.14" +name = "crc32fast" +version = "1.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4fb766fa798726286dbbb842f174001dab8abc7b627a1dd86e0b7222a95d929f" +checksum = "b540bd8bc810d3885c6ea91e2018302f68baba2129ab3e88f32389ee9370880d" dependencies = [ "cfg-if", ] +[[package]] +name = "crossbeam-channel" +version = "0.5.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cf2b3e8478797446514c91ef04bafcb59faba183e621ad488df88983cc14128c" +dependencies = [ + "cfg-if", + "crossbeam-utils", +] + +[[package]] +name = "crossbeam-deque" +version = "0.8.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ce6fd6f855243022dcecf8702fef0c297d4338e226845fe067f6341ad9fa0cef" +dependencies = [ + "cfg-if", + "crossbeam-epoch", + "crossbeam-utils", +] + +[[package]] +name = "crossbeam-epoch" +version = "0.9.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "46bd5f3f85273295a9d14aedfb86f6aadbff6d8f5295c4a9edb08e819dcf5695" +dependencies = [ + "autocfg", + "cfg-if", + "crossbeam-utils", + "memoffset 0.8.0", + "scopeguard", +] + +[[package]] +name = "crossbeam-utils" +version = "0.8.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3c063cd8cc95f5c377ed0d4b49a4b21f632396ff690e8470c29b3359b346984b" +dependencies = [ + "cfg-if", +] + +[[package]] +name = "crunchy" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7a81dae078cea95a014a339291cec439d2f232ebe854a9d672b796c6afafa9b7" + [[package]] name = "crypto-common" version = "0.1.6" @@ -363,6 +574,33 @@ dependencies = [ "typenum", ] +[[package]] +name = "cssparser" +version = "0.29.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f93d03419cb5950ccfd3daf3ff1c7a36ace64609a1a8746d493df1ca0afde0fa" +dependencies = [ + "cssparser-macros", + "dtoa-short", + "itoa", + "matches", + "phf 0.10.1", + "proc-macro2", + "quote", + "smallvec", + "syn", +] + +[[package]] +name = "cssparser-macros" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dfae75de57f2b2e85e8768c3ea840fd159c8f33e2b6522c7835b7abac81be16e" +dependencies = [ + "quote", + "syn", +] + [[package]] name = "ctor" version = "0.1.26" @@ -373,6 +611,56 @@ dependencies = [ "syn", ] +[[package]] +name = "cxx" +version = "1.0.91" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "86d3488e7665a7a483b57e25bdd90d0aeb2bc7608c8d0346acf2ad3f1caf1d62" +dependencies = [ + "cc", + "cxxbridge-flags", + "cxxbridge-macro", + "link-cplusplus", +] + +[[package]] +name = "cxx-build" +version = "1.0.91" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "48fcaf066a053a41a81dfb14d57d99738b767febb8b735c3016e469fac5da690" +dependencies = [ + "cc", + "codespan-reporting", + "once_cell", + "proc-macro2", + "quote", + "scratch", + "syn", +] + +[[package]] +name = "cxxbridge-flags" +version = "1.0.91" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a2ef98b8b717a829ca5603af80e1f9e2e48013ab227b68ef37872ef84ee479bf" + +[[package]] +name = "cxxbridge-macro" +version = "1.0.91" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "086c685979a698443656e5cf7856c95c642295a38599f12fb1ff76fb28d19892" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "data-url" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8d7439c3735f405729d52c3fbbe4de140eaf938a1fe47d227c27f8254d4302a5" + [[package]] name = "derivative" version = "2.2.0" @@ -384,6 +672,19 @@ dependencies = [ "syn", ] +[[package]] +name = "derive_more" +version = "0.99.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4fb810d30a7c1953f91334de7244731fc3f3c10d7fe163338a35b9f640960321" +dependencies = [ + "convert_case", + "proc-macro2", + "quote", + "rustc_version", + "syn", +] + [[package]] name = "digest" version = "0.10.6" @@ -420,6 +721,91 @@ version = "0.3.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fea41bba32d969b513997752735605054bc0dfa92b4c56bf1189f2e174be7a10" +[[package]] +name = "dtoa" +version = "0.4.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "56899898ce76aaf4a0f24d914c97ea6ed976d42fec6ad33fcbb0a1103e07b2b0" + +[[package]] +name = "dtoa-short" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bde03329ae10e79ede66c9ce4dc930aa8599043b0743008548680f25b91502d6" +dependencies = [ + "dtoa", +] + +[[package]] +name = "either" +version = "1.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7fcaabb2fef8c910e7f4c7ce9f67a1283a1715879a7c230ca9d6d1ae31f16d91" + +[[package]] +name = "encoding" +version = "0.2.33" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6b0d943856b990d12d3b55b359144ff341533e516d94098b1d3fc1ac666d36ec" +dependencies = [ + "encoding-index-japanese", + "encoding-index-korean", + "encoding-index-simpchinese", + "encoding-index-singlebyte", + "encoding-index-tradchinese", +] + +[[package]] +name = "encoding-index-japanese" +version = "1.20141219.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "04e8b2ff42e9a05335dbf8b5c6f7567e5591d0d916ccef4e0b1710d32a0d0c91" +dependencies = [ + "encoding_index_tests", +] + +[[package]] +name = "encoding-index-korean" +version = "1.20141219.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4dc33fb8e6bcba213fe2f14275f0963fd16f0a02c878e3095ecfdf5bee529d81" +dependencies = [ + "encoding_index_tests", +] + +[[package]] +name = "encoding-index-simpchinese" +version = "1.20141219.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d87a7194909b9118fc707194baa434a4e3b0fb6a5a757c73c3adb07aa25031f7" +dependencies = [ + "encoding_index_tests", +] + +[[package]] +name = "encoding-index-singlebyte" +version = "1.20141219.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3351d5acffb224af9ca265f435b859c7c01537c0849754d3db3fdf2bfe2ae84a" +dependencies = [ + "encoding_index_tests", +] + +[[package]] +name = "encoding-index-tradchinese" +version = "1.20141219.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fd0e20d5688ce3cab59eb3ef3a2083a5c77bf496cb798dc6fcdb75f323890c18" +dependencies = [ + "encoding_index_tests", +] + +[[package]] +name = "encoding_index_tests" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a246d82be1c9d791c5dfde9a2bd045fc3cbba3fa2b11ad558f27d01712f00569" + [[package]] name = "enumflags2" version = "0.7.5" @@ -441,6 +827,17 @@ dependencies = [ "syn", ] +[[package]] +name = "enumn" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e88bcb3a067a6555d577aba299e75eff9942da276e6506fc6274327daa026132" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + [[package]] name = "env_logger" version = "0.7.1" @@ -490,6 +887,22 @@ version = "2.5.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0206175f82b8d6bf6652ff7d71a1e27fd2e4efde587fd368662814d6ec1d9ce0" +[[package]] +name = "exr" +version = "1.5.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e8af5ef47e2ed89d23d0ecbc1b681b30390069de70260937877514377fc24feb" +dependencies = [ + "bit_field", + "flume", + "half", + "lebe", + "miniz_oxide", + "smallvec", + "threadpool", + "zune-inflate", +] + [[package]] name = "fastrand" version = "1.9.0" @@ -501,14 +914,46 @@ dependencies = [ [[package]] name = "field-offset" -version = "0.3.4" +version = "0.3.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1e1c54951450cbd39f3dbcf1005ac413b49487dabf18a720ad2383eccfeffb92" +checksum = "a3cf3a800ff6e860c863ca6d4b16fd999db8b752819c1606884047b73e468535" dependencies = [ - "memoffset", + "memoffset 0.8.0", "rustc_version", ] +[[package]] +name = "flate2" +version = "1.0.25" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a8a2db397cb1c8772f31494cb8917e48cd1e64f0fa7efac59fbd741a0a8ce841" +dependencies = [ + "crc32fast", + "miniz_oxide", +] + +[[package]] +name = "float-cmp" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "98de4bbd547a563b716d8dfa9aad1cb19bfab00f4fa09a6a4ed21dbcf44ce9c4" +dependencies = [ + "num-traits", +] + +[[package]] +name = "flume" +version = "0.10.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1657b4441c3403d9f7b3409e47575237dac27b1b5726df654a6ecbf92f0f7577" +dependencies = [ + "futures-core", + "futures-sink", + "nanorand", + "pin-project", + "spin", +] + [[package]] name = "form_urlencoded" version = "1.1.0" @@ -518,6 +963,22 @@ dependencies = [ "percent-encoding", ] +[[package]] +name = "four-cc" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "92d73a076bdabd78c2f9045dba1b90664a655fa8372581c238596e1eb3a5e1b7" + +[[package]] +name = "futf" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "df420e2e84819663797d1ec6544b13c5be84629e7bb00dc960d6917db2987843" +dependencies = [ + "mac", + "new_debug_unreachable", +] + [[package]] name = "futures" version = "0.3.26" @@ -622,6 +1083,15 @@ dependencies = [ "slab", ] +[[package]] +name = "fxhash" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c31b6d751ae2c7f11320402d34e41349dd1016f8d5d45e48c4312bc8625af50c" +dependencies = [ + "byteorder", +] + [[package]] name = "gdk-pixbuf" version = "0.17.0" @@ -630,8 +1100,8 @@ checksum = "b023fbe0c6b407bd3d9805d107d9800da3829dc5a676653210f1d5f16d7f59bf" dependencies = [ "bitflags", "gdk-pixbuf-sys", - "gio 0.17.0", - "glib 0.17.1", + "gio 0.17.2", + "glib 0.17.2", "libc", "once_cell", ] @@ -643,7 +1113,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7b41bd2b44ed49d99277d3925652a163038bd5ed943ec9809338ffb2f4391e3b" dependencies = [ "gio-sys 0.17.0", - "glib-sys 0.17.0", + "glib-sys 0.17.2", "gobject-sys 0.17.0", "libc", "system-deps", @@ -651,30 +1121,30 @@ dependencies = [ [[package]] name = "gdk4" -version = "0.6.0" +version = "0.6.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6e4887e17b6926db51f1e538d871a8b1f5ceb5dfa3bd0034dc42ec355b390d8f" +checksum = "e5042053ee765aeef08d9d7e3f0f1e36a4d37f1659b3f93ad3d6997515dbb64a" dependencies = [ "bitflags", "cairo-rs", "gdk-pixbuf", "gdk4-sys", - "gio 0.17.0", - "glib 0.17.1", + "gio 0.17.2", + "glib 0.17.2", "libc", "pango", ] [[package]] name = "gdk4-sys" -version = "0.6.0" +version = "0.6.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f4993c019bf03d18137c00ddafb2b23e73f7cbb45ae244f52af2542a3f4a9452" +checksum = "14f0fb00507af1e9299681dd09965f720e2b5ea95536d49a5681e8994ef10c7a" dependencies = [ "cairo-sys-rs", "gdk-pixbuf-sys", "gio-sys 0.17.0", - "glib-sys 0.17.0", + "glib-sys 0.17.2", "gobject-sys 0.17.0", "libc", "pango-sys", @@ -684,49 +1154,49 @@ dependencies = [ [[package]] name = "gdk4-wayland" -version = "0.6.0" +version = "0.6.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0f6101812fc251eeb7ba5babe62b291505a8e6f64326929b657923049e39acee" +checksum = "4d34175f400cfc1dc20443a97184d8366e78b16ae488f6ad208c44f710713d9a" dependencies = [ "gdk4", "gdk4-wayland-sys", - "gio 0.17.0", - "glib 0.17.1", + "gio 0.17.2", + "glib 0.17.2", "libc", ] [[package]] name = "gdk4-wayland-sys" -version = "0.6.0" +version = "0.6.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f9b33e54388919cb9f289075202eefeee9b4633188d835ce77d81224dc4cd219" +checksum = "d3a7589f15d81c946598b31aa21113011b2465c5e4459f7b34d4895168f5a3e7" dependencies = [ - "glib-sys 0.17.0", + "glib-sys 0.17.2", "libc", "system-deps", ] [[package]] name = "gdk4-x11" -version = "0.6.0" +version = "0.6.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f843aaaa34487384060dd86ef9737ebd5d6dbd90e75d73991f3b706aee645b13" +checksum = "8c2cb4d8362649a35490098008235f242eedff719c6340178a80c3c03ea4dd5a" dependencies = [ "gdk4", "gdk4-x11-sys", - "gio 0.17.0", - "glib 0.17.1", + "gio 0.17.2", + "glib 0.17.2", "libc", ] [[package]] name = "gdk4-x11-sys" -version = "0.6.0" +version = "0.6.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7eafef0f400d191b6176144e7c7d41c8027232d563e1def5460b9328638eecdd" +checksum = "880be6ce14b75f173525d6e1dd62342d7189a01fab1686c0c6e1d58312acf3f1" dependencies = [ "gdk4-sys", - "glib-sys 0.17.0", + "glib-sys 0.17.2", "libc", "system-deps", ] @@ -741,6 +1211,17 @@ dependencies = [ "version_check", ] +[[package]] +name = "getrandom" +version = "0.1.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8fc3cb4d91f53b50155bdcfd23f6a4c39ae1969c2ae85982b135750cccaf5fce" +dependencies = [ + "cfg-if", + "libc", + "wasi 0.9.0+wasi-snapshot-preview1", +] + [[package]] name = "getrandom" version = "0.2.8" @@ -748,8 +1229,10 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c05aeb6a22b8f62540c194aac980f2115af067bfe15a0734d7277a768d396b31" dependencies = [ "cfg-if", + "js-sys", "libc", - "wasi", + "wasi 0.11.0+wasi-snapshot-preview1", + "wasm-bindgen", ] [[package]] @@ -772,6 +1255,22 @@ dependencies = [ "temp-dir", ] +[[package]] +name = "gif" +version = "0.12.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "80792593675e051cf94a4b111980da2ba60d4a83e43e0048c5693baab3977045" +dependencies = [ + "color_quant", + "weezl", +] + +[[package]] +name = "gimli" +version = "0.27.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ad0a93d233ebf96623465aad4046a8d3aa4da22d4f4beba5388838c8a434bbb4" + [[package]] name = "gio" version = "0.15.12" @@ -791,9 +1290,9 @@ dependencies = [ [[package]] name = "gio" -version = "0.17.0" +version = "0.17.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1981edf8679d2f2c8ec3120015867f45aa0a1c2d5e3e129ca2f7dda174d3d2a9" +checksum = "65acfc24267314eee46f49e0a531e08fd6c3025040d1cfb4a7cd8e41c5e06116" dependencies = [ "bitflags", "futures-channel", @@ -801,7 +1300,7 @@ dependencies = [ "futures-io", "futures-util", "gio-sys 0.17.0", - "glib 0.17.1", + "glib 0.17.2", "libc", "once_cell", "pin-project-lite", @@ -828,7 +1327,7 @@ version = "0.17.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b5d3076ecb86c8c3a672c9843d6232b3a344fb81d304d0ba1ac64b23343efa46" dependencies = [ - "glib-sys 0.17.0", + "glib-sys 0.17.2", "gobject-sys 0.17.0", "libc", "system-deps", @@ -857,9 +1356,9 @@ dependencies = [ [[package]] name = "glib" -version = "0.17.1" +version = "0.17.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "91b429154ec5943185aeeaf79e646297567b6a056965f1e89da2657a0e23255b" +checksum = "a78b6a0901e258cb03c761ca94c84d519427ede489cae12cd5ba0d7d584e69e9" dependencies = [ "bitflags", "futures-channel", @@ -868,8 +1367,8 @@ dependencies = [ "futures-task", "futures-util", "gio-sys 0.17.0", - "glib-macros 0.17.1", - "glib-sys 0.17.0", + "glib-macros 0.17.2", + "glib-sys 0.17.2", "gobject-sys 0.17.0", "libc", "memchr", @@ -895,9 +1394,9 @@ dependencies = [ [[package]] name = "glib-macros" -version = "0.17.1" +version = "0.17.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9bc80ac951300ca288dd9ab3863743c37a608fb0e5ca12863495640ec6b781ab" +checksum = "55e93d79ed130f0f0b58bc0aa29fb0e40c9dfd63997fec51f8adf780d1520bc4" dependencies = [ "anyhow", "heck", @@ -920,9 +1419,9 @@ dependencies = [ [[package]] name = "glib-sys" -version = "0.17.0" +version = "0.17.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9ddcb73fa8236277bedadaaadb76aef49c85d66340f83bece244f46c2d4f0e01" +checksum = "72a0985cf568e18cf63b443c9a14f4bdaa947fed7437476000dba84926a20b25" dependencies = [ "libc", "system-deps", @@ -963,7 +1462,7 @@ version = "0.17.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9a0155d388840c77d61b033b66ef4f9bc7f4133d83df83572d6b4fb234a3be7d" dependencies = [ - "glib-sys 0.17.0", + "glib-sys 0.17.2", "libc", "system-deps", ] @@ -974,7 +1473,7 @@ version = "0.17.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "21cf11565bb0e4dfc2f99d4775b6c329f0d40a2cff9c0066214d31a0e1b46256" dependencies = [ - "glib 0.17.1", + "glib 0.17.2", "graphene-sys", "libc", ] @@ -985,7 +1484,7 @@ version = "0.17.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "cf80a4849a8d9565410a8fec6fc3678e9c617f4ac7be182ca55ab75016e07af9" dependencies = [ - "glib-sys 0.17.0", + "glib-sys 0.17.2", "libc", "pkg-config", "system-deps", @@ -993,14 +1492,14 @@ dependencies = [ [[package]] name = "gsk4" -version = "0.6.0" +version = "0.6.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "432f981e4ea9f0739a5731d8a649acb794a3a729d2254e559ce7d613b17caf95" +checksum = "2fa9cd285a72a95124b65c069a9cb1b8fb8e310be71783404c39fccf3bf7774c" dependencies = [ "bitflags", "cairo-rs", "gdk4", - "glib 0.17.1", + "glib 0.17.2", "graphene-rs", "gsk4-sys", "libc", @@ -1009,13 +1508,13 @@ dependencies = [ [[package]] name = "gsk4-sys" -version = "0.6.0" +version = "0.6.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "096cb59175b0915ebf69c05a45263c0c989bd8537b8f2169912d0de644ba6a76" +checksum = "5a445ae1e50cbf181a1d5c61b920a7e7e8657b96e0ecdbbf8911a86fad462a32" dependencies = [ "cairo-sys-rs", "gdk4-sys", - "glib-sys 0.17.0", + "glib-sys 0.17.2", "gobject-sys 0.17.0", "graphene-sys", "libc", @@ -1031,9 +1530,9 @@ checksum = "da5bf7748fd4cd0b2490df8debcc911809dbcbee4ece9531b96c29a9c729de5a" [[package]] name = "gtk4" -version = "0.6.1" +version = "0.6.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "73421200ad9c3919d0720779b345b05d31a6ce9998e74fc27012f12580822e46" +checksum = "e47dca53cb1a8ae3006e869b5711ae7370180db537f6d98e3bcaf23fabfd911f" dependencies = [ "bitflags", "cairo-rs", @@ -1041,8 +1540,8 @@ dependencies = [ "futures-channel", "gdk-pixbuf", "gdk4", - "gio 0.17.0", - "glib 0.17.1", + "gio 0.17.2", + "glib 0.17.2", "graphene-rs", "gsk4", "gtk4-macros", @@ -1069,15 +1568,15 @@ dependencies = [ [[package]] name = "gtk4-sys" -version = "0.6.0" +version = "0.6.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e13cf3bc9559f71963c957eb639060b643e1276ae47b892ef6091d5bc15c3e1b" +checksum = "65463dc801460e498d5e7ffa6e9ae2cfbed7d05fabd1ca5a8d024adbc89eeda6" dependencies = [ "cairo-sys-rs", "gdk-pixbuf-sys", "gdk4-sys", "gio-sys 0.17.0", - "glib-sys 0.17.0", + "glib-sys 0.17.2", "gobject-sys 0.17.0", "graphene-sys", "gsk4-sys", @@ -1099,6 +1598,15 @@ dependencies = [ "system-deps", ] +[[package]] +name = "half" +version = "2.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "02b4af3693f1b705df946e9fe5631932443781d0aabb423b62fcd4d73f6d2fd0" +dependencies = [ + "crunchy", +] + [[package]] name = "hashbrown" version = "0.12.3" @@ -1120,6 +1628,21 @@ dependencies = [ "libc", ] +[[package]] +name = "hermit-abi" +version = "0.2.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ee512640fe35acbfb4bb779db6f0d80704c2cacfa2e39b601ef3e3f47d1ae4c7" +dependencies = [ + "libc", +] + +[[package]] +name = "hermit-abi" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fed44880c466736ef9a5c5b5facefb5ed0785676d0c02d612db14e54f0d84286" + [[package]] name = "hex" version = "0.4.3" @@ -1135,6 +1658,30 @@ dependencies = [ "quick-error", ] +[[package]] +name = "iana-time-zone" +version = "0.1.53" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "64c122667b287044802d6ce17ee2ddf13207ed924c712de9a66a5814d5b64765" +dependencies = [ + "android_system_properties", + "core-foundation-sys", + "iana-time-zone-haiku", + "js-sys", + "wasm-bindgen", + "winapi", +] + +[[package]] +name = "iana-time-zone-haiku" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0703ae284fc167426161c2e3f1da3ea71d94b21bedbcc9494e92b28e334e3dca" +dependencies = [ + "cxx", + "cxx-build", +] + [[package]] name = "idna" version = "0.3.0" @@ -1145,6 +1692,24 @@ dependencies = [ "unicode-normalization", ] +[[package]] +name = "image" +version = "0.24.5" +source = "git+https://github.com/image-rs/image.git#55732d14434cf639f2e79890f43a1d48bfb8617b" +dependencies = [ + "bytemuck", + "byteorder", + "color_quant", + "exr", + "gif", + "jpeg-decoder", + "num-rational", + "num-traits", + "png", + "qoi", + "tiff", +] + [[package]] name = "indexmap" version = "1.9.2" @@ -1175,10 +1740,40 @@ dependencies = [ ] [[package]] -name = "itoa" -version = "1.0.5" +name = "is-terminal" +version = "0.4.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fad582f4b9e86b6caa621cabeb0963332d92eea04729ab12892c2533951e6440" +checksum = "21b6b32576413a8e69b90e952e4a026476040d81017b80445deda5f2d3921857" +dependencies = [ + "hermit-abi 0.3.1", + "io-lifetimes", + "rustix", + "windows-sys 0.45.0", +] + +[[package]] +name = "itertools" +version = "0.10.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b0fd2260e829bddf4cb6ea802289de2f86d6a7a690192fbe91b3f46e0f2c8473" +dependencies = [ + "either", +] + +[[package]] +name = "itoa" +version = "1.0.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "453ad9f582a441959e5f0d088b02ce04cfe8d51a8eaf077f12ac6d3e94164ca6" + +[[package]] +name = "jpeg-decoder" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bc0000e42512c92e31c2252315bda326620a4e034105e900c98ec492fa077b3e" +dependencies = [ + "rayon", +] [[package]] name = "js-sys" @@ -1207,12 +1802,24 @@ dependencies = [ "log", ] +[[package]] +name = "language-tags" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d4345964bb142484797b161f473a503a434de77149dd8c7427788c6e13379388" + [[package]] name = "lazy_static" version = "1.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646" +[[package]] +name = "lebe" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "03087c2bad5e1034e8cace5926dec053fb3790248370865f5117a7d0213354c8" + [[package]] name = "libadwaita" version = "0.3.1" @@ -1223,8 +1830,8 @@ dependencies = [ "futures-channel", "gdk-pixbuf", "gdk4", - "gio 0.17.0", - "glib 0.17.1", + "gio 0.17.2", + "glib 0.17.2", "gtk4", "libadwaita-sys", "libc", @@ -1240,7 +1847,7 @@ checksum = "0727b85b4fe2b1bed5ac90df6343de15cbf8118bfb96d7c3cc1512681a4b34ac" dependencies = [ "gdk4-sys", "gio-sys 0.17.0", - "glib-sys 0.17.0", + "glib-sys 0.17.2", "gobject-sys 0.17.0", "gtk4-sys", "libc", @@ -1267,6 +1874,77 @@ dependencies = [ "libc", ] +[[package]] +name = "libheif-rs" +version = "0.17.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "493fd0e4e813e9262b3eb76f5937385e979ed26733ac770c9ecb7bb005ba444a" +dependencies = [ + "enumn", + "four-cc", + "libheif-sys", +] + +[[package]] +name = "libheif-sys" +version = "1.14.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6af8b7a4151ae10f6d2e8684f7172c43f09c0258c84190fd9704422588ceec63" +dependencies = [ + "libc", +] + +[[package]] +name = "librsvg" +version = "2.55.91" +source = "git+https://gitlab.gnome.org/GNOME/librsvg.git#1f11b3c236c374c58d929baafa0841fef59c4155" +dependencies = [ + "anyhow", + "byteorder", + "cairo-rs", + "cast", + "chrono", + "clap", + "clap_complete", + "cssparser", + "data-url", + "encoding", + "float-cmp", + "gdk-pixbuf", + "gio 0.17.2", + "glib 0.17.2", + "itertools", + "language-tags", + "libc", + "locale_config", + "markup5ever", + "nalgebra", + "num-traits", + "once_cell", + "pango", + "pangocairo", + "rayon", + "rctree", + "regex", + "rgb", + "selectors", + "string_cache", + "system-deps", + "thiserror", + "tinyvec", + "url", + "xml5ever", +] + +[[package]] +name = "link-cplusplus" +version = "1.0.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ecd207c9c713c34f95a097a5b029ac2ce6010530c7b49d7fea24d977dede04f5" +dependencies = [ + "cc", +] + [[package]] name = "linux-raw-sys" version = "0.1.4" @@ -1311,26 +1989,37 @@ name = "loupe" version = "0.1.0" dependencies = [ "anyhow", + "arc-swap", "ashpd", "async-global-executor", "async-std", "futures", "futures-channel", "gettext-rs", - "gio 0.17.0", + "gio 0.17.2", "gtk-macros", "gtk4", + "image", "indexmap", "kamadak-exif", "libadwaita", "libgweather", + "libheif-rs", + "librsvg", "log", "once_cell", "pretty_env_logger", + "safe-transmute", "svg_metadata", "url", ] +[[package]] +name = "mac" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c41e0c4fef86961ac6d6f8a82609f55f31b05e4fce149ac5710e439df7619ba4" + [[package]] name = "malloc_buf" version = "0.0.6" @@ -1340,6 +2029,35 @@ dependencies = [ "libc", ] +[[package]] +name = "markup5ever" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7a2629bb1404f3d34c2e921f21fd34ba00b206124c81f65c50b43b6aaefeb016" +dependencies = [ + "log", + "phf 0.10.1", + "phf_codegen 0.10.0", + "string_cache", + "string_cache_codegen", + "tendril", +] + +[[package]] +name = "matches" +version = "0.1.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2532096657941c2fea9c289d370a250971c689d4f143798ff67113ec042024a5" + +[[package]] +name = "matrixmultiply" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "add85d4dd35074e6fedc608f8c8f513a3548619a9024b751949ef0e8e45a4d84" +dependencies = [ + "rawpointer", +] + [[package]] name = "memchr" version = "2.5.0" @@ -1355,12 +2073,72 @@ dependencies = [ "autocfg", ] +[[package]] +name = "memoffset" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d61c719bcfbcf5d62b3a09efa6088de8c54bc0bfcd3ea7ae39fcc186108b8de1" +dependencies = [ + "autocfg", +] + +[[package]] +name = "miniz_oxide" +version = "0.6.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b275950c28b37e794e8c55d88aeb5e139d0ce23fdbbeda68f8d7174abdf9e8fa" +dependencies = [ + "adler", +] + [[package]] name = "mutate_once" version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "16cf681a23b4d0a43fc35024c176437f9dcd818db34e0f42ab456a0ee5ad497b" +[[package]] +name = "nalgebra" +version = "0.32.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f6515c882ebfddccaa73ead7320ca28036c4bc84c9bcca3cc0cbba8efe89223a" +dependencies = [ + "approx", + "matrixmultiply", + "nalgebra-macros", + "num-complex", + "num-rational", + "num-traits", + "simba", + "typenum", +] + +[[package]] +name = "nalgebra-macros" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d232c68884c0c99810a5a4d333ef7e47689cfd0edc85efc9e54e1e6bf5212766" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "nanorand" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6a51313c5820b0b02bd422f4b44776fbf47961755c74ce64afc73bfad10226c3" +dependencies = [ + "getrandom 0.2.8", +] + +[[package]] +name = "new_debug_unreachable" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e4a24736216ec316047a1fc4252e27dabb04218aa4a3f37c6e7ddbf1f9782b54" + [[package]] name = "nix" version = "0.25.1" @@ -1371,17 +2149,63 @@ dependencies = [ "bitflags", "cfg-if", "libc", - "memoffset", + "memoffset 0.6.5", "pin-utils", ] [[package]] -name = "nom8" -version = "0.2.0" +name = "nodrop" +version = "0.1.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ae01545c9c7fc4486ab7debaf2aad7003ac19431791868fb2e8066df97fad2f8" +checksum = "72ef4a56884ca558e5ddb05a1d1e7e1bfd9a68d9ed024c21704cc98872dae1bb" + +[[package]] +name = "num-complex" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "02e0d21255c828d6f128a1e41534206671e8c3ea0c62f32291e808dc82cff17d" dependencies = [ - "memchr", + "num-traits", +] + +[[package]] +name = "num-integer" +version = "0.1.45" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "225d3389fb3509a24c93f5c29eb6bde2586b98d9f016636dff58d7c6f7569cd9" +dependencies = [ + "autocfg", + "num-traits", +] + +[[package]] +name = "num-rational" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0638a1c9d0a3c0914158145bc76cff373a75a627e6ecbfb71cbe6f453a5a19b0" +dependencies = [ + "autocfg", + "num-integer", + "num-traits", +] + +[[package]] +name = "num-traits" +version = "0.2.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "578ede34cf02f8924ab9447f50c28075b4d3e5b269972345e7e0372b38c6cdcd" +dependencies = [ + "autocfg", +] + +[[package]] +name = "num_cpus" +version = "1.15.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0fac9e2da13b5eb447a6ce3d392f23a29d8694bff781bf03a16cd9ac8697593b" +dependencies = [ + "hermit-abi 0.2.6", + "libc", ] [[package]] @@ -1413,6 +2237,15 @@ dependencies = [ "objc", ] +[[package]] +name = "object" +version = "0.30.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ea86265d3d3dcb6a27fc51bd29a4bf387fae9d2986b823079d4986af253eb439" +dependencies = [ + "memchr", +] + [[package]] name = "once_cell" version = "1.17.1" @@ -1429,6 +2262,12 @@ dependencies = [ "pin-project-lite", ] +[[package]] +name = "os_str_bytes" +version = "6.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9b7820b9daea5457c9f21c69448905d723fbd21136ccf521748f23fd49e723ee" + [[package]] name = "pango" version = "0.17.0" @@ -1436,8 +2275,8 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "243c048be90312220fb3bd578176eed8290568274a93c95040289d39349384bc" dependencies = [ "bitflags", - "gio 0.17.0", - "glib 0.17.1", + "gio 0.17.2", + "glib 0.17.2", "libc", "once_cell", "pango-sys", @@ -1449,12 +2288,39 @@ version = "0.17.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4293d0f0b5525eb5c24734d30b0ed02cd02aa734f216883f376b54de49625de8" dependencies = [ - "glib-sys 0.17.0", + "glib-sys 0.17.2", "gobject-sys 0.17.0", "libc", "system-deps", ] +[[package]] +name = "pangocairo" +version = "0.17.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2feeb7ea7874507f83f5e7ba869c54e321959431c8fbd70d4b735c8b15d90506" +dependencies = [ + "bitflags", + "cairo-rs", + "glib 0.17.2", + "libc", + "pango", + "pangocairo-sys", +] + +[[package]] +name = "pangocairo-sys" +version = "0.17.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "71abd374a8322e2e382039ff2e020c4cc9ef2e12ea488e38d9cec4a55e296470" +dependencies = [ + "cairo-sys-rs", + "glib-sys 0.17.2", + "libc", + "pango-sys", + "system-deps", +] + [[package]] name = "parking" version = "2.0.0" @@ -1484,6 +2350,12 @@ dependencies = [ "windows-sys 0.45.0", ] +[[package]] +name = "paste" +version = "1.0.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d01a5bd0424d00070b0098dd17ebca6f961a959dead1dbcbbbc1d1cd8d3deeba" + [[package]] name = "percent-encoding" version = "2.2.0" @@ -1491,13 +2363,115 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "478c572c3d73181ff3c2539045f6eb99e5491218eae919370993b890cdbdd98e" [[package]] -name = "pest" -version = "2.5.5" +name = "phf" +version = "0.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "028accff104c4e513bad663bbcd2ad7cfd5304144404c31ed0a77ac103d00660" +checksum = "3dfb61232e34fcb633f43d12c58f83c1df82962dcdfa565a4e866ffc17dafe12" dependencies = [ - "thiserror", - "ucd-trie", + "phf_shared 0.8.0", +] + +[[package]] +name = "phf" +version = "0.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fabbf1ead8a5bcbc20f5f8b939ee3f5b0f6f281b6ad3468b84656b658b455259" +dependencies = [ + "phf_macros", + "phf_shared 0.10.0", + "proc-macro-hack", +] + +[[package]] +name = "phf_codegen" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cbffee61585b0411840d3ece935cce9cb6321f01c45477d30066498cd5e1a815" +dependencies = [ + "phf_generator 0.8.0", + "phf_shared 0.8.0", +] + +[[package]] +name = "phf_codegen" +version = "0.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4fb1c3a8bc4dd4e5cfce29b44ffc14bedd2ee294559a294e2a4d4c9e9a6a13cd" +dependencies = [ + "phf_generator 0.10.0", + "phf_shared 0.10.0", +] + +[[package]] +name = "phf_generator" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "17367f0cc86f2d25802b2c26ee58a7b23faeccf78a396094c13dced0d0182526" +dependencies = [ + "phf_shared 0.8.0", + "rand 0.7.3", +] + +[[package]] +name = "phf_generator" +version = "0.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5d5285893bb5eb82e6aaf5d59ee909a06a16737a8970984dd7746ba9283498d6" +dependencies = [ + "phf_shared 0.10.0", + "rand 0.8.5", +] + +[[package]] +name = "phf_macros" +version = "0.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "58fdf3184dd560f160dd73922bea2d5cd6e8f064bf4b13110abd81b03697b4e0" +dependencies = [ + "phf_generator 0.10.0", + "phf_shared 0.10.0", + "proc-macro-hack", + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "phf_shared" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c00cf8b9eafe68dde5e9eaa2cef8ee84a9336a47d566ec55ca16589633b65af7" +dependencies = [ + "siphasher", +] + +[[package]] +name = "phf_shared" +version = "0.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b6796ad771acdc0123d2a88dc428b5e38ef24456743ddb1744ed628f9815c096" +dependencies = [ + "siphasher", +] + +[[package]] +name = "pin-project" +version = "1.0.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ad29a609b6bcd67fee905812e544992d216af9d755757c05ed2d0e15a74c6ecc" +dependencies = [ + "pin-project-internal", +] + +[[package]] +name = "pin-project-internal" +version = "1.0.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "069bdb1e05adc7a8990dce9cc75370895fbe4e3d58b9b73bf1aee56359344a55" +dependencies = [ + "proc-macro2", + "quote", + "syn", ] [[package]] @@ -1518,6 +2492,18 @@ version = "0.3.26" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6ac9a59f73473f1b8d852421e59e64809f025994837ef743615c6d0c5b305160" +[[package]] +name = "png" +version = "0.17.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5d708eaf860a19b19ce538740d2b4bdeeb8337fa53f7738455e706623ad5c638" +dependencies = [ + "bitflags", + "crc32fast", + "flate2", + "miniz_oxide", +] + [[package]] name = "polling" version = "2.5.2" @@ -1538,6 +2524,12 @@ version = "0.2.17" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5b40af805b3121feab8a3c29f04d8ad262fa8e0561883e7653e024ae4479e6de" +[[package]] +name = "precomputed-hash" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "925383efa346730478fb4838dbe9137d2a47675ad789c546d150a6e1dd4ab31c" + [[package]] name = "pretty_env_logger" version = "0.4.0" @@ -1550,9 +2542,9 @@ dependencies = [ [[package]] name = "proc-macro-crate" -version = "1.3.0" +version = "1.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "66618389e4ec1c7afe67d51a9bf34ff9236480f8d51e7489b7d5ab0303c13f34" +checksum = "7f4c021e1093a56626774e81216a4ce732a735e5bad4868a03f3ed65ca0c3919" dependencies = [ "once_cell", "toml_edit", @@ -1582,6 +2574,12 @@ dependencies = [ "version_check", ] +[[package]] +name = "proc-macro-hack" +version = "0.5.20+deprecated" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dc375e1527247fe1a97d8b7156678dfe7c1af2fc075c9a4db3690ecd2a148068" + [[package]] name = "proc-macro2" version = "1.0.51" @@ -1602,6 +2600,15 @@ dependencies = [ "unicase", ] +[[package]] +name = "qoi" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7f6d64c71eb498fe9eae14ce4ec935c555749aef511cca85b5568910d6e48001" +dependencies = [ + "bytemuck", +] + [[package]] name = "quick-error" version = "1.2.3" @@ -1626,6 +2633,20 @@ dependencies = [ "proc-macro2", ] +[[package]] +name = "rand" +version = "0.7.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6a6b1679d49b24bbfe0c803429aa1874472f50d9b363131f0e89fc356b544d03" +dependencies = [ + "getrandom 0.1.16", + "libc", + "rand_chacha 0.2.2", + "rand_core 0.5.1", + "rand_hc", + "rand_pcg", +] + [[package]] name = "rand" version = "0.8.5" @@ -1633,8 +2654,18 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404" dependencies = [ "libc", - "rand_chacha", - "rand_core", + "rand_chacha 0.3.1", + "rand_core 0.6.4", +] + +[[package]] +name = "rand_chacha" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f4c8ed856279c9737206bf725bf36935d8666ead7aa69b52be55af369d193402" +dependencies = [ + "ppv-lite86", + "rand_core 0.5.1", ] [[package]] @@ -1644,7 +2675,16 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e6c10a63a0fa32252be49d21e7709d4d4baf8d231c2dbce1eaa8141b9b127d88" dependencies = [ "ppv-lite86", - "rand_core", + "rand_core 0.6.4", +] + +[[package]] +name = "rand_core" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "90bde5296fc891b0cef12a6d03ddccc162ce7b2aff54160af9338f8d40df6d19" +dependencies = [ + "getrandom 0.1.16", ] [[package]] @@ -1653,9 +2693,61 @@ version = "0.6.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c" dependencies = [ - "getrandom", + "getrandom 0.2.8", ] +[[package]] +name = "rand_hc" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ca3129af7b92a17112d59ad498c6f81eaf463253766b90396d39ea7a39d6613c" +dependencies = [ + "rand_core 0.5.1", +] + +[[package]] +name = "rand_pcg" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "16abd0c1b639e9eb4d7c50c0b8100b0d0f849be2349829c740fe8e6eb4816429" +dependencies = [ + "rand_core 0.5.1", +] + +[[package]] +name = "rawpointer" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "60a357793950651c4ed0f3f52338f53b2f809f32d83a07f72909fa13e4c6c1e3" + +[[package]] +name = "rayon" +version = "1.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1d2df5196e37bcc87abebc0053e20787d73847bb33134a69841207dd0a47f03b" +dependencies = [ + "either", + "rayon-core", +] + +[[package]] +name = "rayon-core" +version = "1.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4b8f95bd6966f5c87776639160a66bd8ab9895d9d4ab01ddba9fc60661aebe8d" +dependencies = [ + "crossbeam-channel", + "crossbeam-deque", + "crossbeam-utils", + "num_cpus", +] + +[[package]] +name = "rctree" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3b42e27ef78c35d3998403c1d26f3efd9e135d3e5121b0a4845cc5cc27547f4f" + [[package]] name = "redox_syscall" version = "0.2.16" @@ -1671,7 +2763,7 @@ version = "0.4.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b033d837a7cf162d7993aded9304e30a83213c648b6e389db233191f891e5c2b" dependencies = [ - "getrandom", + "getrandom 0.2.8", "redox_syscall", "thiserror", ] @@ -1693,6 +2785,15 @@ version = "0.6.28" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "456c603be3e8d448b072f410900c09faf164fbce2d480456f50eea6e25f9c848" +[[package]] +name = "rgb" +version = "0.8.36" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "20ec2d3e3fc7a92ced357df9cebd5a10b6fb2aa1ee797bf7e9ce2f17dffc8f59" +dependencies = [ + "bytemuck", +] + [[package]] name = "roxmltree" version = "0.18.0" @@ -1703,12 +2804,18 @@ dependencies = [ ] [[package]] -name = "rustc_version" -version = "0.3.3" +name = "rustc-demangle" +version = "0.1.21" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f0dfe2087c51c460008730de8b57e6a320782fbfb312e1f4d520e6c6fae155ee" +checksum = "7ef03e0a2b150c7a90d01faf6254c9c48a41e95fb2a8c2ac1c6f0d2b9aefc342" + +[[package]] +name = "rustc_version" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bfa0f585226d2e68097d4f95d113b15b83a82e819ab25717ec0590d9584ef366" dependencies = [ - "semver 0.11.0", + "semver", ] [[package]] @@ -1727,9 +2834,24 @@ dependencies = [ [[package]] name = "ryu" -version = "1.0.12" +version = "1.0.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7b4b9743ed687d4b4bcedf9ff5eaa7398495ae14e61cba0a295704edbc7decde" +checksum = "f91339c0467de62360649f8d3e185ca8de4224ff281f66000de5eb2a77a79041" + +[[package]] +name = "safe-transmute" +version = "0.11.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "98a01dab6acf992653be49205bdd549f32f17cb2803e8eacf1560bf97259aae8" + +[[package]] +name = "safe_arch" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "794821e4ccb0d9f979512f9c1973480123f9bd62a90d74ab0f9426fcf8f4a529" +dependencies = [ + "bytemuck", +] [[package]] name = "same-file" @@ -1747,12 +2869,27 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d29ab0c6d3fc0ee92fe66e2d99f700eab17a8d57d1c1d3b748380fb20baa78cd" [[package]] -name = "semver" -version = "0.11.0" +name = "scratch" +version = "1.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f301af10236f6df4160f7c3f04eec6dbc70ace82d23326abad5edee88801c6b6" +checksum = "5d5e082f6ea090deaf0e6dd04b68360fd5cddb152af6ce8927c9d25db299f98c" + +[[package]] +name = "selectors" +version = "0.24.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0c37578180969d00692904465fb7f6b3d50b9a2b952b87c23d0e2e5cb5013416" dependencies = [ - "semver-parser", + "bitflags", + "cssparser", + "derive_more", + "fxhash", + "log", + "phf 0.8.0", + "phf_codegen 0.8.0", + "precomputed-hash", + "servo_arc", + "smallvec", ] [[package]] @@ -1764,15 +2901,6 @@ dependencies = [ "serde", ] -[[package]] -name = "semver-parser" -version = "0.10.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "00b0bef5b7f9e0df16536d3961cfb6e84331c065b4066afb39768d0e319411f7" -dependencies = [ - "pest", -] - [[package]] name = "serde" version = "1.0.152" @@ -1815,6 +2943,16 @@ dependencies = [ "syn", ] +[[package]] +name = "servo_arc" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d52aa42f8fdf0fed91e5ce7f23d8138441002fa31dca008acf47e6fd4721f741" +dependencies = [ + "nodrop", + "stable_deref_trait", +] + [[package]] name = "sha1" version = "0.10.5" @@ -1826,6 +2964,31 @@ dependencies = [ "digest", ] +[[package]] +name = "simba" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "50582927ed6f77e4ac020c057f37a268fc6aebc29225050365aacbb9deeeddc4" +dependencies = [ + "approx", + "num-complex", + "num-traits", + "paste", + "wide", +] + +[[package]] +name = "simd-adler32" +version = "0.3.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "14a5df39617d7c8558154693a1bb8157a4aab8179209540cc0b10e5dc24e0b18" + +[[package]] +name = "siphasher" +version = "0.3.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7bd3e3206899af3f8b12af284fafc038cc1dc2b41d1b89dd17297221c5d225de" + [[package]] name = "skeptic" version = "0.13.7" @@ -1843,9 +3006,9 @@ dependencies = [ [[package]] name = "slab" -version = "0.4.7" +version = "0.4.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4614a76b2a8be0058caa9dbbaf66d988527d86d003c11a94fbd335d7661edcef" +checksum = "6528351c9bc8ab22353f9d776db39a20288e8d6c37ef8cfe3317cf875eecfc2d" dependencies = [ "autocfg", ] @@ -1858,20 +3021,67 @@ checksum = "a507befe795404456341dfab10cef66ead4c041f62b8b11bbb92bffe5d0953e0" [[package]] name = "socket2" -version = "0.4.7" +version = "0.4.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "02e2d2db9033d13a1567121ddd7a095ee144db4e1ca1b1bda3419bc0da294ebd" +checksum = "64a4a911eed85daf18834cfaa86a79b7d266ff93ff5ba14005426219480ed662" dependencies = [ "libc", "winapi", ] +[[package]] +name = "spin" +version = "0.9.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7dccf47db1b41fa1573ed27ccf5e08e3ca771cb994f776668c5ebda893b248fc" +dependencies = [ + "lock_api", +] + +[[package]] +name = "stable_deref_trait" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a8f112729512f8e442d81f95a8a7ddf2b7c6b8a1a6f509a95864142b30cab2d3" + [[package]] name = "static_assertions" version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f" +[[package]] +name = "string_cache" +version = "0.8.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7d69e88b23f23030bf4d0e9ca7b07434f70e1c1f4d3ca7e93ce958b373654d9f" +dependencies = [ + "new_debug_unreachable", + "once_cell", + "parking_lot", + "phf_shared 0.10.0", + "precomputed-hash", + "serde", +] + +[[package]] +name = "string_cache_codegen" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6bb30289b722be4ff74a408c3cc27edeaad656e06cb1fe8fa9231fa59c728988" +dependencies = [ + "phf_generator 0.10.0", + "phf_shared 0.10.0", + "proc-macro2", + "quote", +] + +[[package]] +name = "strsim" +version = "0.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "73473c0e59e6d5812c5dfe2a064a6444949f089e20eec9a2e5506596494e4623" + [[package]] name = "svg_metadata" version = "0.4.4" @@ -1887,9 +3097,9 @@ dependencies = [ [[package]] name = "syn" -version = "1.0.107" +version = "1.0.109" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1f4064b5b16e03ae50984a5a8ed5d4f8803e6bc1fd170a3cda91a1be4b18e3f5" +checksum = "72b64191b275b66ffe2469e8af2c1cfe3bafa67b529ead792a6d0160888b4237" dependencies = [ "proc-macro2", "quote", @@ -1928,6 +3138,17 @@ dependencies = [ "windows-sys 0.42.0", ] +[[package]] +name = "tendril" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d24a120c5fc464a3458240ee02c299ebcb9d67b5249c8848b09d639dca8d7bb0" +dependencies = [ + "futf", + "mac", + "utf-8", +] + [[package]] name = "termcolor" version = "1.2.0" @@ -1957,6 +3178,37 @@ dependencies = [ "syn", ] +[[package]] +name = "threadpool" +version = "1.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d050e60b33d41c19108b32cea32164033a9013fe3b46cbd4457559bfbf77afaa" +dependencies = [ + "num_cpus", +] + +[[package]] +name = "tiff" +version = "0.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7449334f9ff2baf290d55d73983a7d6fa15e01198faef72af07e2a8db851e471" +dependencies = [ + "flate2", + "jpeg-decoder", + "weezl", +] + +[[package]] +name = "time" +version = "0.1.45" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1b797afad3f312d1c66a56d11d0316f916356d11bd158fbc6ca6389ff6bf805a" +dependencies = [ + "libc", + "wasi 0.10.0+wasi-snapshot-preview1", + "winapi", +] + [[package]] name = "tinyvec" version = "1.6.0" @@ -1983,19 +3235,19 @@ dependencies = [ [[package]] name = "toml_datetime" -version = "0.5.1" +version = "0.6.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4553f467ac8e3d374bc9a177a26801e5d0f9b211aa1673fb137a403afd1c9cf5" +checksum = "3ab8ed2edee10b50132aed5f331333428b011c99402b5a534154ed15746f9622" [[package]] name = "toml_edit" -version = "0.18.1" +version = "0.19.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "56c59d8dd7d0dcbc6428bf7aa2f0e823e26e43b3c9aca15bbc9475d23e5fa12b" +checksum = "9a1eb0622d28f4b9c90adc4ea4b2b46b47663fde9ac5fafcb14a1369d5508825" dependencies = [ "indexmap", - "nom8", "toml_datetime", + "winnow", ] [[package]] @@ -2036,12 +3288,6 @@ version = "1.16.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "497961ef93d974e23eb6f433eb5fe1b7930b659f06d12dec6fc44a8f554c0bba" -[[package]] -name = "ucd-trie" -version = "0.1.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9e79c4d996edb816c91e4308506774452e55e95c3c9de07b6729e17e15a5ef81" - [[package]] name = "uds_windows" version = "1.0.2" @@ -2069,9 +3315,9 @@ checksum = "d54675592c1dbefd78cbd98db9bacd89886e1ca50692a0692baefffdeb92dd58" [[package]] name = "unicode-ident" -version = "1.0.6" +version = "1.0.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "84a22b9f218b40614adcb3f4ff08b703773ad44fa9423e4e0d346d5db86e4ebc" +checksum = "775c11906edafc97bc378816b94585fbd9a054eabaf86fdd0ced94af449efab7" [[package]] name = "unicode-normalization" @@ -2082,6 +3328,12 @@ dependencies = [ "tinyvec", ] +[[package]] +name = "unicode-width" +version = "0.1.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c0edd1e5b14653f783770bce4a4dabb4a5108a5370a5f5d8cfe8710c361f6c8b" + [[package]] name = "url" version = "2.3.1" @@ -2094,6 +3346,12 @@ dependencies = [ "serde", ] +[[package]] +name = "utf-8" +version = "0.7.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "09cc8ee72d2a9becf2f2febe0205bbed8fc6615b7cb429ad062dc7b7ddd036a9" + [[package]] name = "value-bag" version = "1.0.0-alpha.9" @@ -2133,6 +3391,18 @@ dependencies = [ "winapi-util", ] +[[package]] +name = "wasi" +version = "0.9.0+wasi-snapshot-preview1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cccddf32554fecc6acb585f82a32a72e28b48f8c4c1883ddfeeeaa96f7d8e519" + +[[package]] +name = "wasi" +version = "0.10.0+wasi-snapshot-preview1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1a143597ca7c7793eff794def352d41792a93c481eb1042423ff7ff72ba2c31f" + [[package]] name = "wasi" version = "0.11.0+wasi-snapshot-preview1" @@ -2215,6 +3485,12 @@ dependencies = [ "wasm-bindgen", ] +[[package]] +name = "weezl" +version = "0.1.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9193164d4de03a926d909d3bc7c30543cecb35400c02114792c2cae20d5e2dbb" + [[package]] name = "wepoll-ffi" version = "0.1.2" @@ -2224,6 +3500,16 @@ dependencies = [ "cc", ] +[[package]] +name = "wide" +version = "0.7.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b689b6c49d6549434bf944e6b0f39238cf63693cb7a147e9d887507fffa3b223" +dependencies = [ + "bytemuck", + "safe_arch", +] + [[package]] name = "winapi" version = "0.3.9" @@ -2336,6 +3622,26 @@ version = "0.42.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "447660ad36a13288b1db4d4248e857b510e8c3a225c822ba4fb748c0aafecffd" +[[package]] +name = "winnow" +version = "0.3.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c95fb4ff192527911dd18eb138ac30908e7165b8944e528b6af93aa4c842d345" +dependencies = [ + "memchr", +] + +[[package]] +name = "xml5ever" +version = "0.17.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4034e1d05af98b51ad7214527730626f019682d797ba38b51689212118d8e650" +dependencies = [ + "log", + "mac", + "markup5ever", +] + [[package]] name = "xmlparser" version = "0.13.5" @@ -2367,7 +3673,7 @@ dependencies = [ "nix", "once_cell", "ordered-stream", - "rand", + "rand 0.8.5", "serde", "serde_repr", "sha1", @@ -2404,6 +3710,15 @@ dependencies = [ "zvariant", ] +[[package]] +name = "zune-inflate" +version = "0.2.50" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "589245df6230839c305984dcc0a8385cc72af1fd223f360ffd5d65efa4216d40" +dependencies = [ + "simd-adler32", +] + [[package]] name = "zvariant" version = "3.11.0" diff --git a/Cargo.toml b/Cargo.toml index e0160a5..1f55aeb 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -5,21 +5,32 @@ edition = "2021" rust-version = "1.65" license = "GPL-3.0-or-later" +[profile.release] +lto = true +codegen-units = 1 + [dependencies] -anyhow = "1.0.66" +anyhow = { version = "1.0.66", features = ["backtrace"] } +arc-swap = "1.6.0" async-global-executor = "2.3.1" async-std = "1.12.0" +futures = "0.3.25" +futures-channel = "0.3.25" gtk-macros = "0.3.0" +indexmap = "1.9.2" kamadak-exif = "0.5.5" +libgweather = "4.0.0" log = "0.4.17" once_cell = "1.16.0" pretty_env_logger = "0.4.0" -futures-channel = "0.3.25" -futures = "0.3.25" +safe-transmute = "0.11.2" svg_metadata = "0.4.3" url = "2.3.1" -libgweather = "4.0.0" -indexmap = "1.9.2" + +# decoders +image-rs = { package = "image", version = "*", git = "https://github.com/image-rs/image.git" } +libheif-rs = "0.17.0" +rsvg = { package = "librsvg", git = "https://gitlab.gnome.org/GNOME/librsvg.git" } [dependencies.ashpd] version = "0.4.0-alpha.3" @@ -27,7 +38,7 @@ features = ["gtk4"] [dependencies.gtk] package = "gtk4" -version = "0.6.0" +version = "0.6.2" features = ["v4_10", "xml_validation"] [dependencies.gio] diff --git a/README.md b/README.md index 8528b42..dee6b0a 100644 --- a/README.md +++ b/README.md @@ -15,6 +15,34 @@ Download bundle for: Bundles will not be automatically updated after installation. +## Supported Formats + +| Format | ICC | CICP | EXIF | XMP | Animation | Library | +|----------|-----|------|------|-----|-----------|-------------------------------| +| JPEG | ✔ | — | ✔ | ✘ | — | image-rs | +| PNG | ✔ | ✘ | ✔ | ✘ | ✘ | image-rs | +| GIF | ✘ * | — | — | ✘ | ✘ | image-rs | +| WEBP | — | — | ✔ | ✘ | ✘ | image-rs | +| AVIF | ✔ | ✔ | ✔ | ✘ | ✘ | libheif-rs + libheif (C++) | +| HEIC | ✔ | ✔ | ✔ | ✘ | ✘ | libheif-rs + libheif (C++) ** | +| TIFF | ✘ | — | ✔ | ✘ | — | image-rs | +| SVG | ✘ | — | — | ✘ * | — | librsvg | +| BMP | ✘ | — | — | — | — | image-rs | +| ICO | — | — | — | — | — | image-rs | +| PNM | — | — | — | — | — | image-rs | +| DDS | — | — | — | — | — | image-rs | +| TGA | — | — | — | — | — | image-rs | +| OpenEXR | — | — | — | — | — | image-rs | +| farbfeld | — | — | — | — | — | image-rs | + +| Symbol | Meaning | +|--------|------------------------------------------------| +| * | Unclear if used in practice, needs research | +| ** | Split into plugin because of licensing issues | +| ✘ | Supported by format but not implemented yet | +| ? | Unclear if supported by format, needs research | +| ✔ | Supported | + ## Building ### GNOME Builder diff --git a/build-aux/flatpak/org.gnome.Loupe.Devel.json b/build-aux/flatpak/org.gnome.Loupe.Devel.json index a00c3d4..c986f2e 100644 --- a/build-aux/flatpak/org.gnome.Loupe.Devel.json +++ b/build-aux/flatpak/org.gnome.Loupe.Devel.json @@ -90,6 +90,20 @@ } ] }, + { + "name" : "libheif", + "buildsystem" : "cmake", + "config-opts": [ + + ], + "sources" : [ + { + "type" : "git", + "url" : "https://github.com/strukturag/libheif.git", + "tag" : "v1.15.1" + } + ] + }, { "name" : "loupe", "buildsystem" : "meson", diff --git a/data/gtk/image_page.ui b/data/gtk/image_page.ui index 6ad4d93..2e73677 100644 --- a/data/gtk/image_page.ui +++ b/data/gtk/image_page.ui @@ -53,7 +53,7 @@ image-missing-symbolic Could not Load Image - + diff --git a/deny.toml b/deny.toml index 90d78ef..5b53a88 100644 --- a/deny.toml +++ b/deny.toml @@ -3,10 +3,11 @@ unmaintained = "deny" yanked = "deny" notice = "warn" +[advisories] # A list of advisory IDs to ignore. Note that ignored advisories will still # output a note when they are encountered. ignore = [ - #"RUSTSEC-0000-0000", + "RUSTSEC-2020-0071", ] # This section is considered when running `cargo deny check licenses` diff --git a/meson.build b/meson.build index 319dcd4..441d6bc 100644 --- a/meson.build +++ b/meson.build @@ -8,8 +8,10 @@ project('loupe', 'rust', i18n = import('i18n') gnome = import('gnome') -dependency('gtk4', version: '>=4.8.0') -dependency('libadwaita-1', version: '>=1.2.0') +dependency('gtk4', version: '>=4.9.4') +dependency('libadwaita-1', version: '>=1.2.99') +dependency('gweather4', version: '>=4.0.0') +dependency('libheif', version: '>=1.14.2') cargo = find_program('cargo', required: true) diff --git a/src/decoder/formats/heif.rs b/src/decoder/formats/heif.rs new file mode 100644 index 0000000..7cb692d --- /dev/null +++ b/src/decoder/formats/heif.rs @@ -0,0 +1,148 @@ +///! Decode using libheif-rs +use super::*; +use crate::decoder::tiling::{self, TilingStoreExt}; +use crate::deps::*; +use crate::i18n::*; +use gtk::prelude::*; + +use anyhow::Context; +use arc_swap::ArcSwap; +use libheif_rs::{ColorSpace, HeifContext, Plane, RgbChroma}; + +use std::path::PathBuf; +use std::sync::Arc; + +#[derive(Debug)] +pub struct Heif; + +impl Heif { + pub fn new( + path: PathBuf, + updater: UpdateSender, + tiles: Arc>, + ) -> Self { + log::debug!("Loading HEIF"); + updater.spawn_error_handled(move || { + let ctx = HeifContext::read_from_file(&path.display().to_string()) + .context(i18n("Failed to read image"))?; + let handle = ctx.primary_image_handle()?; + + tiles.set_original_dimensions((handle.width(), handle.height())); + + let rgb_chroma = if handle.luma_bits_per_pixel() > 8 { + if handle.has_alpha_channel() { + #[cfg(target_endian = "little")] + { + RgbChroma::HdrRgbaLe + } + #[cfg(target_endian = "big")] + { + RgbChroma::HdrRgbaBe + } + } else { + #[cfg(target_endian = "little")] + { + RgbChroma::HdrRgbLe + } + #[cfg(target_endian = "big")] + { + RgbChroma::HdrRgbBe + } + } + } else if handle.has_alpha_channel() { + RgbChroma::Rgba + } else { + RgbChroma::Rgb + }; + + let mut image = handle + .decode(ColorSpace::Rgb(rgb_chroma), None) + .context(i18n("Failed to decode image"))?; + + let plane = image + .planes_mut() + .interleaved + .context("Interleaved plane does not exist")?; + + let decoded = Decoded { + plane, + has_alpha_channel: handle.has_alpha_channel(), + pre_multiplied_alpha: handle.is_premultiplied_alpha(), + rgb_chroma, + }; + + let tile = tiling::Tile { + position: (0, 0), + zoom_level: tiling::zoom_to_level(1.), + bleed: 0, + texture: decoded.into_texture(), + }; + + tiles.push(tile); + + Ok(()) + }); + + Heif + } +} + +pub struct Decoded<'a> { + plane: Plane<&'a mut [u8]>, + has_alpha_channel: bool, + pre_multiplied_alpha: bool, + rgb_chroma: RgbChroma, +} + +impl<'a> Decoded<'a> { + pub fn into_texture(self) -> gdk::Texture { + let memory_format = match self.rgb_chroma { + RgbChroma::HdrRgbBe + | RgbChroma::HdrRgbaBe + | RgbChroma::HdrRgbLe + | RgbChroma::HdrRgbaLe => { + let transmuted: &mut [u16] = + safe_transmute::transmute_many_pedantic_mut(self.plane.data).unwrap(); + + // Scale HDR pixels to 16bit (they are usually 10bit or 12bit) + for pixel in transmuted.iter_mut() { + *pixel <<= 16 - self.plane.bits_per_pixel; + } + + if self.has_alpha_channel { + if self.pre_multiplied_alpha { + gdk::MemoryFormat::R16g16b16a16Premultiplied + } else { + gdk::MemoryFormat::R16g16b16a16 + } + } else { + gdk::MemoryFormat::R16g16b16 + } + } + RgbChroma::Rgb | RgbChroma::Rgba => { + if self.has_alpha_channel { + if self.pre_multiplied_alpha { + gdk::MemoryFormat::R8g8b8a8Premultiplied + } else { + gdk::MemoryFormat::R8g8b8a8 + } + } else { + gdk::MemoryFormat::R8g8b8 + } + } + RgbChroma::C444 => unreachable!(), + }; + + let bytes = glib::Bytes::from_owned(self.plane.data.to_vec()); + + let tex = gdk::MemoryTexture::new( + self.plane.width as i32, + self.plane.height as i32, + memory_format, + &bytes, + self.plane.stride, + ); + + tex.upcast() + } +} diff --git a/src/decoder/formats/image_rs_other.rs b/src/decoder/formats/image_rs_other.rs new file mode 100644 index 0000000..7c4bcf8 --- /dev/null +++ b/src/decoder/formats/image_rs_other.rs @@ -0,0 +1,156 @@ +///! Decode using image-rs +use super::*; +use crate::decoder::tiling::{self, TilingStoreExt}; +use crate::deps::*; + +use anyhow::Context; +use arc_swap::ArcSwap; +use gtk::prelude::*; + +use std::path::PathBuf; +use std::sync::Arc; + +#[derive(Debug)] +pub struct ImageRsOther; + +impl ImageRsOther { + pub fn new( + path: PathBuf, + updater: UpdateSender, + tiles: Arc>, + ) -> Self { + updater.spawn_error_handled(move || { + let dimensions = image_rs::image_dimensions(&path).context("Failed to read image")?; + + tiles.set_original_dimensions(dimensions); + + let mut reader = image_rs::io::Reader::open(&path).context("Failed to open image")?; + + // TODO: Set something huge? + reader.limits(image_rs::io::Limits::no_limits()); + + let reader = reader + .with_guessed_format() + .context("Failed to open image")?; + let dynamic_image = reader.decode().context("Failed to decode image")?; + + let decoded_image = Decoded { dynamic_image }; + + let tile = tiling::Tile { + position: (0, 0), + zoom_level: tiling::zoom_to_level(1.), + bleed: 0, + texture: decoded_image.into_texture(), + }; + + tiles.push(tile); + + Ok(()) + }); + + ImageRsOther + } +} + +pub struct Decoded { + dynamic_image: image_rs::DynamicImage, +} + +impl Decoded { + pub fn into_texture(self) -> gdk::Texture { + let (n_bytes, memory_format, layout, data) = match self.dynamic_image { + img @ image_rs::DynamicImage::ImageLuma8(_) => { + let buffer = img.to_rgb8(); + ( + 1, + gdk::MemoryFormat::R8g8b8, + buffer.sample_layout(), + buffer.into_raw(), + ) + } + img @ image_rs::DynamicImage::ImageLumaA8(_) => { + let buffer = img.to_rgba8(); + ( + 1, + gdk::MemoryFormat::R8g8b8a8, + buffer.sample_layout(), + buffer.into_raw(), + ) + } + image_rs::DynamicImage::ImageRgb8(buffer) => ( + 1, + gdk::MemoryFormat::R8g8b8, + buffer.sample_layout(), + buffer.into_raw(), + ), + image_rs::DynamicImage::ImageRgba8(buffer) => ( + 1, + gdk::MemoryFormat::R8g8b8a8, + buffer.sample_layout(), + buffer.into_raw(), + ), + img @ image_rs::DynamicImage::ImageLuma16(_) => { + let buffer = img.to_rgb16(); + ( + 2, + gdk::MemoryFormat::R16g16b16, + buffer.sample_layout(), + image_rs::DynamicImage::from(buffer).into_bytes(), + ) + } + img @ image_rs::DynamicImage::ImageLumaA16(_) => { + let buffer = img.to_rgba16(); + ( + 2, + gdk::MemoryFormat::R16g16b16a16, + buffer.sample_layout(), + image_rs::DynamicImage::from(buffer).into_bytes(), + ) + } + image_rs::DynamicImage::ImageRgb16(buffer) => ( + 2, + gdk::MemoryFormat::R16g16b16, + buffer.sample_layout(), + image_rs::DynamicImage::from(buffer).into_bytes(), + ), + image_rs::DynamicImage::ImageRgba16(buffer) => ( + 2, + gdk::MemoryFormat::R16g16b16a16, + buffer.sample_layout(), + image_rs::DynamicImage::from(buffer).into_bytes(), + ), + image_rs::DynamicImage::ImageRgb32F(buffer) => ( + 4, + gdk::MemoryFormat::R32g32b32Float, + buffer.sample_layout(), + image_rs::DynamicImage::from(buffer).into_bytes(), + ), + image_rs::DynamicImage::ImageRgba32F(buffer) => ( + 4, + gdk::MemoryFormat::R32g32b32a32Float, + buffer.sample_layout(), + image_rs::DynamicImage::from(buffer).into_bytes(), + ), + img => { + let buffer = img.to_rgba16(); + ( + 2, + gdk::MemoryFormat::R16g16b16a16, + buffer.sample_layout(), + safe_transmute::transmute_vec(buffer.into_raw()).unwrap(), + ) + } + }; + + let bytes = glib::Bytes::from_owned(data); + + gdk::MemoryTexture::new( + layout.width as i32, + layout.height as i32, + memory_format, + &bytes, + layout.height_stride * n_bytes, + ) + .upcast() + } +} diff --git a/src/decoder/formats/mod.rs b/src/decoder/formats/mod.rs new file mode 100644 index 0000000..496600a --- /dev/null +++ b/src/decoder/formats/mod.rs @@ -0,0 +1,9 @@ +mod heif; +mod image_rs_other; +mod svg; + +pub use heif::Heif; +pub use image_rs_other::ImageRsOther; +pub use svg::Svg; + +use super::{Decoder, UpdateSender}; diff --git a/src/decoder/formats/svg.rs b/src/decoder/formats/svg.rs new file mode 100644 index 0000000..54f6a22 --- /dev/null +++ b/src/decoder/formats/svg.rs @@ -0,0 +1,219 @@ +///! Decode using librsvg +use super::*; +use crate::decoder::tiling::{self, TilingStoreExt}; +use crate::decoder::TileRequest; +use crate::deps::*; + +use anyhow::Context; +use arc_swap::ArcSwap; +use gtk::prelude::*; + +use std::path::PathBuf; +use std::sync::Arc; + +#[derive(Debug)] +pub struct Svg { + thread_handle: std::thread::JoinHandle<()>, + current_request: Arc>, +} + +#[derive(Default, Debug, Clone)] +enum Request { + #[default] + None, + Exit, + Tile(TileRequest), +} + +impl Request { + fn is_waiting(&self) -> bool { + !matches!(self, Self::None) + } +} + +impl Drop for Svg { + fn drop(&mut self) { + let mut request = self.current_request.write().unwrap(); + *request = Request::Exit; + self.thread_handle.thread().unpark(); + } +} + +impl Svg { + pub fn new( + path: PathBuf, + updater: UpdateSender, + tiles: Arc>, + ) -> Self { + let current_request: Arc> = Default::default(); + let request_store = current_request.clone(); + + let thread_handle = updater.spawn_error_handled(move || { + let handle = rsvg::Loader::new().read_path(path)?; + let renderer = rsvg::CairoRenderer::new(&handle); + + let (original_width, original_height) = svg_dimensions(&renderer); + + tiles.set_original_dimensions((original_width, original_height)); + + loop { + let tile_request = { + let mut request = request_store.write().unwrap(); + let value = request.clone(); + *request = Request::None; + value + }; + + match tile_request { + Request::None => { + std::thread::park(); + } + Request::Exit => { + log::debug!("Terminating decoder thread."); + break; + } + Request::Tile(tile_request) => { + let tiling = tiling::Tiling { + tile_size: tiling::TILE_SIZE, + original_dimensions: (original_width, original_height), + zoom: tile_request.zoom, + bleed: 2, + }; + + let relevant_tiles = tiling.relevant_tiles(&tile_request.viewport); + + for tile_instructions in relevant_tiles { + if request_store.read().unwrap().is_waiting() { + break; + } + + if tiles + .load() + .contains(tile_request.zoom, tile_instructions.coordinates) + { + continue; + } + + let area = tile_instructions.area_with_bleed(); + let surface = cairo::ImageSurface::create( + cairo::Format::ARgb32, + area.width() as i32, + area.height() as i32, + )?; + + let context = cairo::Context::new(surface.clone())?; + let (total_width, total_height) = + tile_instructions.tiling.scaled_dimensions(); + + // librsvg does not currently support larger images + // + // TODO: We are only creating tiles, maybe this check in librsvg is wrong? + if total_height > 32_767 || total_width > 32_767 { + continue; + } + + renderer + .render_document( + &context, + &cairo::Rectangle::new( + -area.x() as f64, + -area.y() as f64, + total_width as f64, + total_height as f64, + ), + ) + .context("Failed to render image")?; + drop(context); + + let decoded_image = Decoded { surface }; + + let tile = tiling::Tile { + position: ( + tile_instructions.area.x() as u32, + tile_instructions.area.y() as u32, + ), + zoom_level: tiling::zoom_to_level(tile_instructions.tiling.zoom), + bleed: 2, + texture: decoded_image.into_texture(), + }; + + tiles.push(tile.clone()); + } + } + } + } + + Ok(()) + }); + + Self { + thread_handle, + current_request, + } + } + + pub fn request(&self, request: TileRequest, _abstract_decoder: &Decoder) -> anyhow::Result<()> { + let mut current_request = self.current_request.write().unwrap(); + *current_request = Request::Tile(request); + self.thread_handle.thread().unpark(); + + Ok(()) + } +} + +pub fn svg_dimensions(renderer: &rsvg::CairoRenderer) -> (u32, u32) { + if let Some((width, height)) = renderer.intrinsic_size_in_pixels() { + (width.ceil() as u32, height.ceil() as u32) + } else { + match renderer.intrinsic_dimensions() { + rsvg::IntrinsicDimensions { + width: + rsvg::Length { + length: width, + unit: rsvg::LengthUnit::Percent, + }, + height: + rsvg::Length { + length: height, + unit: rsvg::LengthUnit::Percent, + }, + vbox: Some(vbox), + } => ( + (width * vbox.width()).ceil() as u32, + (height * vbox.height()).ceil() as u32, + ), + dimensions => { + log::warn!("Failed to parse SVG dimensions: {dimensions:?}"); + (300, 300) + } + } + } +} + +struct Decoded { + surface: cairo::ImageSurface, +} + +impl Decoded { + pub fn into_texture(self) -> gdk::Texture { + let memory_format = { + #[cfg(target_endian = "little")] + { + gdk::MemoryFormat::B8g8r8a8 + } + + #[cfg(target_endian = "big")] + { + gdk::MemoryFormat::A8r8g8b8 + } + }; + + let width = self.surface.width(); + let height = self.surface.height(); + let stride = self.surface.stride() as usize; + + let bytes = glib::Bytes::from_owned(self.surface.take_data().unwrap().to_vec()); + + gdk::MemoryTexture::new(width, height, memory_format, &bytes, stride).upcast() + } +} diff --git a/src/decoder/mod.rs b/src/decoder/mod.rs new file mode 100644 index 0000000..b40d292 --- /dev/null +++ b/src/decoder/mod.rs @@ -0,0 +1,158 @@ +///! Decodes several image formats +mod formats; +pub mod tiling; + +use crate::image_metadata::ImageMetadata; +use formats::*; +use tiling::TilingStoreExt; + +use anyhow::Context; +use arc_swap::ArcSwap; +use futures::channel::mpsc; +use gio::prelude::*; +use gtk::graphene; + +use std::io::Read; +use std::sync::Arc; + +#[derive(Clone, Copy, Debug)] +pub enum ImageFormat { + ImageRs(image_rs::ImageFormat), + // TODO: Add details about contained format + Heif, + Svg, +} + +impl std::fmt::Display for ImageFormat { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> Result<(), std::fmt::Error> { + match self { + Self::ImageRs(format) => write!(f, "{:?}", format), + Self::Heif => write!(f, "HEIF"), + Self::Svg => write!(f, "SVG"), + } + } +} + +#[derive(Clone, Copy, Debug)] +/// Renderer requests new tiles +/// +/// This happens for initial loading or when zoom level or viewing area changes. +pub struct TileRequest { + pub viewport: graphene::Rect, + pub zoom: f64, +} + +#[derive(Debug, Clone)] +/// Signals update to the renderer +pub struct UpdateSender { + sender: mpsc::UnboundedSender, +} + +#[derive(Debug)] +/// Signals for renderer +pub enum DecoderUpdate { + /// Dimensions of image in `TilingSore` available/updated + Dimensions, + /// Metadata available + Metadata(ImageMetadata), + /// Image format determined + Format(ImageFormat), + /// New image data available, redraw + Redraw, + /// And error occured during decoding + Error(anyhow::Error), +} + +impl UpdateSender { + pub fn send(&self, update: DecoderUpdate) { + let result = self.sender.unbounded_send(update); + + if let Err(err) = result { + log::error!("Failed to send update: {err}"); + } + } + + /// Send occuring errors to renderer + pub fn spawn_error_handled(&self, f: F) -> std::thread::JoinHandle<()> + where + F: FnOnce() -> Result<(), anyhow::Error> + Send + 'static, + { + let update_sender = self.clone(); + std::thread::spawn(move || { + if let Err(err) = f() { + update_sender.send(DecoderUpdate::Error(err)); + } + }) + } +} + +#[derive(Debug)] +pub struct Decoder { + decoder: FormatDecoder, +} + +#[derive(Debug)] +enum FormatDecoder { + ImageRsOther(ImageRsOther), + Svg(Svg), + Heif(Heif), +} + +impl Decoder { + /// Get new image decoder + /// + /// The textures will be stored in the passed `TilingStore`. + /// The renderer should listen to updates from the returned receiver. + pub fn new( + file: gio::File, + tiles: Arc>, + ) -> anyhow::Result<(Self, mpsc::UnboundedReceiver)> { + let path = file.path().context("Need a file path")?; + let (sender, receiver) = mpsc::unbounded(); + + let update_sender = UpdateSender { sender }; + tiles.set_update_sender(update_sender.clone()); + + update_sender.send(DecoderUpdate::Metadata(ImageMetadata::load(&path))); + + let mut buf = Vec::new(); + let file = std::fs::File::open(&path)?; + file.take(64).read_to_end(&mut buf)?; + let format = image_rs::guess_format(&buf); + + let decoder = if let Ok(format) = format { + match format { + image_rs::ImageFormat::Avif => { + update_sender.send(DecoderUpdate::Format(ImageFormat::Heif)); + FormatDecoder::Heif(Heif::new(path, update_sender, tiles)) + } + format => { + update_sender.send(DecoderUpdate::Format(ImageFormat::ImageRs(format))); + FormatDecoder::ImageRsOther(ImageRsOther::new(path, update_sender, tiles)) + } + } + } + // TODO: Use mime glib mime type detection + else if [Some("svg"), Some("svgz")].contains(&path.extension().and_then(|x| x.to_str())) { + update_sender.send(DecoderUpdate::Format(ImageFormat::Svg)); + FormatDecoder::Svg(Svg::new(path, update_sender, tiles)) + } else if [Some("heic"), Some("avif")].contains(&path.extension().and_then(|x| x.to_str())) + { + update_sender.send(DecoderUpdate::Format(ImageFormat::Heif)); + FormatDecoder::Heif(Heif::new(path, update_sender, tiles)) + } else { + return None.context("unknown image format"); + }; + + Ok((Self { decoder }, receiver)) + } + + /// Request missing tiles + pub fn request(&self, tile_request: TileRequest) { + match &self.decoder { + FormatDecoder::Svg(svg) => svg.request(tile_request, self).unwrap(), + FormatDecoder::Heif(_) => {} + _ => {} + }; + } +} diff --git a/src/decoder/tiling.rs b/src/decoder/tiling.rs new file mode 100644 index 0000000..6e8c47a --- /dev/null +++ b/src/decoder/tiling.rs @@ -0,0 +1,483 @@ +///! Tiled renderer +///! +///! It is not always feasible or desiareble to store the complete decoded +///! image in the VRAM. [`TilingStore`] allows to compose the parts of +///! the image currently viewed, out of smaller [`Tiles`]. +///! +///! This is especially important for SVGs where every the image has to +///! be re-genearted for each zoom level. It can also be used to allow +///! showing large JPEGs etc where the complete decoded image would not +///! even fit in the VRAM. +use super::{DecoderUpdate, UpdateSender}; +use crate::deps::*; + +use arc_swap::ArcSwap; +use gtk::prelude::*; + +use std::collections::BTreeMap; +use std::sync::Arc; + +const ZOOM_SIGNIFICANT_DIGETS: i32 = 6; +pub const TILE_SIZE: u16 = 4000; + +pub type ZoomLevel = u32; +pub type Coordinate = u32; +pub type Coordinates = (u32, u32); + +#[derive(Clone, Debug)] +/// Part of the image +pub struct Tile { + pub position: Coordinates, + pub zoom_level: ZoomLevel, + /// Border that gets cut away to avoid scaling artifacts at the sides + pub bleed: u8, + pub texture: gdk::Texture, +} + +impl Tile { + pub fn area(&self) -> graphene::Rect { + let bleed = self.bleed as f32; + // The texture is rendered slightly larger such that bleed can be cut away + self.area_with_bleed().inset_r(bleed, bleed) + } + + pub fn area_with_bleed(&self) -> graphene::Rect { + let width = self.texture.width() as f32; + let height = self.texture.height() as f32; + let bleed = self.bleed as f32; + graphene::Rect::new( + self.position.0 as f32 - bleed, + self.position.1 as f32 - bleed, + width, + height, + ) + } + + pub fn zoom(&self) -> f32 { + level_to_zoom(self.zoom_level) as f32 + } + + /// Add tile to snapshot + /// + /// This is where parts of images actually land on the screen + pub fn add_to_snapshot(&self, snapshot: >k::Snapshot, options: &RenderOptions) { + let zoom = self.zoom(); + let area = self.area().scale(1. / zoom, 1. / zoom); + let area_with_bleed = self.area_with_bleed().scale(1. / zoom, 1. / zoom); + + // TODO: do not clip outer bounderies of the image + snapshot.push_clip(&area); + snapshot.append_color(&gdk::RGBA::new(0.118, 0.118, 0.118, 1.), &area); + snapshot.append_scaled_texture(&self.texture, options.scaling_filter, &area_with_bleed); + snapshot.pop(); + } + + fn coordinates(&self) -> Coordinates { + self.position + } +} + +#[derive(Clone, Debug)] +/// Store tiles for image +pub struct TilingStore { + pub tiles: BTreeMap>, + /// Tile size without bleed + pub tile_size: u16, + /// Complete image size + pub original_dimensions: Option, + pub update_sender: Option, +} + +impl Default for TilingStore { + fn default() -> Self { + Self::new(TILE_SIZE) + } +} + +#[derive(Debug, Clone)] +pub struct RenderOptions { + pub scaling_filter: gsk::ScalingFilter, +} + +impl TilingStore { + pub fn new(tile_size: u16) -> Self { + TilingStore { + tiles: Default::default(), + tile_size, + original_dimensions: None, + update_sender: None, + } + } + + /// Render tiles as image + /// + /// This is what actually draws the image + pub fn add_to_snapshot(&self, snapshot: >k::Snapshot, zoom: f64, options: &RenderOptions) { + let tiles: Vec<_> = self + .iter_rendering_priority(zoom) + .flat_map(|(_, tiles)| tiles) + .collect(); + + log::trace!("Rendering {} tiles", tiles.len()); + // reverse to put least fitting tiles in the background + for (_, tile) in tiles.iter().rev() { + tile.add_to_snapshot(snapshot, options); + } + } + + pub fn push(&mut self, tile: Tile) { + let map = self.tiles.entry(tile.zoom_level).or_default(); + map.insert(tile.coordinates(), tile); + if let Some(updater) = &self.update_sender { + updater.send(DecoderUpdate::Redraw); + } + } + + pub fn set_original_dimensions(&mut self, dimensions: Coordinates) { + self.original_dimensions = Some(dimensions); + if let Some(updater) = &self.update_sender { + updater.send(DecoderUpdate::Dimensions); + } + } + + pub fn set_update_sender(&mut self, sender: UpdateSender) { + self.update_sender = Some(sender); + } + + pub fn contains(&self, zoom: f64, coordinates: Coordinates) -> bool { + let Some(tile_plane) = self.tiles.get(&zoom_to_level(zoom)) else { return false }; + + tile_plane.contains_key(&coordinates) + } + + /// Tiles ordered by how good they are suited for the zoom level + pub fn iter_rendering_priority( + &self, + zoom: f64, + ) -> impl Iterator)> { + let zoom_level = zoom_to_level(zoom); + self.tiles + // Tiles at zoom level and with better resolution + .range(zoom_level..) + // Afterwards try tiles with worse resolution + .chain(self.tiles.range(..zoom_level).rev()) + } + + /// Viewport within zoom coordinates + /// + /// TODO: This is the most buggy function ever + pub fn cleanup(&mut self, zoom: f64, viewport: graphene::Rect) { + let Some(original_dimensions) = self.original_dimensions else { log::error!("Too-early cleanup: Original dimension not known"); return; }; + + let mut kept_tiles = Self::new(self.tile_size); + + // Always work with 100% zoom level for tiles + let mut missing_tiles = vec![viewport.scale(1. / zoom as f32, 1. / zoom as f32)]; + + // Keep tiles in top layer + // TODO: Change the top layer if window size changes + if let Some(top_layer) = self.tiles.iter().next() { + for tile in top_layer.1.values() { + kept_tiles.push(tile.clone()); + } + } + + // Put all tiles in correct processing order + let stored_tiles = self.iter_rendering_priority(zoom); + + for (zoom_level, tiles) in stored_tiles { + let tile_zoom = level_to_zoom(*zoom_level); + + let mut next_missing_tiles = vec![]; + + // Tiling for this zoom level and viewport + let tiling = Tiling { + tile_size: self.tile_size, + original_dimensions, + zoom: tile_zoom, + bleed: 2, + }; + + for mut tile_instruction in tiling.relevant_tiles(&viewport) { + // scale to 100% reference + tile_instruction.area = tile_instruction + .area + .scale(1. / tile_zoom as f32, 1. / tile_zoom as f32); + + // Determine if tile of this zoom level has overlap with a missing tile + let mut intersections = missing_tiles + .iter() + .filter(|missing| tile_instruction.area.is_intersected(missing)) + .peekable(); + + if intersections.peek().is_some() { + if let Some(tile) = tiles.get(&tile_instruction.coordinates) { + // we have this tile and can provide it + kept_tiles.push(tile.clone()); + } else { + for missing in missing_tiles.iter() { + if let Some(overlap) = missing.intersection(&tile_instruction.area) { + next_missing_tiles.push(overlap); + } + } + } + } + } + + missing_tiles = next_missing_tiles; + } + + kept_tiles.tiles.values().flat_map(|x| x.iter()).count(); + + self.tiles = kept_tiles.tiles; + } +} + +#[derive(Debug, Clone, Copy)] +/// Abstract definition of a tiling +pub struct Tiling { + pub tile_size: u16, + pub original_dimensions: Coordinates, + pub zoom: f64, + pub bleed: u8, +} + +impl Tiling { + pub fn scaled_dimensions(&self) -> Coordinates { + let (w, h) = self.original_dimensions; + ( + (w as f64 * self.zoom) as Coordinate, + (h as f64 * self.zoom) as Coordinate, + ) + } + + /// Returns relevant tiles + /// + /// This gives what tiles are needed for convering the `preload_area`. + /// Decoders use the [`TileInstructions`] to know what they have to render. + pub fn relevant_tiles(&self, preload_area: &graphene::Rect) -> Vec { + let tile_size = self.tile_size as f32; + + let x0 = (preload_area.x() / tile_size).floor() * tile_size; + let x1 = ((preload_area.x() + preload_area.width()) / tile_size).ceil() * tile_size; + let y0 = (preload_area.y() / tile_size).floor() * tile_size; + let y1 = ((preload_area.y() + preload_area.height()) / tile_size).ceil() * tile_size; + + let mut tiles = Vec::new(); + for x in (x0 as u32..x1 as u32).step_by(self.tile_size.into()) { + for y in (y0 as u32..y1 as u32).step_by(self.tile_size.into()) { + let width = tile_size + + f32::min( + 0., + self.scaled_dimensions().0 as f32 - (x as f32 + tile_size), + ); + let height = tile_size + + f32::min( + 0., + self.scaled_dimensions().1 as f32 - (y as f32 + tile_size), + ); + + let area = graphene::Rect::new(x as f32, y as f32, width, height); + let tile = TileInstructions { + tiling: *self, + area, + coordinates: (x, y), + }; + tiles.push(tile); + } + } + + tiles.sort_by_key(|tile| tile.area.center().distance(&preload_area.center()).0 as u32); + + tiles + } +} + +#[derive(Debug, Copy, Clone)] +/// Instruction for decoder to generate this tile +pub struct TileInstructions { + pub tiling: Tiling, + pub area: graphene::Rect, + pub coordinates: Coordinates, +} + +impl TileInstructions { + pub fn area_with_bleed(&self) -> graphene::Rect { + let bleed = self.tiling.bleed as f32; + self.area.inset_r(-bleed, -bleed) + } +} + +/// Integer zoom levels are used because they are hashable +/// and ignore tiny float calculation errors. +pub fn zoom_to_level(zoom: f64) -> u32 { + (zoom * 10_f64.powi(ZOOM_SIGNIFICANT_DIGETS)) as u32 +} + +pub fn level_to_zoom(zoom_level: u32) -> f64 { + zoom_level as f64 * 10_f64.powi(-ZOOM_SIGNIFICANT_DIGETS) +} + +pub fn zoom_normalize(zoom: f64) -> f64 { + level_to_zoom(zoom_to_level(zoom)) +} + +trait RectExt { + fn is_intersected(&self, b: &graphene::Rect) -> bool; +} + +impl RectExt for graphene::Rect { + fn is_intersected(&self, b: &graphene::Rect) -> bool { + self.intersection(b).is_some() + } +} + +pub trait TilingStoreExt { + fn push(&self, tile: Tile); + fn set_original_dimensions(&self, size: Coordinates); + fn set_update_sender(&self, sender: UpdateSender); +} + +impl TilingStoreExt for ArcSwap { + fn push(&self, tile: Tile) { + self.rcu(|tiling_store| { + let mut new_store = (**tiling_store).clone(); + new_store.push(tile.clone()); + Arc::new(new_store) + }); + } + + fn set_original_dimensions(&self, size: Coordinates) { + self.rcu(|tiling_store| { + let mut new_store = (**tiling_store).clone(); + new_store.set_original_dimensions(size); + Arc::new(new_store) + }); + } + + fn set_update_sender(&self, sender: UpdateSender) { + self.rcu(|tiling_store| { + let mut new_store = (**tiling_store).clone(); + new_store.set_update_sender(sender.clone()); + Arc::new(new_store) + }); + } +} + +#[cfg(test)] +mod test { + use super::*; + + #[test] + fn test_tiling_cleanup() { + let mut store = TilingStore::new(2); + + let toplevel = Tile { + position: (0, 0), + zoom_level: zoom_to_level(1.), + bleed: 0, + texture: gdk::MemoryTexture::new( + 4, + 4, + gdk::MemoryFormat::R8g8b8, + &glib::Bytes::from_static(&[0; 4 * 4 * 3]), + 4 * 3, + ) + .upcast(), + }; + store.push(toplevel); + + let right_level = Tile { + position: (0, 0), + zoom_level: zoom_to_level(1.5), + bleed: 0, + texture: gdk::MemoryTexture::new( + 2, + 2, + gdk::MemoryFormat::R8g8b8, + &glib::Bytes::from_static(&[0; 2 * 2 * 3]), + 2 * 3, + ) + .upcast(), + }; + store.push(right_level); + + let low_level = Tile { + position: (2, 0), + zoom_level: zoom_to_level(1.6), + bleed: 0, + texture: gdk::MemoryTexture::new( + 2, + 2, + gdk::MemoryFormat::R8g8b8, + &glib::Bytes::from_static(&[0; 2 * 2 * 3]), + 2 * 3, + ) + .upcast(), + }; + store.push(low_level); + + let overlapped = Tile { + position: (0, 0), + zoom_level: zoom_to_level(1.7), + bleed: 0, + texture: gdk::MemoryTexture::new( + 2, + 2, + gdk::MemoryFormat::R8g8b8, + &glib::Bytes::from_static(&[0; 2 * 2 * 3]), + 2 * 3, + ) + .upcast(), + }; + store.push(overlapped); + + store.cleanup(1.5, graphene::Rect::new(0., 0., 4. * 1.5, 4. * 1.5)); + + assert!(store.tiles.get(&1000000).unwrap().get(&(0, 0)).is_some()); + assert!(store.tiles.get(&1500000).unwrap().get(&(0, 0)).is_some()); + assert!(store.tiles.get(&1600000).unwrap().get(&(2, 0)).is_some()); + // TODO: This should pass, but it does not + //assert!(store.tiles.get(&1700000).is_none()); + } + + #[test] + fn test_tiling_cleanup_simple() { + let mut store = TilingStore::new(2); + + let toplevel = Tile { + position: (0, 0), + zoom_level: zoom_to_level(1.), + bleed: 0, + texture: gdk::MemoryTexture::new( + 4, + 4, + gdk::MemoryFormat::R8g8b8, + &glib::Bytes::from_static(&[0; 4 * 4 * 3]), + 4 * 3, + ) + .upcast(), + }; + store.push(toplevel); + + let detailled = Tile { + position: (2, 2), + zoom_level: zoom_to_level(2.), + bleed: 0, + texture: gdk::MemoryTexture::new( + 4, + 4, + gdk::MemoryFormat::R8g8b8, + &glib::Bytes::from_static(&[0; 4 * 4 * 3]), + 4 * 3, + ) + .upcast(), + }; + store.push(detailled); + + store.cleanup(1.5, graphene::Rect::new(0., 0., 20., 20.)); + + assert!(store.tiles.get(&1000000).unwrap().get(&(0, 0)).is_some()); + assert!(store.tiles.get(&2000000).unwrap().get(&(2, 2)).is_some()); + } +} diff --git a/src/main.rs b/src/main.rs index f7c39be..3fb7ab7 100644 --- a/src/main.rs +++ b/src/main.rs @@ -22,6 +22,7 @@ use gtk::gio::{self, prelude::*}; mod application; mod config; +mod decoder; mod file_model; mod i18n; mod image_metadata; @@ -31,7 +32,7 @@ mod widgets; mod window; mod deps { - pub use gtk::{gdk, gdk_pixbuf, gio, glib, graphene, gsk}; + pub use gtk::{cairo, gdk, gdk_pixbuf, gio, glib, graphene, gsk}; } use application::LpApplication; diff --git a/src/meson.build b/src/meson.build index d41fced..33b1f5d 100644 --- a/src/meson.build +++ b/src/meson.build @@ -67,4 +67,17 @@ cargo_doc = custom_target( cargo, 'doc', cargo_options + ['--no-deps', '--document-private-items'], ], -) \ No newline at end of file +) + +test( + 'cargo-test', + cargo, + args: [ + 'test', + cargo_options, + '--', + '--test-threads=1', + ], + is_parallel: false, + timeout: 1800, +) diff --git a/src/widgets/image.rs b/src/widgets/image.rs index 0d45992..2243860 100644 --- a/src/widgets/image.rs +++ b/src/widgets/image.rs @@ -17,18 +17,22 @@ // SPDX-License-Identifier: GPL-3.0-or-later use crate::deps::*; + +use crate::decoder::{self, tiling, Decoder, DecoderUpdate}; use crate::i18n::i18n; -use crate::image_metadata::{ImageMetadata, LpImageMetadata}; +use crate::image_metadata::LpImageMetadata; use crate::util; use adw::{prelude::*, subclass::prelude::*}; -use anyhow::Context; +use arc_swap::ArcSwap; +use futures::prelude::*; use gtk_macros::spawn; use once_cell::sync::Lazy; use once_cell::unsync::OnceCell; use std::cell::{Cell, RefCell}; use std::path::{Path, PathBuf}; +use std::sync::Arc; /// Milliseconds const ZOOM_ANIMATION_DURATION: u32 = 200; @@ -65,9 +69,13 @@ mod imp { pub is_deleted: Cell, /// Track changes to this image pub file_monitor: RefCell>, - pub texture: RefCell>, - /// Set when image is completely loaded and ready for displaying + pub tiles: Arc>, + pub decoder: RefCell>>, + + /// Set to true when image is ready for displaying pub is_loaded: Cell, + /// Set if an error has occurred, shown on error_page + pub error: RefCell>, /// Rotation final value (can differ from `rotation` during animation) pub rotation_target: Cell, @@ -101,8 +109,6 @@ mod imp { /// Currently EXIF data pub image_metadata: RefCell, - /// This will often be available earlier than the complete image - pub image_original_size: Cell<(i32, i32)>, /// Current pointer position pub pointer_position: Cell>, @@ -142,6 +148,7 @@ mod imp { glib::ParamSpecBoolean::builder("is-loaded") .read_only() .build(), + glib::ParamSpecBoolean::builder("error").read_only().build(), glib::ParamSpecObject::builder::("metadata") .read_only() .build(), @@ -180,6 +187,7 @@ mod imp { "path" => obj.path().to_variant().to_value(), "is-deleted" => obj.is_deleted().to_value(), "is-loaded" => obj.is_loaded().to_value(), + "error" => obj.error().to_value(), "metadata" => obj.metadata().to_value(), "rotation" => obj.rotation().to_value(), "mirrored" => obj.mirrored().to_value(), @@ -228,6 +236,8 @@ mod imp { fn dispose(&self) { let obj = self.obj(); + log::debug!("Disposing LpImage"); + // remove target from zoom animation because it's property of this object obj.rotation_animation() .set_target(&adw::CallbackAnimationTarget::new(|_| {})); @@ -455,13 +465,8 @@ mod imp { // ensure there is an actual size change if self.widget_dimensions.get() != (width, height) { self.widget_dimensions.set((width, height)); - // calculate new zoom value for best fit - if widget.is_best_fit() { - let best_fit_level = widget.zoom_level_best_fit(); - self.zoom.set(best_fit_level); - self.zoom_target.set(best_fit_level); - self.obj().zoom_animation().pause(); - } + + widget.configure_best_fit(); } widget.configure_adjustments(); @@ -469,67 +474,88 @@ mod imp { // called when the widget content should be re-rendered fn snapshot(&self, snapshot: >k::Snapshot) { - if let Some(texture) = self.texture.borrow().as_ref() { - let widget = self.obj(); - let widget_width = widget.width() as f64; - let widget_height = widget.height() as f64; + let widget = self.obj(); + let widget_width = widget.width() as f64; + let widget_height = widget.height() as f64; + let (original_width, original_height) = widget.original_dimensions(); + let display_width = widget.image_displayed_width(); + let display_height = widget.image_displayed_height(); - // make sure the scrollbars are correct - widget.configure_adjustments(); + // make sure the scrollbars are correct + widget.configure_adjustments(); - snapshot.save(); + let applicable_zoom = widget.applicable_zoom(); - // apply the scrolling position to the image - if let Some(adj) = self.hadjustment.borrow().as_ref() { - let x = -(adj.value() - (adj.upper() - widget_width) / 2.); - snapshot.translate(&graphene::Point::new(x as f32, 0.)); - } - if let Some(adj) = self.vadjustment.borrow().as_ref() { - let y = -(adj.value() - (adj.upper() - widget_height) / 2.); - snapshot.translate(&graphene::Point::new(0., y as f32)); - } + let scaling_filter = gsk::ScalingFilter::Linear; + /* + let scaling_filter = if applicable_zoom < 1. { + gsk::ScalingFilter::Trilinear + } else { + gsk::ScalingFilter::Nearest + }; + */ - // Center image in coordinates. - // Needed for rotating around the center of the image, and - // mirroring the image does not put it to a completely different position. - snapshot.translate(&graphene::Point::new( - (widget_width / 2.) as f32, - (widget_height / 2.) as f32, - )); + let render_options = tiling::RenderOptions { scaling_filter }; - // vertical centering in widget when no scrolling - let x = f64::max((widget_width - widget.image_displayed_width()) / 2.0, 0.); - let y = f64::max((widget_height - widget.image_displayed_height()) / 2.0, 0.); - // round to pixel values to not have a half pixel offset to physical pixels - // the offset would leading to a blurry output - snapshot.translate(&graphene::Point::new( - widget.round(x) as f32, - widget.round(y) as f32, - )); + // Operations on snapshots are coordinate transformations + // It might help to read the following code from bottom to top + snapshot.save(); - // apply the transformations from properties - snapshot.rotate(widget.rotation() as f32); - if widget.mirrored() { - snapshot.scale(-1., 1.); - } - snapshot.scale(widget.zoom() as f32, widget.zoom() as f32); - - // scale to actual pixel size - // this is needed since usually the texture would just fill the widget - snapshot.scale( - (texture.width() as f64 / widget_width) as f32, - (texture.height() as f64 / widget_height) as f32, - ); - - // move back to original position - snapshot.translate(&graphene::Point::new( - (-widget_width / 2.) as f32, - (-widget_height / 2.) as f32, - )); - - texture.snapshot(snapshot, widget_width, widget_height); - snapshot.restore(); + // Apply the scrolling position to the image + if let Some(adj) = self.hadjustment.borrow().as_ref() { + let x = -(adj.value() - (adj.upper() - display_width) / 2.); + snapshot.translate(&graphene::Point::new(x as f32, 0.)); } + if let Some(adj) = self.vadjustment.borrow().as_ref() { + let y = -(adj.value() - (adj.upper() - display_height) / 2.); + snapshot.translate(&graphene::Point::new(0., y as f32)); + } + + // Centering in widget when no scrolling (black bars around image) + let x = f64::max((widget_width - display_width) / 2.0, 0.).floor(); + let y = f64::max((widget_height - display_height) / 2.0, 0.).floor(); + // Round to pixel values to not have a half pixel offset to physical pixels + // The offset would leading to a blurry output + snapshot.translate(&graphene::Point::new( + widget.round(x) as f32, + widget.round(y) as f32, + )); + + // Apply zoom + snapshot.scale(applicable_zoom as f32, applicable_zoom as f32); + + // Put image origin at (0, 0) again with rotation + snapshot.translate(&graphene::Point::new( + -(original_width as f32 - display_width as f32 / applicable_zoom as f32) / 2., + -(original_height as f32 - display_height as f32 / applicable_zoom as f32) / 2., + )); + + // Undo centering in coordinates + snapshot.translate(&graphene::Point::new( + original_width as f32 / 2., + original_height as f32 / 2., + )); + + // Apply the transformations from properties + snapshot.rotate(widget.rotation() as f32); + if widget.mirrored() { + snapshot.scale(-1., 1.); + } + + // Center image in coordinates. + // Needed for rotating around the center of the image, and + // mirroring the image does not put it to a completely different position. + snapshot.translate(&graphene::Point::new( + -original_width as f32 / 2., + -original_height as f32 / 2., + )); + + // Add texture(s) + self.tiles + .load() + .add_to_snapshot(snapshot, applicable_zoom, &render_options); + + snapshot.restore(); } fn measure(&self, orientation: gtk::Orientation, _for_size: i32) -> (i32, i32, i32, i32) { @@ -619,65 +645,83 @@ glib::wrapper! { } impl LpImage { - pub async fn load(&self, path: &Path) -> anyhow::Result<()> { - let imp = self.imp(); + pub async fn load(&self, path: &Path) { let path = path.to_path_buf(); let file = gio::File::for_path(&path); self.set_file(&file); if !path.is_file() { - return Err(anyhow::Error::msg(i18n("File does not exist"))); + self.set_error(anyhow::Error::msg(i18n("File does not exist"))); + return; } log::debug!("Loading file {path:?}"); - log::debug!("Loading EXIF"); - let metadata = util::spawn( - "image-load-exif", - glib::clone!(@strong path => move || ImageMetadata::load(&path)), + let tiles = &self.imp().tiles; + // TODO: Fix two unwraps + let (decoder, mut decoder_update) = util::spawn( + "image-init-decoder", + glib::clone!(@strong file, @strong tiles => move || { + Decoder::new(file.clone(), tiles.clone()) + }), ) - .await?; - let orientation = metadata.orientation(); + .await + .unwrap() + .unwrap(); - imp.image_metadata.replace(LpImageMetadata::from(metadata)); - imp.rotation_target.set(-orientation.rotation); - imp.rotation.set(-orientation.rotation); - imp.mirrored.set(orientation.mirrored); + let weak_obj = self.downgrade(); + spawn!(async move { + while let Some(update) = decoder_update.next().await { + if let Some(obj) = weak_obj.upgrade() { + obj.update(update); + } + } + log::debug!("Stopped listening to decoder since sender is gone"); + }); - self.notify("metadata"); + let decoder = Arc::new(decoder); + self.imp().decoder.replace(Some(decoder)); + } - log::debug!("Loading image size"); - let (_, x, y) = gdk_pixbuf::Pixbuf::file_info_future(path.clone()) - .await? - .context(i18n("Failed to load image size"))?; - imp.image_original_size.set((x, y)); - self.notify("image-size"); + /// Called when decoder sends update + pub fn update(&self, update: DecoderUpdate) { + let imp = self.imp(); - log::debug!("Loading complete image"); - let texture = util::spawn( - "image-load-image", - glib::clone!(@weak file => @default-return Err(anyhow::Error::msg(i18n("File does not exist"))), move || { - log::debug!("Creating texture"); - let texture = gdk::Texture::from_file(&file)?; - Ok::<_,anyhow::Error>(texture) - }), - ) - .await? - .context(i18n("Failed to load image"))?; + match update { + DecoderUpdate::Metadata(metadata) => { + log::debug!("Received metadata"); + imp.image_metadata.replace(LpImageMetadata::from(metadata)); + self.notify("metadata"); - imp.texture.replace(Some(texture)); + self.reset_rotation(); + } + DecoderUpdate::Dimensions => { + log::debug!("Received dimensions: {:?}", self.original_dimensions()); + self.notify("image-size"); + self.configure_best_fit(); + self.request_tiles(); + } + DecoderUpdate::Redraw => { + if !self.is_loaded() { + self.imp().is_loaded.set(true); + self.notify("is-loaded"); + } - self.configure_adjustments(); - - self.queue_draw(); - self.queue_allocate(); - self.queue_resize(); - - log::debug!("Image loaded and scheduled for drawing"); - imp.is_loaded.set(true); - self.notify("is-loaded"); - - Ok(()) + self.queue_draw(); + imp.tiles.rcu(|tiles| { + let mut new_tiles = (**tiles).clone(); + // TODO: Use an area larger than the viewport + new_tiles.cleanup(self.imp().zoom_target.get(), self.viewport()); + new_tiles + }); + } + DecoderUpdate::Error(err) => { + self.set_error(err); + } + DecoderUpdate::Format(_) => { + // TODO: Store and use in image properties + } + } } pub fn is_loaded(&self) -> bool { @@ -700,26 +744,34 @@ impl LpImage { /// /// Used for calculating the required zoom level after rotation fn zoom_level_best_fit_for_rotation(&self, rotation: f64) -> f64 { - if let Some(texture) = self.texture() { - let rotated = rotation.to_radians().sin().abs(); - let texture_aspect_ratio = texture.width() as f64 / texture.height() as f64; - let widget_aspect_ratio = self.width() as f64 / self.height() as f64; + let rotated = rotation.to_radians().sin().abs(); + let (image_width, image_height) = self.original_dimensions(); + let texture_aspect_ratio = image_width as f64 / image_height as f64; + let widget_aspect_ratio = self.width() as f64 / self.height() as f64; - let default_zoom = if texture_aspect_ratio > widget_aspect_ratio { - (self.width() as f64 / texture.width() as f64).min(1.) - } else { - (self.height() as f64 / texture.height() as f64).min(1.) - }; - - let rotated_zoom = if 1. / texture_aspect_ratio > widget_aspect_ratio { - (self.width() as f64 / texture.height() as f64).min(1.) - } else { - (self.height() as f64 / texture.width() as f64).min(1.) - }; - - rotated * rotated_zoom + (1. - rotated) * default_zoom + let default_zoom = if texture_aspect_ratio > widget_aspect_ratio { + (self.width() as f64 / image_width as f64).min(1.) } else { - 1. + (self.height() as f64 / image_height as f64).min(1.) + }; + + let rotated_zoom = if 1. / texture_aspect_ratio > widget_aspect_ratio { + (self.width() as f64 / image_height as f64).min(1.) + } else { + (self.height() as f64 / image_width as f64).min(1.) + }; + + rotated * rotated_zoom + (1. - rotated) * default_zoom + } + + /// Sets respective output values if best-fit is active + fn configure_best_fit(&self) { + // calculate new zoom value for best fit + if self.is_best_fit() { + let best_fit_level = self.zoom_level_best_fit(); + self.imp().zoom.set(best_fit_level); + self.set_zoom_target(best_fit_level); + self.zoom_animation().pause(); } } @@ -768,7 +820,7 @@ impl LpImage { let obj = self.clone(); let file = file_a.clone(); // TODO: error handling is missing - spawn! {async move { let _ = obj.load(&file.path().unwrap()).await; }}; + spawn! {async move { obj.load(&file.path().unwrap()).await; }}; } gio::FileMonitorEvent::Deleted | gio::FileMonitorEvent::MovedOut @@ -782,8 +834,7 @@ impl LpImage { /// Texture that contains the original image pub fn texture(&self) -> Option { - let imp = self.imp(); - imp.texture.borrow().clone() + None } fn mirrored(&self) -> bool { @@ -814,6 +865,14 @@ impl LpImage { self.queue_draw(); } + /// Set rotation and mirroring to the state would have after loading + pub fn reset_rotation(&self) { + let orientation = self.metadata().orientation(); + self.imp().rotation_target.set(-orientation.rotation); + self.set_mirrored(orientation.mirrored); + self.set_rotation(-orientation.rotation); + } + pub fn rotate_by(&self, angle: f64) { log::debug!("Rotate by {} degrees", angle); let target = &self.imp().rotation_target; @@ -883,6 +942,10 @@ impl LpImage { self.notify("is-max-zoom"); } + fn applicable_zoom(&self) -> f64 { + decoder::tiling::zoom_normalize(self.zoom()) + } + /// Set zoom level aiming for given position or center if not available fn set_zoom_aiming(&self, mut zoom: f64, aiming: Option<(f64, f64)>) { // allow some deviation from max value for rubberbanding @@ -903,7 +966,6 @@ impl LpImage { minimum * deviation.powf(RUBBERBANDING_EXPONENT), max_deviation, ); - dbg!(zoom); } if zoom == self.zoom() { @@ -953,6 +1015,36 @@ impl LpImage { self.queue_draw(); } + fn set_zoom_target(&self, zoom_target: f64) { + log::debug!("Setting zoom target {zoom_target}"); + + self.imp().zoom_target.set(zoom_target); + + if self.zoom() == self.imp().zoom_target.get() { + self.request_tiles(); + } + } + + fn request_tiles(&self) { + if let Some(decoder) = self.imp().decoder.borrow().as_ref() { + if self.zoom_animation().state() != adw::AnimationState::Playing { + decoder.request(crate::decoder::TileRequest { + viewport: self.viewport(), + zoom: self.imp().zoom_target.get(), + }); + } + } + } + + fn viewport(&self) -> graphene::Rect { + let x = self.hadjustment().value(); + let y = self.vadjustment().value(); + let width = self.widget_width(); + let height = self.widget_height(); + + graphene::Rect::new((x) as f32, (y) as f32, (width) as f32, (height) as f32) + } + /// Animation that makes larger zoom steps (from buttons etc) look smooth fn zoom_animation(&self) -> &adw::TimedAnimation { self.imp().zoom_animation.get_or_init(|| { @@ -965,6 +1057,7 @@ impl LpImage { animation.connect_done(glib::clone!(@weak self as obj => move |_| { obj.imp().zoom_hscrollbar_transition.set(false); obj.imp().zoom_vscrollbar_transition.set(false); + obj.set_zoom_target(obj.imp().zoom_target.get()); })); animation @@ -1050,7 +1143,7 @@ impl LpImage { log::debug!("Zoom to {zoom:.3}"); - self.imp().zoom_target.set(zoom); + self.set_zoom_target(zoom); // abort if already at correct zoom level if zoom == self.zoom() { @@ -1058,39 +1151,44 @@ impl LpImage { return; } - if let Some(texture) = self.texture() { - let current_hborder = self.widget_width() - self.image_displayed_width(); - let target_hborder = self.widget_width() - texture.width() as f64 * zoom; + // wild code + let current_hborder = self.widget_width() - self.image_displayed_width(); + let target_hborder = self.widget_width() - self.image_size().0 as f64 * zoom; - self.imp() - .zoom_hscrollbar_transition - .set(current_hborder.signum() != target_hborder.signum() && current_hborder != 0.); + self.imp() + .zoom_hscrollbar_transition + .set(current_hborder.signum() != target_hborder.signum() && current_hborder != 0.); - let current_vborder = self.widget_height() - self.image_displayed_height(); - let target_vborder = self.widget_height() - texture.height() as f64 * zoom; + let current_vborder = self.widget_height() - self.image_displayed_height(); + let target_vborder = self.widget_height() - self.image_size().1 as f64 * zoom; - self.imp() - .zoom_hscrollbar_transition - .set(current_vborder.signum() != target_vborder.signum() && current_vborder != 0.); + self.imp() + .zoom_hscrollbar_transition + .set(current_vborder.signum() != target_vborder.signum() && current_vborder != 0.); - let animation = self.zoom_animation(); + let animation = self.zoom_animation(); - animation.set_value_from(self.zoom()); - animation.set_value_to(zoom); - animation.play(); - } else { - log::error!("No texture to zoom"); - } + animation.set_value_from(self.zoom()); + animation.set_value_to(zoom); + animation.play(); } /// Image size of original image with EXIF rotation applied pub fn image_size(&self) -> (i32, i32) { let orientation = self.imp().image_metadata.borrow().orientation(); - if orientation.rotation.abs() == 90. { - let (x, y) = self.imp().image_original_size.get(); + if orientation.rotation.abs() == 90. || orientation.rotation.abs() == 270. { + let (x, y) = self.original_dimensions(); (y, x) } else { - self.imp().image_original_size.get() + self.original_dimensions() + } + } + + fn original_dimensions(&self) -> (i32, i32) { + if let Some((width, height)) = self.imp().tiles.load().original_dimensions { + (width as i32, height as i32) + } else { + (0, 0) } } @@ -1100,25 +1198,19 @@ impl LpImage { /// represent the actual size. The size returned might well be /// larger than what can actually be displayed within the widget. pub fn image_displayed_width(&self) -> f64 { - if let Some(texture) = self.texture() { - let rotated = self.rotation().to_radians().sin().abs(); + let (width, height) = self.original_dimensions(); - ((1. - rotated) * texture.width() as f64 + rotated * texture.height() as f64) - * self.zoom() - } else { - 0. - } + let rotated = self.rotation().to_radians().sin().abs(); + + ((1. - rotated) * width as f64 + rotated * height as f64) * self.applicable_zoom() } pub fn image_displayed_height(&self) -> f64 { - if let Some(texture) = self.texture() { - let rotated = self.rotation().to_radians().sin().abs(); + let (width, height) = self.original_dimensions(); - ((1. - rotated) * texture.height() as f64 + rotated * texture.width() as f64) - * self.zoom() - } else { - 0. - } + let rotated = self.rotation().to_radians().sin().abs(); + + ((1. - rotated) * height as f64 + rotated * width as f64) * self.applicable_zoom() } /// Stepwise scrolls inside an image when zoomed in @@ -1162,10 +1254,9 @@ impl LpImage { fn set_hadjustment(&self, adjustment: Option) { if let Some(adj) = &adjustment { adj.connect_value_changed(glib::clone!(@weak self as obj => move |_| { + obj.request_tiles(); obj.queue_draw(); })); - // TODO: needed? - self.queue_allocate(); } self.imp().hadjustment.replace(adjustment); @@ -1184,9 +1275,9 @@ impl LpImage { fn set_vadjustment(&self, adjustment: Option) { if let Some(adj) = &adjustment { adj.connect_value_changed(glib::clone!(@weak self as obj => move |_| { + obj.request_tiles(); obj.queue_draw(); })); - self.queue_allocate(); } self.imp().vadjustment.replace(adjustment); @@ -1201,6 +1292,7 @@ impl LpImage { let widget_width = self.widget_width(); hadjustment.configure( + // value hadjustment.value().clamp(0., self.max_hadjustment_value()), // lower 0., @@ -1269,6 +1361,17 @@ impl LpImage { Some(gdk::ContentProvider::for_value(&list.to_value())) } + /// Returns decoding error if one occured + pub fn error(&self) -> Option { + self.imp().error.borrow().clone() + } + + fn set_error(&self, err: anyhow::Error) { + log::debug!("Decoding error: {err:?}"); + self.imp().error.replace(Some(err.to_string())); + self.notify("error"); + } + /// Returns scaling aware rounded application pixel /// /// One physical pixel is 0.5 application pixels diff --git a/src/widgets/image_page.rs b/src/widgets/image_page.rs index 41a6428..c6101e5 100644 --- a/src/widgets/image_page.rs +++ b/src/widgets/image_page.rs @@ -27,7 +27,6 @@ use gtk::CompositeTemplate; use gtk_macros::spawn; use once_cell::sync::Lazy; -use std::cell::RefCell; use std::path::{Path, PathBuf}; mod imp { @@ -52,9 +51,6 @@ mod imp { pub(super) right_click_gesture: TemplateChild, #[template_child] pub(super) press_gesture: TemplateChild, - - /// set if an error has occurred and is shown on error_page - pub(super) error: RefCell>, } #[glib::object_subclass] @@ -75,12 +71,9 @@ mod imp { impl ObjectImpl for LpImagePage { fn properties() -> &'static [glib::ParamSpec] { static PROPERTIES: Lazy> = Lazy::new(|| { - vec![ - glib::ParamSpecObject::builder::("image") - .read_only() - .build(), - glib::ParamSpecString::builder("error").read_only().build(), - ] + vec![glib::ParamSpecObject::builder::("image") + .read_only() + .build()] }); PROPERTIES.as_ref() @@ -90,7 +83,6 @@ mod imp { let obj = self.obj(); match pspec.name() { "image" => obj.image().to_value(), - "error" => obj.error().to_value(), name => unimplemented!("property {name}"), } } @@ -112,6 +104,30 @@ mod imp { obj.show_popover_at(x, y); gesture.set_state(gtk::EventSequenceState::Claimed); })); + + obj.image().connect_notify_local( + Some("is-loaded"), + clone!(@weak obj => move |_,_| { + if obj.image().is_loaded() { + obj.imp() + .stack + .set_visible_child(&*obj.imp().scrolled_window); + } + }), + ); + + obj.image().connect_notify_local( + Some("error"), + clone!(@weak obj => move |_,_| { + if obj.image().error().is_some() { + obj.imp().stack.set_visible_child(&*obj.imp().error_page); + } + }), + ); + + // Do not waste CPU on spinner if it is not visible + self.spinner.connect_map(|s| s.start()); + self.spinner.connect_unmap(|s| s.stop()); } } @@ -139,19 +155,7 @@ impl LpImagePage { spawn!(clone!(@weak obj, @strong path => async move { let imp = obj.imp(); - match imp.image.load(&path).await { - Ok(()) => { - imp.stack.set_visible_child(&*imp.scrolled_window); - imp.spinner.set_spinning(false); - }, - Err(e) => { - imp.error.replace(Some(e.to_string())); - imp.stack.set_visible_child(&*imp.error_page); - obj.notify("error"); - imp.spinner.set_spinning(false); - log::error!("Could not load image: {e}"); - } - } + imp.image.load(&path).await; })); obj @@ -169,10 +173,6 @@ impl LpImagePage { self.imp().image.texture() } - pub fn error(&self) -> Option { - self.imp().error.borrow().clone() - } - pub fn content_provider(&self) -> Option { self.imp().image.content_provider() } diff --git a/src/widgets/image_view.rs b/src/widgets/image_view.rs index d6f7c22..78dc948 100644 --- a/src/widgets/image_view.rs +++ b/src/widgets/image_view.rs @@ -509,7 +509,7 @@ impl LpImageView { other_pages.remove(&new_page.path()); for (_, page) in other_pages { let image = page.image(); - image.set_rotation(0.); + image.reset_rotation(); image.set_best_fit(true); image.zoom_best_fit(); } diff --git a/src/widgets/sliding_view.rs b/src/widgets/sliding_view.rs index 1ac42c5..584082d 100644 --- a/src/widgets/sliding_view.rs +++ b/src/widgets/sliding_view.rs @@ -163,6 +163,7 @@ mod imp { let transform = gsk::Transform::new().translate(&graphene::Point::new(x, 0.)); page.allocate(width, height, 0, Some(transform)); + page.image().allocate(width, height, 0, None); } } diff --git a/src/window.rs b/src/window.rs index e594565..bc3bdb8 100644 --- a/src/window.rs +++ b/src/window.rs @@ -33,9 +33,6 @@ use crate::util::{self, Direction, Position}; use crate::widgets::{LpImage, LpImageView, LpPropertiesView}; mod imp { - use crate::widgets::LpImagePage; - use once_cell::sync::OnceCell; - use super::*; // To use composite templates, you need @@ -78,8 +75,6 @@ mod imp { pub properties_view: TemplateChild, #[template_child] pub drop_target: TemplateChild, - - current_page_signals: OnceCell, } #[glib::object_subclass] @@ -270,24 +265,20 @@ mod imp { }), ); - let current_page_signals = glib::SignalGroup::new(LpImagePage::static_type()); - self.image_view.connect_notify_local( - Some("current-page"), - glib::clone!(@weak obj, @weak current_page_signals => move |view, _| { - current_page_signals.set_target(view.current_page().as_ref()); - obj.images_available(); - }), - ); - - current_page_signals.connect_closure( + current_image_signals.connect_closure( "notify::error", false, - glib::closure_local!(@watch win => move |_: &LpImagePage, _: &glib::ParamSpec| { + glib::closure_local!(@watch win => move |_: &LpImage, _: &glib::ParamSpec| { win.image_error(); }), ); - self.current_page_signals.set(current_page_signals).unwrap(); + self.image_view.connect_notify_local( + Some("current-page"), + glib::clone!(@weak obj => move |_, _| { + obj.images_available(); + }), + ); // action win.previous status self.image_view.connect_notify_local( @@ -647,7 +638,7 @@ impl LpWindow { let current_page = self.imp().image_view.current_page(); if let Some(page) = current_page { - if page.error().is_some() { + if page.image().error().is_some() { log::debug!("Showing window because loading image failed"); self.present(); }