diff --git a/BUILDING.md b/BUILDING.md index cec0a3ab1a..918b9b789b 100644 --- a/BUILDING.md +++ b/BUILDING.md @@ -139,7 +139,7 @@ Install all needed dependencies and build tools, e.g. for Fedora: ```bash sudo dnf install gcc gcc-c++ clang clang-devel python3 make cmake meson git appstream gettext desktop-file-utils \ shared-mime-info kernel-devel gtk4-devel libadwaita-devel poppler-glib-devel poppler-data alsa-lib-devel \ - appstream-devel + appstream-devel enchant2-devel ``` For Debian based distros: diff --git a/Cargo.lock b/Cargo.lock index b10c9706ba..ca8d6443aa 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -54,12 +54,6 @@ dependencies = [ "pkg-config", ] -[[package]] -name = "android-tzdata" -version = "0.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e999941b234f3131b00bc13c22d06e8c5ff726d1b6318ac7eb276997bbb4fef0" - [[package]] name = "android_system_properties" version = "0.1.5" @@ -71,9 +65,9 @@ dependencies = [ [[package]] name = "anstream" -version = "0.6.20" +version = "0.6.21" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3ae563653d1938f79b1ab1b5e668c87c76a9930414574a6583a7b7e11a8e6192" +checksum = "43d5b281e737544384e969a5ccad3f1cdd24b48086a0fc1b2a5262a26b8f4f4a" dependencies = [ "anstyle", "anstyle-parse", @@ -86,9 +80,9 @@ dependencies = [ [[package]] name = "anstyle" -version = "1.0.11" +version = "1.0.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "862ed96ca487e809f1c8e5a8447f6ee2cf102f846893800b20cebdf541fc6bbd" +checksum = "5192cca8006f1fd4f7237516f40fa183bb07f8fbdfedaa0036de5ea9b0b45e78" [[package]] name = "anstyle-parse" @@ -121,9 +115,9 @@ dependencies = [ [[package]] name = "anyhow" -version = "1.0.99" +version = "1.0.100" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b0674a1ddeecb70197781e945de4b3b8ffb61fa939a5597bcf48503737663100" +checksum = "a23eb6b1614318a8071c9b2521f36b424b2c83db5eb3a0fead4a6c0809af6e61" [[package]] name = "approx" @@ -203,9 +197,9 @@ dependencies = [ [[package]] name = "async-fs" -version = "2.1.3" +version = "2.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "09f7e37c0ed80b2a977691c47dae8625cfb21e205827106c64f7c588766b2e50" +checksum = "8034a681df4aed8b8edbd7fbe472401ecf009251c8b40556b304567052e294c5" dependencies = [ "async-lock", "blocking", @@ -214,11 +208,11 @@ dependencies = [ [[package]] name = "async-io" -version = "2.5.0" +version = "2.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "19634d6336019ef220f09fd31168ce5c184b295cbf80345437cc36094ef223ca" +checksum = "456b8a8feb6f42d237746d4b3e9a178494627745c3c56c6ea55d92ba50d026fc" dependencies = [ - "async-lock", + "autocfg", "cfg-if", "concurrent-queue", "futures-io", @@ -227,7 +221,7 @@ dependencies = [ "polling", "rustix", "slab", - "windows-sys 0.60.2", + "windows-sys 0.61.1", ] [[package]] @@ -254,9 +248,9 @@ dependencies = [ [[package]] name = "async-process" -version = "2.4.0" +version = "2.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "65daa13722ad51e6ab1a1b9c01299142bc75135b337923cfa10e79bbbd669f00" +checksum = "fc50921ec0055cdd8a16de48773bfeec5c972598674347252c0399676be7da75" dependencies = [ "async-channel", "async-io", @@ -272,9 +266,9 @@ dependencies = [ [[package]] name = "async-signal" -version = "0.2.12" +version = "0.2.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f567af260ef69e1d52c2b560ce0ea230763e6fbb9214a85d768760a920e3e3c1" +checksum = "43c070bbf59cd3570b6b2dd54cd772527c7c3620fce8be898406dd3ed6adc64c" dependencies = [ "async-io", "async-lock", @@ -285,7 +279,7 @@ dependencies = [ "rustix", "signal-hook-registry", "slab", - "windows-sys 0.60.2", + "windows-sys 0.61.1", ] [[package]] @@ -425,9 +419,9 @@ checksum = "d71b6127be86fdcfddb610f7182ac57211d4b18a3e9c82eb2d17662f2227ad6a" [[package]] name = "cairo-rs" -version = "0.21.1" +version = "0.21.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1158f326d7b755a9ae2b36c5b5391400e3431f3b77418cedb6d7130126628f10" +checksum = "dfe4354df4da648870e363387679081f8f9fc538ec8b55901e3740c6a0ef81b1" dependencies = [ "bitflags 2.9.4", "cairo-sys-rs", @@ -437,9 +431,9 @@ dependencies = [ [[package]] name = "cairo-sys-rs" -version = "0.21.1" +version = "0.21.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b963177900ec8e783927e5ed99e16c0ec1b723f1f125dff8992db28ef35c62c3" +checksum = "47d6c3300c7103eb8e4de07591003511aa25664438f8c6fc317a3a9902c103f8" dependencies = [ "glib-sys", "libc", @@ -454,9 +448,9 @@ checksum = "37b2a672a2cb129a2e41c10b1224bb368f9f37a2b16b612598138befd7b37eb5" [[package]] name = "cc" -version = "1.2.36" +version = "1.2.39" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5252b3d2648e5eedbc1a6f501e3c795e07025c1e93bbf8bbdd6eef7f447a6d54" +checksum = "e1354349954c6fc9cb0deab020f27f783cf0b604e8bb754dc4658ecf0d29c35f" dependencies = [ "find-msvc-tools", "jobserver", @@ -482,9 +476,9 @@ dependencies = [ [[package]] name = "cfg-expr" -version = "0.20.2" +version = "0.20.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c8d458d63f0f0f482c8da9b7c8b76c21bd885a02056cc94c6404d861ca2b8206" +checksum = "1a2c5f3bf25ec225351aa1c8e230d04d880d3bd89dea133537dafad4ae291e5c" dependencies = [ "smallvec", "target-lexicon 0.13.2", @@ -508,11 +502,10 @@ dependencies = [ [[package]] name = "chrono" -version = "0.4.41" +version = "0.4.42" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c469d952047f47f91b68d1cba3f10d63c11d73e4636f24f08daf0278abf01c4d" +checksum = "145052bdd345b87320e369255277e3fb5152762ad123a901ef5c262dd38fe8d2" dependencies = [ - "android-tzdata", "iana-time-zone", "js-sys", "num-traits", @@ -522,9 +515,9 @@ dependencies = [ [[package]] name = "clap" -version = "4.5.47" +version = "4.5.48" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7eac00902d9d136acd712710d71823fb8ac8004ca445a89e73a41d45aa712931" +checksum = "e2134bb3ea021b78629caa971416385309e0131b351b25e01dc16fb54e1b5fae" dependencies = [ "clap_builder", "clap_derive", @@ -532,9 +525,9 @@ dependencies = [ [[package]] name = "clap_builder" -version = "4.5.47" +version = "4.5.48" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2ad9bbf750e73b5884fb8a211a9424a1906c1e156724260fdae972f31d70e1d6" +checksum = "c2ba64afa3c0a6df7fa517765e31314e983f51dda798ffba27b988194fb65dc9" dependencies = [ "anstream", "anstyle", @@ -593,15 +586,15 @@ dependencies = [ [[package]] name = "console" -version = "0.16.0" +version = "0.16.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2e09ced7ebbccb63b4c65413d821f2e00ce54c5ca4514ddc6b3c892fdbcbc69d" +checksum = "b430743a6eb14e9764d4260d4c0d8123087d504eeb9c48f2b2a5e810dd369df4" dependencies = [ "encode_unicode", "libc", "once_cell", "unicode-width", - "windows-sys 0.60.2", + "windows-sys 0.61.1", ] [[package]] @@ -701,17 +694,26 @@ checksum = "460fbee9c2c2f33933d720630a6a0bac33ba7053db5344fac858d4b8952d77d5" [[package]] name = "cssparser" -version = "0.31.2" +version = "0.35.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5b3df4f93e5fbbe73ec01ec8d3f68bba73107993a5b1e7519273c32db9b0d5be" +checksum = "4e901edd733a1472f944a45116df3f846f54d37e67e68640ac8bb69689aca2aa" dependencies = [ "cssparser-macros", "dtoa-short", "itoa", - "phf 0.11.3", + "phf", "smallvec", ] +[[package]] +name = "cssparser-color" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6eeef9ae8c0e112edd89eb6406b3156ffa99c7e037b3baef1dbdf4158d35c324" +dependencies = [ + "cssparser", +] + [[package]] name = "cssparser-macros" version = "0.6.1" @@ -815,9 +817,18 @@ dependencies = [ [[package]] name = "derive_more" -version = "0.99.20" +version = "2.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "093242cf7570c207c83073cf82f79706fe7b8317e98620a47d5be7c3d8497678" +dependencies = [ + "derive_more-impl", +] + +[[package]] +name = "derive_more-impl" +version = "2.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6edb4b64a43d977b8e99788fe3a04d483834fba1215a7e02caa415b626497f7f" +checksum = "bda628edc44c4bb645fbe0f758797143e4e07926f7ebf4e9bdfbd3d2ce621df3" dependencies = [ "proc-macro2", "quote", @@ -903,6 +914,24 @@ dependencies = [ "log", ] +[[package]] +name = "enchant" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dafd018f9ff5933b0cf9d89a095c31125890f2246952749cbaad230b537644db" +dependencies = [ + "enchant-sys", +] + +[[package]] +name = "enchant-sys" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "97af59f7a6d8a217695598f23dc12051734322e60b58e5ca2f3a2fffa59ba34f" +dependencies = [ + "pkg-config", +] + [[package]] name = "encode_unicode" version = "1.0.0" @@ -946,12 +975,12 @@ checksum = "877a4ace8713b0bcf2a4e7eec82529c029f1d0619886d18145fea96c3ffe5c0f" [[package]] name = "errno" -version = "0.3.13" +version = "0.3.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "778e2ac28f6c47af28e4907f13ffd1e1ddbd400980a9abd7c8df189bf578a5ad" +checksum = "39cab71617ae0d63f51a36d69f866391735b51691dbda63cf6f96d042b63efeb" dependencies = [ "libc", - "windows-sys 0.60.2", + "windows-sys 0.61.1", ] [[package]] @@ -1067,9 +1096,9 @@ dependencies = [ [[package]] name = "find-msvc-tools" -version = "0.1.1" +version = "0.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7fd99930f64d146689264c637b5af2f0233a933bef0d8570e2526bf9e083192d" +checksum = "1ced73b1dacfc750a6db6c0a0c3a3853c8b41997e2e2c563dc90804ae6867959" [[package]] name = "flate2" @@ -1284,9 +1313,9 @@ dependencies = [ [[package]] name = "gdk-pixbuf" -version = "0.21.1" +version = "0.21.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3c7330cdbbc653df431331ae3d9d59e985a0fecaf33d74c7c1c5d13ab0245f6c" +checksum = "2a3c64459f569154f37616fc28923bfac490d4aaa134aaf5eca58a2c0c13050f" dependencies = [ "gdk-pixbuf-sys", "gio", @@ -1296,9 +1325,9 @@ dependencies = [ [[package]] name = "gdk-pixbuf-sys" -version = "0.21.1" +version = "0.21.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e25899cc931dc28cba912ebec793b730f53d2d419f90a562fcb29b53bd10aa82" +checksum = "3854ef7a6a8b8f3b4013a01d5f9cb0d1794ec4e810c6cb4e2cc6d980f1baf724" dependencies = [ "gio-sys", "glib-sys", @@ -1309,9 +1338,9 @@ dependencies = [ [[package]] name = "gdk4" -version = "0.10.0" +version = "0.10.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0a67b064d2f35e649232455c7724f56f977555d2608c43300eabc530eaa4e359" +checksum = "c7e292649dc26e3440c508a00f42ab39156008320dd6e962d63eaf626ba4d7f0" dependencies = [ "cairo-rs", "gdk-pixbuf", @@ -1324,9 +1353,9 @@ dependencies = [ [[package]] name = "gdk4-sys" -version = "0.10.0" +version = "0.10.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2edbda0d879eb85317bdb49a3da591ed70a804a10776e358ef416be38c6db2c5" +checksum = "f4f3174fa4f1e0bf2a7e04469b65db8f4d1db89a6f5cdc57727b14e97ce438cf" dependencies = [ "cairo-sys-rs", "gdk-pixbuf-sys", @@ -1399,7 +1428,7 @@ dependencies = [ "cfg-if", "libc", "r-efi", - "wasi 0.14.4+wasi-0.2.4", + "wasi 0.14.7+wasi-0.2.4", ] [[package]] @@ -1434,9 +1463,9 @@ dependencies = [ [[package]] name = "gio" -version = "0.21.1" +version = "0.21.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "52b5e3f390d01b79e30da451dd00e27cd1ac2de81658e3abf6c1fc3229b24c5f" +checksum = "ed68efc12b748a771be2dccc49480d8584004382967c98323245fc3c38b74a42" dependencies = [ "futures-channel", "futures-core", @@ -1451,15 +1480,15 @@ dependencies = [ [[package]] name = "gio-sys" -version = "0.21.1" +version = "0.21.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a03f2234671e5a588cfe1f59c2b22c103f5772ea351be9cc824a9ce0d06d99fd" +checksum = "171ed2f6dd927abbe108cfd9eebff2052c335013f5879d55bab0dc1dee19b706" dependencies = [ "glib-sys", "gobject-sys", "libc", "system-deps 7.0.5", - "windows-sys 0.60.2", + "windows-sys 0.61.1", ] [[package]] @@ -1554,15 +1583,15 @@ checksum = "8babf46d4c1c9d92deac9f7be466f76dfc4482b6452fc5024b5e8daf6ffeb3ee" [[package]] name = "glam" -version = "0.30.5" +version = "0.30.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f2d1aab06663bdce00d6ca5e5ed586ec8d18033a771906c993a1e3755b368d85" +checksum = "e12d847aeb25f41be4c0ec9587d624e9cd631bc007a8fd7ce3f5851e064c6460" [[package]] name = "glib" -version = "0.21.1" +version = "0.21.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "60bdc26493257b5794ba9301f7cbaf7ab0d69a570bfbefa4d7d360e781cb5205" +checksum = "e1f2cbc4577536c849335878552f42086bfd25a8dcd6f54a18655cf818b20c8f" dependencies = [ "bitflags 2.9.4", "futures-channel", @@ -1590,9 +1619,9 @@ dependencies = [ [[package]] name = "glib-macros" -version = "0.21.0" +version = "0.21.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e772291ebea14c28eb11bb75741f62f4a4894f25e60ce80100797b6b010ef0f9" +checksum = "55eda916eecdae426d78d274a17b48137acdca6fba89621bd3705f2835bc719f" dependencies = [ "heck", "proc-macro-crate", @@ -1603,9 +1632,9 @@ dependencies = [ [[package]] name = "glib-sys" -version = "0.21.1" +version = "0.21.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dc7c43cff6a7dc43821e45ebf172399437acd6716fa2186b6852d2b397bf622d" +checksum = "d09d3d0fddf7239521674e57b0465dfbd844632fec54f059f7f56112e3f927e1" dependencies = [ "libc", "system-deps 7.0.5", @@ -1613,9 +1642,9 @@ dependencies = [ [[package]] name = "gobject-sys" -version = "0.21.1" +version = "0.21.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3e9a190eef2bce144a6aa8434e306974c6062c398e0a33a146d60238f9062d5c" +checksum = "538e41d8776173ec107e7b0f2aceced60abc368d7e1d81c1f0e2ecd35f59080d" dependencies = [ "glib-sys", "libc", @@ -1624,9 +1653,9 @@ dependencies = [ [[package]] name = "graphene-rs" -version = "0.21.1" +version = "0.21.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d96914394464c04df8279c23976293afd53b2588e03c9d8d9662ef6528654a85" +checksum = "e7749aaf5d3b955bf3bfce39e3423705878a666b561384134da0e7786a45ddc3" dependencies = [ "glib", "graphene-sys", @@ -1635,9 +1664,9 @@ dependencies = [ [[package]] name = "graphene-sys" -version = "0.21.1" +version = "0.21.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cf8205bb19b7a041cf059be3c94d6b23b3f2c6c96362c44311dcf184e4a9422a" +checksum = "250abaee850a90a276509890a78029c356173f9573412bded5f155b0e41fa568" dependencies = [ "glib-sys", "libc", @@ -1647,9 +1676,9 @@ dependencies = [ [[package]] name = "gsk4" -version = "0.10.0" +version = "0.10.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d5dbe33ceed6fc20def67c03d36e532f5a4a569ae437ae015a7146094f31e10c" +checksum = "b6687e9f92ca89c000c376400cfaf7914d099413d72fdf4f84a25775a0b1fb2d" dependencies = [ "cairo-rs", "gdk4", @@ -1662,9 +1691,9 @@ dependencies = [ [[package]] name = "gsk4-sys" -version = "0.10.0" +version = "0.10.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8d76011d55dd19fde16ffdedee08877ae6ec942818cfa7bc08a91259bc0b9fc9" +checksum = "5e76bcf64d9c4846f19651f45b400cc0c9c4c17b651849da520f3d77c6988c52" dependencies = [ "cairo-sys-rs", "gdk4-sys", @@ -1678,9 +1707,9 @@ dependencies = [ [[package]] name = "gtk4" -version = "0.10.0" +version = "0.10.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "938d68ad43080ad5ee710c30d467c1bc022ee5947856f593855691d726305b3e" +checksum = "8f7887ee0ceeffedb25a418810a2c61497dacad51767fc13f9d60859b4023b8a" dependencies = [ "cairo-rs", "field-offset", @@ -1699,9 +1728,9 @@ dependencies = [ [[package]] name = "gtk4-macros" -version = "0.10.0" +version = "0.10.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0912d2068695633002b92c5966edc108b2e4f54b58c509d1eeddd4cbceb7315c" +checksum = "821160b4f17e7e4ed748818c23682d0a46bed04c287dbaac54dd4869d2c5e06a" dependencies = [ "proc-macro-crate", "proc-macro2", @@ -1711,9 +1740,9 @@ dependencies = [ [[package]] name = "gtk4-sys" -version = "0.10.0" +version = "0.10.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a923bdcf00e46723801162de24432cbce38a6810e0178a2d0b6dd4ecc26a1c74" +checksum = "d274cbaf7d9aa55b7aff78cb21b43299d64e514e1300671469b66f691cc5a011" dependencies = [ "cairo-sys-rs", "gdk-pixbuf-sys", @@ -1765,6 +1794,12 @@ dependencies = [ "serde", ] +[[package]] +name = "hashbrown" +version = "0.16.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5419bdc4f6a9207fbeba6d11b604d481addf78ecd10c11ad51e76c2f6482748d" + [[package]] name = "heapless" version = "0.8.0" @@ -1833,9 +1868,9 @@ checksum = "155181bc97d770181cf9477da51218a19ee92a8e5be642e796661aee2b601139" [[package]] name = "iana-time-zone" -version = "0.1.63" +version = "0.1.64" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b0c919e5debc312ad217002b8048a17b7d83f80703865bbfcfebb0458b0b27d8" +checksum = "33e57f83510bb73707521ebaffa789ec8caf86f9657cad665b092b581d40e9fb" dependencies = [ "android_system_properties", "core-foundation-sys", @@ -1843,7 +1878,7 @@ dependencies = [ "js-sys", "log", "wasm-bindgen", - "windows-core 0.61.2", + "windows-core 0.62.1", ] [[package]] @@ -1863,11 +1898,31 @@ checksum = "200072f5d0e3614556f94a9930d5dc3e0662a652823904c3a75dc3b0af7fee47" dependencies = [ "displaydoc", "potential_utf", - "yoke", + "yoke 0.8.0", "zerofrom", - "zerovec", + "zerovec 0.11.4", +] + +[[package]] +name = "icu_displaynames" +version = "0.11.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "726c0d83ff52f05907275f39e5bb7949a92fa3d09538de60cf73ccf8ee89a613" +dependencies = [ + "icu_displaynames_data", + "icu_locid", + "icu_locid_transform", + "icu_provider 1.4.0", + "tinystr 0.7.6", + "zerovec 0.10.4", ] +[[package]] +name = "icu_displaynames_data" +version = "1.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "af40e6b723e5e6d9359cf0bb4e4ed6dfb9d6ab16b73b5c82b61f947e88bb30f6" + [[package]] name = "icu_locale_core" version = "2.0.0" @@ -1875,12 +1930,45 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0cde2700ccaed3872079a65fb1a78f6c0a36c91570f28755dda67bc8f7d9f00a" dependencies = [ "displaydoc", - "litemap", - "tinystr", - "writeable", - "zerovec", + "litemap 0.8.0", + "tinystr 0.8.1", + "writeable 0.6.1", + "zerovec 0.11.4", +] + +[[package]] +name = "icu_locid" +version = "1.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5c0aa2536adc14c07e2a521e95512b75ed8ef832f0fdf9299d4a0a45d2be2a9d" +dependencies = [ + "displaydoc", + "litemap 0.7.5", + "tinystr 0.7.6", + "writeable 0.5.5", + "zerovec 0.10.4", +] + +[[package]] +name = "icu_locid_transform" +version = "1.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "57c17d8f6524fdca4471101dd71f0a132eb6382b5d6d7f2970441cb25f6f435a" +dependencies = [ + "displaydoc", + "icu_locid", + "icu_locid_transform_data", + "icu_provider 1.4.0", + "tinystr 0.7.6", + "zerovec 0.10.4", ] +[[package]] +name = "icu_locid_transform_data" +version = "1.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "545c6c3e8bf9580e2dafee8de6f9ec14826aaf359787789c7724f1f85f47d3dc" + [[package]] name = "icu_normalizer" version = "2.0.0" @@ -1891,9 +1979,9 @@ dependencies = [ "icu_collections", "icu_normalizer_data", "icu_properties", - "icu_provider", + "icu_provider 2.0.0", "smallvec", - "zerovec", + "zerovec 0.11.4", ] [[package]] @@ -1912,10 +2000,10 @@ dependencies = [ "icu_collections", "icu_locale_core", "icu_properties_data", - "icu_provider", + "icu_provider 2.0.0", "potential_utf", "zerotrie", - "zerovec", + "zerovec 0.11.4", ] [[package]] @@ -1924,6 +2012,23 @@ version = "2.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "298459143998310acd25ffe6810ed544932242d3f07083eee1084d83a71bd632" +[[package]] +name = "icu_provider" +version = "1.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ba58e782287eb6950247abbf11719f83f5d4e4a5c1f2cd490d30a334bc47c2f4" +dependencies = [ + "displaydoc", + "icu_locid", + "icu_provider_macros", + "stable_deref_trait", + "tinystr 0.7.6", + "writeable 0.5.5", + "yoke 0.7.5", + "zerofrom", + "zerovec 0.10.4", +] + [[package]] name = "icu_provider" version = "2.0.0" @@ -1933,12 +2038,23 @@ dependencies = [ "displaydoc", "icu_locale_core", "stable_deref_trait", - "tinystr", - "writeable", - "yoke", + "tinystr 0.8.1", + "writeable 0.6.1", + "yoke 0.8.0", "zerofrom", "zerotrie", - "zerovec", + "zerovec 0.11.4", +] + +[[package]] +name = "icu_provider_macros" +version = "1.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d2abdd3a62551e8337af119c5899e600ca0c88ec8f23a46c60ba216c803dcf1a" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.106", ] [[package]] @@ -2022,18 +2138,18 @@ checksum = "edcd27d72f2f071c64249075f42e205ff93c9a4c5f6c6da53e79ed9f9832c285" [[package]] name = "imgref" -version = "1.11.0" +version = "1.12.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d0263a3d970d5c054ed9312c0057b4f3bde9c0b33836d3637361d4a9e6e7a408" +checksum = "e7c5cedc30da3a610cac6b4ba17597bdf7152cf974e8aab3afb3d54455e371c8" [[package]] name = "indexmap" -version = "2.11.0" +version = "2.11.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f2481980430f9f78649238835720ddccc57e52df14ffce1c6f37391d61b563e9" +checksum = "4b0f83760fb341a774ed326568e19f5a863af4a952def8c39f9ab92fd95b88e5" dependencies = [ "equivalent", - "hashbrown 0.15.5", + "hashbrown 0.16.0", ] [[package]] @@ -2180,9 +2296,9 @@ dependencies = [ [[package]] name = "js-sys" -version = "0.3.78" +version = "0.3.81" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0c0b063578492ceec17683ef2f8c5e89121fbd0b172cbc280635ab7567db2738" +checksum = "ec48937a97411dcb524a265206ccd4c90bb711fca92b2792c407f268825b9305" dependencies = [ "once_cell", "wasm-bindgen", @@ -2242,9 +2358,9 @@ checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe" [[package]] name = "lebe" -version = "0.5.2" +version = "0.5.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "03087c2bad5e1034e8cace5926dec053fb3790248370865f5117a7d0213354c8" +checksum = "7a79a3332a6609480d7d0c9eab957bca6b455b91bb84e66d19f5ff66294b85b8" [[package]] name = "libadwaita" @@ -2279,9 +2395,9 @@ dependencies = [ [[package]] name = "libc" -version = "0.2.175" +version = "0.2.176" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6a82ae493e598baaea5209805c49bbf2ea7de956d50d7da0da1164f9c6d28543" +checksum = "58f929b4d672ea937a23a1ab494143d968337a5f47e56d0815df1e0890ddf174" [[package]] name = "libfuzzer-sys" @@ -2301,13 +2417,14 @@ checksum = "f9fbbcab51052fe104eb5e5d351cf728d30a5be1fe14d9be8a3b097481fb97de" [[package]] name = "librsvg" -version = "2.61.0" +version = "2.61.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "00a849e5e1cd221a2af11917ac1b86297b3ea07cf1f04a7ebd84a00bfd0e34aa" +checksum = "85f35044d4ba504aac12b7e7286b89af162c88717265865d3cb262ee8df27c4a" dependencies = [ "cairo-rs", "cast", "cssparser", + "cssparser-color", "data-url", "encoding_rs", "float-cmp 0.10.0", @@ -2323,12 +2440,13 @@ dependencies = [ "num-traits", "pango", "pangocairo", + "precomputed-hash", "rayon", "rctree", "regex", "rgb", "selectors", - "string_cache", + "string_cache 0.9.0", "system-deps 7.0.5", "tinyvec", "url", @@ -2337,9 +2455,15 @@ dependencies = [ [[package]] name = "linux-raw-sys" -version = "0.9.4" +version = "0.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cd945864f07fe9f5371a27ad7b52a172b4b499999f1d97574c9fa68373937e12" +checksum = "df1d3c3b53da64cf5760482273a98e575c651a67eec7f77df96b5b642de8f039" + +[[package]] +name = "litemap" +version = "0.7.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "23fb14cb19457329c82206317a5663005a4d404783dc74f4252769b0d5f42856" [[package]] name = "litemap" @@ -2457,9 +2581,9 @@ dependencies = [ [[package]] name = "memchr" -version = "2.7.5" +version = "2.7.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "32a282da65faaf38286cf3be983213fcf1d2e2a58700e808f83f4ea9a4804bc0" +checksum = "f52b00d39961fc5b2736ea853c9cc86238e165017a493d1d5c8eac6bdc4cc273" [[package]] name = "memmap2" @@ -2535,9 +2659,9 @@ dependencies = [ [[package]] name = "nalgebra" -version = "0.34.0" +version = "0.34.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9cd59afb6639828b33677758314a4a1a745c15c02bc597095b851c8fd915cf49" +checksum = "c4d5b3eff5cd580f93da45e64715e8c20a3996342f1e466599cf7a267a0c2f5f" dependencies = [ "approx 0.5.1", "glam 0.14.0", @@ -2555,7 +2679,7 @@ dependencies = [ "glam 0.27.0", "glam 0.28.0", "glam 0.29.3", - "glam 0.30.5", + "glam 0.30.8", "matrixmultiply", "nalgebra-macros 0.3.0", "num-complex", @@ -2900,9 +3024,9 @@ dependencies = [ [[package]] name = "ordered-float" -version = "5.0.0" +version = "5.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e2c1f9f56e534ac6a9b8a4600bdf0f530fb393b5f393e7b4d03489c3cf0c3f01" +checksum = "7f4779c6901a562440c3786d08192c6fbda7c1c2060edd10006b05ee35d10f2d" dependencies = [ "num-traits", ] @@ -2916,7 +3040,7 @@ dependencies = [ "approx 0.5.1", "fast-srgb8", "palette_derive", - "phf 0.11.3", + "phf", ] [[package]] @@ -2933,9 +3057,9 @@ dependencies = [ [[package]] name = "pango" -version = "0.21.1" +version = "0.21.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ab47feb3403aa564edaeb68620c5b9159f8814733a7dd45f0b1a27d19de362fe" +checksum = "e37b7a678e18c2e9f2485f7e39b7b2dac99590d5ddef08a7f56eae38a145402e" dependencies = [ "gio", "glib", @@ -2945,9 +3069,9 @@ dependencies = [ [[package]] name = "pango-sys" -version = "0.21.1" +version = "0.21.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1f855bccb447644e149fae79086e1f81514c30fe5e9b8bd257d9d3c941116c86" +checksum = "f4f5daf21da43fba9f2a0092da0eebeb77637c23552bccaf58f791c518009c94" dependencies = [ "glib-sys", "gobject-sys", @@ -2957,9 +3081,9 @@ dependencies = [ [[package]] name = "pangocairo" -version = "0.21.1" +version = "0.21.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bb23cf0052917cbf75f160d4913a46ce741567f566b514fadc09d761f41eb2fb" +checksum = "7fe686297711b9c0499d0e292e86d343d90b4832d19ebff3d50ce3f627b64e17" dependencies = [ "cairo-rs", "glib", @@ -2970,9 +3094,9 @@ dependencies = [ [[package]] name = "pangocairo-sys" -version = "0.21.1" +version = "0.21.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dcda09c0b17007d7eb6c5eb1643c5b40b067073c15f0cc5a809a6fc68b5d9be7" +checksum = "e6263d7d919c8f3ccd31874774b5f7924e88f5b82ddee3163b3ef49197786a00" dependencies = [ "cairo-sys-rs", "glib-sys", @@ -3024,7 +3148,7 @@ dependencies = [ "ena", "hashbrown 0.15.5", "log", - "nalgebra 0.34.0", + "nalgebra 0.34.1", "num-derive", "num-traits", "ordered-float", @@ -3033,7 +3157,7 @@ dependencies = [ "slab", "smallvec", "spade", - "thiserror 2.0.16", + "thiserror 2.0.17", ] [[package]] @@ -3072,15 +3196,6 @@ version = "2.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9b4f627cb1b25917193a259e49bdad08f671f8d9708acfd5fe0a8c1455d87220" -[[package]] -name = "phf" -version = "0.10.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fabbf1ead8a5bcbc20f5f8b939ee3f5b0f6f281b6ad3468b84656b658b455259" -dependencies = [ - "phf_shared 0.10.0", -] - [[package]] name = "phf" version = "0.11.3" @@ -3091,36 +3206,16 @@ dependencies = [ "phf_shared 0.11.3", ] -[[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_codegen" version = "0.11.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "aef8048c789fa5e851558d709946d6d79a8ff88c0440c587967f8e94bfb1216a" dependencies = [ - "phf_generator 0.11.3", + "phf_generator", "phf_shared 0.11.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_generator" version = "0.11.3" @@ -3137,7 +3232,7 @@ version = "0.11.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f84ac04429c13a7ff43785d75ad27569f2951ce0ffd30a3321230db2fc727216" dependencies = [ - "phf_generator 0.11.3", + "phf_generator", "phf_shared 0.11.3", "proc-macro2", "quote", @@ -3146,18 +3241,18 @@ dependencies = [ [[package]] name = "phf_shared" -version = "0.10.0" +version = "0.11.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b6796ad771acdc0123d2a88dc428b5e38ef24456743ddb1744ed628f9815c096" +checksum = "67eabc2ef2a60eb7faa00097bd1ffdb5bd28e62bf39990626a582201b7a754e5" dependencies = [ - "siphasher 0.3.11", + "siphasher 1.0.1", ] [[package]] name = "phf_shared" -version = "0.11.3" +version = "0.13.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "67eabc2ef2a60eb7faa00097bd1ffdb5bd28e62bf39990626a582201b7a754e5" +checksum = "e57fef6bc5981e38c2ce2d63bfa546861309f875b8a75f092d1d54ae2d64f266" dependencies = [ "siphasher 1.0.1", ] @@ -3246,16 +3341,16 @@ dependencies = [ [[package]] name = "polling" -version = "3.10.0" +version = "3.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b5bd19146350fe804f7cb2669c851c03d69da628803dab0d98018142aaa5d829" +checksum = "5d0e4f59085d47d8241c88ead0f274e8a0cb551f3625263c05eb8dd897c34218" dependencies = [ "cfg-if", "concurrent-queue", "hermit-abi", "pin-project-lite", "rustix", - "windows-sys 0.60.2", + "windows-sys 0.61.1", ] [[package]] @@ -3297,7 +3392,7 @@ version = "0.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "84df19adbe5b5a0782edcab45899906947ab039ccf4573713735ee7de1e6b08a" dependencies = [ - "zerovec", + "zerovec 0.11.4", ] [[package]] @@ -3317,11 +3412,11 @@ checksum = "925383efa346730478fb4838dbe9137d2a47675ad789c546d150a6e1dd4ab31c" [[package]] name = "proc-macro-crate" -version = "3.3.0" +version = "3.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "edce586971a4dfaa28950c6f18ed55e0406c1ab88bbce2c6f6293a7aaba73d35" +checksum = "219cb19e96be00ab2e37d6e299658a0cfa83e52429179969b0f0121b4ac46983" dependencies = [ - "toml_edit", + "toml_edit 0.23.6", ] [[package]] @@ -3354,9 +3449,9 @@ dependencies = [ [[package]] name = "pxfm" -version = "0.1.22" +version = "0.1.24" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "376f733579ac4d3b9fbf0afca99bf8f6b698d541118affca554d0b86f73c2470" +checksum = "83f9b339b02259ada5c0f4a389b7fb472f933aa17ce176fd2ad98f28bb401fde" dependencies = [ "num-traits", ] @@ -3378,9 +3473,9 @@ checksum = "a993555f31e5a609f617c12db6250dedcac1b0a85076912c436e6fc9b2c8e6a3" [[package]] name = "quote" -version = "1.0.40" +version = "1.0.41" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1885c039570dc00dcb4ff087a89e185fd56bae234ddc7f056a945bf36467248d" +checksum = "ce25767e7b499d1b604768e7cde645d14cc8584231ea6b295e9c9eb22c02e1d1" dependencies = [ "proc-macro2", ] @@ -3562,9 +3657,9 @@ dependencies = [ [[package]] name = "regex" -version = "1.11.2" +version = "1.11.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "23d7fd106d8c02486a8d64e778353d1cffe08ce79ac2e82f540c86d0facf6912" +checksum = "8b5288124840bee7b386bc413c487869b360b2b4ec421ea56425128692f2a82c" dependencies = [ "aho-corasick", "memchr", @@ -3574,9 +3669,9 @@ dependencies = [ [[package]] name = "regex-automata" -version = "0.4.10" +version = "0.4.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6b9458fa0bfeeac22b5ca447c63aaf45f28439a709ccd244698632f9aa6394d6" +checksum = "833eb9ce86d40ef33cb1306d8accf7bc8ec2bfea4355cbdebb3df68b40925cad" dependencies = [ "aho-corasick", "memchr", @@ -3612,12 +3707,14 @@ dependencies = [ "gettext-rs", "glib-build-tools", "gtk4", + "icu_displaynames", + "icu_locid", "ijson", "image", "itertools 0.14.0", "kurbo 0.11.3", "libadwaita", - "nalgebra 0.34.0", + "nalgebra 0.34.1", "notify-debouncer-full", "num-derive", "num-traits", @@ -3642,7 +3739,7 @@ dependencies = [ "serde", "serde_json", "svg", - "thiserror 2.0.16", + "thiserror 2.0.17", "tracing", "tracing-subscriber", "unicode-segmentation", @@ -3659,7 +3756,7 @@ dependencies = [ "dialoguer", "image", "indicatif", - "nalgebra 0.34.0", + "nalgebra 0.34.1", "open", "parry2d-f64", "rnote-compose", @@ -3679,7 +3776,7 @@ dependencies = [ "clap", "ink-stroke-modeler-rs", "kurbo 0.11.3", - "nalgebra 0.34.0", + "nalgebra 0.34.1", "num-derive", "num-traits", "once_cell", @@ -3708,6 +3805,7 @@ dependencies = [ "cairo-rs", "chrono", "clap", + "enchant", "flate2", "futures", "geo", @@ -3719,7 +3817,7 @@ dependencies = [ "itertools 0.14.0", "kurbo 0.11.3", "librsvg", - "nalgebra 0.34.0", + "nalgebra 0.34.1", "num-derive", "num-traits", "once_cell", @@ -3743,7 +3841,7 @@ dependencies = [ "serde_json", "slotmap", "svg", - "thiserror 2.0.16", + "thiserror 2.0.17", "tracing", "unicode-segmentation", "usvg", @@ -3825,15 +3923,15 @@ dependencies = [ [[package]] name = "rustix" -version = "1.0.8" +version = "1.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "11181fbabf243db407ef8df94a6ce0b2f9a733bd8be4ad02b4eda9602296cac8" +checksum = "cd15f8a2c5551a84d56efdc1cd049089e409ac19a3072d5037a17fd70719ff3e" dependencies = [ "bitflags 2.9.4", "errno", "libc", "linux-raw-sys", - "windows-sys 0.60.2", + "windows-sys 0.61.1", ] [[package]] @@ -3892,9 +3990,9 @@ checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" [[package]] name = "selectors" -version = "0.25.0" +version = "0.31.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4eb30575f3638fc8f6815f448d50cb1a2e255b0897985c8c59f4d37b72a07b06" +checksum = "5685b6ae43bfcf7d2e7dfcfb5d8e8f61b46442c902531e41a32a9a8bf0ee0fb6" dependencies = [ "bitflags 2.9.4", "cssparser", @@ -3902,8 +4000,8 @@ dependencies = [ "fxhash", "log", "new_debug_unreachable", - "phf 0.10.1", - "phf_codegen 0.10.0", + "phf", + "phf_codegen", "precomputed-hash", "servo_arc", "smallvec", @@ -3911,27 +4009,38 @@ dependencies = [ [[package]] name = "semver" -version = "1.0.26" +version = "1.0.27" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "56e6fa9c48d24d85fb3de5ad847117517440f6beceb7798af16b4a87d616b8d0" +checksum = "d767eb0aabc880b29956c35734170f26ed551a859dbd361d140cdbeca61ab1e2" dependencies = [ "serde", + "serde_core", ] [[package]] name = "serde" -version = "1.0.219" +version = "1.0.228" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5f0e2c6ed6606019b4e29e69dbaba95b11854410e5347d525002456dbbb786b6" +checksum = "9a8e94ea7f378bd32cbbd37198a4a91436180c5bb472411e48b5ec2e2124ae9e" +dependencies = [ + "serde_core", + "serde_derive", +] + +[[package]] +name = "serde_core" +version = "1.0.228" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "41d385c7d4ca58e59fc732af25c3983b67ac852c1a25000afe1175de458b67ad" dependencies = [ "serde_derive", ] [[package]] name = "serde_derive" -version = "1.0.219" +version = "1.0.228" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5b0276cf7f2c73365f7157c8123c21cd9a50fbbd844757af28ca1f5925fc2a00" +checksum = "d540f220d3187173da220f885ab66608367b6574e925011a9353e4badda91d79" dependencies = [ "proc-macro2", "quote", @@ -3940,14 +4049,15 @@ dependencies = [ [[package]] name = "serde_json" -version = "1.0.143" +version = "1.0.145" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d401abef1d108fbd9cbaebc3e46611f4b1021f714a0597a71f41ee463f5f4a5a" +checksum = "402a6f66d8c709116cf22f558eab210f5a50187f702eb4d7e5ef38d9a7f1c79c" dependencies = [ "itoa", "memchr", "ryu", "serde", + "serde_core", ] [[package]] @@ -3961,9 +4071,9 @@ dependencies = [ [[package]] name = "servo_arc" -version = "0.3.0" +version = "0.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d036d71a959e00c77a63538b90a6c2390969f9772b096ea837205c6bd0491a44" +checksum = "204ea332803bd95a0b60388590d59cf6468ec9becf626e2451f1d26a1d972de4" dependencies = [ "stable_deref_trait", ] @@ -4127,13 +4237,26 @@ dependencies = [ "serde", ] +[[package]] +name = "string_cache" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a18596f8c785a729f2819c0f6a7eae6ebeebdfffbfe4214ae6b087f690e31901" +dependencies = [ + "new_debug_unreachable", + "parking_lot", + "phf_shared 0.13.1", + "precomputed-hash", + "serde", +] + [[package]] name = "string_cache_codegen" version = "0.5.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c711928715f1fe0fe509c53b43e993a9a557babc2d0a3567d0a3006f1ac931a0" dependencies = [ - "phf_generator 0.11.3", + "phf_generator", "phf_shared 0.11.3", "proc-macro2", "quote", @@ -4299,7 +4422,7 @@ version = "7.0.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e4be53aa0cba896d2dc615bd42bbc130acdcffa239e0a2d965ea5b3b2a86ffdb" dependencies = [ - "cfg-expr 0.20.2", + "cfg-expr 0.20.3", "heck", "pkg-config", "toml", @@ -4326,15 +4449,15 @@ checksum = "83176759e9416cf81ee66cb6508dbfe9c96f20b8b56265a39917551c23c70964" [[package]] name = "tempfile" -version = "3.21.0" +version = "3.23.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "15b61f8f20e3a6f7e0649d825294eaf317edce30f82cf6026e7e4cb9222a7d1e" +checksum = "2d31c77bdf42a745371d260a26ca7163f1e0924b64afa0b688e61b5a9fa02f16" dependencies = [ "fastrand", "getrandom 0.3.3", "once_cell", "rustix", - "windows-sys 0.60.2", + "windows-sys 0.61.1", ] [[package]] @@ -4359,11 +4482,11 @@ dependencies = [ [[package]] name = "thiserror" -version = "2.0.16" +version = "2.0.17" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3467d614147380f2e4e374161426ff399c91084acd2363eaf549172b3d5e60c0" +checksum = "f63587ca0f12b72a0600bcba1d40081f830876000bb46dd2337a3051618f4fc8" dependencies = [ - "thiserror-impl 2.0.16", + "thiserror-impl 2.0.17", ] [[package]] @@ -4379,9 +4502,9 @@ dependencies = [ [[package]] name = "thiserror-impl" -version = "2.0.16" +version = "2.0.17" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6c5e1be1c48b9172ee610da68fd9cd2770e7a4056cb3fc98710ee6906f0c7960" +checksum = "3ff15c8ecd7de3849db632e14d18d2571fa09dfc5ed93479bc4485c7a517c913" dependencies = [ "proc-macro2", "quote", @@ -4422,6 +4545,16 @@ dependencies = [ "strict-num", ] +[[package]] +name = "tinystr" +version = "0.7.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9117f5d4db391c1cf6927e7bea3db74b9a1c1add8f7eda9ffd5364f40f57b82f" +dependencies = [ + "displaydoc", + "zerovec 0.10.4", +] + [[package]] name = "tinystr" version = "0.8.1" @@ -4429,7 +4562,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5d4f6d1145dcb577acf783d4e601bc1d76a13337bb54e6233add580b07344c8b" dependencies = [ "displaydoc", - "zerovec", + "zerovec 0.11.4", ] [[package]] @@ -4455,8 +4588,8 @@ checksum = "dc1beb996b9d83529a9e75c17a1686767d148d70663143c7854d8b4a09ced362" dependencies = [ "serde", "serde_spanned", - "toml_datetime", - "toml_edit", + "toml_datetime 0.6.11", + "toml_edit 0.22.27", ] [[package]] @@ -4468,6 +4601,15 @@ dependencies = [ "serde", ] +[[package]] +name = "toml_datetime" +version = "0.7.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "32f1085dec27c2b6632b04c80b3bb1b4300d6495d1e129693bdda7d91e72eec1" +dependencies = [ + "serde_core", +] + [[package]] name = "toml_edit" version = "0.22.27" @@ -4477,11 +4619,32 @@ dependencies = [ "indexmap", "serde", "serde_spanned", - "toml_datetime", + "toml_datetime 0.6.11", "toml_write", "winnow", ] +[[package]] +name = "toml_edit" +version = "0.23.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f3effe7c0e86fdff4f69cdd2ccc1b96f933e24811c5441d44904e8683e27184b" +dependencies = [ + "indexmap", + "toml_datetime 0.7.2", + "toml_parser", + "winnow", +] + +[[package]] +name = "toml_parser" +version = "1.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4cf893c33be71572e0e9aa6dd15e6677937abd686b066eac3f8cd3531688a627" +dependencies = [ + "winnow", +] + [[package]] name = "toml_write" version = "0.1.2" @@ -4560,9 +4723,9 @@ dependencies = [ [[package]] name = "typenum" -version = "1.18.0" +version = "1.19.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1dccffe3ce07af9386bfd29e80c0ab1a8205a2fc34e4bcd40364df902cfa8f3f" +checksum = "562d481066bde0658276a35467c4af00bdc6ee726305698a55b86e61d7ad82bb" [[package]] name = "unic-bidi" @@ -4635,9 +4798,9 @@ checksum = "ce61d488bcdc9bc8b5d1772c404828b17fc481c0a582b5581e95fb233aef503e" [[package]] name = "unicode-ident" -version = "1.0.18" +version = "1.0.19" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5a5f39404a5da50712a4c1eecf25e90dd62b613502b7e925fd4e4d19b5c96512" +checksum = "f63a545481291138910575129486daeaf8ac54aee4387fe7906919f7830c7d9d" [[package]] name = "unicode-properties" @@ -4779,18 +4942,27 @@ checksum = "ccf3ec651a847eb01de73ccad15eb7d99f80485de043efb2f370cd654f4ea44b" [[package]] name = "wasi" -version = "0.14.4+wasi-0.2.4" +version = "0.14.7+wasi-0.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "883478de20367e224c0090af9cf5f9fa85bed63a95c1abf3afc5c083ebc06e8c" +dependencies = [ + "wasip2", +] + +[[package]] +name = "wasip2" +version = "1.0.1+wasi-0.2.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "88a5f4a424faf49c3c2c344f166f0662341d470ea185e939657aaff130f0ec4a" +checksum = "0562428422c63773dad2c345a1882263bbf4d65cf3f42e90921f787ef5ad58e7" dependencies = [ "wit-bindgen", ] [[package]] name = "wasm-bindgen" -version = "0.2.101" +version = "0.2.104" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7e14915cadd45b529bb8d1f343c4ed0ac1de926144b746e2710f9cd05df6603b" +checksum = "c1da10c01ae9f1ae40cbfac0bac3b1e724b320abfcf52229f80b547c0d250e2d" dependencies = [ "cfg-if", "once_cell", @@ -4801,9 +4973,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-backend" -version = "0.2.101" +version = "0.2.104" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e28d1ba982ca7923fd01448d5c30c6864d0a14109560296a162f80f305fb93bb" +checksum = "671c9a5a66f49d8a47345ab942e2cb93c7d1d0339065d4f8139c486121b43b19" dependencies = [ "bumpalo", "log", @@ -4815,9 +4987,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-futures" -version = "0.4.51" +version = "0.4.54" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0ca85039a9b469b38336411d6d6ced91f3fc87109a2a27b0c197663f5144dffe" +checksum = "7e038d41e478cc73bae0ff9b36c60cff1c98b8f38f8d7e8061e79ee63608ac5c" dependencies = [ "cfg-if", "js-sys", @@ -4828,9 +5000,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-macro" -version = "0.2.101" +version = "0.2.104" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7c3d463ae3eff775b0c45df9da45d68837702ac35af998361e2c84e7c5ec1b0d" +checksum = "7ca60477e4c59f5f2986c50191cd972e3a50d8a95603bc9434501cf156a9a119" dependencies = [ "quote", "wasm-bindgen-macro-support", @@ -4838,9 +5010,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-macro-support" -version = "0.2.101" +version = "0.2.104" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7bb4ce89b08211f923caf51d527662b75bdc9c9c7aab40f86dcb9fb85ac552aa" +checksum = "9f07d2f20d4da7b26400c9f4a0511e6e0345b040694e8a75bd41d578fa4421d7" dependencies = [ "proc-macro2", "quote", @@ -4851,18 +5023,18 @@ dependencies = [ [[package]] name = "wasm-bindgen-shared" -version = "0.2.101" +version = "0.2.104" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f143854a3b13752c6950862c906306adb27c7e839f7414cec8fea35beab624c1" +checksum = "bad67dc8b2a1a6e5448428adec4c3e84c43e561d8c9ee8a9e5aabeb193ec41d1" dependencies = [ "unicode-ident", ] [[package]] name = "web-sys" -version = "0.3.78" +version = "0.3.81" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "77e4b637749ff0d92b8fad63aa1f7cff3cbe125fd49c175cd6345e7272638b12" +checksum = "9367c417a924a74cae129e6a2ae3b47fabb1f8995595ab474029da749a8be120" dependencies = [ "js-sys", "wasm-bindgen", @@ -4884,9 +5056,9 @@ version = "0.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "57ffde1dc01240bdf9992e3205668b235e59421fd085e8a317ed98da0178d414" dependencies = [ - "phf 0.11.3", - "phf_codegen 0.11.3", - "string_cache", + "phf", + "phf_codegen", + "string_cache 0.8.9", "string_cache_codegen", ] @@ -4924,11 +5096,11 @@ checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" [[package]] name = "winapi-util" -version = "0.1.10" +version = "0.1.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0978bf7171b3d90bac376700cb56d606feb40f251a475a5d6634613564460b22" +checksum = "c2a7b1c03c876122aa43f3020e6c3c3ee5c05081c9a00739faf7503aeba10d22" dependencies = [ - "windows-sys 0.60.2", + "windows-sys 0.61.1", ] [[package]] @@ -4959,22 +5131,22 @@ dependencies = [ [[package]] name = "windows-core" -version = "0.61.2" +version = "0.62.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c0fdd3ddb90610c7638aa2b3a3ab2904fb9e5cdbecc643ddb3647212781c4ae3" +checksum = "6844ee5416b285084d3d3fffd743b925a6c9385455f64f6d4fa3031c4c2749a9" dependencies = [ "windows-implement", "windows-interface", "windows-link", - "windows-result 0.3.4", + "windows-result 0.4.0", "windows-strings", ] [[package]] name = "windows-implement" -version = "0.60.0" +version = "0.60.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a47fddd13af08290e67f4acabf4b459f647552718f683a7b415d290ac744a836" +checksum = "edb307e42a74fb6de9bf3a02d9712678b22399c87e6fa869d6dfcd8c1b7754e0" dependencies = [ "proc-macro2", "quote", @@ -4983,9 +5155,9 @@ dependencies = [ [[package]] name = "windows-interface" -version = "0.59.1" +version = "0.59.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bd9211b69f8dcdfa817bfd14bf1c97c9188afa36f4750130fcdf3f400eca9fa8" +checksum = "c0abd1ddbc6964ac14db11c7213d6532ef34bd9aa042c2e5935f59d7908b46a5" dependencies = [ "proc-macro2", "quote", @@ -4994,9 +5166,9 @@ dependencies = [ [[package]] name = "windows-link" -version = "0.1.3" +version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5e6ad25900d524eaabdbbb96d20b4311e1e7ae1699af4fb28c17ae66c80d798a" +checksum = "45e46c0661abb7180e7b9c281db115305d49ca1709ab8242adf09666d2173c65" [[package]] name = "windows-result" @@ -5009,18 +5181,18 @@ dependencies = [ [[package]] name = "windows-result" -version = "0.3.4" +version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "56f42bd332cc6c8eac5af113fc0c1fd6a8fd2aa08a0119358686e5160d0586c6" +checksum = "7084dcc306f89883455a206237404d3eaf961e5bd7e0f312f7c91f57eb44167f" dependencies = [ "windows-link", ] [[package]] name = "windows-strings" -version = "0.4.2" +version = "0.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "56e6c93f3a0c3b36176cb1327a4958a0353d5d166c2a35cb268ace15e91d3b57" +checksum = "7218c655a553b0bed4426cf54b20d7ba363ef543b52d515b3e48d7fd55318dda" dependencies = [ "windows-link", ] @@ -5058,7 +5230,16 @@ version = "0.60.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f2f500e4d28234f72040990ec9d39e3a6b950f9f22d3dba18416c35882612bcb" dependencies = [ - "windows-targets 0.53.3", + "windows-targets 0.53.4", +] + +[[package]] +name = "windows-sys" +version = "0.61.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6f109e41dd4a3c848907eb83d5a42ea98b3769495597450cf6d153507b166f0f" +dependencies = [ + "windows-link", ] [[package]] @@ -5094,9 +5275,9 @@ dependencies = [ [[package]] name = "windows-targets" -version = "0.53.3" +version = "0.53.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d5fe6031c4041849d7c496a8ded650796e7b6ecc19df1a431c1a363342e5dc91" +checksum = "2d42b7b7f66d2a06854650af09cfdf8713e427a439c97ad65a6375318033ac4b" dependencies = [ "windows-link", "windows_aarch64_gnullvm 0.53.0", @@ -5268,9 +5449,15 @@ dependencies = [ [[package]] name = "wit-bindgen" -version = "0.45.1" +version = "0.46.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5c573471f125075647d03df72e026074b7203790d41351cd6edc96f46bcccd36" +checksum = "f17a85883d4e6d00e8a97c586de764dabcc06133f7f1d55dce5cdc070ad7fe59" + +[[package]] +name = "writeable" +version = "0.5.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e9df38ee2d2c3c5948ea468a8406ff0db0b29ae1ffde1bcf20ef305bcc95c51" [[package]] name = "writeable" @@ -5300,6 +5487,18 @@ version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ec7a2a501ed189703dba8b08142f057e887dfc4b2cc4db2d343ac6376ba3e0b9" +[[package]] +name = "yoke" +version = "0.7.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "120e6aef9aa629e3d4f52dc8cc43a015c7724194c97dfaf45180d2daf2b77f40" +dependencies = [ + "serde", + "stable_deref_trait", + "yoke-derive 0.7.5", + "zerofrom", +] + [[package]] name = "yoke" version = "0.8.0" @@ -5308,10 +5507,22 @@ checksum = "5f41bb01b8226ef4bfd589436a297c53d118f65921786300e427be8d487695cc" dependencies = [ "serde", "stable_deref_trait", - "yoke-derive", + "yoke-derive 0.8.0", "zerofrom", ] +[[package]] +name = "yoke-derive" +version = "0.7.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2380878cad4ac9aac1e2435f3eb4020e8374b5f13c296cb75b4620ff8e229154" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.106", + "synstructure", +] + [[package]] name = "yoke-derive" version = "0.8.0" @@ -5326,18 +5537,18 @@ dependencies = [ [[package]] name = "zerocopy" -version = "0.8.26" +version = "0.8.27" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1039dd0d3c310cf05de012d8a39ff557cb0d23087fd44cad61df08fc31907a2f" +checksum = "0894878a5fa3edfd6da3f88c4805f4c8558e2b996227a3d864f47fe11e38282c" dependencies = [ "zerocopy-derive", ] [[package]] name = "zerocopy-derive" -version = "0.8.26" +version = "0.8.27" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9ecf5b4cc5364572d7f4c329661bcc82724222973f2cab6f050a4e5c22f75181" +checksum = "88d2b8d9c68ad2b9e4340d7832716a4d21a22a1154777ad56ea55c51a9cf3831" dependencies = [ "proc-macro2", "quote", @@ -5367,9 +5578,9 @@ dependencies = [ [[package]] name = "zeroize" -version = "1.8.1" +version = "1.8.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ced3678a2879b30306d323f4542626697a464a97c0a07c9aebf7ebca65cd4dde" +checksum = "b97154e67e32c85465826e8bcc1c59429aaaf107c1e4a9e53c8d8ccd5eff88d0" [[package]] name = "zerotrie" @@ -5378,19 +5589,41 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "36f0bbd478583f79edad978b407914f61b2972f5af6fa089686016be8f9af595" dependencies = [ "displaydoc", - "yoke", + "yoke 0.8.0", "zerofrom", ] +[[package]] +name = "zerovec" +version = "0.10.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "aa2b893d79df23bfb12d5461018d408ea19dfafe76c2c7ef6d4eba614f8ff079" +dependencies = [ + "yoke 0.7.5", + "zerofrom", + "zerovec-derive 0.10.3", +] + [[package]] name = "zerovec" version = "0.11.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e7aa2bd55086f1ab526693ecbe444205da57e25f4489879da80635a46d90e73b" dependencies = [ - "yoke", + "yoke 0.8.0", "zerofrom", - "zerovec-derive", + "zerovec-derive 0.11.1", +] + +[[package]] +name = "zerovec-derive" +version = "0.10.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6eafa6dfb17584ea3e2bd6e76e0cc15ad7af12b09abdd1ca55961bed9b1063c6" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.106", ] [[package]] diff --git a/Cargo.toml b/Cargo.toml index a2ca49e218..436e37b129 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -38,6 +38,9 @@ gio = "0.21.1" glib = "0.21.1" glib-build-tools = "0.21.0" gtk4 = { version = "0.10.0", features = ["v4_18"] } +enchant = "0.3.0" +icu_displaynames = "0.11.2" +icu_locid = "1.4.0" ijson = "0.1.4" image = "0.25.6" indicatif = "0.18.0" diff --git a/build-aux/inno_build.py b/build-aux/inno_build.py index 86686dc275..6e9a30a736 100644 --- a/build-aux/inno_build.py +++ b/build-aux/inno_build.py @@ -57,6 +57,12 @@ def run_command(command, error_message): f"Collecting pixbuf-loader ({loader}) DLLs failed" ) +for enchant_provider in glob.glob(f"{build_environment_path}/lib/enchant-2/*.dll"): + run_command( + f"ldd {enchant_provider} | grep '\\/mingw.*\.dll' -o | xargs -i cp {{}} {dlls_dir}", + f"Collecting enchant provider ({enchant_provider}) DLLs failed", + ) + for angle_dll in itertools.chain( glob.glob(f"{build_environment_path}/bin/libEGL*.dll"), glob.glob(f"{build_environment_path}/bin/libGLES*.dll"), diff --git a/build-aux/rnote_inno.iss.in b/build-aux/rnote_inno.iss.in index 1c7df0acb6..e3c80eec97 100644 --- a/build-aux/rnote_inno.iss.in +++ b/build-aux/rnote_inno.iss.in @@ -57,6 +57,8 @@ Source: "{#meson_build_root}\dlls\*.dll"; DestDir: "{app}\bin"; Flags: ignorever ; gdk-pixbuf loaders Source: "{#build_environment_path}\lib\gdk-pixbuf-2.0\*"; DestDir: "{app}\lib\gdk-pixbuf-2.0"; Flags: ignoreversion recursesubdirs createallsubdirs +; enchant providers +Source: "{#build_environment_path}\lib\enchant-2\*"; DestDir: "{app}\lib\enchant-2"; Flags: ignoreversion recursesubdirs createallsubdirs ; poppler-data Source: "{#build_environment_path}\share\poppler\*"; DestDir: "{app}\share\poppler"; Flags: ignoreversion recursesubdirs createallsubdirs ; Settings GSchema diff --git a/crates/rnote-engine/Cargo.toml b/crates/rnote-engine/Cargo.toml index c8add5331c..3d7c3bbe69 100644 --- a/crates/rnote-engine/Cargo.toml +++ b/crates/rnote-engine/Cargo.toml @@ -22,6 +22,7 @@ futures = { workspace = true } geo = { workspace = true } gio = { workspace = true } glib = { workspace = true } +enchant = { workspace = true } ijson = { workspace = true } image = { workspace = true } itertools = { workspace = true } diff --git a/crates/rnote-engine/src/document/config.rs b/crates/rnote-engine/src/document/config.rs index 3bc46c6534..ab972c7527 100644 --- a/crates/rnote-engine/src/document/config.rs +++ b/crates/rnote-engine/src/document/config.rs @@ -1,7 +1,40 @@ // Imports use super::{Background, Format, Layout}; +use crate::engine::SPELLCHECK_DEFAULT_LANGUAGE; use serde::{Deserialize, Serialize}; +#[derive(Debug, Clone, Serialize, Deserialize)] +#[serde(default, rename = "spellcheck_config")] +pub struct SpellcheckConfig { + #[serde(rename = "enabled")] + pub enabled: bool, + #[serde(rename = "language")] + pub language: Option, +} + +impl Default for SpellcheckConfig { + fn default() -> Self { + Self { + enabled: true, + language: SPELLCHECK_DEFAULT_LANGUAGE.clone(), + } + } +} + +impl SpellcheckConfig { + pub fn dictionary(&self, broker: &mut enchant::Broker) -> Option { + if self.enabled { + if let Some(language) = &self.language { + broker.request_dict(language).ok() + } else { + None + } + } else { + None + } + } +} + #[derive(Debug, Clone, Default, Serialize, Deserialize)] #[serde(default, rename = "document_config")] pub struct DocumentConfig { @@ -11,4 +44,6 @@ pub struct DocumentConfig { pub background: Background, #[serde(rename = "layout", alias = "expand_mode")] pub layout: Layout, + #[serde(rename = "spellcheck")] + pub spellcheck: SpellcheckConfig, } diff --git a/crates/rnote-engine/src/engine/mod.rs b/crates/rnote-engine/src/engine/mod.rs index 936c293fc2..82753c6070 100644 --- a/crates/rnote-engine/src/engine/mod.rs +++ b/crates/rnote-engine/src/engine/mod.rs @@ -30,6 +30,7 @@ use crate::{Camera, Document, PenHolder, StrokeStore}; use futures::StreamExt; use futures::channel::mpsc::UnboundedReceiver; use futures::channel::{mpsc, oneshot}; +use once_cell::sync::Lazy; use p2d::bounding_volume::{Aabb, BoundingVolume}; use rnote_compose::eventresult::EventPropagation; use rnote_compose::ext::AabbExt; @@ -37,10 +38,63 @@ use rnote_compose::penevent::{PenEvent, ShortcutKey}; use rnote_compose::{Color, SplitOrder}; use serde::{Deserialize, Serialize}; use snapshot::Snapshotable; +use std::cell::RefCell; +use std::fmt::Debug; use std::path::PathBuf; use std::sync::{Arc, RwLock}; use std::time::Instant; -use tracing::error; +use tracing::{debug, error}; + +thread_local! { + static SPELLCHECK_BROKER: RefCell = RefCell::new(enchant::Broker::new()); +} + +pub static SPELLCHECK_AVAILABLE_LANGUAGES: Lazy> = Lazy::new(|| { + SPELLCHECK_BROKER.with_borrow_mut(|broker| { + broker + .list_dicts() + .iter() + .map(|dict| dict.lang.to_owned()) + .collect() + }) +}); + +pub static SPELLCHECK_DEFAULT_LANGUAGE: Lazy> = Lazy::new(|| { + for system_language in glib::language_names() { + for available_language in SPELLCHECK_AVAILABLE_LANGUAGES.iter() { + if system_language.contains(available_language) { + debug!( + "found default spellcheck language: {:?}", + available_language + ); + + return Some(available_language.to_string()); + } + } + } + + None +}); + +#[derive(Default)] +pub struct Spellcheck { + pub dict: Option, +} + +impl Debug for Spellcheck { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + f.debug_struct("Spellcheck") + .field( + "dict", + &self + .dict + .as_ref() + .map(|dict| format!("Some({})", dict.get_lang())) + .unwrap_or(String::from("None")), + ) + .finish() + } +} /// An immutable view into the engine, excluding the penholder. #[derive(Debug)] @@ -52,6 +106,7 @@ pub struct EngineView<'a> { pub camera: &'a Camera, pub audioplayer: &'a Option, pub animation: &'a Animation, + pub spellcheck: &'a Spellcheck, } /// Constructs an `EngineView` from an identifier containing an `Engine` instance. @@ -66,6 +121,7 @@ macro_rules! engine_view { camera: &$engine.camera, audioplayer: &$engine.audioplayer, animation: &$engine.animation, + spellcheck: &$engine.spellcheck, } }; } @@ -80,6 +136,7 @@ pub struct EngineViewMut<'a> { pub camera: &'a mut Camera, pub audioplayer: &'a mut Option, pub animation: &'a mut Animation, + pub spellcheck: &'a mut Spellcheck, } /// Constructs an `EngineViewMut` from an identifier containing an `Engine` instance. @@ -94,6 +151,7 @@ macro_rules! engine_view_mut { camera: &mut $engine.camera, audioplayer: &mut $engine.audioplayer, animation: &mut $engine.animation, + spellcheck: &mut $engine.spellcheck, } }; } @@ -109,6 +167,7 @@ impl EngineViewMut<'_> { camera: self.camera, audioplayer: self.audioplayer, animation: self.animation, + spellcheck: self.spellcheck, } } } @@ -189,6 +248,8 @@ pub struct Engine { audioplayer: Option, #[serde(skip)] pub animation: Animation, + #[serde(skip)] + spellcheck: Spellcheck, // the task sender. Must not be modified, only cloned. #[serde(skip)] tasks_tx: EngineTaskSender, @@ -221,6 +282,7 @@ impl Default for Engine { audioplayer: None, animation: Animation::default(), + spellcheck: Spellcheck::default(), tasks_tx: EngineTaskSender(tasks_tx), tasks_rx: Some(EngineTaskReceiver(tasks_rx)), background_tile_image: None, @@ -252,6 +314,7 @@ impl Engine { .penholder .reinstall_pen_current_style(&mut engine_view_mut!(self)); widget_flags |= self.doc_resize_to_fit_content(); + widget_flags |= self.refresh_spellcheck_language(); widget_flags.redraw = true; widget_flags.refresh_ui = true; widget_flags @@ -296,6 +359,41 @@ impl Engine { } } + pub fn refresh_spellcheck_language(&mut self) -> WidgetFlags { + let mut widget_flags = WidgetFlags::default(); + + self.spellcheck.dict = SPELLCHECK_BROKER + .with_borrow_mut(|broker| self.document.config.spellcheck.dictionary(broker)); + + if let Pen::Typewriter(typewriter) = self.penholder.current_pen_ref() { + typewriter.refresh_spellcheck_cache_in_modifying_stroke(&mut engine_view_mut!(self)); + + widget_flags.redraw = true; + } + + widget_flags + } + + pub fn get_spellcheck_corrections(&self) -> Option> { + if let Pen::Typewriter(typewriter) = self.penholder.current_pen_ref() { + return typewriter + .get_spellcheck_correction_in_modifying_stroke(&mut engine_view!(self)); + } + + None + } + + pub fn apply_spellcheck_correction(&mut self, correction: &str) -> WidgetFlags { + if let Pen::Typewriter(typewriter) = self.penholder.current_pen_mut() { + return typewriter.apply_spellcheck_correction_in_modifying_stroke( + correction, + &mut engine_view_mut!(self), + ); + } + + WidgetFlags::default() + } + pub fn optimize_epd(&self) -> bool { self.config.read().optimize_epd } @@ -354,6 +452,7 @@ impl Engine { | self.doc_resize_autoexpand() | self.current_pen_update_state() | self.update_rendering_current_viewport() + | self.refresh_spellcheck_language() } /// Redo the latest changes. @@ -362,6 +461,7 @@ impl Engine { | self.doc_resize_autoexpand() | self.current_pen_update_state() | self.update_rendering_current_viewport() + | self.refresh_spellcheck_language() } pub fn can_undo(&self) -> bool { diff --git a/crates/rnote-engine/src/pens/penholder.rs b/crates/rnote-engine/src/pens/penholder.rs index c4fff50c45..1e34e167a9 100644 --- a/crates/rnote-engine/src/pens/penholder.rs +++ b/crates/rnote-engine/src/pens/penholder.rs @@ -92,7 +92,7 @@ impl PenHolder { self.progress } - pub fn current_pen_ref(&mut self) -> &Pen { + pub fn current_pen_ref(&self) -> &Pen { &self.current_pen } diff --git a/crates/rnote-engine/src/pens/typewriter/mod.rs b/crates/rnote-engine/src/pens/typewriter/mod.rs index 7f67ba8cc1..6ef71342b9 100644 --- a/crates/rnote-engine/src/pens/typewriter/mod.rs +++ b/crates/rnote-engine/src/pens/typewriter/mod.rs @@ -213,6 +213,18 @@ impl DrawableOnDoc for Typewriter { ); } + // Draw error ranges + for (start_index, length) in &textstroke.spellcheck_result.errors { + textstroke.text_style.draw_text_error( + cx, + textstroke.text.clone(), + *start_index, + *start_index + *length, + &textstroke.transform, + engine_view.camera, + ); + } + // Draw the cursor if self.cursor_visible { textstroke.text_style.draw_cursor( @@ -515,6 +527,7 @@ impl PenBehaviour for Typewriter { cursor, selection_cursor, String::from("").as_str(), + engine_view.spellcheck, ); // Update stroke @@ -696,7 +709,10 @@ impl Typewriter { let text_len = text.len(); text_style.ranged_text_attributes.clear(); text_style.set_max_width(Some(text_width)); - let textstroke = TextStroke::new(text, pos, text_style); + + let mut textstroke = TextStroke::new(text, pos, text_style); + textstroke.check_spelling_refresh_cache(engine_view.spellcheck); + let cursor = GraphemeCursor::new(text_len, textstroke.text.len(), true); let stroke_key = engine_view @@ -723,7 +739,10 @@ impl Typewriter { let text_len = text.len(); text_style.ranged_text_attributes.clear(); text_style.set_max_width(Some(text_width)); - let textstroke = TextStroke::new(text, *pos, text_style); + + let mut textstroke = TextStroke::new(text, *pos, text_style); + textstroke.check_spelling_refresh_cache(engine_view.spellcheck); + let cursor = GraphemeCursor::new(text_len, textstroke.text.len(), true); let stroke_key = engine_view @@ -762,6 +781,7 @@ impl Typewriter { cursor, selection_cursor, text.as_str(), + engine_view.spellcheck, ); engine_view.store.update_geometry_for_stroke(*stroke_key); engine_view.store.regenerate_rendering_for_stroke( @@ -788,7 +808,12 @@ impl Typewriter { if let Some(Stroke::TextStroke(textstroke)) = engine_view.store.get_stroke_mut(*stroke_key) { - textstroke.insert_text_after_cursor(text.as_str(), cursor); + textstroke.insert_text_after_cursor( + text.as_str(), + cursor, + engine_view.spellcheck, + ); + engine_view.store.update_geometry_for_stroke(*stroke_key); engine_view.store.regenerate_rendering_for_stroke( *stroke_key, @@ -843,6 +868,72 @@ impl Typewriter { widget_flags } + pub(crate) fn refresh_spellcheck_cache_in_modifying_stroke( + &self, + engine_view: &mut EngineViewMut, + ) { + if let TypewriterState::Modifying { stroke_key, .. } = self.state { + if let Some(Stroke::TextStroke(textstroke)) = + engine_view.store.get_stroke_mut(stroke_key) + { + textstroke.check_spelling_refresh_cache(engine_view.spellcheck); + } + } + } + + pub(crate) fn get_spellcheck_correction_in_modifying_stroke( + &self, + engine_view: &EngineView, + ) -> Option> { + if let TypewriterState::Modifying { + stroke_key, cursor, .. + } = &self.state + { + if let Some(Stroke::TextStroke(textstroke)) = + engine_view.store.get_stroke_ref(*stroke_key) + { + return textstroke.get_spellcheck_corrections_at_index( + engine_view.spellcheck, + cursor.cur_cursor(), + ); + } + } + + None + } + + pub(crate) fn apply_spellcheck_correction_in_modifying_stroke( + &mut self, + correction: &str, + engine_view: &mut EngineViewMut, + ) -> WidgetFlags { + let mut widget_flags = WidgetFlags::default(); + + if let TypewriterState::Modifying { + stroke_key, cursor, .. + } = &mut self.state + { + if let Some(Stroke::TextStroke(textstroke)) = + engine_view.store.get_stroke_mut(*stroke_key) + { + textstroke.apply_spellcheck_correction_at_cursor(cursor, correction); + + engine_view.store.update_geometry_for_stroke(*stroke_key); + engine_view.store.regenerate_rendering_for_stroke( + *stroke_key, + engine_view.camera.viewport(), + engine_view.camera.image_scale(), + ); + + widget_flags |= engine_view.store.record(Instant::now()); + widget_flags.redraw = true; + widget_flags.store_modified = true; + } + } + + widget_flags + } + pub(crate) fn toggle_text_attribute_current_selection( &mut self, text_attribute: TextAttribute, diff --git a/crates/rnote-engine/src/pens/typewriter/penevents.rs b/crates/rnote-engine/src/pens/typewriter/penevents.rs index 5a3b6ea729..5e64138f58 100644 --- a/crates/rnote-engine/src/pens/typewriter/penevents.rs +++ b/crates/rnote-engine/src/pens/typewriter/penevents.rs @@ -45,7 +45,7 @@ impl Typewriter { { // When clicked on a textstroke, we start modifying it if let Some(Stroke::TextStroke(textstroke)) = - engine_view.store.get_stroke_ref(stroke_key) + engine_view.store.get_stroke_mut(stroke_key) { let cursor = if let Ok(new_cursor) = // get the cursor for the current position @@ -56,6 +56,7 @@ impl Typewriter { GraphemeCursor::new(0, textstroke.text.len(), true) }; + textstroke.check_spelling_refresh_cache(engine_view.spellcheck); engine_view.store.update_chrono_to_last(stroke_key); new_state = TypewriterState::Modifying { @@ -547,7 +548,11 @@ impl Typewriter { KeyboardKey::Unicode(keychar) => { text_style.ranged_text_attributes.clear(); text_style.set_max_width(Some(text_width)); - let textstroke = TextStroke::new(String::from(keychar), *pos, text_style); + + let mut textstroke = + TextStroke::new(String::from(keychar), *pos, text_style); + textstroke.check_spelling_refresh_cache(engine_view.spellcheck); + let mut cursor = GraphemeCursor::new(0, textstroke.text.len(), true); textstroke.move_cursor_forward(&mut cursor); @@ -643,6 +648,7 @@ impl Typewriter { textstroke.insert_text_after_cursor( keychar.to_string().as_str(), cursor, + engine_view.spellcheck, ); update_stroke(engine_view.store, keychar.is_whitespace()); } @@ -655,9 +661,15 @@ impl Typewriter { } KeyboardKey::BackSpace => { if modifier_keys.contains(&ModifierKey::KeyboardCtrl) { - textstroke.remove_word_before_cursor(cursor); + textstroke.remove_word_before_cursor( + cursor, + engine_view.spellcheck, + ); } else { - textstroke.remove_grapheme_before_cursor(cursor); + textstroke.remove_grapheme_before_cursor( + cursor, + engine_view.spellcheck, + ); } update_stroke(engine_view.store, false); @@ -668,7 +680,11 @@ impl Typewriter { } } KeyboardKey::HorizontalTab => { - textstroke.insert_text_after_cursor("\t", cursor); + textstroke.insert_text_after_cursor( + "\t", + cursor, + engine_view.spellcheck, + ); update_stroke(engine_view.store, false); EventResult { @@ -678,7 +694,11 @@ impl Typewriter { } } KeyboardKey::CarriageReturn | KeyboardKey::Linefeed => { - textstroke.insert_text_after_cursor("\n", cursor); + textstroke.insert_text_after_cursor( + "\n", + cursor, + engine_view.spellcheck, + ); update_stroke(engine_view.store, true); EventResult { @@ -689,9 +709,15 @@ impl Typewriter { } KeyboardKey::Delete => { if modifier_keys.contains(&ModifierKey::KeyboardCtrl) { - textstroke.remove_word_after_cursor(cursor); + textstroke.remove_word_after_cursor( + cursor, + engine_view.spellcheck, + ); } else { - textstroke.remove_grapheme_after_cursor(cursor); + textstroke.remove_grapheme_after_cursor( + cursor, + engine_view.spellcheck, + ); } update_stroke(engine_view.store, false); @@ -910,6 +936,7 @@ impl Typewriter { cursor, selection_cursor, String::from(keychar).as_str(), + engine_view.spellcheck, ); update_stroke(engine_view.store); quit_selecting = true; @@ -1015,6 +1042,7 @@ impl Typewriter { cursor, selection_cursor, "\n", + engine_view.spellcheck, ); update_stroke(engine_view.store); quit_selecting = true; @@ -1029,6 +1057,7 @@ impl Typewriter { cursor, selection_cursor, "", + engine_view.spellcheck, ); update_stroke(engine_view.store); quit_selecting = true; @@ -1043,6 +1072,7 @@ impl Typewriter { cursor, selection_cursor, "\t", + engine_view.spellcheck, ); update_stroke(engine_view.store); quit_selecting = true; @@ -1136,7 +1166,10 @@ impl Typewriter { text_style.ranged_text_attributes.clear(); text_style.set_max_width(Some(text_width)); let text_len = text.len(); - let textstroke = TextStroke::new(text, *pos, text_style); + + let mut textstroke = TextStroke::new(text, *pos, text_style); + textstroke.check_spelling_refresh_cache(engine_view.spellcheck); + let cursor = GraphemeCursor::new(text_len, text_len, true); let stroke_key = engine_view @@ -1178,7 +1211,12 @@ impl Typewriter { if let Some(Stroke::TextStroke(textstroke)) = engine_view.store.get_stroke_mut(*stroke_key) { - textstroke.insert_text_after_cursor(&text, cursor); + textstroke.insert_text_after_cursor( + &text, + cursor, + engine_view.spellcheck, + ); + engine_view.store.update_geometry_for_stroke(*stroke_key); engine_view.store.regenerate_rendering_for_stroke( *stroke_key, @@ -1224,6 +1262,7 @@ impl Typewriter { cursor, selection_cursor, text.as_str(), + engine_view.spellcheck, ); engine_view.store.update_geometry_for_stroke(*stroke_key); engine_view.store.regenerate_rendering_for_stroke( diff --git a/crates/rnote-engine/src/strokes/textstroke.rs b/crates/rnote-engine/src/strokes/textstroke.rs index 0777a40296..df1cd56f51 100644 --- a/crates/rnote-engine/src/strokes/textstroke.rs +++ b/crates/rnote-engine/src/strokes/textstroke.rs @@ -1,8 +1,9 @@ // Imports use super::Content; +use crate::engine::Spellcheck; use crate::{Camera, Drawable}; use itertools::Itertools; -use kurbo::Shape; +use kurbo::{BezPath, Shape}; use p2d::bounding_volume::Aabb; use piet::{RenderContext, TextLayout, TextLayoutBuilder}; use rnote_compose::ext::{AabbExt, Affine2Ext, Vector2Ext}; @@ -10,6 +11,7 @@ use rnote_compose::shapes::Shapeable; use rnote_compose::transform::Transformable; use rnote_compose::{Color, Transform, color}; use serde::{Deserialize, Serialize}; +use std::collections::BTreeMap; use std::ops::Range; use tracing::error; use unicode_segmentation::{GraphemeCursor, UnicodeSegmentation}; @@ -337,20 +339,20 @@ impl TextStyle { Ok(text_layout.hit_test_text_position(cursor.cur_cursor())) } - pub fn get_selection_rects_for_cursors( + pub fn get_rects_for_indices( &self, text: String, - cursor: &GraphemeCursor, - selection_cursor: &GraphemeCursor, + start_index: usize, + end_index: usize, ) -> anyhow::Result> { let text_layout = self .build_text_layout(&mut piet_cairo::CairoText::new(), text) .map_err(|e| anyhow::anyhow!("Building text layout failed, Err: {e:?}"))?; - let range = if selection_cursor.cur_cursor() >= cursor.cur_cursor() { - cursor.cur_cursor()..selection_cursor.cur_cursor() + let range = if end_index >= start_index { + start_index..end_index } else { - selection_cursor.cur_cursor()..cursor.cur_cursor() + end_index..start_index }; Ok(text_layout.rects_for_range(range)) @@ -403,6 +405,42 @@ impl TextStyle { Ok(()) } + pub fn draw_text_error( + &self, + cx: &mut impl piet::RenderContext, + text: String, + start_index: usize, + end_index: usize, + transform: &Transform, + camera: &Camera, + ) { + const ERROR_COLOR: piet::Color = color::GNOME_REDS[2]; + const STYLE: piet::StrokeStyle = piet::StrokeStyle::new().line_cap(piet::LineCap::Round); + + let scale = 1.0 / camera.total_zoom(); + + if let Ok(selection_rects) = + self.get_rects_for_indices(text.clone(), start_index, end_index) + { + // Get baseline for the current line. Really unnecessary to do this for every error since the font size is uniform, + // but piet does not provide any other way to get the baseline. + + if let Ok(line_metric) = self.cursor_line_metric(cx.text(), text, start_index) { + for selection_rect in selection_rects { + let width = selection_rect.width(); + let origin = transform.to_kurbo() + * kurbo::Point::new( + selection_rect.x0, + selection_rect.y0 + line_metric.baseline + 2.0, + ); + + let path = create_wavy_line(origin, width, scale); + cx.stroke_styled(path, &ERROR_COLOR, 1.5 * scale, &STYLE); + } + } + } + } + pub fn draw_text_selection( &self, cx: &mut impl piet::RenderContext, @@ -417,7 +455,7 @@ impl TextStyle { let outline_width = 1.5 / camera.total_zoom(); if let Ok(selection_rects) = - self.get_selection_rects_for_cursors(text, cursor, selection_cursor) + self.get_rects_for_indices(text, cursor.cur_cursor(), selection_cursor.cur_cursor()) { for selection_rect in selection_rects { let outline = transform.to_kurbo() * selection_rect.to_path(0.5); @@ -429,6 +467,12 @@ impl TextStyle { } } +#[derive(Debug, Clone, Default)] +pub struct SpellcheckResult { + pub language: Option, + pub errors: BTreeMap, +} + #[derive(Debug, Clone, Serialize, Deserialize)] #[serde(default, rename = "textstroke")] pub struct TextStroke { @@ -441,6 +485,8 @@ pub struct TextStroke { pub transform: Transform, #[serde(rename = "text_style")] pub text_style: TextStyle, + #[serde(skip)] + pub spellcheck_result: SpellcheckResult, } impl Default for TextStroke { @@ -449,6 +495,7 @@ impl Default for TextStroke { text: String::default(), transform: Transform::default(), text_style: TextStyle::default(), + spellcheck_result: SpellcheckResult::default(), } } } @@ -545,6 +592,7 @@ impl TextStroke { text, transform: Transform::new_w_isometry(na::Isometry2::new(upper_left_pos, 0.0)), text_style, + spellcheck_result: SpellcheckResult::default(), } } @@ -579,16 +627,148 @@ impl TextStroke { )) } - pub fn insert_text_after_cursor(&mut self, text: &str, cursor: &mut GraphemeCursor) { - self.text.insert_str(cursor.cur_cursor(), text); + fn check_spelling_words(&mut self, words: Vec<(usize, String)>, dict: &enchant::Dict) { + for (word_start_index, word) in words { + if let Ok(valid_word) = dict.check(word.as_str()) { + let word_end_index = word_start_index + word.len(); + let word_range = word_start_index..word_end_index; + + self.spellcheck_result + .errors + .retain(|key, _| !word_range.contains(key)); + + // TODO: maybe faster for large texts + // let keys_to_remove = self + // .error_words + // .range(word_range) + // .map(|(&key, _)| key) + // .collect_vec(); + + // for existing_word in keys_to_remove { + // self.error_words.remove(&existing_word); + // } + + if !valid_word { + self.spellcheck_result + .errors + .insert(word_start_index, word.len()); + } + } else { + error!("Failed to check spelling for word '{word}'"); + } + } + } + + pub fn check_spelling_refresh_cache(&mut self, spellcheck: &Spellcheck) { + if let Some(dict) = &spellcheck.dict { + let language = dict.get_lang(); + + let language_changed = self + .spellcheck_result + .language + .clone() + .is_none_or(|cached_language| cached_language != language); + + if language_changed { + self.spellcheck_result.errors.clear(); + self.spellcheck_result.language = Some(language.to_owned()); + + let words = self + .text + .unicode_word_indices() + .map(|(index, word)| (index, word.to_owned())) + .collect_vec(); + + self.check_spelling_words(words, dict); + } + } else { + self.spellcheck_result.errors.clear(); + self.spellcheck_result.language = None; + } + } + + pub fn get_spellcheck_corrections_at_index( + &self, + spellcheck: &Spellcheck, + index: usize, + ) -> Option> { + let Some(dict) = &spellcheck.dict else { + return None; + }; + + let start_index = self.get_prev_word_start_index(index); + + if let Some(length) = self.spellcheck_result.errors.get(&start_index) { + let word = self.get_text_slice_for_range(start_index..start_index + length); + return Some(dict.suggest(word)); + } + + None + } + + pub fn apply_spellcheck_correction_at_cursor( + &mut self, + cursor: &mut GraphemeCursor, + correction: &str, + ) { + let cur_pos = cursor.cur_cursor(); + let start_index = self.get_prev_word_start_index(cur_pos); + + if let Some(length) = self.spellcheck_result.errors.get(&start_index) { + let old_length = *length; + let new_length = correction.len(); + + self.text + .replace_range(start_index..start_index + old_length, correction); + + self.spellcheck_result.errors.remove(&start_index); + + // translate the text attributes + self.translate_attrs_after_cursor( + start_index + old_length, + (new_length as i32) - (old_length as i32), + ); + + *cursor = GraphemeCursor::new(start_index + new_length, self.text.len(), true); + } + } + + pub fn check_spelling_range( + &mut self, + start_index: usize, + end_index: usize, + spellcheck: &Spellcheck, + ) { + if let Some(dict) = &spellcheck.dict { + let words = self.get_surrounding_words(start_index, end_index); + self.check_spelling_words(words, dict); + } + } + + pub fn insert_text_after_cursor( + &mut self, + text: &str, + cursor: &mut GraphemeCursor, + spellcheck: &Spellcheck, + ) { + let cur_pos = cursor.cur_cursor(); + let next_pos = cur_pos + text.len(); + + self.text.insert_str(cur_pos, text); // translate the text attributes - self.translate_attrs_after_cursor(cursor.cur_cursor(), text.len() as i32); + self.translate_attrs_after_cursor(cur_pos, text.len() as i32); + + self.check_spelling_range(cur_pos, next_pos, spellcheck); - *cursor = GraphemeCursor::new(cursor.cur_cursor() + text.len(), self.text.len(), true); + *cursor = GraphemeCursor::new(next_pos, self.text.len(), true); } - pub fn remove_grapheme_before_cursor(&mut self, cursor: &mut GraphemeCursor) { + pub fn remove_grapheme_before_cursor( + &mut self, + cursor: &mut GraphemeCursor, + spellcheck: &Spellcheck, + ) { if !self.text.is_empty() && self.text.len() >= cursor.cur_cursor() { let cur_pos = cursor.cur_cursor(); @@ -600,6 +780,8 @@ impl TextStroke { prev_pos, prev_pos as i32 - cur_pos as i32 + "".len() as i32, ); + + self.check_spelling_range(prev_pos, cur_pos, spellcheck); } // New text length, new cursor @@ -607,7 +789,11 @@ impl TextStroke { } } - pub fn remove_grapheme_after_cursor(&mut self, cursor: &mut GraphemeCursor) { + pub fn remove_grapheme_after_cursor( + &mut self, + cursor: &mut GraphemeCursor, + spellcheck: &Spellcheck, + ) { if !self.text.is_empty() && self.text.len() > cursor.cur_cursor() { let cur_pos = cursor.cur_cursor(); @@ -619,6 +805,8 @@ impl TextStroke { cur_pos, -(next_pos as i32 - cur_pos as i32) + "".len() as i32, ); + + self.check_spelling_range(cur_pos, next_pos, spellcheck); } // New text length, new cursor @@ -626,7 +814,11 @@ impl TextStroke { } } - pub fn remove_word_before_cursor(&mut self, cursor: &mut GraphemeCursor) { + pub fn remove_word_before_cursor( + &mut self, + cursor: &mut GraphemeCursor, + spellcheck: &Spellcheck, + ) { let cur_pos = cursor.cur_cursor(); let prev_pos = self.get_prev_word_start_index(cur_pos); @@ -639,12 +831,18 @@ impl TextStroke { prev_pos as i32 - cur_pos as i32 + "".len() as i32, ); + self.check_spelling_range(prev_pos, cur_pos, spellcheck); + // New text length, new cursor *cursor = GraphemeCursor::new(prev_pos, self.text.len(), true); } } - pub fn remove_word_after_cursor(&mut self, cursor: &mut GraphemeCursor) { + pub fn remove_word_after_cursor( + &mut self, + cursor: &mut GraphemeCursor, + spellcheck: &Spellcheck, + ) { let cur_pos = cursor.cur_cursor(); let next_pos = self.get_next_word_end_index(cur_pos); @@ -657,6 +855,8 @@ impl TextStroke { -(next_pos as i32 - cur_pos as i32) + "".len() as i32, ); + self.check_spelling_range(cur_pos, next_pos, spellcheck); + // New text length, new cursor *cursor = GraphemeCursor::new(cur_pos, self.text.len(), true); } @@ -667,6 +867,7 @@ impl TextStroke { cursor: &mut GraphemeCursor, selection_cursor: &mut GraphemeCursor, replace_text: &str, + spellcheck: &Spellcheck, ) { let cursor_pos = cursor.cur_cursor(); let selection_cursor_pos = selection_cursor.cur_cursor(); @@ -694,12 +895,43 @@ impl TextStroke { cursor.cur_cursor(), -(cursor_range.end as i32 - cursor_range.start as i32) + replace_text.len() as i32, ); + + self.check_spelling_range( + cursor_range.start, + cursor_range.start + replace_text.len(), + spellcheck, + ); } /// Translate the ranged text attributes after the given cursor. /// /// Overlapping ranges are extended / shrunk + /// + /// * `from_pos` is always the start of the range to translate. + /// * `offset` is the translation. The end of the range is calculated by adding the **absolute** value of the offset. fn translate_attrs_after_cursor(&mut self, from_pos: usize, offset: i32) { + let translated_words = if offset < 0 { + let to_pos = from_pos.saturating_add(offset.unsigned_abs() as usize); + self.spellcheck_result + .errors + .split_off(&from_pos) + .split_off(&to_pos) + } else { + self.spellcheck_result.errors.split_off(&from_pos) + }; + + for (word_start, word_length) in translated_words { + let Some(new_word_start) = word_start.checked_add_signed(offset as isize) else { + continue; + }; + + if new_word_start >= from_pos { + self.spellcheck_result + .errors + .insert(new_word_start, word_length); + } + } + for attr in self.text_style.ranged_text_attributes.iter_mut() { if attr.range.start > from_pos { if offset >= 0 { @@ -830,6 +1062,22 @@ impl TextStroke { selection_cursor.set_cursor(0); } + fn get_surrounding_words(&self, start_index: usize, end_index: usize) -> Vec<(usize, String)> { + let mut words = Vec::new(); + + for (word_start, word) in self.text.unicode_word_indices() { + let word_end = word_start + word.len(); + + if word_end >= start_index && word_start <= end_index { + words.push((word_start, word.to_owned())); + } + } + + // debug!("surrounding words: {words:?}"); + + words + } + fn get_prev_word_start_index(&self, current_char_index: usize) -> usize { for (start_index, _) in self.text.unicode_word_indices().rev() { if start_index < current_char_index { @@ -1058,3 +1306,35 @@ fn remove_intersecting_attrs_in_range( .filter(|attr| !attr.range.is_empty()) .collect::>() } + +fn create_wavy_line(origin: kurbo::Point, max_width: f64, scale: f64) -> BezPath { + const WIDTH: f64 = 3.5; + const HEIGHT: f64 = 4.0; + + if !max_width.is_finite() { + return BezPath::new(); + } + + let width = WIDTH * scale; + let half_height = (HEIGHT / 2.0) * scale; + + let mut path = BezPath::new(); + path.move_to(origin + (0.0, half_height)); + + let mut x = 0.0; + let mut direction = 1.0; + + while x < max_width { + let center_point = origin + (x, half_height); + + let stationary_point = center_point + (width / 2.0, half_height * direction); + let next_center_point = center_point + (width, 0.0); + + path.quad_to(stationary_point, next_center_point); + + x += width; + direction = -direction; + } + + path +} diff --git a/crates/rnote-ui/Cargo.toml b/crates/rnote-ui/Cargo.toml index 426d8e9e00..0e1d6c41b3 100644 --- a/crates/rnote-ui/Cargo.toml +++ b/crates/rnote-ui/Cargo.toml @@ -23,6 +23,8 @@ fs_extra = { workspace = true } futures = { workspace = true } gettext-rs = { workspace = true } gtk4 = { workspace = true } +icu_displaynames = { workspace = true } +icu_locid = { workspace = true } ijson = { workspace = true } image = { workspace = true } itertools = { workspace = true } diff --git a/crates/rnote-ui/data/icons/scalable/actions/text-squiggly-symbolic.svg b/crates/rnote-ui/data/icons/scalable/actions/text-squiggly-symbolic.svg new file mode 100644 index 0000000000..b920855b0f --- /dev/null +++ b/crates/rnote-ui/data/icons/scalable/actions/text-squiggly-symbolic.svg @@ -0,0 +1,2 @@ + + diff --git a/crates/rnote-ui/data/meson.build b/crates/rnote-ui/data/meson.build index 338ec3d8e4..59cfd651ec 100644 --- a/crates/rnote-ui/data/meson.build +++ b/crates/rnote-ui/data/meson.build @@ -275,6 +275,7 @@ rnote_ui_gresources_icons_files = files( 'icons/scalable/actions/text-indent-less-symbolic.svg', 'icons/scalable/actions/text-indent-more-symbolic.svg', 'icons/scalable/actions/text-italic-symbolic.svg', + 'icons/scalable/actions/text-squiggly-symbolic.svg', 'icons/scalable/actions/text-strikethrough-symbolic.svg', 'icons/scalable/actions/text-underline-symbolic.svg', 'icons/scalable/actions/touch-two-finger-long-press-symbolic.svg', diff --git a/crates/rnote-ui/data/resources.gresource.xml b/crates/rnote-ui/data/resources.gresource.xml index 5532364b4b..a41de38595 100644 --- a/crates/rnote-ui/data/resources.gresource.xml +++ b/crates/rnote-ui/data/resources.gresource.xml @@ -148,6 +148,7 @@ icons/scalable/actions/text-indent-less-symbolic.svg icons/scalable/actions/text-indent-more-symbolic.svg icons/scalable/actions/text-italic-symbolic.svg + icons/scalable/actions/text-squiggly-symbolic.svg icons/scalable/actions/text-strikethrough-symbolic.svg icons/scalable/actions/text-underline-symbolic.svg icons/scalable/actions/touch-two-finger-long-press-symbolic.svg diff --git a/crates/rnote-ui/data/ui/penssidebar/typewriterpage.ui b/crates/rnote-ui/data/ui/penssidebar/typewriterpage.ui index d30e43e93f..a25d2d6ccf 100644 --- a/crates/rnote-ui/data/ui/penssidebar/typewriterpage.ui +++ b/crates/rnote-ui/data/ui/penssidebar/typewriterpage.ui @@ -53,6 +53,99 @@ right + + + left + Spellcheck Corrections + text-squiggly-symbolic + + + + + + + true + + + + + + + + vertical + 12 + 18 + 18 + 18 + 18 + + + text-squiggly-symbolic + 64 + + + + + + center + No Corrections +Available + + + + + + center + Move the cursor over an underlined +word to get suggestions. + + + + + + + + + + vertical + 12 + 18 + 18 + 18 + 18 + + + text-squiggly-symbolic + 64 + + + + + + center + No Corrections +Found + + + + + + vertical diff --git a/crates/rnote-ui/data/ui/settingspanel.ui b/crates/rnote-ui/data/ui/settingspanel.ui index 957792fc86..4c076545ee 100644 --- a/crates/rnote-ui/data/ui/settingspanel.ui +++ b/crates/rnote-ui/data/ui/settingspanel.ui @@ -440,6 +440,23 @@ gets disabled. Set whether the document origin indicator is shown + + + Spellcheck + Enable or disable spellcheck + + + + + Spellcheck Language + Choose the language used by spellcheck + true + substring + + + + + Invert Color Brightness diff --git a/crates/rnote-ui/src/appwindow/imp.rs b/crates/rnote-ui/src/appwindow/imp.rs index c2da89c122..f7e984a22f 100644 --- a/crates/rnote-ui/src/appwindow/imp.rs +++ b/crates/rnote-ui/src/appwindow/imp.rs @@ -723,6 +723,11 @@ impl RnAppWindow { .typewriter_page() .emojichooser_menubutton() .set_direction(ArrowType::Right); + obj.overlays() + .penssidebar() + .typewriter_page() + .spellcheck_corrections_menubutton() + .set_direction(ArrowType::Right); obj.overlays() .penssidebar() .eraser_page() @@ -840,6 +845,11 @@ impl RnAppWindow { .typewriter_page() .emojichooser_menubutton() .set_direction(ArrowType::Left); + obj.overlays() + .penssidebar() + .typewriter_page() + .spellcheck_corrections_menubutton() + .set_direction(ArrowType::Left); obj.overlays() .penssidebar() .eraser_page() diff --git a/crates/rnote-ui/src/penssidebar/typewriterpage.rs b/crates/rnote-ui/src/penssidebar/typewriterpage.rs index 6c6787c83b..6e0b30d4c4 100644 --- a/crates/rnote-ui/src/penssidebar/typewriterpage.rs +++ b/crates/rnote-ui/src/penssidebar/typewriterpage.rs @@ -1,8 +1,11 @@ // Imports use crate::RnAppWindow; +use gtk4::ListView; +use gtk4::Popover; use gtk4::{ - Button, CompositeTemplate, EmojiChooser, FontDialog, MenuButton, SpinButton, ToggleButton, - Widget, glib, glib::clone, pango, prelude::*, subclass::prelude::*, + Button, CompositeTemplate, ConstantExpression, EmojiChooser, FontDialog, ListItem, MenuButton, + NoSelection, PropertyExpression, SignalListItemFactory, SpinButton, StringList, StringObject, + ToggleButton, Widget, glib, glib::clone, pango, prelude::*, subclass::prelude::*, }; use rnote_engine::strokes::textstroke::{FontStyle, TextAlignment, TextAttribute, TextStyle}; use std::cell::RefCell; @@ -25,6 +28,16 @@ mod imp { #[template_child] pub(crate) emojichooser: TemplateChild, #[template_child] + pub(crate) spellcheck_corrections_menubutton: TemplateChild, + #[template_child] + pub(crate) spellcheck_corrections_empty: TemplateChild, + #[template_child] + pub(crate) spellcheck_corrections_unavailable: TemplateChild, + #[template_child] + pub(crate) spellcheck_corrections: TemplateChild, + #[template_child] + pub(crate) spellcheck_corrections_listview: TemplateChild, + #[template_child] pub(crate) text_reset_button: TemplateChild