From 14070d547913e407abf8c7cad2d5ca7fcd00cb59 Mon Sep 17 00:00:00 2001 From: Chad Brokaw Date: Thu, 22 Aug 2024 13:33:27 -0400 Subject: [PATCH] [parley] new selection logic and an editor example (#106) Adds a new `Selection` type for dealing with cursor movement and selection. Also includes a `vello_editor` example that serves as a testbed for checking the logic. This supports mouse selections, arrow keys (+ shift to extend selections), backspace, delete and basic text input. Marked as draft because the code is a mess but making this public so people can play with it and report bugs. --- .typos.toml | 10 + Cargo.lock | 3201 ++++++++++++++++++++++++++--- Cargo.toml | 1 + examples/swash_render/src/main.rs | 1 - examples/vello_editor/Cargo.toml | 24 + examples/vello_editor/src/main.rs | 223 ++ examples/vello_editor/src/text.rs | 400 ++++ parley/src/layout/cluster.rs | 373 +++- parley/src/layout/cursor.rs | 859 ++++++-- parley/src/layout/line/greedy.rs | 81 +- parley/src/layout/line/mod.rs | 34 +- parley/src/layout/mod.rs | 83 +- parley/src/layout/run.rs | 36 +- 13 files changed, 4854 insertions(+), 472 deletions(-) create mode 100644 examples/vello_editor/Cargo.toml create mode 100644 examples/vello_editor/src/main.rs create mode 100644 examples/vello_editor/src/text.rs diff --git a/.typos.toml b/.typos.toml index 98e24772..d43c5c85 100644 --- a/.typos.toml +++ b/.typos.toml @@ -6,6 +6,16 @@ # word is treated as always correct. If the value is an empty string, the word # is treated as always incorrect. +[default] +extend-ignore-re = [ + # Matches lorem ipsum text. + # In general, regexes are only matched until the end of a line by typos, + # and the repeated matcher at the end of both of these also ensures that + # matching ends at quotes or symbols commonly used to terminate comments. + "Lorem ipsum [a-zA-Z .,]*", + "Phasellus in viverra dolor [a-zA-Z .,]*", +] + # Match Identifier - Case Sensitive [default.extend-identifiers] Beng = "Beng" diff --git a/Cargo.lock b/Cargo.lock index 3bfbdb82..2563a945 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2,6 +2,22 @@ # It is not intended for manual editing. version = 3 +[[package]] +name = "ab_glyph" +version = "0.2.28" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "79faae4620f45232f599d9bc7b290f88247a0834162c4495ab2f02d60004adfb" +dependencies = [ + "ab_glyph_rasterizer", + "owned_ttf_parser", +] + +[[package]] +name = "ab_glyph_rasterizer" +version = "0.1.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c71b1793ee61086797f5c80b6efa2b8ffa6d5dd703f118545808a7f2e27f7046" + [[package]] name = "adler" version = "1.0.2" @@ -15,17 +31,83 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e89da841a80418a9b391ebaea17f5c112ffaaa96f621d2c285b5174da76b9011" dependencies = [ "cfg-if", + "getrandom", "once_cell", "version_check", "zerocopy", ] +[[package]] +name = "aligned-vec" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4aa90d7ce82d4be67b64039a3d588d38dbcc6736577de4a847025ce5b0c468d1" + [[package]] name = "allocator-api2" version = "0.2.18" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5c6cb57a04249c6480766f7f7cef5467412af1490f8d1e243141daddada3264f" +[[package]] +name = "android-activity" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ef6978589202a00cd7e118380c448a08b6ed394c3a8df3a430d0898e3a42d046" +dependencies = [ + "android-properties", + "bitflags 2.5.0", + "cc", + "cesu8", + "jni", + "jni-sys", + "libc", + "log", + "ndk", + "ndk-context", + "ndk-sys 0.6.0+11769913", + "num_enum", + "thiserror", +] + +[[package]] +name = "android-properties" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fc7eb209b1518d6bb87b283c20095f5228ecda460da70b44f0802523dea6da04" + +[[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.86" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b3d1d046238990b9cf5bcde22a3fb3584ee5cf65fb2765f454ed428c7a0063da" + +[[package]] +name = "arbitrary" +version = "1.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7d5a26814d8dcb93b0e5a0ff3c6d80a8843bafb21b39e8e18a6f05471870e110" + +[[package]] +name = "arg_enum_proc_macro" +version = "0.3.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0ae92a5119aa49cdbcf6b9f893fe4e1d98b04ccbf82ee0584ad948a44a734dea" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.65", +] + [[package]] name = "arrayref" version = "0.3.7" @@ -38,12 +120,77 @@ version = "0.7.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "96d30a06541fbafbc7f82ed10c06164cfbd2c401138f6addd8404629c4b16711" +[[package]] +name = "as-raw-xcb-connection" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "175571dd1d178ced59193a6fc02dde1b972eb0bc56c892cde9beeceac5bf0f6b" + +[[package]] +name = "ash" +version = "0.37.3+1.3.251" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "39e9c3835d686b0a6084ab4234fcd1b07dbf6e4767dce60874b12356a25ecd4a" +dependencies = [ + "libloading 0.7.4", +] + +[[package]] +name = "atomic-waker" +version = "1.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1505bd5d3d116872e7271a6d4e16d81d0c8570876c8de68093a09ac269d8aac0" + [[package]] name = "autocfg" version = "1.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0c4b4d0bd25bd0b74681c0ad21497610ce1b7c91b1022cd21c80c6fbdd9476b0" +[[package]] +name = "av1-grain" +version = "0.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6678909d8c5d46a42abcf571271e15fdbc0a225e3646cf23762cd415046c78bf" +dependencies = [ + "anyhow", + "arrayvec", + "log", + "nom", + "num-rational", + "v_frame", +] + +[[package]] +name = "avif-serialize" +version = "0.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "876c75a42f6364451a033496a14c44bffe41f5f4a8236f697391f11024e596d2" +dependencies = [ + "arrayvec", +] + +[[package]] +name = "bit-set" +version = "0.5.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0700ddab506f33b20a03b13996eccd309a48e5ff77d0d95926aa0210fb4e95f1" +dependencies = [ + "bit-vec", +] + +[[package]] +name = "bit-vec" +version = "0.6.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "349f9b6a179ed607305526ca489b34ad0a41aed5f7980fa90eb03160b69598fb" + +[[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" @@ -56,6 +203,18 @@ version = "2.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "cf4b9d6a944f767f8e5e0db018570623c85f3d925ac718db4e06d0187adb21c1" +[[package]] +name = "bitstream-io" +version = "2.5.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b81e1519b0d82120d2fd469d5bfb2919a9361c48b02d82d04befc1cdd2002452" + +[[package]] +name = "block" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0d8c1fef690941d3e7788d328517591fecc684c084084702d6ff1641e993699a" + [[package]] name = "block2" version = "0.5.1" @@ -65,6 +224,18 @@ dependencies = [ "objc2", ] +[[package]] +name = "built" +version = "0.7.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "236e6289eda5a812bc6b53c3b024039382a2895fbbeef2d748b2931546d392c4" + +[[package]] +name = "bumpalo" +version = "3.16.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "79296716171880943b8470b5f8d03aa55eb2e645a4874bdbb28adb49162e012c" + [[package]] name = "bytemuck" version = "1.16.0" @@ -82,7 +253,7 @@ checksum = "4da9a32f3fed317401fa3c862968128267c3106685286e15d5aaa3d7389c2f60" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.65", ] [[package]] @@ -91,12 +262,207 @@ version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b" +[[package]] +name = "byteorder-lite" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8f1fe948ff07f4bd06c30984e69f5b4899c516a3ef74f34df92a2df2ab535495" + +[[package]] +name = "bytes" +version = "1.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8318a53db07bb3f8dca91a600466bdb3f2eaadeedfdbcf02e1accbad9271ba50" + +[[package]] +name = "calloop" +version = "0.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b99da2f8558ca23c71f4fd15dc57c906239752dd27ff3c00a1d56b685b7cbfec" +dependencies = [ + "bitflags 2.5.0", + "log", + "polling", + "rustix", + "slab", + "thiserror", +] + +[[package]] +name = "calloop-wayland-source" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "95a66a987056935f7efce4ab5668920b5d0dac4a7c99991a67395f13702ddd20" +dependencies = [ + "calloop", + "rustix", + "wayland-backend", + "wayland-client", +] + +[[package]] +name = "cc" +version = "1.1.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "68064e60dbf1f17005c2fde4d07c16d8baa506fd7ffed8ccab702d93617975c7" +dependencies = [ + "jobserver", + "libc", + "shlex", +] + +[[package]] +name = "cesu8" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6d43a04d8753f35258c91f8ec639f792891f748a1edbd759cf1dcea3382ad83c" + +[[package]] +name = "cfg-expr" +version = "0.15.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d067ad48b8650848b989a59a86c6c36a995d02d2bf778d45c3c5d57bc2718f02" +dependencies = [ + "smallvec", + "target-lexicon", +] + [[package]] name = "cfg-if" version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" +[[package]] +name = "cfg_aliases" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fd16c4719339c4530435d38e511904438d07cce7950afa3718a84ac36c10e89e" + +[[package]] +name = "cfg_aliases" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "613afe47fcd5fac7ccf1db93babcb082c5994d996f20b8b159f2ad1658eb5724" + +[[package]] +name = "clipboard-rs" +version = "0.1.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c718c0784ab208e35cc764e8b3f864caf5cd82a65d793fb99ddb3565ae028726" +dependencies = [ + "clipboard-win", + "cocoa", + "image", + "x11rb", +] + +[[package]] +name = "clipboard-win" +version = "5.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "15efe7a882b08f34e38556b14f2fb3daa98769d06c7f0c1b076dfd0d983bc892" +dependencies = [ + "error-code", + "windows-win", +] + +[[package]] +name = "cocoa" +version = "0.25.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f6140449f97a6e97f9511815c5632d84c8aacf8ac271ad77c559218161a1373c" +dependencies = [ + "bitflags 1.3.2", + "block", + "cocoa-foundation", + "core-foundation", + "core-graphics", + "foreign-types", + "libc", + "objc", +] + +[[package]] +name = "cocoa-foundation" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8c6234cbb2e4c785b456c0644748b1ac416dd045799740356f8363dfe00c93f7" +dependencies = [ + "bitflags 1.3.2", + "block", + "core-foundation", + "core-graphics-types", + "libc", + "objc", +] + +[[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 = "com" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7e17887fd17353b65b1b2ef1c526c83e26cd72e74f598a8dc1bee13a48f3d9f6" +dependencies = [ + "com_macros", +] + +[[package]] +name = "com_macros" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d375883580a668c7481ea6631fc1a8863e33cc335bf56bfad8d7e6d4b04b13a5" +dependencies = [ + "com_macros_support", + "proc-macro2", + "syn 1.0.109", +] + +[[package]] +name = "com_macros_support" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ad899a1087a9296d5644792d7cb72b8e34c1bec8e7d4fbc002230169a6e8710c" +dependencies = [ + "proc-macro2", + "quote", + "syn 1.0.109", +] + +[[package]] +name = "combine" +version = "4.6.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ba5a308b75df32fe02788e748662718f03fde005016435c444eea572398219fd" +dependencies = [ + "bytes", + "memchr", +] + +[[package]] +name = "concurrent-queue" +version = "2.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4ca0197aee26d1ae37445ee532fefce43251d24cc7c166799f4d46817f1d3973" +dependencies = [ + "crossbeam-utils", +] + [[package]] name = "core-foundation" version = "0.9.4" @@ -168,71 +534,217 @@ dependencies = [ ] [[package]] -name = "displaydoc" -version = "0.2.4" +name = "crossbeam-deque" +version = "0.8.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "487585f4d0c6655fe74905e2504d8ad6908e4db67f744eb140876906c2f3175d" +checksum = "613f8cc01fe9cf1a3eb3d7f488fd2fa8388403e97039e2f73692932e291a770d" dependencies = [ - "proc-macro2", - "quote", - "syn", + "crossbeam-epoch", + "crossbeam-utils", ] [[package]] -name = "dwrote" -version = "0.11.0" +name = "crossbeam-epoch" +version = "0.9.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "439a1c2ba5611ad3ed731280541d36d2e9c4ac5e7fb818a27b604bdc5a6aa65b" +checksum = "5b82ac4a3c2ca9c3460964f020e1402edd5753411d7737aa39c3714ad1b5420e" dependencies = [ - "lazy_static", - "libc", - "serde", - "serde_derive", - "winapi", - "wio", + "crossbeam-utils", ] [[package]] -name = "fdeflate" -version = "0.3.4" +name = "crossbeam-utils" +version = "0.8.20" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4f9bfee30e4dedf0ab8b422f03af778d9612b63f502710fc500a334ebe2de645" -dependencies = [ - "simd-adler32", -] +checksum = "22ec99545bb0ed0ea7bb9b8e1e9122ea386ff8a48c0922e43f36d45ab09e0e80" [[package]] -name = "flate2" -version = "1.0.30" +name = "crunchy" +version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5f54427cfd1c7829e2a139fcefea601bf088ebca651d2bf53ebc600eac295dae" -dependencies = [ - "crc32fast", - "miniz_oxide", -] +checksum = "7a81dae078cea95a014a339291cec439d2f232ebe854a9d672b796c6afafa9b7" [[package]] -name = "font-types" -version = "0.5.3" +name = "cursor-icon" +version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bdf6aa1de86490d8e39e04589bd04eb5953cc2a5ef0c25e389e807f44fd24e41" +checksum = "96a6ac251f4a2aca6b3f91340350eab87ae57c3f127ffeb585e92bd336717991" + +[[package]] +name = "d3d12" +version = "0.20.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b28bfe653d79bd16c77f659305b195b82bb5ce0c0eb2a4846b82ddbd77586813" dependencies = [ - "bytemuck", + "bitflags 2.5.0", + "libloading 0.8.5", + "winapi", ] [[package]] -name = "fontconfig-cache-parser" +name = "dispatch" version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a7f8afb20c8069fd676d27b214559a337cc619a605d25a87baa90b49a06f3b18" +checksum = "bd0c93bb4b0c6d9b77f4435b0ae98c24d17f1c45b2ff844c6151a07256ca923b" + +[[package]] +name = "displaydoc" +version = "0.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "487585f4d0c6655fe74905e2504d8ad6908e4db67f744eb140876906c2f3175d" dependencies = [ - "bytemuck", - "thiserror", + "proc-macro2", + "quote", + "syn 2.0.65", ] [[package]] -name = "fontique" -version = "0.1.0" +name = "dlib" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "330c60081dcc4c72131f8eb70510f1ac07223e5d4163db481a04a0befcffa412" +dependencies = [ + "libloading 0.8.5", +] + +[[package]] +name = "document-features" +version = "0.2.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cb6969eaabd2421f8a2775cfd2471a2b634372b4a25d41e3bd647b79912850a0" +dependencies = [ + "litrs", +] + +[[package]] +name = "downcast-rs" +version = "1.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "75b325c5dbd37f80359721ad39aca5a29fb04c89279657cffdda8736d0c0b9d2" + +[[package]] +name = "dpi" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f25c0e292a7ca6d6498557ff1df68f32c99850012b6ea401cf8daf771f22ff53" + +[[package]] +name = "dwrote" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "439a1c2ba5611ad3ed731280541d36d2e9c4ac5e7fb818a27b604bdc5a6aa65b" +dependencies = [ + "lazy_static", + "libc", + "serde", + "serde_derive", + "winapi", + "wio", +] + +[[package]] +name = "either" +version = "1.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "60b1af1c220855b6ceac025d3f6ecdd2b7c4894bfe9cd9bda4fbb4bc7c0d4cf0" + +[[package]] +name = "equivalent" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5443807d6dff69373d433ab9ef5378ad8df50ca6298caf15de6e52e24aaf54d5" + +[[package]] +name = "errno" +version = "0.3.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "534c5cf6194dfab3db3242765c03bbe257cf92f22b38f6bc0c58d59108a820ba" +dependencies = [ + "libc", + "windows-sys 0.52.0", +] + +[[package]] +name = "error-code" +version = "3.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a0474425d51df81997e2f90a21591180b38eccf27292d755f3e30750225c175b" + +[[package]] +name = "euclid" +version = "0.22.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e0f0eb73b934648cd7a4a61f1b15391cd95dab0b4da6e2e66c2a072c144b4a20" +dependencies = [ + "num-traits", +] + +[[package]] +name = "exr" +version = "1.72.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "887d93f60543e9a9362ef8a21beedd0a833c5d9610e18c67abe15a5963dcb1a4" +dependencies = [ + "bit_field", + "flume", + "half", + "lebe", + "miniz_oxide", + "rayon-core", + "smallvec", + "zune-inflate", +] + +[[package]] +name = "fdeflate" +version = "0.3.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4f9bfee30e4dedf0ab8b422f03af778d9612b63f502710fc500a334ebe2de645" +dependencies = [ + "simd-adler32", +] + +[[package]] +name = "flate2" +version = "1.0.30" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5f54427cfd1c7829e2a139fcefea601bf088ebca651d2bf53ebc600eac295dae" +dependencies = [ + "crc32fast", + "miniz_oxide", +] + +[[package]] +name = "flume" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "55ac459de2512911e4b674ce33cf20befaba382d05b62b008afc1c8b57cbf181" +dependencies = [ + "spin", +] + +[[package]] +name = "font-types" +version = "0.5.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "34fd7136aca682873d859ef34494ab1a7d3f57ecd485ed40eb6437ee8c85aa29" +dependencies = [ + "bytemuck", +] + +[[package]] +name = "fontconfig-cache-parser" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a7f8afb20c8069fd676d27b214559a337cc619a605d25a87baa90b49a06f3b18" +dependencies = [ + "bytemuck", + "thiserror", +] + +[[package]] +name = "fontique" +version = "0.1.0" dependencies = [ "core-foundation", "core-text", @@ -272,7 +784,7 @@ checksum = "1a5c6c585bc94aaf2c7b51dd4c2ba22680844aba4c687be581871a6f518c5742" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.65", ] [[package]] @@ -281,6 +793,158 @@ version = "0.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "aa9a19cbb55df58761df49b23516a86d432839add4af60fc256da840f66ed35b" +[[package]] +name = "futures-core" +version = "0.3.30" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dfc6580bb841c5a68e9ef15c77ccc837b40a7504914d52e47b8b0e9bbda25a1d" + +[[package]] +name = "futures-intrusive" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1d930c203dd0b6ff06e0201a4a2fe9149b43c684fd4420555b26d21b1a02956f" +dependencies = [ + "futures-core", + "lock_api", + "parking_lot", +] + +[[package]] +name = "gethostname" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0176e0459c2e4a1fe232f984bca6890e681076abb9934f6cea7c326f3fc47818" +dependencies = [ + "libc", + "windows-targets 0.48.5", +] + +[[package]] +name = "getrandom" +version = "0.2.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c4567c8db10ae91089c99af84c68c38da3ec2f087c3f82960bcdbf3656b6f4d7" +dependencies = [ + "cfg-if", + "libc", + "wasi", +] + +[[package]] +name = "gif" +version = "0.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3fb2d69b19215e18bb912fa30f7ce15846e301408695e44e0ef719f1da9e19f2" +dependencies = [ + "color_quant", + "weezl", +] + +[[package]] +name = "gl_generator" +version = "0.14.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1a95dfc23a2b4a9a2f5ab41d194f8bfda3cabec42af4e39f08c339eb2a0c124d" +dependencies = [ + "khronos_api", + "log", + "xml-rs", +] + +[[package]] +name = "glow" +version = "0.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bd348e04c43b32574f2de31c8bb397d96c9fcfa1371bd4ca6d8bdc464ab121b1" +dependencies = [ + "js-sys", + "slotmap", + "wasm-bindgen", + "web-sys", +] + +[[package]] +name = "glutin_wgl_sys" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6c8098adac955faa2d31079b65dc48841251f69efd3ac25477903fc424362ead" +dependencies = [ + "gl_generator", +] + +[[package]] +name = "gpu-alloc" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fbcd2dba93594b227a1f57ee09b8b9da8892c34d55aa332e034a228d0fe6a171" +dependencies = [ + "bitflags 2.5.0", + "gpu-alloc-types", +] + +[[package]] +name = "gpu-alloc-types" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "98ff03b468aa837d70984d55f5d3f846f6ec31fe34bbb97c4f85219caeee1ca4" +dependencies = [ + "bitflags 2.5.0", +] + +[[package]] +name = "gpu-allocator" +version = "0.25.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6f56f6318968d03c18e1bcf4857ff88c61157e9da8e47c5f29055d60e1228884" +dependencies = [ + "log", + "presser", + "thiserror", + "winapi", + "windows", +] + +[[package]] +name = "gpu-descriptor" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9c08c1f623a8d0b722b8b99f821eb0ba672a1618f0d3b16ddbee1cedd2dd8557" +dependencies = [ + "bitflags 2.5.0", + "gpu-descriptor-types", + "hashbrown", +] + +[[package]] +name = "gpu-descriptor-types" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fdf242682df893b86f33a73828fb09ca4b2d3bb6cc95249707fc684d27484b91" +dependencies = [ + "bitflags 2.5.0", +] + +[[package]] +name = "guillotiere" +version = "0.6.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b62d5865c036cb1393e23c50693df631d3f5d7bcca4c04fe4cc0fd592e74a782" +dependencies = [ + "euclid", + "svg_fmt", +] + +[[package]] +name = "half" +version = "2.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6dd08c532ae367adf81c312a4580bc67f1d0fe8bc9c460520283f4c0ff277888" +dependencies = [ + "cfg-if", + "crunchy", +] + [[package]] name = "hashbrown" version = "0.14.5" @@ -291,6 +955,39 @@ dependencies = [ "allocator-api2", ] +[[package]] +name = "hassle-rs" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "af2a7e73e1f34c48da31fb668a907f250794837e08faa144fd24f0b8b741e890" +dependencies = [ + "bitflags 2.5.0", + "com", + "libc", + "libloading 0.8.5", + "thiserror", + "widestring", + "winapi", +] + +[[package]] +name = "heck" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea" + +[[package]] +name = "hermit-abi" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fbf6a919d6cf397374f7dfeeea91d974c7c0a7221d0d0f4f20d859d329e53fcc" + +[[package]] +name = "hexf-parse" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dfa686283ad6dd069f105e5ab091b04c62850d3e4cf5d67debad1933f55023df" + [[package]] name = "icu_collections" version = "1.5.0" @@ -382,61 +1079,284 @@ checksum = "1ec89e9337638ecdc08744df490b221a7399bf8d164eb52a665454e60e075ad6" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.65", ] [[package]] name = "image" -version = "0.25.1" +version = "0.25.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fd54d660e773627692c524beaad361aca785a4f9f5730ce91f42aabe5bce3d11" +checksum = "99314c8a2152b8ddb211f924cdae532d8c5e4c8bb54728e12fff1b0cd5963a10" dependencies = [ "bytemuck", - "byteorder", + "byteorder-lite", + "color_quant", + "exr", + "gif", + "image-webp", "num-traits", "png", + "qoi", + "ravif", + "rayon", + "rgb", + "tiff", + "zune-core", + "zune-jpeg", ] [[package]] -name = "kurbo" -version = "0.11.0" +name = "image-webp" +version = "0.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6e5aa9f0f96a938266bdb12928a67169e8d22c6a786fda8ed984b85e6ba93c3c" +checksum = "f79afb8cbee2ef20f59ccd477a218c12a93943d075b492015ecb1bb81f8ee904" dependencies = [ - "arrayvec", - "libm", - "smallvec", + "byteorder-lite", + "quick-error", ] [[package]] -name = "lazy_static" -version = "1.4.0" +name = "imgref" +version = "1.10.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646" +checksum = "44feda355f4159a7c757171a77de25daf6411e217b4cabd03bd6650690468126" [[package]] -name = "libc" -version = "0.2.155" +name = "indexmap" +version = "2.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "97b3888a4aecf77e811145cadf6eef5901f4782c53886191b2f693f24761847c" +checksum = "93ead53efc7ea8ed3cfb0c79fc8023fbb782a5432b52830b6518941cebe6505c" +dependencies = [ + "equivalent", + "hashbrown", +] [[package]] -name = "libm" -version = "0.2.8" +name = "interpolate_name" +version = "0.2.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4ec2a862134d2a7d32d7983ddcdd1c4923530833c9f2ea1a44fc5fa473989058" +checksum = "c34819042dc3d3971c46c2190835914dfbe0c3c13f61449b2997f4e9722dfa60" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.65", +] [[package]] -name = "litemap" -version = "0.7.3" +name = "itertools" +version = "0.12.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "643cb0b8d4fcc284004d5fd0d67ccf61dfffadb7f75e1e71bc420f4688a3a704" +checksum = "ba291022dbbd398a455acf126c1e341954079855bc60dfdda641363bd6922569" +dependencies = [ + "either", +] [[package]] -name = "log" -version = "0.4.21" +name = "jni" +version = "0.21.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "90ed8c1e510134f979dbc4f070f87d4313098b704861a105fe34231c70a3901c" +checksum = "1a87aa2bb7d2af34197c04845522473242e1aa17c12f4935d5856491a7fb8c97" +dependencies = [ + "cesu8", + "cfg-if", + "combine", + "jni-sys", + "log", + "thiserror", + "walkdir", + "windows-sys 0.45.0", +] + +[[package]] +name = "jni-sys" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8eaf4bc02d17cbdd7ff4c7438cafcdf7fb9a4613313ad11b4f8fefe7d3fa0130" + +[[package]] +name = "jobserver" +version = "0.1.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "48d1dbcbbeb6a7fec7e059840aa538bd62aaccf972c7346c4d9d2059312853d0" +dependencies = [ + "libc", +] + +[[package]] +name = "jpeg-decoder" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f5d4a7da358eff58addd2877a45865158f0d78c911d43a5784ceb7bbf52833b0" + +[[package]] +name = "js-sys" +version = "0.3.70" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1868808506b929d7b0cfa8f75951347aa71bb21144b7791bae35d9bccfcfe37a" +dependencies = [ + "wasm-bindgen", +] + +[[package]] +name = "khronos-egl" +version = "6.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6aae1df220ece3c0ada96b8153459b67eebe9ae9212258bb0134ae60416fdf76" +dependencies = [ + "libc", + "libloading 0.8.5", + "pkg-config", +] + +[[package]] +name = "khronos_api" +version = "3.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e2db585e1d738fc771bf08a151420d3ed193d9d895a36df7f6f8a9456b911ddc" + +[[package]] +name = "kurbo" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6e5aa9f0f96a938266bdb12928a67169e8d22c6a786fda8ed984b85e6ba93c3c" +dependencies = [ + "arrayvec", + "libm", + "smallvec", +] + +[[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 = "libc" +version = "0.2.155" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "97b3888a4aecf77e811145cadf6eef5901f4782c53886191b2f693f24761847c" + +[[package]] +name = "libfuzzer-sys" +version = "0.4.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a96cfd5557eb82f2b83fed4955246c988d331975a002961b07c81584d107e7f7" +dependencies = [ + "arbitrary", + "cc", + "once_cell", +] + +[[package]] +name = "libloading" +version = "0.7.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b67380fd3b2fbe7527a606e18729d21c6f3951633d0500574c4dc22d2d638b9f" +dependencies = [ + "cfg-if", + "winapi", +] + +[[package]] +name = "libloading" +version = "0.8.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4979f22fdb869068da03c9f7528f8297c6fd2606bc3a4affe42e6a823fdb8da4" +dependencies = [ + "cfg-if", + "windows-targets 0.52.6", +] + +[[package]] +name = "libm" +version = "0.2.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4ec2a862134d2a7d32d7983ddcdd1c4923530833c9f2ea1a44fc5fa473989058" + +[[package]] +name = "libredox" +version = "0.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3af92c55d7d839293953fcd0fda5ecfe93297cfde6ffbdec13b41d99c0ba6607" +dependencies = [ + "bitflags 2.5.0", + "libc", + "redox_syscall 0.4.1", +] + +[[package]] +name = "linux-raw-sys" +version = "0.4.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "78b3ae25bc7c8c38cec158d1f2757ee79e9b3740fbc7ccf0e59e4b08d793fa89" + +[[package]] +name = "litemap" +version = "0.7.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "643cb0b8d4fcc284004d5fd0d67ccf61dfffadb7f75e1e71bc420f4688a3a704" + +[[package]] +name = "litrs" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b4ce301924b7887e9d637144fdade93f9dfff9b60981d4ac161db09720d39aa5" + +[[package]] +name = "lock_api" +version = "0.4.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "07af8b9cdd281b7915f413fa73f29ebd5d55d0d3f0155584dade1ff18cea1b17" +dependencies = [ + "autocfg", + "scopeguard", +] + +[[package]] +name = "log" +version = "0.4.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "90ed8c1e510134f979dbc4f070f87d4313098b704861a105fe34231c70a3901c" + +[[package]] +name = "loop9" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0fae87c125b03c1d2c0150c90365d7d6bcc53fb73a9acaef207d2d065860f062" +dependencies = [ + "imgref", +] + +[[package]] +name = "malloc_buf" +version = "0.0.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "62bb907fe88d54d8d9ce32a3cceab4218ed2f6b7d35617cafe9adf84e43919cb" +dependencies = [ + "libc", +] + +[[package]] +name = "maybe-rayon" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8ea1f30cedd69f0a2954655f7188c6a834246d2bcf1e315e2ac40c4b24dc9519" +dependencies = [ + "cfg-if", +] + +[[package]] +name = "memchr" +version = "2.7.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "78ca9ab1a0babb1e7d5695e3530886289c18cf2f87ec19a575a0abdce112e3a3" [[package]] name = "memmap2" @@ -447,6 +1367,27 @@ dependencies = [ "libc", ] +[[package]] +name = "metal" +version = "0.28.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5637e166ea14be6063a3f8ba5ccb9a4159df7d8f6d61c02fc3d480b1f90dcfcb" +dependencies = [ + "bitflags 2.5.0", + "block", + "core-graphics-types", + "foreign-types", + "log", + "objc", + "paste", +] + +[[package]] +name = "minimal-lexical" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "68354c5c6bd36d73ff3feceb05efa59b6acb7626617f4962be322a825e61f79a" + [[package]] name = "miniz_oxide" version = "0.7.3" @@ -457,6 +1398,129 @@ dependencies = [ "simd-adler32", ] +[[package]] +name = "naga" +version = "0.20.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e536ae46fcab0876853bd4a632ede5df4b1c2527a58f6c5a4150fe86be858231" +dependencies = [ + "arrayvec", + "bit-set", + "bitflags 2.5.0", + "codespan-reporting", + "hexf-parse", + "indexmap", + "log", + "num-traits", + "rustc-hash", + "spirv", + "termcolor", + "thiserror", + "unicode-xid", +] + +[[package]] +name = "ndk" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c3f42e7bbe13d351b6bead8286a43aac9534b82bd3cc43e47037f012ebfd62d4" +dependencies = [ + "bitflags 2.5.0", + "jni-sys", + "log", + "ndk-sys 0.6.0+11769913", + "num_enum", + "raw-window-handle", + "thiserror", +] + +[[package]] +name = "ndk-context" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "27b02d87554356db9e9a873add8782d4ea6e3e58ea071a9adb9a2e8ddb884a8b" + +[[package]] +name = "ndk-sys" +version = "0.5.0+25.2.9519653" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8c196769dd60fd4f363e11d948139556a344e79d451aeb2fa2fd040738ef7691" +dependencies = [ + "jni-sys", +] + +[[package]] +name = "ndk-sys" +version = "0.6.0+11769913" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ee6cda3051665f1fb8d9e08fc35c96d5a244fb1be711a03b71118828afc9a873" +dependencies = [ + "jni-sys", +] + +[[package]] +name = "new_debug_unreachable" +version = "1.0.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "650eef8c711430f1a879fdd01d4745a7deea475becfb90269c06775983bbf086" + +[[package]] +name = "nom" +version = "7.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d273983c5a657a70a3e8f2a01329822f3b8c8172b73826411a55751e404a0a4a" +dependencies = [ + "memchr", + "minimal-lexical", +] + +[[package]] +name = "noop_proc_macro" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0676bb32a98c1a483ce53e500a81ad9c3d5b3f7c920c28c24e9cb0980d0b5bc8" + +[[package]] +name = "num-bigint" +version = "0.4.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a5e44f723f1133c9deac646763579fdb3ac745e418f2a7af9cd0c431da1f20b9" +dependencies = [ + "num-integer", + "num-traits", +] + +[[package]] +name = "num-derive" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ed3955f1a9c7c0c15e092f9c887db08b1fc683305fdf6eb6684f22555355e202" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.65", +] + +[[package]] +name = "num-integer" +version = "0.1.46" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7969661fd2958a5cb096e56c8e1ad0444ac2bbcd0061bd28660485a44879858f" +dependencies = [ + "num-traits", +] + +[[package]] +name = "num-rational" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f83d14da390562dca69fc84082e73e548e1ad308d24accdedd2720017cb37824" +dependencies = [ + "num-bigint", + "num-integer", + "num-traits", +] + [[package]] name = "num-traits" version = "0.2.19" @@ -466,6 +1530,36 @@ dependencies = [ "autocfg", ] +[[package]] +name = "num_enum" +version = "0.7.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4e613fc340b2220f734a8595782c551f1250e969d87d3be1ae0579e8d4065179" +dependencies = [ + "num_enum_derive", +] + +[[package]] +name = "num_enum_derive" +version = "0.7.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "af1844ef2428cc3e1cb900be36181049ef3d3193c63e43026cfe202983b27a56" +dependencies = [ + "proc-macro-crate", + "proc-macro2", + "quote", + "syn 2.0.65", +] + +[[package]] +name = "objc" +version = "0.2.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "915b1b472bc21c53464d6c8461c9d3af805ba1ef837e1cac254428f4a77177b1" +dependencies = [ + "malloc_buf", +] + [[package]] name = "objc-sys" version = "0.3.5" @@ -473,310 +1567,1809 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "cdb91bdd390c7ce1a8607f35f3ca7151b65afc0ff5ff3b34fa350f7d7c7e4310" [[package]] -name = "objc2" -version = "0.5.2" +name = "objc2" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "46a785d4eeff09c14c487497c162e92766fbb3e4059a71840cecc03d9a50b804" +dependencies = [ + "objc-sys", + "objc2-encode", +] + +[[package]] +name = "objc2-app-kit" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e4e89ad9e3d7d297152b17d39ed92cd50ca8063a89a9fa569046d41568891eff" +dependencies = [ + "bitflags 2.5.0", + "block2", + "libc", + "objc2", + "objc2-core-data", + "objc2-core-image", + "objc2-foundation", + "objc2-quartz-core", +] + +[[package]] +name = "objc2-cloud-kit" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "74dd3b56391c7a0596a295029734d3c1c5e7e510a4cb30245f8221ccea96b009" +dependencies = [ + "bitflags 2.5.0", + "block2", + "objc2", + "objc2-core-location", + "objc2-foundation", +] + +[[package]] +name = "objc2-contacts" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a5ff520e9c33812fd374d8deecef01d4a840e7b41862d849513de77e44aa4889" +dependencies = [ + "block2", + "objc2", + "objc2-foundation", +] + +[[package]] +name = "objc2-core-data" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "617fbf49e071c178c0b24c080767db52958f716d9eabdf0890523aeae54773ef" +dependencies = [ + "bitflags 2.5.0", + "block2", + "objc2", + "objc2-foundation", +] + +[[package]] +name = "objc2-core-image" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "55260963a527c99f1819c4f8e3b47fe04f9650694ef348ffd2227e8196d34c80" +dependencies = [ + "block2", + "objc2", + "objc2-foundation", + "objc2-metal", +] + +[[package]] +name = "objc2-core-location" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "000cfee34e683244f284252ee206a27953279d370e309649dc3ee317b37e5781" +dependencies = [ + "block2", + "objc2", + "objc2-contacts", + "objc2-foundation", +] + +[[package]] +name = "objc2-encode" +version = "4.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7891e71393cd1f227313c9379a26a584ff3d7e6e7159e988851f0934c993f0f8" + +[[package]] +name = "objc2-foundation" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0ee638a5da3799329310ad4cfa62fbf045d5f56e3ef5ba4149e7452dcf89d5a8" +dependencies = [ + "bitflags 2.5.0", + "block2", + "dispatch", + "libc", + "objc2", +] + +[[package]] +name = "objc2-link-presentation" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a1a1ae721c5e35be65f01a03b6d2ac13a54cb4fa70d8a5da293d7b0020261398" +dependencies = [ + "block2", + "objc2", + "objc2-app-kit", + "objc2-foundation", +] + +[[package]] +name = "objc2-metal" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dd0cba1276f6023976a406a14ffa85e1fdd19df6b0f737b063b95f6c8c7aadd6" +dependencies = [ + "bitflags 2.5.0", + "block2", + "objc2", + "objc2-foundation", +] + +[[package]] +name = "objc2-quartz-core" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e42bee7bff906b14b167da2bac5efe6b6a07e6f7c0a21a7308d40c960242dc7a" +dependencies = [ + "bitflags 2.5.0", + "block2", + "objc2", + "objc2-foundation", + "objc2-metal", +] + +[[package]] +name = "objc2-symbols" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0a684efe3dec1b305badae1a28f6555f6ddd3bb2c2267896782858d5a78404dc" +dependencies = [ + "objc2", + "objc2-foundation", +] + +[[package]] +name = "objc2-ui-kit" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b8bb46798b20cd6b91cbd113524c490f1686f4c4e8f49502431415f3512e2b6f" +dependencies = [ + "bitflags 2.5.0", + "block2", + "objc2", + "objc2-cloud-kit", + "objc2-core-data", + "objc2-core-image", + "objc2-core-location", + "objc2-foundation", + "objc2-link-presentation", + "objc2-quartz-core", + "objc2-symbols", + "objc2-uniform-type-identifiers", + "objc2-user-notifications", +] + +[[package]] +name = "objc2-uniform-type-identifiers" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "44fa5f9748dbfe1ca6c0b79ad20725a11eca7c2218bceb4b005cb1be26273bfe" +dependencies = [ + "block2", + "objc2", + "objc2-foundation", +] + +[[package]] +name = "objc2-user-notifications" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "76cfcbf642358e8689af64cee815d139339f3ed8ad05103ed5eaf73db8d84cb3" +dependencies = [ + "bitflags 2.5.0", + "block2", + "objc2", + "objc2-core-location", + "objc2-foundation", +] + +[[package]] +name = "once_cell" +version = "1.19.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3fdb12b2476b595f9358c5161aa467c2438859caa136dec86c26fdd2efe17b92" + +[[package]] +name = "orbclient" +version = "0.3.47" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "52f0d54bde9774d3a51dcf281a5def240c71996bc6ca05d2c847ec8b2b216166" +dependencies = [ + "libredox", +] + +[[package]] +name = "owned_ttf_parser" +version = "0.24.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "490d3a563d3122bf7c911a59b0add9389e5ec0f5f0c3ac6b91ff235a0e6a7f90" +dependencies = [ + "ttf-parser", +] + +[[package]] +name = "parking_lot" +version = "0.12.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f1bf18183cf54e8d6059647fc3063646a1801cf30896933ec2311622cc4b9a27" +dependencies = [ + "lock_api", + "parking_lot_core", +] + +[[package]] +name = "parking_lot_core" +version = "0.9.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e401f977ab385c9e4e3ab30627d6f26d00e2c73eef317493c4ec6d468726cf8" +dependencies = [ + "cfg-if", + "libc", + "redox_syscall 0.5.3", + "smallvec", + "windows-targets 0.52.6", +] + +[[package]] +name = "parley" +version = "0.1.0" +dependencies = [ + "fontique", + "peniko", + "skrifa", + "swash", +] + +[[package]] +name = "paste" +version = "1.0.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "57c0d7b74b563b49d38dae00a0c37d4d6de9b432382b2892f0574ddcae73fd0a" + +[[package]] +name = "peniko" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3c28d7294093837856bb80ad191cc46a2fcec8a30b43b7a3b0285325f0a917a9" +dependencies = [ + "kurbo", + "smallvec", +] + +[[package]] +name = "percent-encoding" +version = "2.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e3148f5046208a5d56bcfc03053e3ca6334e51da8dfb19b6cdc8b306fae3283e" + +[[package]] +name = "pin-project" +version = "1.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b6bf43b791c5b9e34c3d182969b4abb522f9343702850a2e57f460d00d09b4b3" +dependencies = [ + "pin-project-internal", +] + +[[package]] +name = "pin-project-internal" +version = "1.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2f38a4412a78282e09a2cf38d195ea5420d15ba0602cb375210efbc877243965" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.65", +] + +[[package]] +name = "pin-project-lite" +version = "0.2.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bda66fc9667c18cb2758a2ac84d1167245054bcf85d5d1aaa6923f45801bdd02" + +[[package]] +name = "pkg-config" +version = "0.3.30" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d231b230927b5e4ad203db57bbcbee2802f6bce620b1e4a9024a07d94e2907ec" + +[[package]] +name = "png" +version = "0.17.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "06e4b0d3d1312775e782c86c91a111aa1f910cbb65e1337f9975b5f9a554b5e1" +dependencies = [ + "bitflags 1.3.2", + "crc32fast", + "fdeflate", + "flate2", + "miniz_oxide", +] + +[[package]] +name = "polling" +version = "3.7.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cc2790cd301dec6cd3b7a025e4815cf825724a51c98dccfe6a3e55f05ffb6511" +dependencies = [ + "cfg-if", + "concurrent-queue", + "hermit-abi", + "pin-project-lite", + "rustix", + "tracing", + "windows-sys 0.59.0", +] + +[[package]] +name = "pollster" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "22686f4785f02a4fcc856d3b3bb19bf6c8160d103f7a99cc258bddd0251dc7f2" + +[[package]] +name = "ppv-lite86" +version = "0.2.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "77957b295656769bb8ad2b6a6b09d897d94f05c41b069aede1fcdaa675eaea04" +dependencies = [ + "zerocopy", +] + +[[package]] +name = "presser" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e8cf8e6a8aa66ce33f63993ffc4ea4271eb5b0530a9002db8455ea6050c77bfa" + +[[package]] +name = "proc-macro-crate" +version = "3.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6d37c51ca738a55da99dc0c4a34860fd675453b8b36209178c2249bb13651284" +dependencies = [ + "toml_edit 0.21.1", +] + +[[package]] +name = "proc-macro2" +version = "1.0.83" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0b33eb56c327dec362a9e55b3ad14f9d2f0904fb5a5b03b513ab5465399e9f43" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "profiling" +version = "1.0.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "43d84d1d7a6ac92673717f9f6d1518374ef257669c24ebc5ac25d5033828be58" +dependencies = [ + "profiling-procmacros", +] + +[[package]] +name = "profiling-procmacros" +version = "1.0.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8021cf59c8ec9c432cfc2526ac6b8aa508ecaf29cd415f271b8406c1b851c3fd" +dependencies = [ + "quote", + "syn 2.0.65", +] + +[[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 = "2.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a993555f31e5a609f617c12db6250dedcac1b0a85076912c436e6fc9b2c8e6a3" + +[[package]] +name = "quick-xml" +version = "0.34.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6f24d770aeca0eacb81ac29dfbc55ebcc09312fdd1f8bbecdc7e4a84e000e3b4" +dependencies = [ + "memchr", +] + +[[package]] +name = "quote" +version = "1.0.36" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0fa76aaf39101c457836aec0ce2316dbdc3ab723cdda1c6bd4e6ad4208acaca7" +dependencies = [ + "proc-macro2", +] + +[[package]] +name = "rand" +version = "0.8.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404" +dependencies = [ + "libc", + "rand_chacha", + "rand_core", +] + +[[package]] +name = "rand_chacha" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e6c10a63a0fa32252be49d21e7709d4d4baf8d231c2dbce1eaa8141b9b127d88" +dependencies = [ + "ppv-lite86", + "rand_core", +] + +[[package]] +name = "rand_core" +version = "0.6.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c" +dependencies = [ + "getrandom", +] + +[[package]] +name = "range-alloc" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9c8a99fddc9f0ba0a85884b8d14e3592853e787d581ca1816c91349b10e4eeab" + +[[package]] +name = "rav1e" +version = "0.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cd87ce80a7665b1cce111f8a16c1f3929f6547ce91ade6addf4ec86a8dda5ce9" +dependencies = [ + "arbitrary", + "arg_enum_proc_macro", + "arrayvec", + "av1-grain", + "bitstream-io", + "built", + "cfg-if", + "interpolate_name", + "itertools", + "libc", + "libfuzzer-sys", + "log", + "maybe-rayon", + "new_debug_unreachable", + "noop_proc_macro", + "num-derive", + "num-traits", + "once_cell", + "paste", + "profiling", + "rand", + "rand_chacha", + "simd_helpers", + "system-deps", + "thiserror", + "v_frame", + "wasm-bindgen", +] + +[[package]] +name = "ravif" +version = "0.11.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a8f0bfd976333248de2078d350bfdf182ff96e168a24d23d2436cef320dd4bdd" +dependencies = [ + "avif-serialize", + "imgref", + "loop9", + "quick-error", + "rav1e", + "rgb", +] + +[[package]] +name = "raw-window-handle" +version = "0.6.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "20675572f6f24e9e76ef639bc5552774ed45f1c30e2951e1e99c59888861c539" + +[[package]] +name = "rayon" +version = "1.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b418a60154510ca1a002a752ca9714984e21e4241e804d32555251faf8b78ffa" +dependencies = [ + "either", + "rayon-core", +] + +[[package]] +name = "rayon-core" +version = "1.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1465873a3dfdaa8ae7cb14b4383657caab0b3e8a0aa9ae8e04b044854c8dfce2" +dependencies = [ + "crossbeam-deque", + "crossbeam-utils", +] + +[[package]] +name = "read-fonts" +version = "0.19.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e8b8af39d1f23869711ad4cea5e7835a20daa987f80232f7f2a2374d648ca64d" +dependencies = [ + "bytemuck", + "font-types", +] + +[[package]] +name = "redox_syscall" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4722d768eff46b75989dd134e5c353f0d6296e5aaa3132e776cbdb56be7731aa" +dependencies = [ + "bitflags 1.3.2", +] + +[[package]] +name = "redox_syscall" +version = "0.5.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2a908a6e00f1fdd0dfd9c0eb08ce85126f6d8bbda50017e74bc4a4b7d4a926a4" +dependencies = [ + "bitflags 2.5.0", +] + +[[package]] +name = "renderdoc-sys" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "19b30a45b0cd0bcca8037f3d0dc3421eaf95327a17cad11964fb8179b4fc4832" + +[[package]] +name = "rgb" +version = "0.8.48" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0f86ae463694029097b846d8f99fd5536740602ae00022c0c50c5600720b2f71" +dependencies = [ + "bytemuck", +] + +[[package]] +name = "roxmltree" +version = "0.19.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3cd14fd5e3b777a7422cca79358c57a8f6e3a703d9ac187448d0daf220c2407f" + +[[package]] +name = "rustc-hash" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "08d43f7aa6b08d49f382cde6a7982047c3426db949b1424bc4b7ec9ae12c6ce2" + +[[package]] +name = "rustix" +version = "0.38.34" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "70dc5ec042f7a43c4a73241207cecc9873a06d45debb38b329f8541d85c2730f" +dependencies = [ + "bitflags 2.5.0", + "errno", + "libc", + "linux-raw-sys", + "windows-sys 0.52.0", +] + +[[package]] +name = "same-file" +version = "1.0.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "93fc1dc3aaa9bfed95e02e6eadabb4baf7e3078b0bd1b4d7b6b0b68378900502" +dependencies = [ + "winapi-util", +] + +[[package]] +name = "scoped-tls" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e1cf6437eb19a8f4a6cc0f7dca544973b0b78843adbfeb3683d1a94a0024a294" + +[[package]] +name = "scopeguard" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" + +[[package]] +name = "sctk-adwaita" +version = "0.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b6277f0217056f77f1d8f49f2950ac6c278c0d607c45f5ee99328d792ede24ec" +dependencies = [ + "ab_glyph", + "log", + "memmap2", + "smithay-client-toolkit", + "tiny-skia", +] + +[[package]] +name = "serde" +version = "1.0.202" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "226b61a0d411b2ba5ff6d7f73a476ac4f8bb900373459cd00fab8512828ba395" +dependencies = [ + "serde_derive", +] + +[[package]] +name = "serde_derive" +version = "1.0.202" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6048858004bcff69094cd972ed40a32500f153bd3be9f716b2eed2e8217c4838" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.65", +] + +[[package]] +name = "serde_spanned" +version = "0.6.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eb5b1b31579f3811bf615c144393417496f152e12ac8b7663bf664f4a815306d" +dependencies = [ + "serde", +] + +[[package]] +name = "shlex" +version = "1.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64" + +[[package]] +name = "simd-adler32" +version = "0.3.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d66dc143e6b11c1eddc06d5c423cfc97062865baf299914ab64caa38182078fe" + +[[package]] +name = "simd_helpers" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "95890f873bec569a0362c235787f3aca6e1e887302ba4840839bcc6459c42da6" +dependencies = [ + "quote", +] + +[[package]] +name = "skrifa" +version = "0.19.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0ab45fb68b53576a43d4fc0e9ec8ea64e29a4d2cc7f44506964cb75f288222e9" +dependencies = [ + "bytemuck", + "core_maths", + "read-fonts", +] + +[[package]] +name = "slab" +version = "0.4.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8f92a496fb766b417c996b9c5e57daf2f7ad3b0bebe1ccfca4856390e3d3bb67" +dependencies = [ + "autocfg", +] + +[[package]] +name = "slotmap" +version = "1.0.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dbff4acf519f630b3a3ddcfaea6c06b42174d9a44bc70c620e9ed1649d58b82a" +dependencies = [ + "version_check", +] + +[[package]] +name = "smallvec" +version = "1.13.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3c5e1a9a646d36c3599cd173a41282daf47c44583ad367b8e6837255952e5c67" + +[[package]] +name = "smithay-client-toolkit" +version = "0.19.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3457dea1f0eb631b4034d61d4d8c32074caa6cd1ab2d59f2327bd8461e2c0016" +dependencies = [ + "bitflags 2.5.0", + "calloop", + "calloop-wayland-source", + "cursor-icon", + "libc", + "log", + "memmap2", + "rustix", + "thiserror", + "wayland-backend", + "wayland-client", + "wayland-csd-frame", + "wayland-cursor", + "wayland-protocols", + "wayland-protocols-wlr", + "wayland-scanner", + "xkeysym", +] + +[[package]] +name = "smol_str" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dd538fb6910ac1099850255cf94a94df6551fbdd602454387d0adb2d1ca6dead" +dependencies = [ + "serde", +] + +[[package]] +name = "spin" +version = "0.9.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6980e8d7511241f8acf4aebddbb1ff938df5eebe98691418c4468d0b72a96a67" +dependencies = [ + "lock_api", +] + +[[package]] +name = "spirv" +version = "0.3.0+sdk-1.3.268.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eda41003dc44290527a59b13432d4a0379379fa074b70174882adfbdfd917844" +dependencies = [ + "bitflags 2.5.0", +] + +[[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 = "strict-num" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6637bab7722d379c8b41ba849228d680cc12d0a45ba1fa2b48f2a30577a06731" + +[[package]] +name = "svg_fmt" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "20e16a0f46cf5fd675563ef54f26e83e20f2366bcf027bcb3cc3ed2b98aaf2ca" + +[[package]] +name = "swash" +version = "0.1.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "682a612b50baf09e8a039547ecf49e6c155690dcb751b1bcb19c93cdeb3d42d4" +dependencies = [ + "read-fonts", + "yazi", + "zeno", +] + +[[package]] +name = "swash_render" +version = "0.1.0" +dependencies = [ + "image", + "parley", + "peniko", + "skrifa", + "swash", +] + +[[package]] +name = "syn" +version = "1.0.109" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "72b64191b275b66ffe2469e8af2c1cfe3bafa67b529ead792a6d0160888b4237" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "syn" +version = "2.0.65" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d2863d96a84c6439701d7a38f9de935ec562c8832cc55d1dde0f513b52fad106" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "synstructure" +version = "0.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c8af7666ab7b6390ab78131fb5b0fce11d6b7a6951602017c35fa82800708971" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.65", +] + +[[package]] +name = "system-deps" +version = "6.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a3e535eb8dded36d55ec13eddacd30dec501792ff23a0b1682c38601b8cf2349" +dependencies = [ + "cfg-expr", + "heck", + "pkg-config", + "toml", + "version-compare", +] + +[[package]] +name = "target-lexicon" +version = "0.12.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "61c41af27dd6d1e27b1b16b489db798443478cef1f06a660c96db617ba5de3b1" + +[[package]] +name = "termcolor" +version = "1.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "06794f8f6c5c898b3275aebefa6b8a1cb24cd2c6c79397ab15774837a0bc5755" +dependencies = [ + "winapi-util", +] + +[[package]] +name = "thiserror" +version = "1.0.61" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c546c80d6be4bc6a00c0f01730c08df82eaa7a7a61f11d656526506112cc1709" +dependencies = [ + "thiserror-impl", +] + +[[package]] +name = "thiserror-impl" +version = "1.0.61" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "46c3384250002a6d5af4d114f2845d37b57521033f30d5c3f46c4d70e1197533" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.65", +] + +[[package]] +name = "tiff" +version = "0.9.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ba1310fcea54c6a9a4fd1aad794ecc02c31682f6bfbecdf460bf19533eed1e3e" +dependencies = [ + "flate2", + "jpeg-decoder", + "weezl", +] + +[[package]] +name = "tiny-skia" +version = "0.11.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "83d13394d44dae3207b52a326c0c85a8bf87f1541f23b0d143811088497b09ab" +dependencies = [ + "arrayref", + "arrayvec", + "bytemuck", + "cfg-if", + "log", + "png", + "tiny-skia-path", +] + +[[package]] +name = "tiny-skia-path" +version = "0.11.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9c9e7fc0c2e86a30b117d0462aa261b72b7a99b7ebd7deb3a14ceda95c5bdc93" +dependencies = [ + "arrayref", + "bytemuck", + "strict-num", +] + +[[package]] +name = "tiny_skia_render" +version = "0.1.0" +dependencies = [ + "parley", + "peniko", + "skrifa", + "tiny-skia", +] + +[[package]] +name = "tinystr" +version = "0.7.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "83c02bf3c538ab32ba913408224323915f4ef9a6d61c0e85d493f355921c0ece" +dependencies = [ + "displaydoc", + "zerovec", +] + +[[package]] +name = "toml" +version = "0.8.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a1ed1f98e3fdc28d6d910e6737ae6ab1a93bf1985935a1193e68f93eeb68d24e" +dependencies = [ + "serde", + "serde_spanned", + "toml_datetime", + "toml_edit 0.22.20", +] + +[[package]] +name = "toml_datetime" +version = "0.6.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0dd7358ecb8fc2f8d014bf86f6f638ce72ba252a2c3a2572f2a795f1d23efb41" +dependencies = [ + "serde", +] + +[[package]] +name = "toml_edit" +version = "0.21.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6a8534fd7f78b5405e860340ad6575217ce99f38d4d5c8f2442cb5ecb50090e1" +dependencies = [ + "indexmap", + "toml_datetime", + "winnow 0.5.40", +] + +[[package]] +name = "toml_edit" +version = "0.22.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "583c44c02ad26b0c3f3066fe629275e50627026c51ac2e595cca4c230ce1ce1d" +dependencies = [ + "indexmap", + "serde", + "serde_spanned", + "toml_datetime", + "winnow 0.6.18", +] + +[[package]] +name = "tracing" +version = "0.1.40" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c3523ab5a71916ccf420eebdf5521fcef02141234bbc0b8a49f2fdc4544364ef" +dependencies = [ + "pin-project-lite", + "tracing-core", +] + +[[package]] +name = "tracing-core" +version = "0.1.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c06d3da6113f116aaee68e4d601191614c9053067f9ab7f6edbcb161237daa54" + +[[package]] +name = "ttf-parser" +version = "0.24.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5be21190ff5d38e8b4a2d3b6a3ae57f612cc39c96e83cedeaf7abc338a8bac4a" + +[[package]] +name = "unicode-ident" +version = "1.0.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3354b9ac3fae1ff6755cb6db53683adb661634f67557942dea4facebec0fee4b" + +[[package]] +name = "unicode-script" +version = "0.5.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ad8d71f5726e5f285a935e9fe8edfd53f0491eb6e9a5774097fdabee7cd8c9cd" + +[[package]] +name = "unicode-segmentation" +version = "1.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d4c87d22b6e3f4a18d4d40ef354e97c90fcb14dd91d7dc0aa9d8a1172ebf7202" + +[[package]] +name = "unicode-width" +version = "0.1.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0336d538f7abc86d282a4189614dfaa90810dfc2c6f6427eaf88e16311dd225d" + +[[package]] +name = "unicode-xid" +version = "0.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f962df74c8c05a667b5ee8bcf162993134c104e96440b663c8daa176dc772d8c" + +[[package]] +name = "v_frame" +version = "0.3.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d6f32aaa24bacd11e488aa9ba66369c7cd514885742c9fe08cfe85884db3e92b" +dependencies = [ + "aligned-vec", + "num-traits", + "wasm-bindgen", +] + +[[package]] +name = "vello" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "861c12258ed7e72762765e2c88a07bb528040ec4e5f87514d65b19b29a7cccf0" +dependencies = [ + "bytemuck", + "futures-intrusive", + "log", + "peniko", + "raw-window-handle", + "skrifa", + "static_assertions", + "thiserror", + "vello_encoding", + "vello_shaders", + "wgpu", +] + +[[package]] +name = "vello_editor" +version = "0.1.0" +dependencies = [ + "anyhow", + "clipboard-rs", + "parley", + "peniko", + "pollster", + "vello", + "winit", +] + +[[package]] +name = "vello_encoding" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e5d73777327877fa824a45c7195f850390dd3f91feb15f47d331db1fc01abf6d" +dependencies = [ + "bytemuck", + "guillotiere", + "peniko", + "skrifa", + "smallvec", +] + +[[package]] +name = "vello_shaders" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "13ab6bcb2b079c3cf57e964d1ba0b1f08901284be1c7f5cba34d3e0e08154bce" +dependencies = [ + "bytemuck", + "naga", + "thiserror", + "vello_encoding", +] + +[[package]] +name = "version-compare" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "852e951cb7832cb45cb1169900d19760cfa39b82bc0ea9c0e5a14ae88411c98b" + +[[package]] +name = "version_check" +version = "0.9.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "49874b5167b65d7193b8aba1567f5c7d93d001cafc34600cee003eda787e483f" + +[[package]] +name = "walkdir" +version = "2.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "29790946404f91d9c5d06f9874efddea1dc06c5efe94541a7d6863108e3a5e4b" +dependencies = [ + "same-file", + "winapi-util", +] + +[[package]] +name = "wasi" +version = "0.11.0+wasi-snapshot-preview1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" + +[[package]] +name = "wasm-bindgen" +version = "0.2.93" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a82edfc16a6c469f5f44dc7b571814045d60404b55a0ee849f9bcfa2e63dd9b5" +dependencies = [ + "cfg-if", + "once_cell", + "wasm-bindgen-macro", +] + +[[package]] +name = "wasm-bindgen-backend" +version = "0.2.93" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9de396da306523044d3302746f1208fa71d7532227f15e347e2d93e4145dd77b" +dependencies = [ + "bumpalo", + "log", + "once_cell", + "proc-macro2", + "quote", + "syn 2.0.65", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-futures" +version = "0.4.43" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "61e9300f63a621e96ed275155c108eb6f843b6a26d053f122ab69724559dc8ed" +dependencies = [ + "cfg-if", + "js-sys", + "wasm-bindgen", + "web-sys", +] + +[[package]] +name = "wasm-bindgen-macro" +version = "0.2.93" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "585c4c91a46b072c92e908d99cb1dcdf95c5218eeb6f3bf1efa991ee7a68cccf" +dependencies = [ + "quote", + "wasm-bindgen-macro-support", +] + +[[package]] +name = "wasm-bindgen-macro-support" +version = "0.2.93" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "afc340c74d9005395cf9dd098506f7f44e38f2b4a21c6aaacf9a105ea5e1e836" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.65", + "wasm-bindgen-backend", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-shared" +version = "0.2.93" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c62a0a307cb4a311d3a07867860911ca130c3494e8c2719593806c08bc5d0484" + +[[package]] +name = "wayland-backend" +version = "0.3.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f90e11ce2ca99c97b940ee83edbae9da2d56a08f9ea8158550fd77fa31722993" +dependencies = [ + "cc", + "downcast-rs", + "rustix", + "scoped-tls", + "smallvec", + "wayland-sys", +] + +[[package]] +name = "wayland-client" +version = "0.31.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7e321577a0a165911bdcfb39cf029302479d7527b517ee58ab0f6ad09edf0943" +dependencies = [ + "bitflags 2.5.0", + "rustix", + "wayland-backend", + "wayland-scanner", +] + +[[package]] +name = "wayland-csd-frame" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "625c5029dbd43d25e6aa9615e88b829a5cad13b2819c4ae129fdbb7c31ab4c7e" +dependencies = [ + "bitflags 2.5.0", + "cursor-icon", + "wayland-backend", +] + +[[package]] +name = "wayland-cursor" +version = "0.31.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6ef9489a8df197ebf3a8ce8a7a7f0a2320035c3743f3c1bd0bdbccf07ce64f95" +dependencies = [ + "rustix", + "wayland-client", + "xcursor", +] + +[[package]] +name = "wayland-protocols" +version = "0.32.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "62989625a776e827cc0f15d41444a3cea5205b963c3a25be48ae1b52d6b4daaa" +dependencies = [ + "bitflags 2.5.0", + "wayland-backend", + "wayland-client", + "wayland-scanner", +] + +[[package]] +name = "wayland-protocols-plasma" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f79f2d57c7fcc6ab4d602adba364bf59a5c24de57bd194486bf9b8360e06bfc4" +dependencies = [ + "bitflags 2.5.0", + "wayland-backend", + "wayland-client", + "wayland-protocols", + "wayland-scanner", +] + +[[package]] +name = "wayland-protocols-wlr" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fd993de54a40a40fbe5601d9f1fbcaef0aebcc5fda447d7dc8f6dcbaae4f8953" +dependencies = [ + "bitflags 2.5.0", + "wayland-backend", + "wayland-client", + "wayland-protocols", + "wayland-scanner", +] + +[[package]] +name = "wayland-scanner" +version = "0.31.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d7b56f89937f1cf2ee1f1259cf2936a17a1f45d8f0aa1019fae6d470d304cfa6" +dependencies = [ + "proc-macro2", + "quick-xml", + "quote", +] + +[[package]] +name = "wayland-sys" +version = "0.31.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "43676fe2daf68754ecf1d72026e4e6c15483198b5d24e888b74d3f22f887a148" +dependencies = [ + "dlib", + "log", + "once_cell", + "pkg-config", +] + +[[package]] +name = "web-sys" +version = "0.3.70" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "26fdeaafd9bd129f65e7c031593c24d62186301e0c72c8978fa1678be7d532c0" +dependencies = [ + "js-sys", + "wasm-bindgen", +] + +[[package]] +name = "web-time" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5a6580f308b1fad9207618087a65c04e7a10bc77e02c8e84e9b00dd4b12fa0bb" +dependencies = [ + "js-sys", + "wasm-bindgen", +] + +[[package]] +name = "weezl" +version = "0.1.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "53a85b86a771b1c87058196170769dd264f66c0782acf1ae6cc51bfd64b39082" + +[[package]] +name = "wgpu" +version = "0.20.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "90e37c7b9921b75dfd26dd973fdcbce36f13dfa6e2dc82aece584e0ed48c355c" +dependencies = [ + "arrayvec", + "cfg-if", + "cfg_aliases 0.1.1", + "document-features", + "js-sys", + "log", + "naga", + "parking_lot", + "profiling", + "raw-window-handle", + "smallvec", + "static_assertions", + "wasm-bindgen", + "wasm-bindgen-futures", + "web-sys", + "wgpu-core", + "wgpu-hal", + "wgpu-types", +] + +[[package]] +name = "wgpu-core" +version = "0.21.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d50819ab545b867d8a454d1d756b90cd5f15da1f2943334ca314af10583c9d39" +dependencies = [ + "arrayvec", + "bit-vec", + "bitflags 2.5.0", + "cfg_aliases 0.1.1", + "codespan-reporting", + "document-features", + "indexmap", + "log", + "naga", + "once_cell", + "parking_lot", + "profiling", + "raw-window-handle", + "rustc-hash", + "smallvec", + "thiserror", + "web-sys", + "wgpu-hal", + "wgpu-types", +] + +[[package]] +name = "wgpu-hal" +version = "0.21.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "172e490a87295564f3fcc0f165798d87386f6231b04d4548bca458cbbfd63222" +dependencies = [ + "android_system_properties", + "arrayvec", + "ash", + "bit-set", + "bitflags 2.5.0", + "block", + "cfg_aliases 0.1.1", + "core-graphics-types", + "d3d12", + "glow", + "glutin_wgl_sys", + "gpu-alloc", + "gpu-allocator", + "gpu-descriptor", + "hassle-rs", + "js-sys", + "khronos-egl", + "libc", + "libloading 0.8.5", + "log", + "metal", + "naga", + "ndk-sys 0.5.0+25.2.9519653", + "objc", + "once_cell", + "parking_lot", + "profiling", + "range-alloc", + "raw-window-handle", + "renderdoc-sys", + "rustc-hash", + "smallvec", + "thiserror", + "wasm-bindgen", + "web-sys", + "wgpu-types", + "winapi", +] + +[[package]] +name = "wgpu-types" +version = "0.20.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1353d9a46bff7f955a680577f34c69122628cc2076e1d6f3a9be6ef00ae793ef" +dependencies = [ + "bitflags 2.5.0", + "js-sys", + "web-sys", +] + +[[package]] +name = "widestring" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7219d36b6eac893fa81e84ebe06485e7dcbb616177469b142df14f1f4deb1311" + +[[package]] +name = "winapi" +version = "0.3.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419" +dependencies = [ + "winapi-i686-pc-windows-gnu", + "winapi-x86_64-pc-windows-gnu", +] + +[[package]] +name = "winapi-i686-pc-windows-gnu" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" + +[[package]] +name = "winapi-util" +version = "0.1.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cf221c93e13a30d793f7645a0e7762c55d169dbb0a49671918a2319d289b10bb" +dependencies = [ + "windows-sys 0.59.0", +] + +[[package]] +name = "winapi-x86_64-pc-windows-gnu" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" + +[[package]] +name = "windows" +version = "0.52.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "46a785d4eeff09c14c487497c162e92766fbb3e4059a71840cecc03d9a50b804" +checksum = "e48a53791691ab099e5e2ad123536d0fff50652600abaf43bbf952894110d0be" dependencies = [ - "objc-sys", - "objc2-encode", + "windows-core", + "windows-targets 0.52.6", ] [[package]] -name = "objc2-encode" -version = "4.0.3" +name = "windows-core" +version = "0.52.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7891e71393cd1f227313c9379a26a584ff3d7e6e7159e988851f0934c993f0f8" +checksum = "33ab640c8d7e35bf8ba19b884ba838ceb4fba93a4e8c65a9059d08afcfc683d9" +dependencies = [ + "windows-targets 0.52.6", +] [[package]] -name = "objc2-foundation" -version = "0.2.2" +name = "windows-sys" +version = "0.45.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0ee638a5da3799329310ad4cfa62fbf045d5f56e3ef5ba4149e7452dcf89d5a8" +checksum = "75283be5efb2831d37ea142365f009c02ec203cd29a3ebecbc093d52315b66d0" dependencies = [ - "bitflags 2.5.0", - "block2", - "libc", - "objc2", + "windows-targets 0.42.2", ] [[package]] -name = "once_cell" -version = "1.19.0" +name = "windows-sys" +version = "0.52.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3fdb12b2476b595f9358c5161aa467c2438859caa136dec86c26fdd2efe17b92" - -[[package]] -name = "parley" -version = "0.1.0" +checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d" dependencies = [ - "fontique", - "peniko", - "skrifa", - "swash", + "windows-targets 0.52.6", ] [[package]] -name = "peniko" -version = "0.1.0" +name = "windows-sys" +version = "0.59.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "caaf7fec601d640555d9a4cab7343eba1e1c7a5a71c9993ff63b4c26bc5d50c5" +checksum = "1e38bc4d79ed67fd075bcc251a1c39b32a1776bbe92e5bef1f0bf1f8c531853b" dependencies = [ - "kurbo", - "smallvec", + "windows-targets 0.52.6", ] [[package]] -name = "png" -version = "0.17.13" +name = "windows-targets" +version = "0.42.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "06e4b0d3d1312775e782c86c91a111aa1f910cbb65e1337f9975b5f9a554b5e1" +checksum = "8e5180c00cd44c9b1c88adb3693291f1cd93605ded80c250a75d472756b4d071" dependencies = [ - "bitflags 1.3.2", - "crc32fast", - "fdeflate", - "flate2", - "miniz_oxide", + "windows_aarch64_gnullvm 0.42.2", + "windows_aarch64_msvc 0.42.2", + "windows_i686_gnu 0.42.2", + "windows_i686_msvc 0.42.2", + "windows_x86_64_gnu 0.42.2", + "windows_x86_64_gnullvm 0.42.2", + "windows_x86_64_msvc 0.42.2", ] [[package]] -name = "proc-macro2" -version = "1.0.83" +name = "windows-targets" +version = "0.48.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0b33eb56c327dec362a9e55b3ad14f9d2f0904fb5a5b03b513ab5465399e9f43" +checksum = "9a2fa6e2155d7247be68c096456083145c183cbbbc2764150dda45a87197940c" dependencies = [ - "unicode-ident", + "windows_aarch64_gnullvm 0.48.5", + "windows_aarch64_msvc 0.48.5", + "windows_i686_gnu 0.48.5", + "windows_i686_msvc 0.48.5", + "windows_x86_64_gnu 0.48.5", + "windows_x86_64_gnullvm 0.48.5", + "windows_x86_64_msvc 0.48.5", ] [[package]] -name = "quote" -version = "1.0.36" +name = "windows-targets" +version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0fa76aaf39101c457836aec0ce2316dbdc3ab723cdda1c6bd4e6ad4208acaca7" +checksum = "9b724f72796e036ab90c1021d4780d4d3d648aca59e491e6b98e725b84e99973" dependencies = [ - "proc-macro2", + "windows_aarch64_gnullvm 0.52.6", + "windows_aarch64_msvc 0.52.6", + "windows_i686_gnu 0.52.6", + "windows_i686_gnullvm", + "windows_i686_msvc 0.52.6", + "windows_x86_64_gnu 0.52.6", + "windows_x86_64_gnullvm 0.52.6", + "windows_x86_64_msvc 0.52.6", ] [[package]] -name = "read-fonts" -version = "0.19.1" +name = "windows-win" +version = "3.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "af4749db2bd1c853db31a7ae5ee2fc6c30bbddce353ea8fedf673fed187c68c7" +checksum = "58e23e33622b3b52f948049acbec9bcc34bf6e26d74176b88941f213c75cf2dc" dependencies = [ - "bytemuck", - "font-types", + "error-code", ] [[package]] -name = "roxmltree" -version = "0.19.0" +name = "windows_aarch64_gnullvm" +version = "0.42.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3cd14fd5e3b777a7422cca79358c57a8f6e3a703d9ac187448d0daf220c2407f" +checksum = "597a5118570b68bc08d8d59125332c54f1ba9d9adeedeef5b99b02ba2b0698f8" [[package]] -name = "serde" -version = "1.0.202" +name = "windows_aarch64_gnullvm" +version = "0.48.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "226b61a0d411b2ba5ff6d7f73a476ac4f8bb900373459cd00fab8512828ba395" -dependencies = [ - "serde_derive", -] +checksum = "2b38e32f0abccf9987a4e3079dfb67dcd799fb61361e53e2882c3cbaf0d905d8" [[package]] -name = "serde_derive" -version = "1.0.202" +name = "windows_aarch64_gnullvm" +version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6048858004bcff69094cd972ed40a32500f153bd3be9f716b2eed2e8217c4838" -dependencies = [ - "proc-macro2", - "quote", - "syn", -] +checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3" [[package]] -name = "simd-adler32" -version = "0.3.7" +name = "windows_aarch64_msvc" +version = "0.42.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d66dc143e6b11c1eddc06d5c423cfc97062865baf299914ab64caa38182078fe" +checksum = "e08e8864a60f06ef0d0ff4ba04124db8b0fb3be5776a5cd47641e942e58c4d43" [[package]] -name = "skrifa" -version = "0.19.1" +name = "windows_aarch64_msvc" +version = "0.48.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8dbed6a0c9addb84c2c8f66f35f504bb875b901e2a022419173e6ee2adfd0fb4" -dependencies = [ - "bytemuck", - "core_maths", - "read-fonts", -] +checksum = "dc35310971f3b2dbbf3f0690a219f40e2d9afcf64f9ab7cc1be722937c26b4bc" [[package]] -name = "smallvec" -version = "1.13.2" +name = "windows_aarch64_msvc" +version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3c5e1a9a646d36c3599cd173a41282daf47c44583ad367b8e6837255952e5c67" +checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469" [[package]] -name = "stable_deref_trait" -version = "1.2.0" +name = "windows_i686_gnu" +version = "0.42.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a8f112729512f8e442d81f95a8a7ddf2b7c6b8a1a6f509a95864142b30cab2d3" +checksum = "c61d927d8da41da96a81f029489353e68739737d3beca43145c8afec9a31a84f" [[package]] -name = "strict-num" -version = "0.1.1" +name = "windows_i686_gnu" +version = "0.48.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6637bab7722d379c8b41ba849228d680cc12d0a45ba1fa2b48f2a30577a06731" +checksum = "a75915e7def60c94dcef72200b9a8e58e5091744960da64ec734a6c6e9b3743e" [[package]] -name = "swash" -version = "0.1.16" +name = "windows_i686_gnu" +version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "682a612b50baf09e8a039547ecf49e6c155690dcb751b1bcb19c93cdeb3d42d4" -dependencies = [ - "read-fonts", - "yazi", - "zeno", -] +checksum = "8e9b5ad5ab802e97eb8e295ac6720e509ee4c243f69d781394014ebfe8bbfa0b" [[package]] -name = "swash_render" -version = "0.1.0" -dependencies = [ - "image", - "parley", - "peniko", - "skrifa", - "swash", -] +name = "windows_i686_gnullvm" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66" [[package]] -name = "syn" -version = "2.0.65" +name = "windows_i686_msvc" +version = "0.42.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d2863d96a84c6439701d7a38f9de935ec562c8832cc55d1dde0f513b52fad106" -dependencies = [ - "proc-macro2", - "quote", - "unicode-ident", -] +checksum = "44d840b6ec649f480a41c8d80f9c65108b92d89345dd94027bfe06ac444d1060" [[package]] -name = "synstructure" -version = "0.13.1" +name = "windows_i686_msvc" +version = "0.48.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c8af7666ab7b6390ab78131fb5b0fce11d6b7a6951602017c35fa82800708971" -dependencies = [ - "proc-macro2", - "quote", - "syn", -] +checksum = "8f55c233f70c4b27f66c523580f78f1004e8b5a8b659e05a4eb49d4166cca406" [[package]] -name = "thiserror" -version = "1.0.61" +name = "windows_i686_msvc" +version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c546c80d6be4bc6a00c0f01730c08df82eaa7a7a61f11d656526506112cc1709" -dependencies = [ - "thiserror-impl", -] +checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66" [[package]] -name = "thiserror-impl" -version = "1.0.61" +name = "windows_x86_64_gnu" +version = "0.42.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "46c3384250002a6d5af4d114f2845d37b57521033f30d5c3f46c4d70e1197533" -dependencies = [ - "proc-macro2", - "quote", - "syn", -] +checksum = "8de912b8b8feb55c064867cf047dda097f92d51efad5b491dfb98f6bbb70cb36" [[package]] -name = "tiny-skia" -version = "0.11.4" +name = "windows_x86_64_gnu" +version = "0.48.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "83d13394d44dae3207b52a326c0c85a8bf87f1541f23b0d143811088497b09ab" -dependencies = [ - "arrayref", - "arrayvec", - "bytemuck", - "cfg-if", - "log", - "png", - "tiny-skia-path", -] +checksum = "53d40abd2583d23e4718fddf1ebec84dbff8381c07cae67ff7768bbf19c6718e" [[package]] -name = "tiny-skia-path" -version = "0.11.4" +name = "windows_x86_64_gnu" +version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9c9e7fc0c2e86a30b117d0462aa261b72b7a99b7ebd7deb3a14ceda95c5bdc93" -dependencies = [ - "arrayref", - "bytemuck", - "strict-num", -] +checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78" [[package]] -name = "tiny_skia_render" -version = "0.1.0" -dependencies = [ - "parley", - "peniko", - "skrifa", - "tiny-skia", -] +name = "windows_x86_64_gnullvm" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "26d41b46a36d453748aedef1486d5c7a85db22e56aff34643984ea85514e94a3" [[package]] -name = "tinystr" -version = "0.7.5" +name = "windows_x86_64_gnullvm" +version = "0.48.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "83c02bf3c538ab32ba913408224323915f4ef9a6d61c0e85d493f355921c0ece" -dependencies = [ - "displaydoc", - "zerovec", -] +checksum = "0b7b52767868a23d5bab768e390dc5f5c55825b6d30b86c844ff2dc7414044cc" [[package]] -name = "unicode-ident" -version = "1.0.12" +name = "windows_x86_64_gnullvm" +version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3354b9ac3fae1ff6755cb6db53683adb661634f67557942dea4facebec0fee4b" +checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d" [[package]] -name = "unicode-script" -version = "0.5.6" +name = "windows_x86_64_msvc" +version = "0.42.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ad8d71f5726e5f285a935e9fe8edfd53f0491eb6e9a5774097fdabee7cd8c9cd" +checksum = "9aec5da331524158c6d1a4ac0ab1541149c0b9505fde06423b02f5ef0106b9f0" [[package]] -name = "version_check" -version = "0.9.4" +name = "windows_x86_64_msvc" +version = "0.48.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "49874b5167b65d7193b8aba1567f5c7d93d001cafc34600cee003eda787e483f" +checksum = "ed94fce61571a4006852b7389a063ab983c02eb1bb37b47f8272ce92d06d9538" [[package]] -name = "winapi" -version = "0.3.9" +name = "windows_x86_64_msvc" +version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419" +checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec" + +[[package]] +name = "winit" +version = "0.30.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0be9e76a1f1077e04a411f0b989cbd3c93339e1771cb41e71ac4aee95bfd2c67" dependencies = [ - "winapi-i686-pc-windows-gnu", - "winapi-x86_64-pc-windows-gnu", + "ahash", + "android-activity", + "atomic-waker", + "bitflags 2.5.0", + "block2", + "bytemuck", + "calloop", + "cfg_aliases 0.2.1", + "concurrent-queue", + "core-foundation", + "core-graphics", + "cursor-icon", + "dpi", + "js-sys", + "libc", + "memmap2", + "ndk", + "objc2", + "objc2-app-kit", + "objc2-foundation", + "objc2-ui-kit", + "orbclient", + "percent-encoding", + "pin-project", + "raw-window-handle", + "redox_syscall 0.4.1", + "rustix", + "sctk-adwaita", + "smithay-client-toolkit", + "smol_str", + "tracing", + "unicode-segmentation", + "wasm-bindgen", + "wasm-bindgen-futures", + "wayland-backend", + "wayland-client", + "wayland-protocols", + "wayland-protocols-plasma", + "web-sys", + "web-time", + "windows-sys 0.52.0", + "x11-dl", + "x11rb", + "xkbcommon-dl", ] [[package]] -name = "winapi-i686-pc-windows-gnu" -version = "0.4.0" +name = "winnow" +version = "0.5.40" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" +checksum = "f593a95398737aeed53e489c785df13f3618e41dbcd6718c6addbf1395aa6876" +dependencies = [ + "memchr", +] [[package]] -name = "winapi-x86_64-pc-windows-gnu" -version = "0.4.0" +name = "winnow" +version = "0.6.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" +checksum = "68a9bda4691f099d435ad181000724da8e5899daa10713c2d432552b9ccd3a6f" +dependencies = [ + "memchr", +] [[package]] name = "wio" @@ -793,6 +3386,69 @@ version = "0.5.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1e9df38ee2d2c3c5948ea468a8406ff0db0b29ae1ffde1bcf20ef305bcc95c51" +[[package]] +name = "x11-dl" +version = "2.21.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "38735924fedd5314a6e548792904ed8c6de6636285cb9fec04d5b1db85c1516f" +dependencies = [ + "libc", + "once_cell", + "pkg-config", +] + +[[package]] +name = "x11rb" +version = "0.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5d91ffca73ee7f68ce055750bf9f6eca0780b8c85eff9bc046a3b0da41755e12" +dependencies = [ + "as-raw-xcb-connection", + "gethostname", + "libc", + "libloading 0.8.5", + "once_cell", + "rustix", + "x11rb-protocol", +] + +[[package]] +name = "x11rb-protocol" +version = "0.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ec107c4503ea0b4a98ef47356329af139c0a4f7750e621cf2973cd3385ebcb3d" + +[[package]] +name = "xcursor" +version = "0.3.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0ef33da6b1660b4ddbfb3aef0ade110c8b8a781a3b6382fa5f2b5b040fd55f61" + +[[package]] +name = "xkbcommon-dl" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d039de8032a9a8856a6be89cea3e5d12fdd82306ab7c94d74e6deab2460651c5" +dependencies = [ + "bitflags 2.5.0", + "dlib", + "log", + "once_cell", + "xkeysym", +] + +[[package]] +name = "xkeysym" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b9cc00251562a284751c9973bace760d86c0276c471b4be569fe6b068ee97a56" + +[[package]] +name = "xml-rs" +version = "0.8.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "539a77ee7c0de333dcc6da69b177380a0b81e0dacfa4f7344c465a36871ee601" + [[package]] name = "yazi" version = "0.1.6" @@ -819,7 +3475,7 @@ checksum = "28cc31741b18cb6f1d5ff12f5b7523e3d6eb0852bbbad19d73905511d9849b95" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.65", "synstructure", ] @@ -835,6 +3491,7 @@ version = "0.7.34" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ae87e3fcd617500e5d106f0380cf7b77f3c6092aae37191433159dda23cfb087" dependencies = [ + "byteorder", "zerocopy-derive", ] @@ -846,7 +3503,7 @@ checksum = "15e934569e47891f7d9411f1a451d947a60e000ab3bd24fbb970f000387d1b3b" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.65", ] [[package]] @@ -866,7 +3523,7 @@ checksum = "e6a647510471d372f2e6c2e6b7219e44d8c574d24fdc11c610a61455782f18c3" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.65", "synstructure", ] @@ -889,5 +3546,29 @@ checksum = "6eafa6dfb17584ea3e2bd6e76e0cc15ad7af12b09abdd1ca55961bed9b1063c6" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.65", +] + +[[package]] +name = "zune-core" +version = "0.4.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3f423a2c17029964870cfaabb1f13dfab7d092a62a29a89264f4d36990ca414a" + +[[package]] +name = "zune-inflate" +version = "0.2.54" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "73ab332fe2f6680068f3582b16a24f90ad7096d5d39b974d1c0aff0125116f02" +dependencies = [ + "simd-adler32", +] + +[[package]] +name = "zune-jpeg" +version = "0.4.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "16099418600b4d8f028622f73ff6e3deaabdff330fb9a2a131dea781ee8b0768" +dependencies = [ + "zune-core", ] diff --git a/Cargo.toml b/Cargo.toml index 734f63c3..b96d2d42 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -5,6 +5,7 @@ members = [ "parley", "examples/tiny_skia_render", "examples/swash_render", + "examples/vello_editor", ] [workspace.package] diff --git a/examples/swash_render/src/main.rs b/examples/swash_render/src/main.rs index 74959d9d..2bbf2ec1 100644 --- a/examples/swash_render/src/main.rs +++ b/examples/swash_render/src/main.rs @@ -80,7 +80,6 @@ fn main() { let mut layout: Layout = builder.build(); // Perform layout (including bidi resolution and shaping) with start alignment - layout.break_all_lines(max_advance); layout.align(max_advance, Alignment::Start); // Create image to render into diff --git a/examples/vello_editor/Cargo.toml b/examples/vello_editor/Cargo.toml new file mode 100644 index 00000000..472d3ce2 --- /dev/null +++ b/examples/vello_editor/Cargo.toml @@ -0,0 +1,24 @@ +[package] +name = "vello_editor" +version = "0.1.0" +edition.workspace = true +rust-version.workspace = true +license.workspace = true +repository.workspace = true + +[dependencies] +vello = "0.2.1" +anyhow = "1.0.86" +pollster = "0.3.0" +winit = "0.30.3" +parley = { workspace = true, default-features = true } +peniko = { workspace = true } + +[lints] +workspace = true + +[target.'cfg(target_os = "android")'.dependencies] +winit = { version = "0.30.3", features = ["android-native-activity"] } + +[target.'cfg(not(target_os = "android"))'.dependencies] +clipboard-rs = "0.1.11" diff --git a/examples/vello_editor/src/main.rs b/examples/vello_editor/src/main.rs new file mode 100644 index 00000000..4fc2e071 --- /dev/null +++ b/examples/vello_editor/src/main.rs @@ -0,0 +1,223 @@ +// Copyright 2024 the Parley Authors +// SPDX-License-Identifier: Apache-2.0 OR MIT + +use anyhow::Result; +use std::num::NonZeroUsize; +use std::sync::Arc; +use vello::peniko::Color; +use vello::util::{RenderContext, RenderSurface}; +use vello::wgpu; +use vello::{AaConfig, Renderer, RendererOptions, Scene}; +use winit::application::ApplicationHandler; +use winit::dpi::LogicalSize; +use winit::event::*; +use winit::event_loop::{ActiveEventLoop, ControlFlow, EventLoop}; +use winit::window::Window; + +// #[path = "text2.rs"] +mod text; + +// Simple struct to hold the state of the renderer +pub struct ActiveRenderState<'s> { + // The fields MUST be in this order, so that the surface is dropped before the window + surface: RenderSurface<'s>, + window: Arc, +} + +enum RenderState<'s> { + Active(ActiveRenderState<'s>), + // Cache a window so that it can be reused when the app is resumed after being suspended + Suspended(Option>), +} + +struct SimpleVelloApp<'s> { + // The vello RenderContext which is a global context that lasts for the + // lifetime of the application + context: RenderContext, + + // An array of renderers, one per wgpu device + renderers: Vec>, + + // State for our example where we store the winit Window and the wgpu Surface + state: RenderState<'s>, + + // A vello Scene which is a data structure which allows one to build up a + // description a scene to be drawn (with paths, fills, images, text, etc) + // which is then passed to a renderer for rendering + scene: Scene, + + // Our text state object + editor: text::Editor, +} + +impl<'s> ApplicationHandler for SimpleVelloApp<'s> { + fn resumed(&mut self, event_loop: &ActiveEventLoop) { + let RenderState::Suspended(cached_window) = &mut self.state else { + return; + }; + + // Get the winit window cached in a previous Suspended event or else create a new window + let window = cached_window + .take() + .unwrap_or_else(|| create_winit_window(event_loop)); + + // Create a vello Surface + let size = window.inner_size(); + let surface_future = self.context.create_surface( + window.clone(), + size.width, + size.height, + wgpu::PresentMode::AutoVsync, + ); + let surface = pollster::block_on(surface_future).expect("Error creating surface"); + self.editor.update_layout(size.width as _, 1.0); + + // Create a vello Renderer for the surface (using its device id) + self.renderers + .resize_with(self.context.devices.len(), || None); + self.renderers[surface.dev_id] + .get_or_insert_with(|| create_vello_renderer(&self.context, &surface)); + + // Save the Window and Surface to a state variable + self.state = RenderState::Active(ActiveRenderState { window, surface }); + + event_loop.set_control_flow(ControlFlow::Poll); + } + + fn suspended(&mut self, event_loop: &ActiveEventLoop) { + if let RenderState::Active(state) = &self.state { + self.state = RenderState::Suspended(Some(state.window.clone())); + } + event_loop.set_control_flow(ControlFlow::Wait); + } + + fn window_event( + &mut self, + event_loop: &ActiveEventLoop, + window_id: winit::window::WindowId, + event: WindowEvent, + ) { + // Ignore the event (return from the function) if + // - we have no render_state + // - OR the window id of the event doesn't match the window id of our render_state + // + // Else extract a mutable reference to the render state from its containing option for use below + let render_state = match &mut self.state { + RenderState::Active(state) if state.window.id() == window_id => state, + _ => return, + }; + + self.editor.handle_event(&event); + render_state.window.request_redraw(); + // render_state + // .window + // .set_cursor(winit::window::Cursor::Icon(winit::window::CursorIcon::Text)); + + match event { + // Exit the event loop when a close is requested (e.g. window's close button is pressed) + WindowEvent::CloseRequested => event_loop.exit(), + + // Resize the surface when the window is resized + WindowEvent::Resized(size) => { + self.context + .resize_surface(&mut render_state.surface, size.width, size.height); + render_state.window.request_redraw(); + self.editor.update_layout(size.width as _, 1.0); + } + + // This is where all the rendering happens + WindowEvent::RedrawRequested => { + // Empty the scene of objects to draw. You could create a new Scene each time, but in this case + // the same Scene is reused so that the underlying memory allocation can also be reused. + self.scene.reset(); + + self.editor.draw(&mut self.scene); + // Re-add the objects to draw to the scene. + // add_shapes_to_scene(&mut self.scene); + + // Get the RenderSurface (surface + config) + let surface = &render_state.surface; + + // Get the window size + let width = surface.config.width; + let height = surface.config.height; + + // Get a handle to the device + let device_handle = &self.context.devices[surface.dev_id]; + + // Get the surface's texture + let surface_texture = surface + .surface + .get_current_texture() + .expect("failed to get surface texture"); + + // Render to the surface's texture + self.renderers[surface.dev_id] + .as_mut() + .unwrap() + .render_to_surface( + &device_handle.device, + &device_handle.queue, + &self.scene, + &surface_texture, + &vello::RenderParams { + base_color: Color::rgb8(30, 30, 30), // Background color + width, + height, + antialiasing_method: AaConfig::Msaa16, + }, + ) + .expect("failed to render to surface"); + + // Queue the texture to be presented on the surface + surface_texture.present(); + + device_handle.device.poll(wgpu::Maintain::Poll); + } + _ => {} + } + } +} + +fn main() -> Result<()> { + // Setup a bunch of state: + let mut app = SimpleVelloApp { + context: RenderContext::new(), + renderers: vec![], + state: RenderState::Suspended(None), + scene: Scene::new(), + editor: text::Editor::default(), + }; + + app.editor.set_text(text::LOREM); + + // Create and run a winit event loop + let event_loop = EventLoop::new()?; + event_loop + .run_app(&mut app) + .expect("Couldn't run event loop"); + Ok(()) +} + +/// Helper function that creates a Winit window and returns it (wrapped in an Arc for sharing between threads) +fn create_winit_window(event_loop: &ActiveEventLoop) -> Arc { + let attr = Window::default_attributes() + .with_inner_size(LogicalSize::new(1044, 800)) + .with_resizable(true) + .with_title("Vello Text Editor"); + Arc::new(event_loop.create_window(attr).unwrap()) +} + +/// Helper function that creates a vello `Renderer` for a given `RenderContext` and `RenderSurface` +fn create_vello_renderer(render_cx: &RenderContext, surface: &RenderSurface) -> Renderer { + Renderer::new( + &render_cx.devices[surface.dev_id].device, + RendererOptions { + surface_format: Some(surface.format), + use_cpu: false, + antialiasing_support: vello::AaSupport::all(), + num_init_threads: NonZeroUsize::new(1), + }, + ) + .expect("Couldn't create renderer") +} diff --git a/examples/vello_editor/src/text.rs b/examples/vello_editor/src/text.rs new file mode 100644 index 00000000..897654a8 --- /dev/null +++ b/examples/vello_editor/src/text.rs @@ -0,0 +1,400 @@ +// Copyright 2024 the Parley Authors +// SPDX-License-Identifier: Apache-2.0 OR MIT + +#[cfg(not(target_os = "android"))] +use clipboard_rs::{Clipboard, ClipboardContext}; +use parley::layout::cursor::{Selection, VisualMode}; +use parley::layout::Affinity; +use parley::{layout::PositionedLayoutItem, FontContext}; +use peniko::{kurbo::Affine, Color, Fill}; +use std::time::Instant; +use vello::Scene; +use winit::{ + event::{Modifiers, WindowEvent}, + keyboard::{KeyCode, PhysicalKey}, +}; + +type LayoutContext = parley::LayoutContext; +type Layout = parley::Layout; + +const INSET: f32 = 32.0; + +#[allow(dead_code)] +#[derive(Copy, Clone, Debug)] +pub enum ActiveText<'a> { + FocusedCluster(Affinity, &'a str), + Selection(&'a str), +} + +#[derive(Default)] +pub struct Editor { + font_cx: FontContext, + layout_cx: LayoutContext, + buffer: String, + layout: Layout, + selection: Selection, + cursor_mode: VisualMode, + last_click_time: Option, + click_count: u32, + pointer_down: bool, + cursor_pos: (f32, f32), + modifiers: Option, + width: f32, +} + +impl Editor { + pub fn set_text(&mut self, text: &str) { + self.buffer.clear(); + self.buffer.push_str(text); + } + + pub fn update_layout(&mut self, width: f32, scale: f32) { + let mut builder = self + .layout_cx + .ranged_builder(&mut self.font_cx, &self.buffer, scale); + builder.push_default(&parley::style::StyleProperty::FontSize(32.0)); + builder.push_default(&parley::style::StyleProperty::LineHeight(1.2)); + builder.push_default(&parley::style::StyleProperty::FontStack( + parley::style::FontStack::Source("system-ui"), + )); + builder.build_into(&mut self.layout); + self.layout.break_all_lines(Some(width - INSET * 2.0)); + self.layout + .align(Some(width - INSET * 2.0), parley::layout::Alignment::Start); + self.width = width; + } + + #[allow(unused)] + pub fn active_text(&self) -> ActiveText { + if self.selection.is_collapsed() { + let range = self + .selection + .focus() + .cluster_path() + .cluster(&self.layout) + .map(|c| c.text_range()) + .unwrap_or_default(); + ActiveText::FocusedCluster(self.selection.focus().affinity(), &self.buffer[range]) + } else { + ActiveText::Selection(&self.buffer[self.selection.text_range()]) + } + } + + #[cfg(not(target_os = "android"))] + fn handle_clipboard(&mut self, code: KeyCode) { + match code { + KeyCode::KeyC => { + if !self.selection.is_collapsed() { + let text = &self.buffer[self.selection.text_range()]; + let cb = ClipboardContext::new().unwrap(); + cb.set_text(text.to_owned()).ok(); + } + } + KeyCode::KeyX => { + if !self.selection.is_collapsed() { + let text = &self.buffer[self.selection.text_range()]; + let cb = ClipboardContext::new().unwrap(); + cb.set_text(text.to_owned()).ok(); + if let Some(start) = self.delete_current_selection() { + self.update_layout(self.width, 1.0); + let (start, affinity) = if start > 0 { + (start - 1, Affinity::Upstream) + } else { + (start, Affinity::Downstream) + }; + self.selection = Selection::from_index(&self.layout, start, affinity); + } + } + } + KeyCode::KeyV => { + let cb = ClipboardContext::new().unwrap(); + let text = cb.get_text().unwrap_or_default(); + let start = self + .delete_current_selection() + .unwrap_or_else(|| self.selection.focus().text_range().start); + self.buffer.insert_str(start, &text); + self.update_layout(self.width, 1.0); + self.selection = + Selection::from_index(&self.layout, start + text.len(), Affinity::default()); + } + _ => {} + } + } + + #[cfg(target_os = "android")] + fn handle_clipboard(&mut self, _code: KeyCode) { + // TODO: support clipboard on Android + } + + pub fn handle_event(&mut self, event: &WindowEvent) { + match event { + WindowEvent::Resized(size) => { + self.update_layout(size.width as f32, 1.0); + self.selection = self.selection.refresh(&self.layout); + } + WindowEvent::ModifiersChanged(modifiers) => { + self.modifiers = Some(*modifiers); + } + WindowEvent::KeyboardInput { event, .. } => { + if !event.state.is_pressed() { + return; + } + #[allow(unused)] + let (shift, ctrl, cmd) = self + .modifiers + .map(|mods| { + ( + mods.state().shift_key(), + mods.state().control_key(), + mods.state().super_key(), + ) + }) + .unwrap_or_default(); + #[cfg(target_os = "macos")] + let action_mod = cmd; + #[cfg(not(target_os = "macos"))] + let action_mod = ctrl; + if let PhysicalKey::Code(code) = event.physical_key { + match code { + KeyCode::KeyC if action_mod => { + self.handle_clipboard(code); + } + KeyCode::KeyX if action_mod => { + self.handle_clipboard(code); + } + KeyCode::KeyV if action_mod => { + self.handle_clipboard(code); + } + KeyCode::ArrowLeft => { + self.selection = if ctrl { + self.selection.previous_word(&self.layout, shift) + } else { + self.selection.previous_visual( + &self.layout, + self.cursor_mode, + shift, + ) + }; + } + KeyCode::ArrowRight => { + self.selection = if ctrl { + self.selection.next_word(&self.layout, shift) + } else { + self.selection + .next_visual(&self.layout, self.cursor_mode, shift) + }; + } + KeyCode::ArrowUp => { + self.selection = self.selection.previous_line(&self.layout, shift); + } + KeyCode::ArrowDown => { + self.selection = self.selection.next_line(&self.layout, shift); + } + KeyCode::Home => { + if ctrl { + self.selection = + self.selection.move_lines(&self.layout, isize::MIN, shift); + } else { + self.selection = self.selection.line_start(&self.layout, shift); + } + } + KeyCode::End => { + if ctrl { + self.selection = + self.selection.move_lines(&self.layout, isize::MAX, shift); + } else { + self.selection = self.selection.line_end(&self.layout, shift); + } + } + KeyCode::Delete => { + let start = if self.selection.is_collapsed() { + let range = self.selection.focus().text_range(); + let start = range.start; + self.buffer.replace_range(range, ""); + Some(start) + } else { + self.delete_current_selection() + }; + if let Some(start) = start { + self.update_layout(self.width, 1.0); + self.selection = + Selection::from_index(&self.layout, start, Affinity::default()); + } + } + KeyCode::Backspace => { + let start = if self.selection.is_collapsed() { + let end = self.selection.focus().text_range().start; + if let Some((start, _)) = + self.buffer[..end].char_indices().next_back() + { + self.buffer.replace_range(start..end, ""); + Some(start) + } else { + None + } + } else { + self.delete_current_selection() + }; + if let Some(start) = start { + self.update_layout(self.width, 1.0); + let (start, affinity) = if start > 0 { + (start - 1, Affinity::Upstream) + } else { + (start, Affinity::Downstream) + }; + self.selection = + Selection::from_index(&self.layout, start, affinity); + } + } + _ => { + if let Some(text) = &event.text { + let start = self + .delete_current_selection() + .unwrap_or_else(|| self.selection.focus().text_range().start); + self.buffer.insert_str(start, text); + self.update_layout(self.width, 1.0); + self.selection = Selection::from_index( + &self.layout, + start + text.len() - 1, + Affinity::Upstream, + ); + } + } + } + } + + // println!("Active text: {:?}", self.active_text()); + } + WindowEvent::MouseInput { state, button, .. } => { + if *button == winit::event::MouseButton::Left { + self.pointer_down = state.is_pressed(); + if self.pointer_down { + let now = Instant::now(); + if let Some(last) = self.last_click_time.take() { + if now.duration_since(last).as_secs_f64() < 0.25 { + self.click_count = (self.click_count + 1) % 4; + } else { + self.click_count = 1; + } + } else { + self.click_count = 1; + } + self.last_click_time = Some(now); + match self.click_count { + 2 => { + self.selection = Selection::word_from_point( + &self.layout, + self.cursor_pos.0, + self.cursor_pos.1, + ); + } + 3 => { + let focus = *Selection::from_point( + &self.layout, + self.cursor_pos.0, + self.cursor_pos.1, + ) + .line_start(&self.layout, true) + .focus(); + self.selection = + Selection::from(focus).line_end(&self.layout, true); + } + _ => { + self.selection = Selection::from_point( + &self.layout, + self.cursor_pos.0, + self.cursor_pos.1, + ); + } + } + // println!("Active text: {:?}", self.active_text()); + } + } + } + WindowEvent::CursorMoved { position, .. } => { + let prev_pos = self.cursor_pos; + self.cursor_pos = (position.x as f32 - INSET, position.y as f32 - INSET); + // macOS seems to generate a spurious move after selecting word? + if self.pointer_down && prev_pos != self.cursor_pos { + self.selection = self.selection.extend_to_point( + &self.layout, + self.cursor_pos.0, + self.cursor_pos.1, + ); + // println!("Active text: {:?}", self.active_text()); + } + } + _ => {} + } + } + + fn delete_current_selection(&mut self) -> Option { + if !self.selection.is_collapsed() { + let range = self.selection.text_range(); + let start = range.start; + self.buffer.replace_range(range, ""); + Some(start) + } else { + None + } + } + + pub fn draw(&self, scene: &mut Scene) { + let transform = Affine::translate((INSET as f64, INSET as f64)); + self.selection.geometry_with(&self.layout, |rect| { + scene.fill(Fill::NonZero, transform, Color::STEEL_BLUE, None, &rect); + }); + if let Some(cursor) = self.selection.focus().strong_geometry(&self.layout, 1.5) { + scene.fill(Fill::NonZero, transform, Color::WHITE, None, &cursor); + }; + if let Some(cursor) = self.selection.focus().weak_geometry(&self.layout, 1.5) { + scene.fill(Fill::NonZero, transform, Color::LIGHT_GRAY, None, &cursor); + }; + for line in self.layout.lines() { + for item in line.items() { + let PositionedLayoutItem::GlyphRun(glyph_run) = item else { + continue; + }; + let mut x = glyph_run.offset(); + let y = glyph_run.baseline(); + let run = glyph_run.run(); + let font = run.font(); + let font_size = run.font_size(); + let synthesis = run.synthesis(); + let glyph_xform = synthesis + .skew() + .map(|angle| Affine::skew(angle.to_radians().tan() as f64, 0.0)); + let coords = run + .normalized_coords() + .iter() + .map(|coord| vello::skrifa::instance::NormalizedCoord::from_bits(*coord)) + .collect::>(); + scene + .draw_glyphs(font) + .brush(Color::WHITE) + .hint(true) + .transform(transform) + .glyph_transform(glyph_xform) + .font_size(font_size) + .normalized_coords(&coords) + .draw( + Fill::NonZero, + glyph_run.glyphs().map(|glyph| { + let gx = x + glyph.x; + let gy = y - glyph.y; + x += glyph.advance; + vello::glyph::Glyph { + id: glyph.id as _, + x: gx, + y: gy, + } + }), + ); + } + } + } +} + +pub const LOREM: &str = r" Lorem ipsum dolor sit amet, consectetur adipiscing elit. Morbi cursus mi sed euismod euismod. Orci varius natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus. Nullam placerat efficitur tellus at semper. Morbi ac risus magna. Donec ut cursus ex. Etiam quis posuere tellus. Mauris posuere dui et turpis mollis, vitae luctus tellus consectetur. Lorem ipsum dolor sit amet, consectetur adipiscing elit. Curabitur eu facilisis nisl. + +Phasellus in viverra dolor, vitae facilisis est. Maecenas malesuada massa vel ultricies feugiat. Vivamus venenatis et gהתעשייה בנושא האינטרנטa nibh nec pharetra. Phasellus vestibulum elit enim, nec scelerisque orci faucibus id. Vivamus consequat purus sit amet orci egestas, non iaculis massa porttitor. Vestibulum ut eros leo. In fermentum convallis magna in finibus. Donec justo leo, maximus ac laoreet id, volutpat ut elit. Mauris sed leo non neque laoreet faucibus. Aliquam orci arcu, faucibus in molestie eget, ornare non dui. Donec volutpat nulla in fringilla elementum. Aliquam vitae ante egestas ligula tempus vestibulum sit amet sed ante. "; diff --git a/parley/src/layout/cluster.rs b/parley/src/layout/cluster.rs index 98650540..e56bc7fa 100644 --- a/parley/src/layout/cluster.rs +++ b/parley/src/layout/cluster.rs @@ -4,6 +4,79 @@ use super::*; impl<'a, B: Brush> Cluster<'a, B> { + /// Returns the cluster for the given layout and byte index. + pub fn from_index(layout: &'a Layout, byte_index: usize) -> Option { + let mut path = ClusterPath::default(); + if let Some((line_index, line)) = layout.line_for_byte_index(byte_index) { + path.line_index = line_index as u32; + for (run_index, run) in line.runs().enumerate() { + path.run_index = run_index as u32; + if !run.text_range().contains(&byte_index) { + continue; + } + for (cluster_index, cluster) in run.clusters().enumerate() { + path.logical_index = cluster_index as u32; + if cluster.text_range().contains(&byte_index) { + return path.cluster(layout); + } + } + } + } + path.cluster(layout) + } + + /// Returns the cluster and affinity for the given layout and point. + pub fn from_point(layout: &'a Layout, x: f32, y: f32) -> Option<(Self, Affinity)> { + let mut path = ClusterPath::default(); + if let Some((line_index, line)) = layout.line_for_offset(y) { + path.line_index = line_index as u32; + let mut offset = 0.0; + let last_run_index = line.len().saturating_sub(1); + for (run_index, run) in line.runs().enumerate() { + let is_last_run = run_index == last_run_index; + let run_advance = run.advance(); + path.run_index = run_index as u32; + path.logical_index = 0; + if x > offset + run_advance && !is_last_run { + offset += run_advance; + continue; + } + let last_cluster_index = run.cluster_range().len().saturating_sub(1); + for (visual_index, cluster) in run.visual_clusters().enumerate() { + let is_last_cluster = is_last_run && visual_index == last_cluster_index; + path.logical_index = + run.visual_to_logical(visual_index).unwrap_or_default() as u32; + let cluster_advance = cluster.advance(); + let edge = offset; + offset += cluster_advance; + if x > offset && !is_last_cluster { + continue; + } + let affinity = + Affinity::new(cluster.is_rtl(), x <= edge + cluster_advance * 0.5); + return Some((path.cluster(layout)?, affinity)); + } + } + } + Some((path.cluster(layout)?, Affinity::default())) + } + + /// Returns the line that contains the cluster. + pub fn line(&self) -> Line<'a, B> { + self.run.layout.get(self.run.line_index as usize).unwrap() + } + + /// Returns the run that contains the cluster. + pub fn run(&self) -> Run<'a, B> { + self.run.clone() + } + + /// Returns the path that contains the set of indices to reach the cluster + /// from a layout. + pub fn path(&self) -> ClusterPath { + self.path + } + /// Returns the range of text that defines the cluster. pub fn text_range(&self) -> Range { self.data.text_range(self.run.data) @@ -14,6 +87,11 @@ impl<'a, B: Brush> Cluster<'a, B> { self.data.advance } + /// Returns true if this is a right-to-left cluster. + pub fn is_rtl(&self) -> bool { + self.run.is_rtl() + } + /// Returns `true` if the cluster is the beginning of a ligature. pub fn is_ligature_start(&self) -> bool { self.data.is_ligature_start() @@ -57,16 +135,309 @@ impl<'a, B: Brush> Cluster<'a, B> { } else { let start = self.run.data.glyph_start + self.data.glyph_offset as usize; GlyphIter::Slice( - self.run.layout.glyphs[start..start + self.data.glyph_len as usize].iter(), + self.run.layout.data.glyphs[start..start + self.data.glyph_len as usize].iter(), ) } } + /// Returns true if this cluster is at the beginning of a line. + pub fn is_start_of_line(&self) -> bool { + self.path.run_index == 0 && self.run.logical_to_visual(self.path.logical_index()) == Some(0) + } + + /// Returns true if this cluster is at the end of a line. + pub fn is_end_of_line(&self) -> bool { + self.line().len().saturating_sub(1) == self.path.run_index() + && self.run.logical_to_visual(self.path.logical_index()) + == Some(self.run.cluster_range().len().saturating_sub(1)) + } + + /// If the cluster as at the end of the line, returns the reason + /// for the line break. + pub fn is_line_break(&self) -> Option { + if self.is_end_of_line() { + Some(self.line().data.break_reason) + } else { + None + } + } + + /// If this cluster, combined with the given affinity, sits on a + /// directional boundary, returns the cluster that represents the alternate + /// insertion position. + /// + /// For example, if this cluster is a left-to-right cluster, then this + /// will return the cluster that represents the position where a + /// right-to-left character would be inserted, and vice versa. + pub fn bidi_link(&self, affinity: Affinity) -> Option { + let run_end = self.run.len().checked_sub(1)?; + let visual_index = self.run.logical_to_visual(self.path.logical_index())?; + let is_rtl = self.is_rtl(); + let is_leading = affinity.is_visually_leading(is_rtl); + let at_start = visual_index == 0 && is_leading; + let at_end = visual_index == run_end && !is_leading; + let other = if (at_start && !is_rtl) || (at_end && is_rtl) { + self.previous_logical()? + } else if (at_end && !is_rtl) || (at_start && is_rtl) { + self.next_logical()? + } else { + return None; + }; + if other.is_rtl() == is_rtl { + return None; + } + Some(other) + } + + /// Returns the cluster that follows this one in logical order. + pub fn next_logical(&self) -> Option { + if self.path.logical_index() + 1 < self.run.cluster_range().len() { + // Fast path: next cluster is in the same run + ClusterPath { + line_index: self.path.line_index, + run_index: self.path.run_index, + logical_index: self.path.logical_index + 1, + } + .cluster(self.run.layout) + } else { + let index = self.text_range().end; + if index >= self.run.layout.data.text_len { + return None; + } + // We have to search for the cluster containing our end index + Self::from_index(self.run.layout, index) + } + } + + /// Returns the cluster that precedes this one in logical order. + pub fn previous_logical(&self) -> Option { + if self.path.logical_index > 0 { + // Fast path: previous cluster is in the same run + ClusterPath { + line_index: self.path.line_index, + run_index: self.path.run_index, + logical_index: self.path.logical_index - 1, + } + .cluster(self.run.layout) + } else { + Self::from_index(self.run.layout, self.text_range().start.checked_sub(1)?) + } + } + + /// Returns the cluster that follows this one in visual order. + pub fn next_visual(&self) -> Option { + let layout = self.run.layout; + let run = self.run.clone(); + let visual_index = run.logical_to_visual(self.path.logical_index())?; + if let Some(cluster_index) = run.visual_to_logical(visual_index + 1) { + // Fast path: next visual cluster is in the same run + run.get(cluster_index) + } else { + // We just want to find the first line/run following this one that + // contains any cluster. + let mut run_index = self.path.run_index() + 1; + for line_index in self.path.line_index()..layout.len() { + let line = layout.get(line_index)?; + for run_index in run_index..line.len() { + if let Some(run) = line.run(run_index) { + if !run.cluster_range().is_empty() { + return ClusterPath { + line_index: line_index as u32, + run_index: run_index as u32, + logical_index: run.visual_to_logical(0)? as u32, + } + .cluster(layout); + } + } + } + // Restart at first run on next line + run_index = 0; + } + None + } + } + + /// Returns the cluster that precedes this one in visual order. + pub fn previous_visual(&self) -> Option { + let visual_index = self.run.logical_to_visual(self.path.logical_index())?; + if let Some(cluster_index) = visual_index + .checked_sub(1) + .and_then(|visual_index| self.run.visual_to_logical(visual_index)) + { + // Fast path: previous visual cluster is in the same run + ClusterPath { + line_index: self.path.line_index, + run_index: self.path.run_index, + logical_index: cluster_index as u32, + } + .cluster(self.run.layout) + } else { + // We just want to find the first line/run preceding this one that + // contains any cluster. + let layout = self.run.layout; + let mut run_index = Some(self.path.run_index()); + for line_index in (0..=self.path.line_index()).rev() { + let line = layout.get(line_index)?; + let first_run = run_index.unwrap_or(line.len()); + for run_index in (0..first_run).rev() { + if let Some(run) = line.run(run_index) { + let range = run.cluster_range(); + if !range.is_empty() { + return ClusterPath { + line_index: line_index as u32, + run_index: run_index as u32, + logical_index: run.visual_to_logical(range.len() - 1)? as u32, + } + .cluster(layout); + } + } + } + run_index = None; + } + None + } + } + + /// Returns the next cluster that is marked as a word boundary. + pub fn next_word(&self) -> Option { + let mut cluster = self.clone(); + while let Some(next) = cluster.next_logical() { + if next.is_word_boundary() { + return Some(next); + } + cluster = next; + } + None + } + + /// Returns the previous cluster that is marked as a word boundary. + pub fn previous_word(&self) -> Option { + let mut cluster = self.clone(); + while let Some(prev) = cluster.previous_logical() { + if prev.is_word_boundary() { + return Some(prev); + } + cluster = prev; + } + None + } + + /// Returns the visual offset of this cluster along direction of text flow. + /// + /// This cost of this function is roughly linear in the number of clusters + /// on the containing line. + pub fn visual_offset(&self) -> Option { + let line = self.path.line(self.run.layout)?; + let mut offset = 0.0; + for run_index in 0..=self.path.run_index() { + let run = line.run(run_index)?; + if run_index != self.path.run_index() { + offset += run.advance(); + } else { + let visual_index = run.logical_to_visual(self.path.logical_index())?; + for cluster in run.visual_clusters().take(visual_index) { + offset += cluster.advance(); + } + } + } + Some(offset) + } + pub(crate) fn info(&self) -> ClusterInfo { self.data.info } } +/// Determines how a cursor attaches to a cluster. +#[derive(Copy, Clone, PartialEq, Eq, Default, Debug)] +pub enum Affinity { + /// Left side for LTR clusters and right side for RTL clusters. + #[default] + Downstream = 0, + /// Right side for LTR clusters and left side for RTL clusters. + Upstream = 1, +} + +impl Affinity { + pub(crate) fn new(is_rtl: bool, is_leading: bool) -> Self { + match (is_rtl, is_leading) { + // trailing edge of RTL and leading edge of LTR + (true, false) | (false, true) => Affinity::Downstream, + // leading edge of RTL and trailing edge of LTR + (true, true) | (false, false) => Affinity::Upstream, + } + } + + pub fn invert(&self) -> Self { + match self { + Self::Downstream => Self::Upstream, + Self::Upstream => Self::Downstream, + } + } + + /// Returns true if the cursor should be placed on the leading edge. + pub fn is_visually_leading(&self, is_rtl: bool) -> bool { + match (*self, is_rtl) { + (Self::Upstream, true) | (Self::Downstream, false) => true, + (Self::Upstream, false) | (Self::Downstream, true) => false, + } + } + + /// Returns true if the cursor should be placed on the trailing edge. + pub fn is_visually_trailing(&self, is_rtl: bool) -> bool { + !self.is_visually_leading(is_rtl) + } +} + +/// Index based path to a cluster. +#[derive(Copy, Clone, PartialEq, Eq, Default, Debug)] +pub struct ClusterPath { + line_index: u32, + run_index: u32, + logical_index: u32, +} + +impl ClusterPath { + pub(crate) fn new(line_index: u32, run_index: u32, logical_index: u32) -> Self { + Self { + line_index, + run_index, + logical_index, + } + } + + /// Returns the index of the line containing this cluster. + pub fn line_index(&self) -> usize { + self.line_index as usize + } + + /// Returns the index of the run (within the owning line) containing this + /// cluster. + pub fn run_index(&self) -> usize { + self.run_index as usize + } + + /// Returns the logical index of the cluster within the owning run. + pub fn logical_index(&self) -> usize { + self.logical_index as usize + } + + /// Returns the line for this path and the specified layout. + pub fn line<'a, B: Brush>(&self, layout: &'a Layout) -> Option> { + layout.get(self.line_index()) + } + + /// Returns the run for this path and the specified layout. + pub fn run<'a, B: Brush>(&self, layout: &'a Layout) -> Option> { + self.line(layout)?.run(self.run_index()) + } + + /// Returns the cluster for this path and the specified layout. + pub fn cluster<'a, B: Brush>(&self, layout: &'a Layout) -> Option> { + self.run(layout)?.get(self.logical_index()) + } +} + #[derive(Clone)] enum GlyphIter<'a> { Single(Option), diff --git a/parley/src/layout/cursor.rs b/parley/src/layout/cursor.rs index 83064e2f..ca3dd5c7 100644 --- a/parley/src/layout/cursor.rs +++ b/parley/src/layout/cursor.rs @@ -1,186 +1,757 @@ // Copyright 2021 the Parley Authors // SPDX-License-Identifier: Apache-2.0 OR MIT -//! Hit testing. +//! Text selection support. -use super::*; +use super::{Affinity, BreakReason, Brush, Cluster, ClusterPath, Layout}; +use alloc::vec::Vec; +use core::ops::Range; +use peniko::kurbo::Rect; -/// Represents a position within a layout. -#[derive(Copy, Clone, Default, Debug)] +/// Defines how a cursor will bind to a text position when moving visually. +#[derive(Copy, Clone, PartialEq, Eq, Default, Debug)] +pub enum VisualMode { + /// During cursor motion, affinity is adjusted to prioritize the dominant + /// direction of the layout. + /// + /// That is, if the base direction of the layout is left-to-right, then + /// the visual cursor will represent the position where the next + /// left-to-right character would be inserted, and vice versa. + /// + /// This matches the behavior of Pango's strong cursor. + #[default] + Strong, + /// During cursor motion, affinity is adjusted to prioritize the non-dominant + /// direction of the layout. + /// + /// That is, if the base direction of the layout is left-to-right, then + /// the visual cursor will represent the position where the next + /// right-to-left character would be inserted, and vice versa. + /// + /// This matches the behavior of Pango's weak cursor. + Weak, + /// During cursor motion, affinity is adjusted based on the directionality + /// of the incoming position. + /// + /// That is, if a directional boundary is entered from a left-to-right run + /// of text, then the cursor will represent the position where the next + /// left-to-right character would be inserted, and vice versa. + /// + /// This matches the behavior of Firefox. + Adaptive, +} + +impl VisualMode { + /// Returns the preferred RTL state for the given layout. + /// + /// This is used to handle cursor modes when moving visually + /// by cluster. + fn prefer_rtl(self, layout: &Layout) -> Option { + match self { + Self::Strong => Some(layout.is_rtl()), + Self::Weak => Some(!layout.is_rtl()), + Self::Adaptive => None, + } + } +} + +/// A single position in a layout. +#[derive(Copy, Clone, PartialEq, Default, Debug)] pub struct Cursor { - /// Path to the target cluster. - pub path: CursorPath, - /// Offset to the baseline. - pub baseline: f32, - /// Offset to the target cluster along the baseline. - pub offset: f32, - /// Advance of the target cluster. - pub advance: f32, - /// Start of the target cluster. - pub text_start: usize, - /// End of the target cluster. - pub text_end: usize, - /// Insert point of the cursor (leading or trailing). - pub insert_point: usize, - /// `true` if the target cluster is in a right-to-left run. - pub is_rtl: bool, - /// `true` if the cursor was created from a point or position inside the layout - pub is_inside: bool, + path: ClusterPath, + index: u32, + text_start: u32, + text_end: u32, + visual_offset: f32, + is_rtl: bool, + affinity: Affinity, } impl Cursor { - /// Creates a new cursor from the specified layout and point. - pub fn from_point(layout: &Layout, mut x: f32, y: f32) -> Self { - let mut result = Self { - is_inside: x >= 0. && y >= 0., - ..Default::default() + /// Returns a new cursor for the given layout, byte index and affinity. + pub fn from_index(layout: &Layout, index: usize, affinity: Affinity) -> Self { + let Some(cluster) = Cluster::from_index(layout, index) else { + return Self::default(); + }; + Self::from_cluster(cluster, affinity) + } + + /// Creates a new cursor for the given layout and point. + pub fn from_point(layout: &Layout, x: f32, y: f32) -> Self { + let Some((cluster, affinity)) = Cluster::from_point(layout, x, y) else { + return Self::default(); + }; + Self::from_cluster(cluster, affinity) + } + + fn from_cluster(cluster: Cluster, mut affinity: Affinity) -> Self { + let mut range = cluster.text_range(); + let index = range.start as u32; + let mut offset = cluster.visual_offset().unwrap_or_default(); + let is_rtl = cluster.is_rtl(); + if cluster.is_line_break() == Some(BreakReason::Explicit) { + affinity = Affinity::Downstream; + } + let is_left_side = affinity.is_visually_leading(is_rtl); + if !is_left_side { + offset += cluster.advance(); + if !is_rtl { + range = cluster + .next_logical() + .map(|cluster| cluster.text_range()) + .unwrap_or(range.end..range.end); + } + } else if is_rtl { + range = cluster + .next_logical() + .map(|cluster| cluster.text_range()) + .unwrap_or(range.end..range.end); + } + Self { + path: cluster.path(), + index, + text_start: range.start as u32, + text_end: range.end as u32, + visual_offset: offset, + is_rtl, + affinity, + } + } + + /// Returns a new cursor with internal state recomputed to match the given + /// layout. + /// + /// This should be called whenever the layout is rebuilt or resized. + #[must_use] + pub fn refresh(&self, layout: &Layout) -> Self { + Self::from_index(layout, self.index as usize, self.affinity) + } + + /// Returns the path to the target cluster. + pub fn cluster_path(&self) -> ClusterPath { + self.path + } + + /// Returns the text range of the target cluster. + pub fn text_range(&self) -> Range { + self.text_start as usize..self.text_end as usize + } + + /// Returns the visual offset of the target cluster along the direction of + /// text flow. + pub fn visual_offset(&self) -> f32 { + self.visual_offset + } + + /// Returns the byte index associated with the cursor. + pub fn index(&self) -> usize { + self.index as usize + } + + /// Returns the associated affinity for this cursor. + pub fn affinity(&self) -> Affinity { + self.affinity + } + + /// Returns the visual geometry of the cursor where the next character + /// matching the base direction of the layout would be inserted. + /// + /// If the current cursor is not on a directional boundary, this is also + /// the location where characters opposite the base direction would be + /// inserted. + pub fn strong_geometry(&self, layout: &Layout, size: f32) -> Option { + if self.is_rtl == layout.is_rtl() { + self.geometry(layout, size) + } else { + self.bidi_link_geometry(layout, size) + .or_else(|| self.geometry(layout, size)) + } + } + + /// Returns the visual geometry of the cursor where the next character + /// that is opposite the base direction of the layout would be inserted. + /// + /// This returns `None` when the current cursor is not on a directional + /// boundary. + pub fn weak_geometry(&self, layout: &Layout, size: f32) -> Option { + // Weak cursor only exists if we're on a directional boundary + let bidi_link = self.bidi_link_geometry(layout, size)?; + if self.is_rtl == layout.is_rtl() { + Some(bidi_link) + } else { + self.geometry(layout, size) + } + } + + fn geometry(&self, layout: &Layout, size: f32) -> Option { + let metrics = *self.path.line(layout)?.metrics(); + let line_x = self.visual_offset as f64; + Some(Rect::new( + line_x, + metrics.min_coord as f64, + line_x + size as f64, + metrics.max_coord as f64, + )) + } + + fn bidi_link_geometry(&self, layout: &Layout, size: f32) -> Option { + let cluster = self.path.cluster(layout)?.bidi_link(self.affinity)?; + let mut line_x = cluster.visual_offset()? as f64; + let run = cluster.run(); + let path = cluster.path(); + if run.logical_to_visual(path.logical_index())? != 0 { + line_x += cluster.advance() as f64; + } + let metrics = *path.line(layout)?.metrics(); + Some(Rect::new( + line_x, + metrics.min_coord as f64, + line_x + size as f64, + metrics.max_coord as f64, + )) + } + + pub fn next_visual(&self, layout: &Layout, mode: VisualMode) -> Self { + let prefer_rtl = mode.prefer_rtl(layout); + let Some(cluster) = self.path.cluster(layout) else { + return *self; }; - let last_line = layout.data.lines.len().saturating_sub(1); - for (line_index, line) in layout.lines().enumerate() { - let line_metrics = line.metrics(); - if y > line_metrics.baseline + line_metrics.descent + line_metrics.leading * 0.5 { - if line_index != last_line { - continue; + if self.affinity.is_visually_leading(self.is_rtl) { + if let Some(next_cluster) = cluster.next_visual() { + // Handle hard line breaks + if cluster.is_line_break() == Some(BreakReason::Explicit) { + // If we're at the front of a hard line break and moving + // right, skip directly to the leading edge of the next cluster + return Self::from_cluster(next_cluster, self.affinity); } - result.is_inside = false; - x = f32::MAX; - } else if y < 0. { - x = 0.; - } - result.baseline = line_metrics.baseline; - result.path.line_index = line_index; - let mut last_edge = line_metrics.offset; - for (run_index, run) in line.runs().enumerate() { - result.path.run_index = run_index; - for (cluster_index, cluster) in run.visual_clusters().enumerate() { - let range = cluster.text_range(); - result.text_start = range.start; - result.text_end = range.end; - result.is_rtl = run.is_rtl(); - result.path.cluster_index = run.visual_to_logical(cluster_index).unwrap(); - if x >= last_edge { - let advance = cluster.advance(); - let next_edge = last_edge + advance; - result.offset = next_edge; - result.insert_point = range.end; - if x >= next_edge { - last_edge = next_edge; - continue; + // Handle text direction boundaries + if next_cluster.is_rtl() != self.is_rtl { + if let Some(prefer_rtl) = prefer_rtl { + if self.is_rtl != prefer_rtl { + return Self::from_cluster(next_cluster, self.affinity.invert()); } - result.advance = advance; - if x <= (last_edge + next_edge) * 0.5 { - result.insert_point = range.start; - result.offset = last_edge; + } + } + } + // We're moving right so we want to track right-side affinity; + // let's swap. + Self::from_index(layout, self.index as usize, self.affinity.invert()) + } else if let Some(next) = cluster.next_visual() { + // Handle soft line breaks + if matches!( + cluster.is_line_break(), + Some(BreakReason::Regular) | Some(BreakReason::Emergency) + ) { + // Without this check, moving to the next line will + // skip the first character which can be jarring + return Self::from_cluster(next, self.affinity.invert()); + } + // And hard line breaks + if next.is_line_break() == Some(BreakReason::Explicit) { + if let Some(next_next) = next.next_visual() { + return Self::from_cluster(next_next, self.affinity.invert()); + } + } + let next_rtl = next.is_rtl(); + if let Some(next_next) = next.next_visual() { + // Check for directional boundary condition + if next_next.is_rtl() != next_rtl { + if let Some(prefer_rtl) = prefer_rtl { + if next_rtl != prefer_rtl { + return Self::from_cluster(next_next, self.affinity); + } else { + return Self::from_cluster(next, self.affinity); } - } else { - result.is_inside = false; - result.insert_point = range.start; - result.offset = line_metrics.offset; } - return result; } } - break; + let affinity = if self.is_rtl != next_rtl { + self.affinity.invert() + } else { + self.affinity + }; + Self::from_cluster(next, affinity) + } else { + *self } - result.is_inside = false; - result } - /// Creates a new cursor for the specified layout and text position. - pub fn from_position( - layout: &Layout, - mut position: usize, - is_leading: bool, - ) -> Self { - let mut result = Self { - is_inside: true, - ..Default::default() + pub fn previous_visual(&self, layout: &Layout, mode: VisualMode) -> Self { + let prefer_rtl = mode.prefer_rtl(layout); + let Some(cluster) = self.path.cluster(layout) else { + return *self; }; - if position >= layout.data.text_len { - result.is_inside = false; - position = layout.data.text_len.saturating_sub(1); - } - let last_line = layout.data.lines.len().saturating_sub(1); - for (line_index, line) in layout.lines().enumerate() { - let line_metrics = line.metrics(); - result.baseline = line_metrics.baseline; - result.path.line_index = line_index; - if !line.text_range().contains(&position) && line_index != last_line { - continue; + if !self.affinity.is_visually_leading(self.is_rtl) { + // Handle hard line breaks + if cluster.is_hard_line_break() { + // If we're at the back of a hard line break and moving + // left, skip directly to the trailing edge of the next cluster + if let Some(next) = cluster.previous_logical() { + return Self::from_cluster(next, self.affinity); + } } - let mut last_edge = line_metrics.offset; - result.offset = last_edge; - for (run_index, run) in line.runs().enumerate() { - result.path.run_index = run_index; - if !run.text_range().contains(&position) { - last_edge += run.advance(); - result.offset = last_edge; - continue; + // Check for directional boundary condition + if let Some(prev) = cluster.previous_visual() { + if prev.is_rtl() != self.is_rtl { + if let Some(prefer_rtl) = prefer_rtl { + if self.is_rtl != prefer_rtl { + return Self::from_cluster(prev, self.affinity.invert()); + } + } } - for (cluster_index, cluster) in run.visual_clusters().enumerate() { - let range = cluster.text_range(); - result.text_start = range.start; - result.text_end = range.end; - result.offset = last_edge; - result.is_rtl = run.is_rtl(); - result.path.cluster_index = run.visual_to_logical(cluster_index).unwrap(); - let advance = cluster.advance(); - if range.contains(&position) { - if !is_leading || !result.is_inside { - result.offset += advance; + } + // We're moving left so we want to track left-side affinity; + // let's swap + Self::from_index(layout, self.index as usize, self.affinity.invert()) + } else if let Some(prev) = cluster.previous_visual() { + // Handle soft line breaks + if matches!( + prev.is_line_break(), + Some(BreakReason::Regular) | Some(BreakReason::Emergency) + ) { + // Match the behavior of next_visual: move to the end of the soft line + // break + return Self::from_cluster(prev, self.affinity.invert()); + } + let prev_rtl = prev.is_rtl(); + // Check for directional boundary condition + if let Some(prev_prev) = prev.previous_visual() { + if prev_prev.is_rtl() != prev_rtl { + if let Some(prefer_rtl) = prefer_rtl { + if prev_rtl != prefer_rtl { + return Self::from_cluster(prev_prev, self.affinity); + } else { + return Self::from_cluster(prev, self.affinity); } - result.insert_point = if is_leading { range.start } else { range.end }; - result.advance = advance; - return result; } - last_edge += advance; } } - result.offset = last_edge; - break; + let affinity = if self.is_rtl != prev_rtl { + self.affinity.invert() + } else { + self.affinity + }; + Self::from_cluster(prev, affinity) + } else { + *self } - result.insert_point = result.text_end; - result.is_inside = false; - result } - /// Returns `true` if the cursor is on the leading edge of the target - /// cluster. - pub fn is_leading(&self) -> bool { - self.text_start == self.insert_point + pub fn next_word(&self, layout: &Layout) -> Self { + let Some(mut next) = self.path.cluster(layout) else { + return *self; + }; + if self.affinity == Affinity::Upstream { + next = next.next_logical().unwrap_or(next); + } + while let Some(cluster) = next.next_word() { + next = cluster.clone(); + if !cluster.is_space_or_nbsp() { + break; + } + } + Self::from_cluster(next, Affinity::default()) + } + + pub fn previous_word(&self, layout: &Layout) -> Self { + let Some(mut next) = self.path.cluster(layout) else { + return *self; + }; + if self.affinity == Affinity::Upstream { + next = next.next_logical().unwrap_or(next); + } + while let Some(cluster) = next.previous_word() { + next = cluster.clone(); + if !cluster.is_space_or_nbsp() { + break; + } + } + Self::from_cluster(next, Affinity::default()) } - /// Returns `true` if the cursor is on the trailing edge of the target - /// cluster. - pub fn is_trailing(&self) -> bool { - self.text_end == self.insert_point + /// Used for determining visual order of two cursors. + fn visual_order_key(&self) -> (usize, f32) { + (self.path.line_index(), self.visual_offset) } } -/// Index based path to a cluster. -#[derive(Copy, Clone, Default, Debug)] -pub struct CursorPath { - /// Index of the containing line. - pub line_index: usize, - /// Index of the run within the containing line. - pub run_index: usize, - /// Index of the cluster within the containing run. - pub cluster_index: usize, +/// A range within a layout. +#[derive(Copy, Clone, PartialEq, Default, Debug)] +pub struct Selection { + anchor: Cursor, + focus: Cursor, + /// Current horizontal position. Used for tracking line movement. + h_pos: Option, } -impl CursorPath { - /// Returns the line for this path and the specified layout. - pub fn line<'a, B: Brush>(&self, layout: &'a Layout) -> Option> { - layout.get(self.line_index) +impl From for Selection { + fn from(value: Cursor) -> Self { + Self { + anchor: value, + focus: value, + h_pos: None, + } } +} - /// Returns the run for this path and the specified layout. - pub fn run<'a, B: Brush>(&self, layout: &'a Layout) -> Option> { - self.line(layout)?.run(self.run_index) +impl Selection { + /// Creates a collapsed selection with the anchor and focus set to the + /// position associated with the given byte index and affinity. + pub fn from_index(layout: &Layout, index: usize, affinity: Affinity) -> Self { + Cursor::from_index(layout, index, affinity).into() } - /// Returns the cluster for this path and the specified layout. - pub fn cluster<'a, B: Brush>(&self, layout: &'a Layout) -> Option> { - self.run(layout)?.get(self.cluster_index) + /// Creates a collapsed selection with the anchor and focus set to the + /// position associated with the given point. + pub fn from_point(layout: &Layout, x: f32, y: f32) -> Self { + Cursor::from_point(layout, x, y).into() + } + + /// Creates a new selection bounding the word at the given point. + pub fn word_from_point(layout: &Layout, x: f32, y: f32) -> Self { + let mut anchor = Cursor::from_point(layout, x, y); + if !(anchor.affinity == Affinity::Downstream + && anchor + .cluster_path() + .cluster(layout) + .map(|cluster| cluster.is_word_boundary()) + .unwrap_or_default()) + { + anchor = anchor.previous_word(layout); + } + let mut focus = anchor.next_word(layout); + if anchor.is_rtl { + core::mem::swap(&mut anchor, &mut focus); + } + Self { + anchor, + focus, + h_pos: None, + } + } + + /// Returns the anchor point of the selection. + /// + /// This represents the location where the selection was initiated. + pub fn anchor(&self) -> &Cursor { + &self.anchor + } + + /// Returns the focus point of the selection. + /// + /// This represents the current location of the selection. + pub fn focus(&self) -> &Cursor { + &self.focus + } + + /// Returns true when the anchor and focus are at the same position. + pub fn is_collapsed(&self) -> bool { + self.anchor.text_start == self.focus.text_start + } + + /// Returns the range of text bounded by this selection. + /// + /// This is equivalent to the text that would be removed when pressing the + /// delete key. + pub fn text_range(&self) -> Range { + if self.is_collapsed() { + self.focus.text_range() + } else if self.anchor.text_start < self.focus.text_start { + self.anchor.text_start as usize..self.focus.text_start as usize + } else { + self.focus.text_start as usize..self.anchor.text_start as usize + } + } + + /// Returns the index where text should be inserted based on this + /// selection. + pub fn insertion_index(&self) -> usize { + self.focus.text_start as usize + } + + /// Returns a new collapsed selection at the position of the current + /// focus. + #[must_use] + pub fn collapse(&self) -> Self { + Self { + anchor: self.focus, + focus: self.focus, + h_pos: self.h_pos, + } + } + + /// Refreshes the internal cursor state to match the the given layout. + /// + /// This should be called whenever the layout is rebuilt or resized. + #[must_use] + pub fn refresh(&self, layout: &Layout) -> Self { + let anchor = self.anchor.refresh(layout); + let focus = self.focus.refresh(layout); + Self { + anchor, + focus, + h_pos: None, + } + } + + /// Returns a new selection with the focus extended to the given point. + #[must_use] + pub fn extend_to_point(&self, layout: &Layout, x: f32, y: f32) -> Self { + let focus = Cursor::from_point(layout, x, y); + Self { + anchor: self.anchor, + focus, + h_pos: None, + } + } + + /// Returns a new selection with the focus moved to the next cluster in + /// visual order. + /// + /// If `extend` is `true` then the current anchor will be retained, + /// otherwise the new selection will be collapsed. + #[must_use] + pub fn next_visual( + &self, + layout: &Layout, + mode: VisualMode, + extend: bool, + ) -> Self { + if !extend && !self.is_collapsed() { + if self.focus.visual_order_key() > self.anchor.visual_order_key() { + return self.focus.into(); + } else { + return self.anchor.into(); + } + } + self.maybe_extend(self.focus.next_visual(layout, mode), extend) + } + + /// Returns a new selection with the focus moved to the previous cluster in + /// visual order. + /// + /// If `extend` is `true` then the current anchor will be retained, + /// otherwise the new selection will be collapsed. + #[must_use] + pub fn previous_visual( + &self, + layout: &Layout, + mode: VisualMode, + extend: bool, + ) -> Self { + if !extend && !self.is_collapsed() { + if self.focus.visual_order_key() < self.anchor.visual_order_key() { + return self.focus.into(); + } else { + return self.anchor.into(); + } + } + self.maybe_extend(self.focus.previous_visual(layout, mode), extend) + } + + /// Returns a new selection with the focus moved to the next word. + /// + /// If `extend` is `true` then the current anchor will be retained, + /// otherwise the new selection will be collapsed. + #[must_use] + pub fn next_word(&self, layout: &Layout, extend: bool) -> Self { + self.maybe_extend(self.focus.next_word(layout), extend) + } + + /// Returns a new selection with the focus moved to the previous word. + /// + /// If `extend` is `true` then the current anchor will be retained, + /// otherwise the new selection will be collapsed. + #[must_use] + pub fn previous_word(&self, layout: &Layout, extend: bool) -> Self { + self.maybe_extend(self.focus.previous_word(layout), extend) + } + + fn maybe_extend(&self, focus: Cursor, extend: bool) -> Self { + if extend { + Self { + anchor: self.anchor, + focus, + h_pos: None, + } + } else { + focus.into() + } + } + + /// Returns a new selection with the focus moved to the start of the + /// current line. + /// + /// If `extend` is `true` then the current anchor will be retained, + /// otherwise the new selection will be collapsed. + #[must_use] + pub fn line_start(&self, layout: &Layout, extend: bool) -> Self { + if let Some(line) = self.focus.path.line(layout) { + self.maybe_extend( + Cursor::from_index(layout, line.text_range().start, Affinity::Downstream), + extend, + ) + } else { + *self + } + } + + /// Returns a new selection with the focus moved to the end of the + /// current line. + /// + /// If `extend` is `true` then the current anchor will be retained, + /// otherwise the new selection will be collapsed. + #[must_use] + pub fn line_end(&self, layout: &Layout, extend: bool) -> Self { + if let Some(line) = self.focus.path.line(layout) { + self.maybe_extend( + Cursor::from_index( + layout, + line.text_range().end.saturating_sub(1), + Affinity::Upstream, + ), + extend, + ) + } else { + *self + } + } + + /// Returns a new selection with the focus moved to the next line. The + /// current horizontal position will be maintained. + /// + /// If `extend` is `true` then the current anchor will be retained, + /// otherwise the new selection will be collapsed. + #[must_use] + pub fn next_line(&self, layout: &Layout, extend: bool) -> Self { + self.move_lines(layout, 1, extend) + } + + /// Returns a new selection with the focus moved to the previous line. The + /// current horizontal position will be maintained. + /// + /// If `extend` is `true` then the current anchor will be retained, + /// otherwise the new selection will be collapsed. + #[must_use] + pub fn previous_line(&self, layout: &Layout, extend: bool) -> Self { + self.move_lines(layout, -1, extend) + } + + /// Returns a new selection with the focus moved the specified number of + /// lines. + /// + /// The sign of the `delta` parameter determines the direction to move with + /// negative values moving toward previous lines and positive ones moving + /// toward next lines. + /// + /// If `extend` is `true` then the current anchor will be retained, + /// otherwise the new selection will be collapsed. + #[must_use] + pub fn move_lines(&self, layout: &Layout, delta: isize, extend: bool) -> Self { + if delta == 0 { + return *self; + } + let line_limit = layout.len().saturating_sub(1); + let line_index = self.focus.path.line_index(); + let new_line_index = line_index.saturating_add_signed(delta); + if delta < 0 && line_index.checked_add_signed(delta).is_none() { + return self + .move_lines(layout, -(line_index as isize), extend) + .line_start(layout, extend); + } else if delta > 0 && new_line_index > line_limit { + return self + .move_lines(layout, (line_limit - line_index) as isize, extend) + .line_end(layout, extend); + } + let Some(line) = layout.get(new_line_index) else { + return *self; + }; + let y = line.metrics().baseline - line.metrics().ascent * 0.5; + let h_pos = self.h_pos.unwrap_or(self.focus.visual_offset); + let new_focus = Cursor::from_point(layout, h_pos, y); + let h_pos = Some(h_pos); + if extend { + Self { + anchor: self.anchor, + focus: new_focus, + h_pos, + } + } else { + Self { + anchor: new_focus, + focus: new_focus, + h_pos, + } + } + } + + /// Returns a vector containing the rectangles which represent the visual + /// geometry of this selection for the given layout. + /// + /// This is a convenience method built on [`geometry_with`](Self::geometry_with). + pub fn geometry(&self, layout: &Layout) -> Vec { + let mut rects = Vec::new(); + self.geometry_with(layout, |rect| rects.push(rect)); + rects + } + + /// Invokes `f` with the sequence of rectangles which represent the visual + /// geometry of this selection for the given layout. + /// + /// This avoids allocation if the intent is to render the rectangles + /// immediately. + pub fn geometry_with(&self, layout: &Layout, mut f: impl FnMut(Rect)) { + // Ensure we add some visual indicator for selected empty + // lines. + // Make this configurable? + const MIN_RECT_WIDTH: f64 = 4.0; + if self.is_collapsed() { + return; + } + let mut start = self.anchor; + let mut end = self.focus; + if start.text_start > end.text_start { + core::mem::swap(&mut start, &mut end); + } + let text_range = start.text_start..end.text_start; + let line_start_ix = start.path.line_index(); + let line_end_ix = end.path.line_index(); + for line_ix in line_start_ix..=line_end_ix { + let Some(line) = layout.get(line_ix) else { + continue; + }; + let metrics = line.metrics(); + let line_min = metrics.min_coord as f64; + let line_max = metrics.max_coord as f64; + if line_ix == line_start_ix || line_ix == line_end_ix { + // We only need to run the expensive logic on the first and + // last lines + let mut start_x = metrics.offset as f64; + let mut cur_x = start_x; + for run in line.runs() { + for cluster in run.visual_clusters() { + let advance = cluster.advance() as f64; + if text_range.contains(&(cluster.text_range().start as u32)) { + cur_x += advance; + } else { + if cur_x != start_x { + let width = (cur_x - start_x).max(MIN_RECT_WIDTH); + f(Rect::new(start_x as _, line_min, start_x + width, line_max)); + } + cur_x += advance; + start_x = cur_x; + } + } + } + if cur_x != start_x { + let width = (cur_x - start_x).max(MIN_RECT_WIDTH); + f(Rect::new(start_x, line_min, start_x + width, line_max)); + } + } else { + let x = metrics.offset as f64; + let width = (metrics.advance as f64).max(MIN_RECT_WIDTH); + f(Rect::new(x, line_min, x + width, line_max)); + } + } } } diff --git a/parley/src/layout/line/greedy.rs b/parley/src/layout/line/greedy.rs index ffce5eb2..3c831a7a 100644 --- a/parley/src/layout/line/greedy.rs +++ b/parley/src/layout/line/greedy.rs @@ -93,7 +93,7 @@ impl BreakerState { /// Line breaking support for a paragraph. pub struct BreakLines<'a, B: Brush> { - layout: &'a mut LayoutData, + layout: &'a mut Layout, lines: LineLayout, state: BreakerState, prev_state: Option, @@ -101,12 +101,12 @@ pub struct BreakLines<'a, B: Brush> { } impl<'a, B: Brush> BreakLines<'a, B> { - pub(crate) fn new(layout: &'a mut LayoutData) -> Self { - unjustify(layout); - layout.width = 0.; - layout.height = 0.; + pub(crate) fn new(layout: &'a mut Layout) -> Self { + unjustify(&mut layout.data); + layout.data.width = 0.; + layout.data.height = 0.; let mut lines = LineLayout::default(); - lines.swap(layout); + lines.swap(&mut layout.data); lines.lines.clear(); lines.line_items.clear(); Self { @@ -163,9 +163,9 @@ impl<'a, B: Brush> BreakLines<'a, B> { // dbg!(&self.state.line.items); // Iterate over remaining runs in the Layout - let item_count = self.layout.items.len(); + let item_count = self.layout.data.items.len(); while self.state.item_idx < item_count { - let item = &self.layout.items[self.state.item_idx]; + let item = &self.layout.data.items[self.state.item_idx]; // println!( // "\nitem = {} {:?}. x: {}", @@ -175,7 +175,7 @@ impl<'a, B: Brush> BreakLines<'a, B> { match item.kind { LayoutItemKind::InlineBox => { - let inline_box = &self.layout.inline_boxes[item.index]; + let inline_box = &self.layout.data.inline_boxes[item.index]; // Compute the x position of the content being currently processed let next_x = self.state.line.x + inline_box.width; @@ -211,9 +211,9 @@ impl<'a, B: Brush> BreakLines<'a, B> { } LayoutItemKind::TextRun => { let run_idx = item.index; - let run_data = &self.layout.runs[run_idx]; + let run_data = &self.layout.data.runs[run_idx]; - let run = Run::new(self.layout, run_data, None); + let run = Run::new(self.layout, 0, 0, run_data, None); let cluster_start = run_data.cluster_range.start; let cluster_end = run_data.cluster_range.end; @@ -427,7 +427,7 @@ impl<'a, B: Brush> BreakLines<'a, B> { run.is_whitespace = true; if run.bidi_level & 1 != 0 { // RTL runs check for "trailing" whitespace at the front. - for cluster in self.layout.clusters[run.cluster_range.clone()].iter() { + for cluster in self.layout.data.clusters[run.cluster_range.clone()].iter() { if cluster.info.is_whitespace() { run.has_trailing_whitespace = true; } else { @@ -436,7 +436,10 @@ impl<'a, B: Brush> BreakLines<'a, B> { } } } else { - for cluster in self.layout.clusters[run.cluster_range.clone()].iter().rev() { + for cluster in self.layout.data.clusters[run.cluster_range.clone()] + .iter() + .rev() + { if cluster.info.is_whitespace() { run.has_trailing_whitespace = true; } else { @@ -465,7 +468,7 @@ impl<'a, B: Brush> BreakLines<'a, B> { { match line_item.kind { LayoutItemKind::InlineBox => { - let item = &self.layout.inline_boxes[line_item.index]; + let item = &self.layout.data.inline_boxes[line_item.index]; // Advance is already computed in "commit line" for items @@ -490,22 +493,24 @@ impl<'a, B: Brush> BreakLines<'a, B> { needs_reorder = true; } + let run = &self.layout.data.runs[line_item.index]; + let line_height = line_item.compute_line_height(&self.layout.data); + line.metrics.line_height = line.metrics.line_height.max(line_height); + + // Compute the run's advance by summing the advances of its constituent clusters + line_item.advance = self.layout.data.clusters + [line_item.cluster_range.clone()] + .iter() + .map(|c| c.advance) + .sum(); + // Ignore trailing whitespace for metrics computation // (we are iterating backwards so trailing whitespace comes first) if !have_metrics && line_item.is_whitespace { continue; } - // Compute the run's advance by summing the advances of its constituent clusters - line_item.advance = self.layout.clusters[line_item.cluster_range.clone()] - .iter() - .map(|c| c.advance) - .sum(); - // Compute the run's vertical metrics - let run = &self.layout.runs[line_item.index]; - let line_height = line_item.compute_line_height(self.layout); - line.metrics.line_height = line.metrics.line_height.max(line_height); line.metrics.ascent = line.metrics.ascent.max(run.metrics.ascent); line.metrics.descent = line.metrics.descent.max(run.metrics.descent); line.metrics.leading = line.metrics.leading.max(run.metrics.leading); @@ -534,7 +539,7 @@ impl<'a, B: Brush> BreakLines<'a, B> { .rfind(|item| item.is_text_run()); if let Some(last_run) = last_run { if !last_run.cluster_range.is_empty() { - let cluster = &self.layout.clusters[last_run.cluster_range.end - 1]; + let cluster = &self.layout.data.clusters[last_run.cluster_range.end - 1]; if cluster.info.whitespace().is_space_or_nbsp() { line.metrics.trailing_whitespace = cluster.advance; } @@ -547,7 +552,7 @@ impl<'a, B: Brush> BreakLines<'a, B> { if !line.item_range.is_empty() { let line_item = &self.lines.line_items[line.item_range.start]; if line_item.is_text_run() { - let run = &self.layout.runs[line_item.index]; + let run = &self.layout.data.runs[line_item.index]; line.metrics.ascent = run.metrics.ascent; line.metrics.descent = run.metrics.descent; line.metrics.leading = run.metrics.leading; @@ -565,8 +570,10 @@ impl<'a, B: Brush> BreakLines<'a, B> { // Compute let above = (line.metrics.ascent + line.metrics.leading * 0.5).round(); let below = (line.metrics.descent + line.metrics.leading * 0.5).round(); + line.metrics.min_coord = y; line.metrics.baseline = y + above; y = line.metrics.baseline + below; + line.metrics.max_coord = y; } } } @@ -585,9 +592,9 @@ impl<'a, B: Brush> Drop for BreakLines<'a, B> { } // Save the computed widths/height to the layout - self.layout.width = width; - self.layout.full_width = full_width; - self.layout.height = height; + self.layout.data.width = width; + self.layout.data.full_width = full_width; + self.layout.data.height = height; // for (i, line) in self.lines.lines.iter().enumerate() { // println!("LINE {i}"); @@ -598,7 +605,7 @@ impl<'a, B: Brush> Drop for BreakLines<'a, B> { // } // Save the computed lines to the layout - self.lines.swap(self.layout); + self.lines.swap(&mut self.layout.data); } } @@ -650,23 +657,23 @@ impl<'a, B: Brush> Drop for BreakLines<'a, B> { // } fn try_commit_line( - layout: &LayoutData, + layout: &Layout, lines: &mut LineLayout, state: &mut LineState, max_advance: f32, alignment: Alignment, break_reason: BreakReason, ) -> bool { - let is_empty = layout.text_len == 0; + let is_empty = layout.data.text_len == 0; // Ensure that the cluster and item endpoints are within range - state.clusters.end = state.clusters.end.min(layout.clusters.len()); - state.items.end = state.items.end.min(layout.items.len()); + state.clusters.end = state.clusters.end.min(layout.data.clusters.len()); + state.items.end = state.items.end.min(layout.data.items.len()); let start_item_idx = lines.line_items.len(); // let start_run_idx = lines.line_items.last().map(|item| item.index).unwrap_or(0); - let items_to_commit = &layout.items[state.items.clone()]; + let items_to_commit = &layout.data.items[state.items.clone()]; // Compute first and last run index let is_text_run = |item: &LayoutItem| item.kind == LayoutItemKind::TextRun; @@ -689,7 +696,7 @@ fn try_commit_line( match item.kind { LayoutItemKind::InlineBox => { - let inline_box = &layout.inline_boxes[item.index]; + let inline_box = &layout.data.inline_boxes[item.index]; lines.line_items.push(LineItemData { kind: LayoutItemKind::InlineBox, @@ -705,7 +712,7 @@ fn try_commit_line( }); } LayoutItemKind::TextRun => { - let run_data = &layout.runs[item.index]; + let run_data = &layout.data.runs[item.index]; // Compute cluster range // The first and last ranges have overrides to account for line-breaks within runs @@ -727,7 +734,7 @@ fn try_commit_line( } // Push run to line - let run = Run::new(layout, run_data, None); + let run = Run::new(layout, 0, 0, run_data, None); let text_range = if run_data.cluster_range.is_empty() { 0..0 } else { diff --git a/parley/src/layout/line/mod.rs b/parley/src/layout/line/mod.rs index 9f1e3349..2fcfcc75 100644 --- a/parley/src/layout/line/mod.rs +++ b/parley/src/layout/line/mod.rs @@ -32,22 +32,25 @@ impl<'a, B: Brush> Line<'a, B> { if index >= self.data.item_range.end { return None; } - let item = self.layout.line_items.get(index)?; + let item = self.layout.data.line_items.get(index)?; Some(item) } /// Returns the run at the specified index. pub fn run(&self, index: usize) -> Option> { + let original_index = index; let index = self.data.item_range.start + index; if index >= self.data.item_range.end { return None; } - let item = self.layout.line_items.get(index)?; + let item = self.layout.data.line_items.get(index)?; if item.kind == LayoutItemKind::TextRun { Some(Run { layout: self.layout, - data: self.layout.runs.get(item.index)?, + line_index: self.index, + index: original_index as u32, + data: self.layout.data.runs.get(item.index)?, line_data: Some(item), }) } else { @@ -59,13 +62,16 @@ impl<'a, B: Brush> Line<'a, B> { // TODO: provide iterator over inline_boxes and items pub fn runs(&self) -> impl Iterator> + 'a + Clone { let copy = self.clone(); - let line_items = ©.layout.line_items[self.data.item_range.clone()]; + let line_items = ©.layout.data.line_items[self.data.item_range.clone()]; line_items .iter() - .filter(|item| item.kind == LayoutItemKind::TextRun) - .map(move |line_data| Run { + .enumerate() + .filter(|(_, item)| item.kind == LayoutItemKind::TextRun) + .map(move |(index, line_data)| Run { layout: copy.layout, - data: ©.layout.runs[line_data.index], + line_index: copy.index, + index: index as u32, + data: ©.layout.data.runs[line_data.index], line_data: Some(line_data), }) } @@ -101,6 +107,16 @@ pub struct LineMetrics { pub advance: f32, /// Advance of trailing whitespace. pub trailing_whitespace: f32, + /// Minimum coordinate in the direction orthogonal to line + /// direction. + /// + /// For horizontal text, this would be the top of the line. + pub min_coord: f32, + /// Maximum coordinate in the direction orthogonal to line + /// direction. + /// + /// For horizontal text, this would be the bottom of the line. + pub max_coord: f32, } impl LineMetrics { @@ -206,7 +222,7 @@ impl<'a, B: Brush> Iterator for GlyphRunIter<'a, B> { let item = self.line.item(self.item_index)?; match item.kind { LayoutItemKind::InlineBox => { - let inline_box = &self.line.layout.inline_boxes[item.index]; + let inline_box = &self.line.layout.data.inline_boxes[item.index]; let x = self.offset + self.line.data.metrics.offset; @@ -237,7 +253,7 @@ impl<'a, B: Brush> Iterator for GlyphRunIter<'a, B> { glyph_count += 1; advance += glyph.advance; } - let style = run.layout.styles.get(style_index)?; + let style = run.layout.data.styles.get(style_index)?; let glyph_start = self.glyph_start; self.glyph_start += glyph_count; let offset = self.offset; diff --git a/parley/src/layout/mod.rs b/parley/src/layout/mod.rs index d868f995..7633df82 100644 --- a/parley/src/layout/mod.rs +++ b/parley/src/layout/mod.rs @@ -16,11 +16,12 @@ use self::alignment::align; use super::style::Brush; use crate::{Font, InlineBox}; -use core::ops::Range; +use core::{cmp::Ordering, ops::Range}; use data::*; use swash::text::cluster::{Boundary, ClusterInfo}; use swash::{GlyphId, NormalizedCoord, Synthesis}; +pub use cluster::{Affinity, ClusterPath}; pub use cursor::Cursor; pub use line::greedy::BreakLines; pub use line::{GlyphRun, LineMetrics, PositionedInlineBox, PositionedLayoutItem}; @@ -88,11 +89,17 @@ impl Layout { /// Returns the line at the specified index. pub fn get(&self, index: usize) -> Option> { Some(Line { - layout: &self.data, + index: index as u32, + layout: self, data: self.data.lines.get(index)?, }) } + /// Returns true if the dominant direction of the layout is right-to-left. + pub fn is_rtl(&self) -> bool { + self.data.base_level & 1 != 0 + } + pub fn inline_boxes(&self) -> &[InlineBox] { &self.data.inline_boxes } @@ -103,15 +110,20 @@ impl Layout { /// Returns an iterator over the lines in the layout. pub fn lines(&self) -> impl Iterator> + '_ + Clone { - self.data.lines.iter().map(move |data| Line { - layout: &self.data, - data, - }) + self.data + .lines + .iter() + .enumerate() + .map(move |(index, data)| Line { + index: index as u32, + layout: self, + data, + }) } /// Returns line breaker to compute lines for the layout. pub fn break_lines(&mut self) -> BreakLines { - BreakLines::new(&mut self.data) + BreakLines::new(self) } /// Breaks all lines with the specified maximum advance. @@ -126,13 +138,48 @@ impl Layout { align(&mut self.data, container_width, alignment); } - /// Returns an iterator over the runs in the layout. - pub fn runs(&self) -> impl Iterator> + '_ + Clone { - self.data.runs.iter().map(move |data| Run { - layout: &self.data, - data, - line_data: None, - }) + /// Returns the index and `Line` object for the line containing the + /// given byte `index` in the source text. + pub(crate) fn line_for_byte_index(&self, index: usize) -> Option<(usize, Line)> { + let line_index = self + .data + .lines + .binary_search_by(|line| { + if index < line.text_range.start { + Ordering::Greater + } else if index >= line.text_range.end { + Ordering::Less + } else { + Ordering::Equal + } + }) + .ok()?; + Some((line_index, self.get(line_index)?)) + } + + /// Returns the index and `Line` object for the line containing the + /// given `offset`. + /// + /// The offset is specified in the direction orthogonal to line direction. + /// For horizontal text, this is a vertical or y offset. + pub(crate) fn line_for_offset(&self, offset: f32) -> Option<(usize, Line)> { + if offset < 0.0 { + return Some((0, self.get(0)?)); + } + let maybe_line_index = self.data.lines.binary_search_by(|line| { + if offset < line.metrics.min_coord { + Ordering::Greater + } else if offset > line.metrics.max_coord { + Ordering::Less + } else { + Ordering::Equal + } + }); + let line_index = match maybe_line_index { + Ok(index) => index, + Err(index) => index.saturating_sub(1), + }; + Some((line_index, self.get(line_index)?)) } } @@ -147,7 +194,9 @@ impl Default for Layout { /// Sequence of clusters with a single font and style. #[derive(Copy, Clone)] pub struct Run<'a, B: Brush> { - layout: &'a LayoutData, + layout: &'a Layout, + line_index: u32, + index: u32, data: &'a RunData, line_data: Option<&'a LineItemData>, } @@ -155,6 +204,7 @@ pub struct Run<'a, B: Brush> { /// Atomic unit of text. #[derive(Copy, Clone)] pub struct Cluster<'a, B: Brush> { + path: ClusterPath, run: Run<'a, B>, data: &'a ClusterData, } @@ -179,7 +229,8 @@ impl Glyph { /// Line in a text layout. #[derive(Copy, Clone)] pub struct Line<'a, B: Brush> { - layout: &'a LayoutData, + layout: &'a Layout, + index: u32, data: &'a LineData, } diff --git a/parley/src/layout/run.rs b/parley/src/layout/run.rs index 43c8305c..147232f8 100644 --- a/parley/src/layout/run.rs +++ b/parley/src/layout/run.rs @@ -5,12 +5,16 @@ use super::*; impl<'a, B: Brush> Run<'a, B> { pub(crate) fn new( - layout: &'a LayoutData, + layout: &'a Layout, + line_index: u32, + index: u32, data: &'a RunData, line_data: Option<&'a LineItemData>, ) -> Self { Self { layout, + line_index, + index, data, line_data, } @@ -18,7 +22,7 @@ impl<'a, B: Brush> Run<'a, B> { /// Returns the font for the run. pub fn font(&self) -> &Font { - self.layout.fonts.get(self.data.font_index).unwrap() + self.layout.data.fonts.get(self.data.font_index).unwrap() } /// Returns the font size for the run. @@ -35,6 +39,7 @@ impl<'a, B: Brush> Run<'a, B> { /// with the run. pub fn normalized_coords(&self) -> &[NormalizedCoord] { self.layout + .data .coords .get(self.data.coords_range.clone()) .unwrap_or(&[]) @@ -89,10 +94,12 @@ impl<'a, B: Brush> Run<'a, B> { .line_data .map(|d| &d.cluster_range) .unwrap_or(&self.data.cluster_range); + let original_index = index; let index = range.start + index; Some(Cluster { + path: ClusterPath::new(self.line_index, self.index, original_index as u32), run: self.clone(), - data: self.layout.clusters.get(index)?, + data: self.layout.data.clusters.get(index)?, }) } @@ -106,6 +113,22 @@ impl<'a, B: Brush> Run<'a, B> { } } + /// Returns the visual cluster index for the specified logical cluster index. + pub fn logical_to_visual(&self, logical_index: usize) -> Option { + let num_clusters = self.len(); + if logical_index >= num_clusters { + return None; + } + + let visual_index = if self.is_rtl() { + num_clusters - 1 - logical_index + } else { + logical_index + }; + + Some(visual_index) + } + /// Returns the logical cluster index for the specified visual cluster index. pub fn visual_to_logical(&self, visual_index: usize) -> Option { let num_clusters = self.len(); @@ -159,8 +182,13 @@ impl<'a, B: Brush> Iterator for Clusters<'a, B> { self.range.next()? }; Some(Cluster { + path: ClusterPath::new( + self.run.line_index, + self.run.index, + (index - self.run.cluster_range().start) as u32, + ), run: self.run.clone(), - data: self.run.layout.clusters.get(index)?, + data: self.run.layout.data.clusters.get(index)?, }) } }