From 517e5b64e5d885880947f90485d3bbf8b17b9502 Mon Sep 17 00:00:00 2001 From: Joshua Thijssen Date: Sat, 4 Jan 2025 20:13:33 +0100 Subject: [PATCH 1/2] initial attempt to setup a font system --- .cargo/config.toml | 2 +- Cargo.lock | 1310 ++++++++++++----- Cargo.toml | 3 +- cloc-it.sh | 36 + crates/gosub_cairo/Cargo.toml | 9 +- crates/gosub_cairo/src/debug/text.rs | 36 +- crates/gosub_cairo/src/elements/rect.rs | 2 + crates/gosub_cairo/src/elements/text.rs | 191 +-- crates/gosub_cairo/src/lib.rs | 3 +- crates/gosub_cairo/src/scene.rs | 47 +- crates/gosub_fontmanager/Cargo.toml | 51 + .../src/bin/display-fonts.rs | 55 + .../gosub_fontmanager/src/bin/generate-svg.rs | 131 ++ crates/gosub_fontmanager/src/bin/gtk-test.rs | 187 +++ crates/gosub_fontmanager/src/bin/gtk2-test.rs | 317 ++++ crates/gosub_fontmanager/src/bin/parley.rs | 265 ++++ .../gosub_fontmanager/src/bin/vello-test.rs | 178 +++ crates/gosub_fontmanager/src/flatland.rs | 69 + crates/gosub_fontmanager/src/font_manager.rs | 3 + .../src/font_manager/cache.rs | 49 + .../src/font_manager/font_info.rs | 139 ++ .../src/font_manager/manager.rs | 135 ++ crates/gosub_fontmanager/src/lib.rs | 5 + crates/gosub_interface/src/config.rs | 2 + crates/gosub_interface/src/config/layouter.rs | 5 +- .../gosub_interface/src/config/render_tree.rs | 3 +- crates/gosub_interface/src/css3.rs | 1 + crates/gosub_interface/src/document.rs | 14 - crates/gosub_interface/src/draw.rs | 12 +- crates/gosub_interface/src/font.rs | 77 + crates/gosub_interface/src/layout.rs | 74 +- crates/gosub_interface/src/lib.rs | 1 + crates/gosub_interface/src/render_backend.rs | 16 +- crates/gosub_interface/src/render_tree.rs | 12 +- crates/gosub_renderer/Cargo.toml | 2 + crates/gosub_renderer/src/draw.rs | 18 +- crates/gosub_rendering/src/lib.rs | 1 - crates/gosub_rendering/src/render_tree.rs | 172 +-- crates/gosub_rendering/src/text.rs | 1 - crates/gosub_taffy/Cargo.toml | 1 + crates/gosub_taffy/src/compute/inline.rs | 291 ++-- crates/gosub_taffy/src/conversions.rs | 1 - crates/gosub_taffy/src/lib.rs | 36 +- crates/gosub_taffy/src/style.rs | 3 +- crates/gosub_taffy/src/style/parse.rs | 4 + crates/gosub_taffy/src/text.rs | 37 +- crates/gosub_vello/Cargo.toml | 1 + crates/gosub_vello/src/lib.rs | 11 +- crates/gosub_vello/src/text.rs | 37 +- .../gosub_web_platform/src/event_listeners.rs | 2 - examples/gtk-renderer/main.rs | 17 +- examples/vello-renderer/main.rs | 5 + 52 files changed, 3257 insertions(+), 823 deletions(-) create mode 100644 cloc-it.sh create mode 100644 crates/gosub_fontmanager/Cargo.toml create mode 100644 crates/gosub_fontmanager/src/bin/display-fonts.rs create mode 100644 crates/gosub_fontmanager/src/bin/generate-svg.rs create mode 100644 crates/gosub_fontmanager/src/bin/gtk-test.rs create mode 100644 crates/gosub_fontmanager/src/bin/gtk2-test.rs create mode 100644 crates/gosub_fontmanager/src/bin/parley.rs create mode 100644 crates/gosub_fontmanager/src/bin/vello-test.rs create mode 100644 crates/gosub_fontmanager/src/flatland.rs create mode 100644 crates/gosub_fontmanager/src/font_manager.rs create mode 100644 crates/gosub_fontmanager/src/font_manager/cache.rs create mode 100644 crates/gosub_fontmanager/src/font_manager/font_info.rs create mode 100644 crates/gosub_fontmanager/src/font_manager/manager.rs create mode 100644 crates/gosub_fontmanager/src/lib.rs create mode 100644 crates/gosub_interface/src/font.rs delete mode 100644 crates/gosub_rendering/src/text.rs delete mode 100644 crates/gosub_taffy/src/conversions.rs diff --git a/.cargo/config.toml b/.cargo/config.toml index e386546ba..2e26d66ab 100644 --- a/.cargo/config.toml +++ b/.cargo/config.toml @@ -1,2 +1,2 @@ [registries] -gosub = { index = "sparse+https://regsitry.gosub.io/" } +gosub = { index = "sparse+https://registry.gosub.io/" } diff --git a/Cargo.lock b/Cargo.lock index 0c940ea59..1b1f081ac 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -115,7 +115,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ef6978589202a00cd7e118380c448a08b6ed394c3a8df3a430d0898e3a42d046" dependencies = [ "android-properties", - "bitflags 2.6.0", + "bitflags 2.8.0", "cc", "cesu8", "jni", @@ -191,11 +191,12 @@ dependencies = [ [[package]] name = "anstyle-wincon" -version = "3.0.6" +version = "3.0.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2109dbce0e72be3ec00bed26e6a7479ca384ad226efdd66db8fa2e3a38c83125" +checksum = "ca3534e77181a9cc07539ad51f2141fe32f6c3ffd4df76db8ad92346b003ae4e" dependencies = [ "anstyle", + "once_cell", "windows-sys 0.59.0", ] @@ -219,7 +220,7 @@ checksum = "0ae92a5119aa49cdbcf6b9f893fe4e1d98b04ccbf82ee0584ad948a44a734dea" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.98", ] [[package]] @@ -251,13 +252,13 @@ dependencies = [ [[package]] name = "async-trait" -version = "0.1.83" +version = "0.1.86" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "721cae7de5c34fbb2acd27e21e6d2cf7b886dce0c27388d46c4e6c47ea4318dd" +checksum = "644dd749086bf3771a2fbc5f256fdb982d53f011c7d5d560304eafeecebce79d" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.98", ] [[package]] @@ -304,7 +305,7 @@ dependencies = [ "addr2line", "cfg-if", "libc", - "miniz_oxide 0.8.2", + "miniz_oxide 0.8.3", "object", "rustc-demangle", "windows-targets 0.52.6", @@ -322,10 +323,10 @@ version = "0.70.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f49d8fed880d473ea71efb9bf597651e77201bdd4893efe54c9e5d65ae04ce6f" dependencies = [ - "bitflags 2.6.0", + "bitflags 2.8.0", "cexpr", "clang-sys", - "itertools 0.12.1", + "itertools 0.13.0", "log", "prettyplease", "proc-macro2", @@ -333,7 +334,7 @@ dependencies = [ "regex", "rustc-hash", "shlex", - "syn", + "syn 2.0.98", ] [[package]] @@ -380,9 +381,9 @@ checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" [[package]] name = "bitflags" -version = "2.6.0" +version = "2.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b048fb63fd8b5923fc5aa7b340d8e156aec7ec02f0c78fa8a6ddc2613f6f71de" +checksum = "8f68f53c83ab957f72c32642f3868eec03eb974d1fb82e453128456482613d36" [[package]] name = "bitstream-io" @@ -422,9 +423,9 @@ checksum = "c360505aed52b7ec96a3636c3f039d99103c37d1d9b4f7a8c743d3ea9ffcd03b" [[package]] name = "bumpalo" -version = "3.16.0" +version = "3.17.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "79296716171880943b8470b5f8d03aa55eb2e645a4874bdbb28adb49162e012c" +checksum = "1628fb46dfa0b37568d12e5edd512553eccf6a22a78e8bde00bb4aed84d5bdbf" [[package]] name = "bytecount" @@ -449,7 +450,7 @@ checksum = "3fa76293b4f7bb636ab88fd78228235b5248b4d05cc589aed610f954af5d7c7a" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.98", ] [[package]] @@ -466,31 +467,33 @@ checksum = "8f1fe948ff07f4bd06c30984e69f5b4899c516a3ef74f34df92a2df2ab535495" [[package]] name = "bytes" -version = "1.9.0" +version = "1.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "325918d6fe32f23b19878fe4b34794ae41fc19ddbe53b10571a4874d44ffd39b" +checksum = "f61dac84819c6588b558454b194026eb1f09c293b9036ae9b159e74e73ab6cf9" [[package]] name = "cairo-rs" -version = "0.20.7" +version = "0.19.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ae50b5510d86cf96ac2370e66d8dc960882f3df179d6a5a1e52bd94a1416c0f7" +checksum = "b2ac2a4d0e69036cf0062976f6efcba1aaee3e448594e6514bb2ddf87acce562" dependencies = [ - "bitflags 2.6.0", + "bitflags 2.8.0", "cairo-sys-rs", - "glib", + "freetype-rs 0.35.0", + "glib 0.19.9", "libc", + "thiserror 1.0.69", ] [[package]] name = "cairo-sys-rs" -version = "0.20.7" +version = "0.19.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f18b6bb8e43c7eb0f2aac7976afe0c61b6f5fc2ab7bc4c139537ea56c92290df" +checksum = "fd3bb3119664efbd78b5e6c93957447944f16bdbced84c17a9f41c7829b81e64" dependencies = [ - "glib-sys", + "glib-sys 0.19.8", "libc", - "system-deps 7.0.3", + "system-deps 6.2.2", ] [[package]] @@ -499,7 +502,7 @@ version = "0.13.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b99da2f8558ca23c71f4fd15dc57c906239752dd27ff3c00a1d56b685b7cbfec" dependencies = [ - "bitflags 2.6.0", + "bitflags 2.8.0", "log", "polling", "rustix", @@ -527,9 +530,9 @@ checksum = "37b2a672a2cb129a2e41c10b1224bb368f9f37a2b16b612598138befd7b37eb5" [[package]] name = "cc" -version = "1.2.5" +version = "1.2.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c31a0499c1dc64f458ad13872de75c0eb7e3fdb0e67964610c914b034fc5956e" +checksum = "c7777341816418c02e033934a09f20dc0ccaf65a5201ef8a450ae0105a573fda" dependencies = [ "jobserver", "libc", @@ -656,9 +659,9 @@ dependencies = [ [[package]] name = "clap" -version = "4.5.23" +version = "4.5.28" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3135e7ec2ef7b10c6ed8950f0f792ed96ee093fa088608f1c76e569722700c84" +checksum = "3e77c3243bd94243c03672cb5154667347c457ca271254724f9f393aee1c05ff" dependencies = [ "clap_builder", "clap_derive", @@ -666,9 +669,9 @@ dependencies = [ [[package]] name = "clap_builder" -version = "4.5.23" +version = "4.5.27" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "30582fc632330df2bd26877bde0c1f4470d57c582bbc070376afcd04d8cb4838" +checksum = "1b26884eb4b57140e4d2d93652abfa49498b938b3c9179f9fc487b0acc3edad7" dependencies = [ "anstream", "anstyle", @@ -678,14 +681,14 @@ dependencies = [ [[package]] name = "clap_derive" -version = "4.5.18" +version = "4.5.28" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4ac6a0c7b1a9e9a5186361f67dfa1b88213572f427fb9ab038efb2bd8c582dab" +checksum = "bf4ced95c6f4a675af3da73304b9ac4ed991640c36374e4b46795c49e17cf1ed" dependencies = [ "heck", "proc-macro2", "quote", - "syn", + "syn 2.0.98", ] [[package]] @@ -704,6 +707,17 @@ dependencies = [ "unicode-width", ] +[[package]] +name = "colog" +version = "1.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2c426b7af8d5e0ad79de6713996632ce31f0d68ba84068fb0d654b396e519df0" +dependencies = [ + "colored", + "env_logger", + "log", +] + [[package]] name = "color" version = "0.2.3" @@ -738,6 +752,37 @@ version = "0.2.11" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9226dbc05df4fb986f48d730b001532580883c4c06c5d1c213f4b34c1c157178" +[[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" @@ -848,9 +893,9 @@ dependencies = [ [[package]] name = "core_maths" -version = "0.1.0" +version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e3b02505ccb8c50b0aa21ace0fc08c3e53adebd4e58caa18a36152803c7709a3" +checksum = "77745e017f5edba1a9c1d854f6f3a52dac8a12dd5af5d2f54aecf61e43d80d30" dependencies = [ "libm", ] @@ -863,9 +908,9 @@ checksum = "417bef24afe1460300965a25ff4a24b8b45ad011948302ec221e8a0a81eb2c79" [[package]] name = "cpufeatures" -version = "0.2.16" +version = "0.2.17" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "16b80225097f2e5ae4e7179dd2266824648f3e2f49d9134d584b76389d31c4c3" +checksum = "59ed5838eebb26a2bb2e58f6d5b5316989ae9d08bab10e0e6d103e656d1b0280" dependencies = [ "libc", ] @@ -942,9 +987,9 @@ checksum = "d0a5c400df2834b80a4c3327b3aad3a4c4cd4de0629063962b03235697506a28" [[package]] name = "crunchy" -version = "0.2.2" +version = "0.2.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7a81dae078cea95a014a339291cec439d2f232ebe854a9d672b796c6afafa9b7" +checksum = "43da5946c66ffcc7745f48db692ffbb10a83bfe0afd96235c5c2a4fb23994929" [[package]] name = "crypto-common" @@ -957,6 +1002,27 @@ dependencies = [ "typenum", ] +[[package]] +name = "csv" +version = "1.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "acdc4883a9c96732e4733212c01447ebd805833b7275a73ca3ee080fd77afdaf" +dependencies = [ + "csv-core", + "itoa", + "ryu", + "serde", +] + +[[package]] +name = "csv-core" +version = "0.1.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5efa2b3d7902f4b634a20cae3c9c4e6209dc4779feb6863329607560143efa70" +dependencies = [ + "memchr", +] + [[package]] name = "ctr" version = "0.9.2" @@ -972,11 +1038,22 @@ version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "96a6ac251f4a2aca6b3f91340350eab87ae57c3f127ffeb585e92bd336717991" +[[package]] +name = "d3d12" +version = "22.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bdbd1f579714e3c809ebd822c81ef148b1ceaeb3d535352afc73fd0c4c6a0017" +dependencies = [ + "bitflags 2.8.0", + "libloading", + "winapi", +] + [[package]] name = "data-encoding" -version = "2.6.0" +version = "2.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e8566979429cf69b49a5c740c60791108e86440e8be149bbea4fe54d2c32d6e2" +checksum = "0e60eed09d8c01d3cee5b7d30acb059b76614c918fa0f992e0dd6eeb10daad6f" [[package]] name = "data-url" @@ -1010,7 +1087,7 @@ checksum = "cb7330aeadfbe296029522e6c40f315320aba36fc43a5b3632f3795348f3bd22" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.98", "unicode-xid", ] @@ -1025,6 +1102,48 @@ dependencies = [ "subtle", ] +[[package]] +name = "dirs" +version = "5.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "44c45a9d03d6676652bcb5e724c7e988de1acad23a711b5217ab9cbecbec2225" +dependencies = [ + "dirs-sys", +] + +[[package]] +name = "dirs-next" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b98cf8ebf19c3d1b223e151f99a4f9f0690dca41414773390fc824184ac833e1" +dependencies = [ + "cfg-if", + "dirs-sys-next", +] + +[[package]] +name = "dirs-sys" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "520f05a5cbd335fae5a99ff7a6ab8627577660ee5cfd6a94a6a929b52ff0321c" +dependencies = [ + "libc", + "option-ext", + "redox_users", + "windows-sys 0.48.0", +] + +[[package]] +name = "dirs-sys-next" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4ebda144c4fe02d1f7ea1a7d9641b6fc6b580adcfa024ae48797ecdeb6825b4d" +dependencies = [ + "libc", + "redox_users", + "winapi", +] + [[package]] name = "dispatch" version = "0.2.0" @@ -1039,7 +1158,7 @@ checksum = "97369cbbc041bc366949bc74d34658d6cda5621039731c6310521892a3a20ae0" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.98", ] [[package]] @@ -1078,12 +1197,30 @@ version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f25c0e292a7ca6d6498557ff1df68f32c99850012b6ea401cf8daf771f22ff53" +[[package]] +name = "dwrote" +version = "0.11.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "70182709525a3632b2ba96b6569225467b18ecb4a77f46d255f713a6bebf05fd" +dependencies = [ + "lazy_static", + "libc", + "winapi", + "wio", +] + [[package]] name = "either" version = "1.13.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "60b1af1c220855b6ceac025d3f6ecdd2b7c4894bfe9cd9bda4fbb4bc7c0d4cf0" +[[package]] +name = "encode_unicode" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "34aa73646ffb006b8f5147f3dc182bd4bcb190227ce861fc4a4844bf8e3cb2c0" + [[package]] name = "encoding_rs" version = "0.8.35" @@ -1102,7 +1239,30 @@ dependencies = [ "heck", "proc-macro2", "quote", - "syn", + "syn 2.0.98", +] + +[[package]] +name = "env_filter" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "186e05a59d4c50738528153b83b0b0194d3a29507dfec16eccd4b342903397d0" +dependencies = [ + "log", + "regex", +] + +[[package]] +name = "env_logger" +version = "0.11.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dcaee3d8e3cfc3fd92428d477bc97fc29ec8716d180c0d74c643bb26166660e0" +dependencies = [ + "anstream", + "anstyle", + "env_filter", + "humantime", + "log", ] [[package]] @@ -1139,7 +1299,7 @@ dependencies = [ "bit_field", "half", "lebe", - "miniz_oxide 0.8.2", + "miniz_oxide 0.8.3", "rayon-core", "smallvec", "zune-inflate", @@ -1183,7 +1343,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c936bfdafb507ebbf50b8074c54fa31c5be9a1e7e5f467dd659697041407d07c" dependencies = [ "crc32fast", - "miniz_oxide 0.8.2", + "miniz_oxide 0.8.3", ] [[package]] @@ -1192,6 +1352,12 @@ version = "0.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "98de4bbd547a563b716d8dfa9aad1cb19bfab00f4fa09a6a4ed21dbcf44ce9c4" +[[package]] +name = "float-ord" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8ce81f49ae8a0482e4c55ea62ebbd7e5a686af544c00b9d090bba3ff9be97b3d" + [[package]] name = "fnv" version = "1.0.7" @@ -1204,6 +1370,31 @@ version = "0.1.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a0d2fde1f7b3d48b8395d5f2de76c18a528bd6a9cdde438df747bfcba3e05d6f" +[[package]] +name = "font-kit" +version = "0.14.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b64b34f4efd515f905952d91bc185039863705592c0c53ae6d979805dd154520" +dependencies = [ + "bitflags 2.8.0", + "byteorder", + "core-foundation", + "core-graphics", + "core-text", + "dirs", + "dwrote", + "float-ord", + "freetype-sys", + "lazy_static", + "libc", + "log", + "pathfinder_geometry", + "pathfinder_simd", + "walkdir", + "winapi", + "yeslogic-fontconfig-sys", +] + [[package]] name = "font-types" version = "0.7.3" @@ -1273,8 +1464,8 @@ dependencies = [ "roxmltree 0.19.0", "skrifa 0.22.3", "smallvec", - "windows", - "windows-core", + "windows 0.58.0", + "windows-core 0.58.0", ] [[package]] @@ -1295,7 +1486,7 @@ checksum = "1a5c6c585bc94aaf2c7b51dd4c2ba22680844aba4c687be581871a6f518c5742" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.98", ] [[package]] @@ -1315,23 +1506,34 @@ dependencies = [ [[package]] name = "freetype-rs" -version = "0.38.0" +version = "0.35.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b1fad2be0bf06af23adddcf6cd143c94ff0ba3b329691f92d1a38dae5c5aeebf" +dependencies = [ + "bitflags 2.8.0", + "freetype-sys", + "libc", +] + +[[package]] +name = "freetype-rs" +version = "0.36.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6d228d6de56c90dd7585341f341849441b3490180c62d27133e525eb726809b4" +checksum = "5442dee36ca09604133580dc0553780e867936bb3cbef3275859e889026d2b17" dependencies = [ - "bitflags 2.6.0", + "bitflags 2.8.0", "freetype-sys", "libc", ] [[package]] name = "freetype-sys" -version = "0.23.0" +version = "0.20.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "eab537ce43cab850c64b4cdc390ce7e4f47f877485ddc323208e268280c308ae" +checksum = "0e7edc5b9669349acfda99533e9e0bcf26a51862ab43b08ee7745c55d28eb134" dependencies = [ "cc", - "libz-sys", + "libc", "pkg-config", ] @@ -1412,7 +1614,7 @@ checksum = "162ee34ebcb7c64a8abebc059ce0fee27c2262618d7b60ed8faf72fef13c3650" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.98", ] [[package]] @@ -1447,59 +1649,59 @@ dependencies = [ [[package]] name = "gdk-pixbuf" -version = "0.20.7" +version = "0.19.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b6efc7705f7863d37b12ad6974cbb310d35d054f5108cdc1e69037742f573c4c" +checksum = "624eaba126021103c7339b2e179ae4ee8cdab842daab419040710f38ed9f8699" dependencies = [ "gdk-pixbuf-sys", - "gio", - "glib", + "gio 0.19.8", + "glib 0.19.9", "libc", ] [[package]] name = "gdk-pixbuf-sys" -version = "0.20.7" +version = "0.19.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "67f2587c9202bf997476bbba6aaed4f78a11538a2567df002a5f57f5331d0b5c" +checksum = "4efa05a4f83c8cc50eb4d883787b919b85e5f1d8dd10b5a1df53bf5689782379" dependencies = [ - "gio-sys", - "glib-sys", - "gobject-sys", + "gio-sys 0.19.8", + "glib-sys 0.19.8", + "gobject-sys 0.19.8", "libc", - "system-deps 7.0.3", + "system-deps 6.2.2", ] [[package]] name = "gdk4" -version = "0.9.5" +version = "0.8.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d0196720118f880f71fe7da971eff58cc43a89c9cf73f46076b7cb1e60889b15" +checksum = "db265c9dd42d6a371e09e52deab3a84808427198b86ac792d75fd35c07990a07" dependencies = [ "cairo-rs", "gdk-pixbuf", "gdk4-sys", - "gio", - "glib", + "gio 0.19.8", + "glib 0.19.9", "libc", - "pango", + "pango 0.19.8", ] [[package]] name = "gdk4-sys" -version = "0.9.5" +version = "0.8.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "60b0e1340bd15e7a78810cf39fed9e5d85f0a8f80b1d999d384ca17dcc452b60" +checksum = "c9418fb4e8a67074919fe7604429c45aa74eb9df82e7ca529767c6d4e9dc66dd" dependencies = [ "cairo-sys-rs", "gdk-pixbuf-sys", - "gio-sys", - "glib-sys", - "gobject-sys", + "gio-sys 0.19.8", + "glib-sys 0.19.8", + "gobject-sys 0.19.8", "libc", - "pango-sys", + "pango-sys 0.19.8", "pkg-config", - "system-deps 7.0.3", + "system-deps 6.2.2", ] [[package]] @@ -1573,6 +1775,24 @@ version = "0.31.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "07e28edb80900c19c28f1072f2e8aeca7fa06b23cd4169cefe1af5aa3260783f" +[[package]] +name = "gio" +version = "0.19.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4c49f117d373ffcc98a35d114db5478bc223341cff53e39a5d6feced9e2ddffe" +dependencies = [ + "futures-channel", + "futures-core", + "futures-io", + "futures-util", + "gio-sys 0.19.8", + "glib 0.19.9", + "libc", + "pin-project-lite", + "smallvec", + "thiserror 1.0.69", +] + [[package]] name = "gio" version = "0.20.7" @@ -1583,21 +1803,34 @@ dependencies = [ "futures-core", "futures-io", "futures-util", - "gio-sys", - "glib", + "gio-sys 0.20.8", + "glib 0.20.7", "libc", "pin-project-lite", "smallvec", ] +[[package]] +name = "gio-sys" +version = "0.19.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2cd743ba4714d671ad6b6234e8ab2a13b42304d0e13ab7eba1dcdd78a7d6d4ef" +dependencies = [ + "glib-sys 0.19.8", + "gobject-sys 0.19.8", + "libc", + "system-deps 6.2.2", + "windows-sys 0.52.0", +] + [[package]] name = "gio-sys" version = "0.20.8" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8446d9b475730ebef81802c1738d972db42fde1c5a36a627ebc4d665fc87db04" dependencies = [ - "glib-sys", - "gobject-sys", + "glib-sys 0.20.7", + "gobject-sys 0.20.7", "libc", "system-deps 7.0.3", "windows-sys 0.59.0", @@ -1614,27 +1847,62 @@ dependencies = [ "xml-rs", ] +[[package]] +name = "glib" +version = "0.19.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "39650279f135469465018daae0ba53357942a5212137515777d5fdca74984a44" +dependencies = [ + "bitflags 2.8.0", + "futures-channel", + "futures-core", + "futures-executor", + "futures-task", + "futures-util", + "gio-sys 0.19.8", + "glib-macros 0.19.9", + "glib-sys 0.19.8", + "gobject-sys 0.19.8", + "libc", + "memchr", + "smallvec", + "thiserror 1.0.69", +] + [[package]] name = "glib" version = "0.20.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f969edf089188d821a30cde713b6f9eb08b20c63fc2e584aba2892a7984a8cc0" dependencies = [ - "bitflags 2.6.0", + "bitflags 2.8.0", "futures-channel", "futures-core", "futures-executor", "futures-task", "futures-util", - "gio-sys", - "glib-macros", - "glib-sys", - "gobject-sys", + "gio-sys 0.20.8", + "glib-macros 0.20.7", + "glib-sys 0.20.7", + "gobject-sys 0.20.7", "libc", "memchr", "smallvec", ] +[[package]] +name = "glib-macros" +version = "0.19.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4429b0277a14ae9751350ad9b658b1be0abb5b54faa5bcdf6e74a3372582fad7" +dependencies = [ + "heck", + "proc-macro-crate", + "proc-macro2", + "quote", + "syn 2.0.98", +] + [[package]] name = "glib-macros" version = "0.20.7" @@ -1645,7 +1913,17 @@ dependencies = [ "proc-macro-crate", "proc-macro2", "quote", - "syn", + "syn 2.0.98", +] + +[[package]] +name = "glib-sys" +version = "0.19.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5c2dc18d3a82b0006d470b13304fbbb3e0a9bd4884cf985a60a7ed733ac2c4a5" +dependencies = [ + "libc", + "system-deps 6.2.2", ] [[package]] @@ -1660,9 +1938,21 @@ dependencies = [ [[package]] name = "glob" -version = "0.3.1" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a8d1add55171497b4705a648c6b583acafb01d58050a51727785f0b2c8e0a2b2" + +[[package]] +name = "glow" +version = "0.13.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d2fabcfbdc87f4758337ca535fb41a6d701b65693ce38287d856d1674551ec9b" +checksum = "bd348e04c43b32574f2de31c8bb397d96c9fcfa1371bd4ca6d8bdc464ab121b1" +dependencies = [ + "js-sys", + "slotmap", + "wasm-bindgen", + "web-sys", +] [[package]] name = "glow" @@ -1678,20 +1968,31 @@ dependencies = [ [[package]] name = "glutin_wgl_sys" -version = "0.6.0" +version = "0.6.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0a4e1951bbd9434a81aa496fe59ccc2235af3820d27b85f9314e279609211e2c" +checksum = "2c4ee00b289aba7a9e5306d57c2d05499b2e5dc427f84ac708bd2c090212cf3e" dependencies = [ "gl_generator", ] +[[package]] +name = "gobject-sys" +version = "0.19.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2e697e252d6e0416fd1d9e169bda51c0f1c926026c39ca21fbe8b1bb5c3b8b9e" +dependencies = [ + "glib-sys 0.19.8", + "libc", + "system-deps 6.2.2", +] + [[package]] name = "gobject-sys" version = "0.20.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "67a56235e971a63bfd75abb13ef70064e1346388723422a68580d8a6fbac6423" dependencies = [ - "glib-sys", + "glib-sys 0.20.7", "libc", "system-deps 7.0.3", ] @@ -1702,16 +2003,18 @@ version = "0.1.1" dependencies = [ "anyhow", "cairo-rs", - "cairo-sys-rs", - "freetype-rs", "futures", + "gosub_fontmanager", "gosub_interface", + "gosub_renderer", "gosub_shared", "gosub_svg", "image", "kurbo", "log", "once_cell", + "pango 0.19.8", + "pangocairo", "parley", "peniko 0.3.1", "skrifa 0.26.5", @@ -1775,6 +2078,7 @@ dependencies = [ "gosub_cairo", "gosub_config", "gosub_css3", + "gosub_fontmanager", "gosub_html5", "gosub_instance", "gosub_interface", @@ -1803,6 +2107,31 @@ dependencies = [ "winit", ] +[[package]] +name = "gosub_fontmanager" +version = "0.1.0" +dependencies = [ + "anyhow", + "cairo-rs", + "colog", + "cow-utils", + "font-kit", + "freetype-rs 0.36.0", + "gosub_interface", + "gtk4", + "image", + "lazy_static", + "log", + "pangocairo", + "parley", + "pollster", + "prettytable", + "rand 0.9.0", + "swash", + "vello 0.3.0", + "winit", +] + [[package]] name = "gosub_html5" version = "0.1.1" @@ -1886,12 +2215,14 @@ name = "gosub_renderer" version = "0.1.1" dependencies = [ "anyhow", + "gosub_fontmanager", "gosub_interface", "gosub_net", "gosub_rendering", "gosub_shared", "image", "log", + "pango 0.20.7", "url", "wasm-bindgen-futures", "web-sys", @@ -1954,6 +2285,7 @@ name = "gosub_taffy" version = "0.1.1" dependencies = [ "anyhow", + "gosub_fontmanager", "gosub_interface", "gosub_shared", "log", @@ -1982,6 +2314,7 @@ dependencies = [ "anyhow", "cow-utils", "futures", + "gosub_fontmanager", "gosub_html5", "gosub_interface", "gosub_shared", @@ -2025,7 +2358,7 @@ dependencies = [ "lazy_static", "proc-macro2", "quote", - "syn", + "syn 2.0.98", ] [[package]] @@ -2034,7 +2367,7 @@ version = "0.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fbcd2dba93594b227a1f57ee09b8b9da8892c34d55aa332e034a228d0fe6a171" dependencies = [ - "bitflags 2.6.0", + "bitflags 2.8.0", "gpu-alloc-types", ] @@ -2044,7 +2377,20 @@ version = "0.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "98ff03b468aa837d70984d55f5d3f846f6ec31fe34bbb97c4f85219caeee1ca4" dependencies = [ - "bitflags 2.6.0", + "bitflags 2.8.0", +] + +[[package]] +name = "gpu-allocator" +version = "0.26.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fdd4240fc91d3433d5e5b0fc5b67672d771850dc19bbee03c1381e19322803d7" +dependencies = [ + "log", + "presser", + "thiserror 1.0.69", + "winapi", + "windows 0.52.0", ] [[package]] @@ -2056,7 +2402,7 @@ dependencies = [ "log", "presser", "thiserror 1.0.69", - "windows", + "windows 0.58.0", ] [[package]] @@ -2065,7 +2411,7 @@ version = "0.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "dcf29e94d6d243368b7a56caa16bc213e4f9f8ed38c4d9557069527b5d5281ca" dependencies = [ - "bitflags 2.6.0", + "bitflags 2.8.0", "gpu-descriptor-types", "hashbrown 0.15.2", ] @@ -2076,30 +2422,30 @@ version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fdf242682df893b86f33a73828fb09ca4b2d3bb6cc95249707fc684d27484b91" dependencies = [ - "bitflags 2.6.0", + "bitflags 2.8.0", ] [[package]] name = "graphene-rs" -version = "0.20.7" +version = "0.19.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f39d3bcd2e24fd9c2874a56f277b72c03e728de9bdc95a8d4ef4c962f10ced98" +checksum = "f5fb86031d24d9ec0a2a15978fc7a65d545a2549642cf1eb7c3dda358da42bcf" dependencies = [ - "glib", + "glib 0.19.9", "graphene-sys", "libc", ] [[package]] name = "graphene-sys" -version = "0.20.7" +version = "0.19.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "11a68d39515bf340e879b72cecd4a25c1332557757ada6e8aba8654b4b81d23a" +checksum = "2f530e0944bccba4b55065e9c69f4975ad691609191ebac16e13ab8e1f27af05" dependencies = [ - "glib-sys", + "glib-sys 0.19.8", "libc", "pkg-config", - "system-deps 7.0.3", + "system-deps 6.2.2", ] [[package]] @@ -2110,85 +2456,85 @@ checksum = "36119f3a540b086b4e436bb2b588cf98a68863470e0e880f4d0842f112a3183a" [[package]] name = "gsk4" -version = "0.9.5" +version = "0.8.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "32b9188db0a6219e708b6b6e7225718e459def664023dbddb8395ca1486d8102" +checksum = "7563884bf6939f4468e5d94654945bdd9afcaf8c3ba4c5dd17b5342b747221be" dependencies = [ "cairo-rs", "gdk4", - "glib", + "glib 0.19.9", "graphene-rs", "gsk4-sys", "libc", - "pango", + "pango 0.19.8", ] [[package]] name = "gsk4-sys" -version = "0.9.5" +version = "0.8.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bca10fc65d68528a548efa3d8747934adcbe7058b73695c9a7f43a25352fce14" +checksum = "23024bf2636c38bbd1f822f58acc9d1c25b28da896ff0f291a1a232d4272b3dc" dependencies = [ "cairo-sys-rs", "gdk4-sys", - "glib-sys", - "gobject-sys", + "glib-sys 0.19.8", + "gobject-sys 0.19.8", "graphene-sys", "libc", - "pango-sys", - "system-deps 7.0.3", + "pango-sys 0.19.8", + "system-deps 6.2.2", ] [[package]] name = "gtk4" -version = "0.9.5" +version = "0.8.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b697ff938136625f6acf75f01951220f47a45adcf0060ee55b4671cf734dac44" +checksum = "b04e11319b08af11358ab543105a9e49b0c491faca35e2b8e7e36bfba8b671ab" dependencies = [ "cairo-rs", "field-offset", "futures-channel", "gdk-pixbuf", "gdk4", - "gio", - "glib", + "gio 0.19.8", + "glib 0.19.9", "graphene-rs", "gsk4", "gtk4-macros", "gtk4-sys", "libc", - "pango", + "pango 0.19.8", ] [[package]] name = "gtk4-macros" -version = "0.9.5" +version = "0.8.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0ed1786c4703dd196baf7e103525ce0cf579b3a63a0570fe653b7ee6bac33999" +checksum = "ec655a7ef88d8ce9592899deb8b2d0fa50bab1e6dd69182deb764e643c522408" dependencies = [ "proc-macro-crate", "proc-macro2", "quote", - "syn", + "syn 2.0.98", ] [[package]] name = "gtk4-sys" -version = "0.9.5" +version = "0.8.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3af4b680cee5d2f786a2f91f1c77e95ecf2254522f0ca4edf3a2dce6cb35cecf" +checksum = "8c8aa86b7f85ea71d66ea88c1d4bae1cfacf51ca4856274565133838d77e57b5" dependencies = [ "cairo-sys-rs", "gdk-pixbuf-sys", "gdk4-sys", - "gio-sys", - "glib-sys", - "gobject-sys", + "gio-sys 0.19.8", + "glib-sys 0.19.8", + "gobject-sys 0.19.8", "graphene-sys", "gsk4-sys", "libc", - "pango-sys", - "system-deps 7.0.3", + "pango-sys 0.19.8", + "system-deps 6.2.2", ] [[package]] @@ -2257,6 +2603,21 @@ dependencies = [ "hashbrown 0.14.5", ] +[[package]] +name = "hassle-rs" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "af2a7e73e1f34c48da31fb668a907f250794837e08faa144fd24f0b8b741e890" +dependencies = [ + "bitflags 2.8.0", + "com", + "libc", + "libloading", + "thiserror 1.0.69", + "widestring", + "winapi", +] + [[package]] name = "heapless" version = "0.8.0" @@ -2287,9 +2648,9 @@ checksum = "dfa686283ad6dd069f105e5ab091b04c62850d3e4cf5d67debad1933f55023df" [[package]] name = "hickory-proto" -version = "0.24.2" +version = "0.24.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "447afdcdb8afb9d0a852af6dc65d9b285ce720ed7a59e42a8bf2e931c67bc1b5" +checksum = "2ad3d6d98c648ed628df039541a5577bee1a7c83e9e16fe3dbedeea4cdfeb971" dependencies = [ "async-trait", "cfg-if", @@ -2311,9 +2672,9 @@ dependencies = [ [[package]] name = "hickory-resolver" -version = "0.24.2" +version = "0.24.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0a2e2aba9c389ce5267d31cf1e4dace82390ae276b0b364ea55630b1fa1b44b4" +checksum = "dcf287bde7b776e85d7188e6e5db7cf410a2f9531fe82817eb87feed034c8d14" dependencies = [ "cfg-if", "futures-util", @@ -2385,6 +2746,12 @@ version = "1.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f2d708df4e7140240a16cd6ab0ab65c972d7433ab77819ea693fde9c43811e2a" +[[package]] +name = "humantime" +version = "2.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9a3a5bfb195931eeb336b2a7b4d761daec841b97f947d34394601737a7bba5e4" + [[package]] name = "icu_collections" version = "1.5.0" @@ -2500,7 +2867,7 @@ checksum = "1ec89e9337638ecdc08744df490b221a7399bf8d164eb52a665454e60e075ad6" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.98", ] [[package]] @@ -2535,7 +2902,7 @@ dependencies = [ "color_quant", "exr", "gif", - "image-webp 0.2.0", + "image-webp 0.2.1", "num-traits", "png", "qoi", @@ -2559,9 +2926,9 @@ dependencies = [ [[package]] name = "image-webp" -version = "0.2.0" +version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e031e8e3d94711a9ccb5d6ea357439ef3dcbed361798bd4071dc4d9793fbe22f" +checksum = "b77d01e822461baa8409e156015a1d91735549f0f2c17691bd2d996bef238f7f" dependencies = [ "byteorder-lite", "quick-error 2.0.1", @@ -2581,9 +2948,9 @@ checksum = "d0263a3d970d5c054ed9312c0057b4f3bde9c0b33836d3637361d4a9e6e7a408" [[package]] name = "indexmap" -version = "2.7.0" +version = "2.7.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "62f822373a4fe84d4bb149bf54e584a7f4abec90e072ed49cda0edea5b95471f" +checksum = "8c9c992b02b5b4c94ea26e32fe5bccb7aa7d9f390ab5c1221ff895bc7ea8b652" dependencies = [ "equivalent", "hashbrown 0.15.2", @@ -2606,7 +2973,7 @@ checksum = "c34819042dc3d3971c46c2190835914dfbe0c3c13f61449b2997f4e9722dfa60" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.98", ] [[package]] @@ -2623,19 +2990,19 @@ dependencies = [ [[package]] name = "ipnet" -version = "2.10.1" +version = "2.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ddc24109865250148c2e0f3d25d4f0f479571723792d3802153c60922a4fb708" +checksum = "469fb0b9cefa57e3ef31275ee7cacb78f2fdca44e4765491884a2b119d4eb130" [[package]] name = "is-terminal" -version = "0.4.13" +version = "0.4.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "261f68e344040fbd0edea105bef17c66edf46f984ddb1115b775ce31be948f4b" +checksum = "e19b23d53f35ce9f56aebc7d1bb4e6ac1e9c0db7ac85c8d1760c04379edced37" dependencies = [ "hermit-abi", "libc", - "windows-sys 0.52.0", + "windows-sys 0.59.0", ] [[package]] @@ -2648,16 +3015,25 @@ checksum = "7943c866cc5cd64cbc25b2e01621d07fa8eb2a1a23160ee81ce38704e97b8ecf" name = "itertools" version = "0.10.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b0fd2260e829bddf4cb6ea802289de2f86d6a7a690192fbe91b3f46e0f2c8473" +checksum = "b0fd2260e829bddf4cb6ea802289de2f86d6a7a690192fbe91b3f46e0f2c8473" +dependencies = [ + "either", +] + +[[package]] +name = "itertools" +version = "0.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ba291022dbbd398a455acf126c1e341954079855bc60dfdda641363bd6922569" dependencies = [ "either", ] [[package]] name = "itertools" -version = "0.12.1" +version = "0.13.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ba291022dbbd398a455acf126c1e341954079855bc60dfdda641363bd6922569" +checksum = "413ee7dfc52ee1a4949ceeb7dbc8a33f2d6c088194d9f922fb8318faf1f01186" dependencies = [ "either", ] @@ -2771,9 +3147,9 @@ checksum = "b5aba8db14291edd000dfcc4d620c7ebfb122c613afb886ca8803fa4e128a20a" [[package]] name = "libfuzzer-sys" -version = "0.4.8" +version = "0.4.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9b9569d2f74e257076d8c6bfa73fb505b46b851e51ddaecc825944aa3bed17fa" +checksum = "cf78f52d400cf2d84a3a973a78a592b4adc535739e0a5597a0da6f0c357adc75" dependencies = [ "arbitrary", "cc", @@ -2801,7 +3177,7 @@ version = "0.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c0ff37bd590ca25063e35af745c343cb7a0271906fb7b37e4813e8f79f00268d" dependencies = [ - "bitflags 2.6.0", + "bitflags 2.8.0", "libc", "redox_syscall 0.5.8", ] @@ -2816,18 +3192,6 @@ dependencies = [ "vcpkg", ] -[[package]] -name = "libz-sys" -version = "1.1.21" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "df9b68e50e6e0b26f672573834882eb57759f6db9b3be2ea3c35c91188bb4eaa" -dependencies = [ - "cc", - "libc", - "pkg-config", - "vcpkg", -] - [[package]] name = "linked-hash-map" version = "0.5.6" @@ -2836,9 +3200,9 @@ checksum = "0717cef1bc8b636c6e1c1bbdefc09e6322da8a9321966e8928ef80d20f7f770f" [[package]] name = "linux-raw-sys" -version = "0.4.14" +version = "0.4.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "78b3ae25bc7c8c38cec158d1f2757ee79e9b3740fbc7ccf0e59e4b08d793fa89" +checksum = "d26c52dbd32dccf2d10cac7725f8eae5296885fb5703b261f7d0a0739ec807ab" [[package]] name = "litemap" @@ -2864,9 +3228,9 @@ dependencies = [ [[package]] name = "log" -version = "0.4.22" +version = "0.4.25" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a7a70ba024b9dc04c27ea2f0c0548feb474ec5c54bba33a7f72f873a39d07b24" +checksum = "04cbf5b083de1c7e0222a7a51dbfdba1cbe1c6ab0b15e29fff3f6c077fd9cd9f" [[package]] name = "loop9" @@ -2941,7 +3305,7 @@ version = "0.29.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7ecfd3296f8c56b7c1f6fbac3c71cefa9d78ce009850c45000015f206dc7fa21" dependencies = [ - "bitflags 2.6.0", + "bitflags 2.8.0", "block", "core-graphics-types", "foreign-types", @@ -2977,9 +3341,9 @@ dependencies = [ [[package]] name = "miniz_oxide" -version = "0.8.2" +version = "0.8.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4ffbe83022cedc1d264172192511ae958937694cd57ce297164951b8b3568394" +checksum = "b8402cab7aefae129c6977bb0ff1b8fd9a04eb5b51efc50a70bea51cda0c7924" dependencies = [ "adler2", "simd-adler32", @@ -3004,13 +3368,14 @@ checksum = "8bd5a652b6faf21496f2cfd88fc49989c8db0825d1f6746b1a71a6ede24a63ad" dependencies = [ "arrayvec", "bit-set 0.6.0", - "bitflags 2.6.0", + "bitflags 2.8.0", "cfg_aliases 0.1.1", "codespan-reporting", "hexf-parse", "indexmap", "log", "rustc-hash", + "spirv", "termcolor", "thiserror 1.0.69", "unicode-xid", @@ -3024,7 +3389,7 @@ checksum = "364f94bc34f61332abebe8cad6f6cd82a5b65cff22c828d05d0968911462ca4f" dependencies = [ "arrayvec", "bit-set 0.8.0", - "bitflags 2.6.0", + "bitflags 2.8.0", "cfg_aliases 0.1.1", "codespan-reporting", "hexf-parse", @@ -3043,7 +3408,7 @@ version = "0.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c3f42e7bbe13d351b6bead8286a43aac9534b82bd3cc43e47037f012ebfd62d4" dependencies = [ - "bitflags 2.6.0", + "bitflags 2.8.0", "jni-sys", "log", "ndk-sys 0.6.0+11769913", @@ -3142,7 +3507,7 @@ checksum = "ed3955f1a9c7c0c15e092f9c887db08b1fc683305fdf6eb6684f22555355e202" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.98", ] [[package]] @@ -3193,7 +3558,7 @@ dependencies = [ "proc-macro-crate", "proc-macro2", "quote", - "syn", + "syn 2.0.98", ] [[package]] @@ -3236,7 +3601,7 @@ version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e4e89ad9e3d7d297152b17d39ed92cd50ca8063a89a9fa569046d41568891eff" dependencies = [ - "bitflags 2.6.0", + "bitflags 2.8.0", "block2", "libc", "objc2", @@ -3252,7 +3617,7 @@ version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "74dd3b56391c7a0596a295029734d3c1c5e7e510a4cb30245f8221ccea96b009" dependencies = [ - "bitflags 2.6.0", + "bitflags 2.8.0", "block2", "objc2", "objc2-core-location", @@ -3276,7 +3641,7 @@ version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "617fbf49e071c178c0b24c080767db52958f716d9eabdf0890523aeae54773ef" dependencies = [ - "bitflags 2.6.0", + "bitflags 2.8.0", "block2", "objc2", "objc2-foundation", @@ -3308,9 +3673,9 @@ dependencies = [ [[package]] name = "objc2-encode" -version = "4.0.3" +version = "4.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7891e71393cd1f227313c9379a26a584ff3d7e6e7159e988851f0934c993f0f8" +checksum = "ef25abbcd74fb2609453eb695bd2f860d389e457f67dc17cafc8b8cbc89d0c33" [[package]] name = "objc2-foundation" @@ -3318,7 +3683,7 @@ version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0ee638a5da3799329310ad4cfa62fbf045d5f56e3ef5ba4149e7452dcf89d5a8" dependencies = [ - "bitflags 2.6.0", + "bitflags 2.8.0", "block2", "dispatch", "libc", @@ -3343,7 +3708,7 @@ version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "dd0cba1276f6023976a406a14ffa85e1fdd19df6b0f737b063b95f6c8c7aadd6" dependencies = [ - "bitflags 2.6.0", + "bitflags 2.8.0", "block2", "objc2", "objc2-foundation", @@ -3355,7 +3720,7 @@ version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e42bee7bff906b14b167da2bac5efe6b6a07e6f7c0a21a7308d40c960242dc7a" dependencies = [ - "bitflags 2.6.0", + "bitflags 2.8.0", "block2", "objc2", "objc2-foundation", @@ -3378,7 +3743,7 @@ version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b8bb46798b20cd6b91cbd113524c490f1686f4c4e8f49502431415f3512e2b6f" dependencies = [ - "bitflags 2.6.0", + "bitflags 2.8.0", "block2", "objc2", "objc2-cloud-kit", @@ -3410,7 +3775,7 @@ version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "76cfcbf642358e8689af64cee815d139339f3ed8ad05103ed5eaf73db8d84cb3" dependencies = [ - "bitflags 2.6.0", + "bitflags 2.8.0", "block2", "objc2", "objc2-core-location", @@ -3428,9 +3793,9 @@ dependencies = [ [[package]] name = "once_cell" -version = "1.20.2" +version = "1.20.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1261fe7e33c73b354eab43b1273a57c8f967d0391e80353e51f764ac02cf6775" +checksum = "945462a4b81e43c4e3ba96bd7b49d834c6f61198356aa858733bc4acf3cbe62e" [[package]] name = "oorandom" @@ -3444,6 +3809,12 @@ version = "0.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c08d65885ee38876c4f86fa503fb49d7b507c2b62552df7c70b2fce627e06381" +[[package]] +name = "option-ext" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "04744f49eae99ab78e0d5c0b603ab218f515ea8cfe5a456d7629ad883a3b6e7d" + [[package]] name = "orbclient" version = "0.3.48" @@ -3462,16 +3833,40 @@ dependencies = [ "ttf-parser 0.25.1", ] +[[package]] +name = "pango" +version = "0.19.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3f0d328648058085cfd6897c9ae4272884098a926f3a833cd50c8c73e6eccecd" +dependencies = [ + "gio 0.19.8", + "glib 0.19.9", + "libc", + "pango-sys 0.19.8", +] + [[package]] name = "pango" version = "0.20.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9e89bd74250a03a05cec047b43465469102af803be2bf5e5a1088f8b8455e087" dependencies = [ - "gio", - "glib", + "gio 0.20.7", + "glib 0.20.7", + "libc", + "pango-sys 0.20.7", +] + +[[package]] +name = "pango-sys" +version = "0.19.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ff03da4fa086c0b244d4a4587d3e20622a3ecdb21daea9edf66597224c634ba0" +dependencies = [ + "glib-sys 0.19.8", + "gobject-sys 0.19.8", "libc", - "pango-sys", + "system-deps 6.2.2", ] [[package]] @@ -3480,12 +3875,38 @@ version = "0.20.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "71787e0019b499a5eda889279e4adb455a4f3fdd6870cd5ab7f4a5aa25df6699" dependencies = [ - "glib-sys", - "gobject-sys", + "glib-sys 0.20.7", + "gobject-sys 0.20.7", "libc", "system-deps 7.0.3", ] +[[package]] +name = "pangocairo" +version = "0.19.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e4c8b43c02ec1c4e16daf7fc50fbce6b8ead5705c18ae56274f703233cce1cd9" +dependencies = [ + "cairo-rs", + "glib 0.19.9", + "libc", + "pango 0.19.8", + "pangocairo-sys", +] + +[[package]] +name = "pangocairo-sys" +version = "0.19.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "591904498438879785f5b7a2fdf7c38e9ec08c514b93c614b5c3b48cd11dd8d7" +dependencies = [ + "cairo-sys-rs", + "glib-sys 0.19.8", + "libc", + "pango-sys 0.19.8", + "system-deps 6.2.2", +] + [[package]] name = "parking_lot" version = "0.12.3" @@ -3527,6 +3948,25 @@ version = "1.0.15" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "57c0d7b74b563b49d38dae00a0c37d4d6de9b432382b2892f0574ddcae73fd0a" +[[package]] +name = "pathfinder_geometry" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0b7b7e7b4ea703700ce73ebf128e1450eb69c3a8329199ffbfb9b2a0418e5ad3" +dependencies = [ + "log", + "pathfinder_simd", +] + +[[package]] +name = "pathfinder_simd" +version = "0.5.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5cf07ef4804cfa9aea3b04a7bbdd5a40031dbb6b4f2cbaf2b011666c80c5b4f2" +dependencies = [ + "rustc_version", +] + [[package]] name = "peniko" version = "0.2.0" @@ -3566,9 +4006,9 @@ dependencies = [ [[package]] name = "phf_generator" -version = "0.11.2" +version = "0.11.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "48e4cc64c2ad9ebe670cb8fd69dd50ae301650392e81c05f9bfcb2d5bdbc24b0" +checksum = "3c80231409c20246a13fddb31776fb942c38553c51e871f8cbd687a4cfb5843d" dependencies = [ "phf_shared", "rand 0.8.5", @@ -3584,7 +4024,7 @@ dependencies = [ "phf_shared", "proc-macro2", "quote", - "syn", + "syn 2.0.98", ] [[package]] @@ -3604,29 +4044,29 @@ checksum = "5be167a7af36ee22fe3115051bc51f6e6c7054c9348e28deb4f49bd6f705a315" [[package]] name = "pin-project" -version = "1.1.7" +version = "1.1.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "be57f64e946e500c8ee36ef6331845d40a93055567ec57e8fae13efd33759b95" +checksum = "dfe2e71e1471fe07709406bf725f710b02927c9c54b2b5b2ec0e8087d97c327d" dependencies = [ "pin-project-internal", ] [[package]] name = "pin-project-internal" -version = "1.1.7" +version = "1.1.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3c0f5fad0874fc7abcd4d750e76917eaebbecaa2c20bde22e1dbeeba8beb758c" +checksum = "f6e859e6e5bd50440ab63c47e3ebabc90f26251f7c73c3d3e837b74a1cc3fa67" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.98", ] [[package]] name = "pin-project-lite" -version = "0.2.15" +version = "0.2.16" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "915a1e146535de9163f3987b8944ed8cf49a18bb0056bcebcdcece385cece4ff" +checksum = "3b3cff922bd51709b605d9ead9aa71031d81447142d828eb4a6eba76fe619f9b" [[package]] name = "pin-utils" @@ -3678,7 +4118,7 @@ dependencies = [ "crc32fast", "fdeflate", "flate2", - "miniz_oxide 0.8.2", + "miniz_oxide 0.8.3", ] [[package]] @@ -3696,6 +4136,12 @@ dependencies = [ "windows-sys 0.59.0", ] +[[package]] +name = "pollster" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2f3a9f18d041e6d0e102a0a46750538147e5e8992d3b4873aaafee2520b00ce3" + [[package]] name = "polyval" version = "0.6.2" @@ -3731,12 +4177,26 @@ checksum = "e8cf8e6a8aa66ce33f63993ffc4ea4271eb5b0530a9002db8455ea6050c77bfa" [[package]] name = "prettyplease" -version = "0.2.25" +version = "0.2.29" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "64d1ec885c64d0457d564db4ec299b2dae3f9c02808b8ad9c3a089c591b18033" +checksum = "6924ced06e1f7dfe3fa48d57b9f74f55d8915f5036121bef647ef4b204895fac" dependencies = [ "proc-macro2", - "syn", + "syn 2.0.98", +] + +[[package]] +name = "prettytable" +version = "0.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "46480520d1b77c9a3482d39939fcf96831537a250ec62d4fd8fbdf8e0302e781" +dependencies = [ + "csv", + "encode_unicode", + "is-terminal", + "lazy_static", + "term", + "unicode-width", ] [[package]] @@ -3750,9 +4210,9 @@ dependencies = [ [[package]] name = "proc-macro2" -version = "1.0.92" +version = "1.0.93" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "37d3544b3f2748c54e147655edb5025752e2303145b5aefb3c3ea2c78b973bb0" +checksum = "60946a68e5f9d28b0dc1c21bb8a97ee7d018a8b322fa57838ba31cc878e22d99" dependencies = [ "unicode-ident", ] @@ -3773,7 +4233,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a65f2e60fbf1063868558d69c6beacf412dc755f9fc020f514b7955fc914fe30" dependencies = [ "quote", - "syn", + "syn 2.0.98", ] [[package]] @@ -3799,9 +4259,9 @@ checksum = "a993555f31e5a609f617c12db6250dedcac1b0a85076912c436e6fc9b2c8e6a3" [[package]] name = "quick-xml" -version = "0.36.2" +version = "0.37.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f7649a7b4df05aed9ea7ec6f628c67c9953a43869b8bc50929569b2999d443fe" +checksum = "165859e9e55f79d67b96c5d96f4e88b6f2695a1972849c15a6a3f5c59fc2c003" dependencies = [ "memchr", ] @@ -3834,7 +4294,7 @@ checksum = "3779b94aeb87e8bd4e834cee3650289ee9e0d5677f976ecdb6d219e5f4f6cd94" dependencies = [ "rand_chacha 0.9.0", "rand_core 0.9.0", - "zerocopy 0.8.14", + "zerocopy 0.8.17", ] [[package]] @@ -3873,14 +4333,14 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b08f3c9802962f7e1b25113931d94f43ed9725bebc59db9d0c3e9a23b67e15ff" dependencies = [ "getrandom 0.3.1", - "zerocopy 0.8.14", + "zerocopy 0.8.17", ] [[package]] name = "range-alloc" -version = "0.1.3" +version = "0.1.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9c8a99fddc9f0ba0a85884b8d14e3592853e787d581ca1816c91349b10e4eeab" +checksum = "c3d6831663a5098ea164f89cff59c6284e95f4e3c76ce9848d4529f5ccca9bde" [[package]] name = "rav1e" @@ -4003,7 +4463,18 @@ version = "0.5.8" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "03a862b389f93e68874fbf580b9de08dd02facb9a788ebadaf4a3fd33cf58834" dependencies = [ - "bitflags 2.6.0", + "bitflags 2.8.0", +] + +[[package]] +name = "redox_users" +version = "0.4.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ba009ff324d1fc1b900bd1fdb31564febe58a8ccc8a6fdbb93b543d33b13ca43" +dependencies = [ + "getrandom 0.2.15", + "libredox", + "thiserror 1.0.69", ] [[package]] @@ -4121,7 +4592,7 @@ version = "0.32.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7753b721174eb8ff87a9a0e799e2d7bc3749323e773db92e0984debb00019d6e" dependencies = [ - "bitflags 2.6.0", + "bitflags 2.8.0", "fallible-iterator", "fallible-streaming-iterator", "hashlink", @@ -4152,11 +4623,11 @@ dependencies = [ [[package]] name = "rustix" -version = "0.38.42" +version = "0.38.44" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f93dc38ecbab2eb790ff964bb77fa94faf256fd3e73285fd7ba0903b76bedb85" +checksum = "fdb5bc1ae2baa591800df16c9ca78619bf65c0488b41b96ccec5d11220d8c154" dependencies = [ - "bitflags 2.6.0", + "bitflags 2.8.0", "errno", "libc", "linux-raw-sys", @@ -4165,9 +4636,9 @@ dependencies = [ [[package]] name = "rustls" -version = "0.23.20" +version = "0.23.22" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5065c3f250cbd332cd894be57c40fa52387247659b14a2d6041d121547903b1b" +checksum = "9fb9263ab4eb695e42321db096e3b8fbd715a59b154d5c88d82db2175b681ba7" dependencies = [ "log", "once_cell", @@ -4189,9 +4660,9 @@ dependencies = [ [[package]] name = "rustls-pki-types" -version = "1.10.1" +version = "1.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d2bf47e6ff922db3825eb750c4e2ff784c6ff8fb9e13046ef6a1d1c5401b0b37" +checksum = "917ce264624a4b4db1c364dcc35bfca9ded014d0a958cd47ad3e960e988ea51c" [[package]] name = "rustls-webpki" @@ -4216,7 +4687,7 @@ version = "0.18.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c85d1ccd519e61834798eb52c4e886e8c2d7d698dd3d6ce0b1b47eb8557f1181" dependencies = [ - "bitflags 2.6.0", + "bitflags 2.8.0", "bytemuck", "core_maths", "log", @@ -4230,9 +4701,9 @@ dependencies = [ [[package]] name = "ryu" -version = "1.0.18" +version = "1.0.19" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f3cb5ba0dc43242ce17de99c180e96db90b235b8a9fdc9543c96d2209116bd9f" +checksum = "6ea1a2d0a644769cc99faa24c3ad26b379b786fe7c36fd3c546254801650e6dd" [[package]] name = "same-file" @@ -4270,9 +4741,9 @@ dependencies = [ [[package]] name = "semver" -version = "1.0.24" +version = "1.0.25" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3cb6eb87a131f756572d7fb904f6e7b68633f09cca868c5df1c4b8d1a694bbba" +checksum = "f79dfe2d285b0488816f30e700a7438c5a73d816b5b7d3ac72fbc48b0d185e03" [[package]] name = "serde" @@ -4291,14 +4762,14 @@ checksum = "5a9bf7cf98d04a2b28aead066b7496853d4779c9cc183c440dbac457641e19a0" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.98", ] [[package]] name = "serde_json" -version = "1.0.137" +version = "1.0.138" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "930cfb6e6abf99298aaad7d29abbef7a9999a9a8806a40088f55f0dcec03146b" +checksum = "d434192e7da787e94a6ea7e9670b26a036d0ca41e0b7efb2676dd32bae872949" dependencies = [ "indexmap", "itoa", @@ -4362,9 +4833,9 @@ dependencies = [ [[package]] name = "simplecss" -version = "0.2.1" +version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a11be7c62927d9427e9f40f3444d5499d868648e2edbc4e2116de69e7ec0e89d" +checksum = "7a9c6883ca9c3c7c90e888de77b7a5c849c779d25d74a1269b0218b14e8b136c" dependencies = [ "log", ] @@ -4435,7 +4906,7 @@ version = "0.19.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3457dea1f0eb631b4034d61d4d8c32074caa6cd1ab2d59f2327bd8461e2c0016" dependencies = [ - "bitflags 2.6.0", + "bitflags 2.8.0", "calloop", "calloop-wayland-source", "cursor-icon", @@ -4485,7 +4956,7 @@ version = "0.3.0+sdk-1.3.268.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "eda41003dc44290527a59b13432d4a0379379fa074b70174882adfbdfd917844" dependencies = [ - "bitflags 2.6.0", + "bitflags 2.8.0", ] [[package]] @@ -4529,9 +5000,9 @@ checksum = "ce5d813d71d82c4cbc1742135004e4a79fd870214c155443451c139c9470a0aa" [[package]] name = "svgtypes" -version = "0.15.2" +version = "0.15.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "794de53cc48eaabeed0ab6a3404a65f40b3e38c067e4435883a65d2aa4ca000e" +checksum = "68c7541fff44b35860c1a7a47a7cadf3e4a304c457b58f9870d9706ece028afc" dependencies = [ "kurbo", "siphasher", @@ -4544,13 +5015,26 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "cbd59f3f359ddd2c95af4758c18270eddd9c730dde98598023cdabff472c2ca2" dependencies = [ "skrifa 0.22.3", + "yazi", + "zeno", +] + +[[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.96" +version = "2.0.98" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d5d0adab1ae378d7f53bdebc67a39f1f151407ef230f0ce2883572f5d8985c80" +checksum = "36147f1a48ae0ec2b5b3bc5b537d267457555a10dc06f3dbc8cb11ba3006d3b1" dependencies = [ "proc-macro2", "quote", @@ -4565,7 +5049,7 @@ checksum = "c8af7666ab7b6390ab78131fb5b0fce11d6b7a6951602017c35fa82800708971" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.98", ] [[package]] @@ -4612,6 +5096,17 @@ version = "0.12.16" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "61c41af27dd6d1e27b1b16b489db798443478cef1f06a660c96db617ba5de3b1" +[[package]] +name = "term" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c59df8ac95d96ff9bede18eb7300b0fda5e5d8d90960e76f8e14ae765eedbf1f" +dependencies = [ + "dirs-next", + "rustversion", + "winapi", +] + [[package]] name = "termcolor" version = "1.4.1" @@ -4639,7 +5134,7 @@ dependencies = [ "cfg-if", "proc-macro2", "quote", - "syn", + "syn 2.0.98", ] [[package]] @@ -4650,7 +5145,7 @@ checksum = "5c89e72a01ed4c579669add59014b9a524d609c0c88c6a585ce37485879f6ffb" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.98", "test-case-core", ] @@ -4689,7 +5184,7 @@ checksum = "4fee6c4efc90059e10f81e6d42c60a18f76588c3d74cb83a0b242a2b6c7504c1" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.98", ] [[package]] @@ -4700,7 +5195,7 @@ checksum = "26afc1baea8a989337eeb52b6e72a039780ce45c3edfcc9c5b9d112feeb173c2" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.98", ] [[package]] @@ -4832,14 +5327,14 @@ checksum = "6e06d43f1345a3bcd39f6a56dbb7dcab2ba47e68e8ac134855e7e2bdbaf8cab8" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.98", ] [[package]] name = "toml" -version = "0.8.19" +version = "0.8.20" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a1ed1f98e3fdc28d6d910e6737ae6ab1a93bf1985935a1193e68f93eeb68d24e" +checksum = "cd87a5cdd6ffab733b2f74bc4fd7ee5fff6634124999ac278c35fc78c6120148" dependencies = [ "serde", "serde_spanned", @@ -4858,9 +5353,9 @@ dependencies = [ [[package]] name = "toml_edit" -version = "0.22.22" +version = "0.22.23" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4ae48d6208a266e853d946088ed816055e556cc6028c5e8e2b84d9fa5dd7c7f5" +checksum = "02a8b472d1a3d7c18e2d61a489aee3453fd9031c33e4f55bd533f4a7adca1bee" dependencies = [ "indexmap", "serde", @@ -4888,7 +5383,7 @@ checksum = "395ae124c09f9e6918a2310af6038fba074bcf474ac352496d5910dd59a2226d" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.98", ] [[package]] @@ -4941,9 +5436,9 @@ checksum = "260bc6647b3893a9a90668360803a15f96b85a5257b1c3a0c3daf6ae2496de42" [[package]] name = "unicode-ident" -version = "1.0.14" +version = "1.0.16" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "adb9e6ca4f869e1180728b7950e35922a7fc6397f7b641499e8f3ef06e50dc83" +checksum = "a210d160f08b701c8721ba1c726c11662f877ea6b7094007e1ca9a1041945034" [[package]] name = "unicode-properties" @@ -4999,12 +5494,11 @@ checksum = "8ecb6da28b8a351d773b68d5825ac39017e680750f980f3a1a85cd8dd28a47c1" [[package]] name = "ureq" -version = "3.0.3" +version = "3.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "217751151c53226090391713e533d9a5e904ba2570dabaaace29032687589c3e" +checksum = "18250fd12e095bc29eb65b753431ae5d326f7c259a818cb3bf3faaf740ee3344" dependencies = [ "base64", - "cc", "flate2", "log", "percent-encoding", @@ -5092,11 +5586,11 @@ checksum = "06abde3611657adf66d383f00b093d7faecc7fa57071cce2578660c9f1010821" [[package]] name = "uuid" -version = "1.12.1" +version = "1.13.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b3758f5e68192bb96cc8f9b7e2c2cfdabb435499a28499a42f8f984092adad4b" +checksum = "ced87ca4be083373936a67f8de945faa23b6b42384bd5b64434850802c6dccd0" dependencies = [ - "getrandom 0.2.15", + "getrandom 0.3.1", ] [[package]] @@ -5106,7 +5600,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a511192602f7b435b0a241c1947aa743eb7717f20a9195f4b5e8ed1952e01db1" dependencies = [ "bindgen", - "bitflags 2.6.0", + "bitflags 2.8.0", "fslock", "gzip-header", "home", @@ -5150,6 +5644,7 @@ dependencies = [ "thiserror 1.0.69", "vello_encoding 0.3.0", "vello_shaders 0.3.0", + "wgpu 22.1.0", ] [[package]] @@ -5168,7 +5663,7 @@ dependencies = [ "thiserror 2.0.11", "vello_encoding 0.4.0", "vello_shaders 0.4.0", - "wgpu", + "wgpu 23.0.1", ] [[package]] @@ -5292,7 +5787,7 @@ dependencies = [ "log", "proc-macro2", "quote", - "syn", + "syn 2.0.98", "wasm-bindgen-shared", ] @@ -5327,7 +5822,7 @@ checksum = "8ae87ea40c9f689fc23f209965b6fb8a99ad69aeeb0231408be24920604395de" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.98", "wasm-bindgen-backend", "wasm-bindgen-shared", ] @@ -5362,14 +5857,14 @@ checksum = "17d5042cc5fa009658f9a7333ef24291b1291a25b6382dd68862a7f3b969f69b" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.98", ] [[package]] name = "wayland-backend" -version = "0.3.7" +version = "0.3.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "056535ced7a150d45159d3a8dc30f91a2e2d588ca0b23f70e56033622b8016f6" +checksum = "b7208998eaa3870dad37ec8836979581506e0c5c64c20c9e79e9d2a10d6f47bf" dependencies = [ "cc", "downcast-rs", @@ -5381,11 +5876,11 @@ dependencies = [ [[package]] name = "wayland-client" -version = "0.31.7" +version = "0.31.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b66249d3fc69f76fd74c82cc319300faa554e9d865dab1f7cd66cc20db10b280" +checksum = "c2120de3d33638aaef5b9f4472bff75f07c56379cf76ea320bd3a3d65ecaf73f" dependencies = [ - "bitflags 2.6.0", + "bitflags 2.8.0", "rustix", "wayland-backend", "wayland-scanner", @@ -5397,16 +5892,16 @@ version = "0.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "625c5029dbd43d25e6aa9615e88b829a5cad13b2819c4ae129fdbb7c31ab4c7e" dependencies = [ - "bitflags 2.6.0", + "bitflags 2.8.0", "cursor-icon", "wayland-backend", ] [[package]] name = "wayland-cursor" -version = "0.31.7" +version = "0.31.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "32b08bc3aafdb0035e7fe0fdf17ba0c09c268732707dca4ae098f60cb28c9e4c" +checksum = "a93029cbb6650748881a00e4922b076092a6a08c11e7fbdb923f064b23968c5d" dependencies = [ "rustix", "wayland-client", @@ -5415,11 +5910,11 @@ dependencies = [ [[package]] name = "wayland-protocols" -version = "0.32.5" +version = "0.32.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7cd0ade57c4e6e9a8952741325c30bf82f4246885dca8bf561898b86d0c1f58e" +checksum = "0781cf46869b37e36928f7b432273c0995aa8aed9552c556fb18754420541efc" dependencies = [ - "bitflags 2.6.0", + "bitflags 2.8.0", "wayland-backend", "wayland-client", "wayland-scanner", @@ -5427,11 +5922,11 @@ dependencies = [ [[package]] name = "wayland-protocols-plasma" -version = "0.3.5" +version = "0.3.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9b31cab548ee68c7eb155517f2212049dc151f7cd7910c2b66abfd31c3ee12bd" +checksum = "7ccaacc76703fefd6763022ac565b590fcade92202492381c95b2edfdf7d46b3" dependencies = [ - "bitflags 2.6.0", + "bitflags 2.8.0", "wayland-backend", "wayland-client", "wayland-protocols", @@ -5440,11 +5935,11 @@ dependencies = [ [[package]] name = "wayland-protocols-wlr" -version = "0.3.5" +version = "0.3.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "782e12f6cd923c3c316130d56205ebab53f55d6666b7faddfad36cecaeeb4022" +checksum = "248a02e6f595aad796561fa82d25601bd2c8c3b145b1c7453fc8f94c1a58f8b2" dependencies = [ - "bitflags 2.6.0", + "bitflags 2.8.0", "wayland-backend", "wayland-client", "wayland-protocols", @@ -5453,9 +5948,9 @@ dependencies = [ [[package]] name = "wayland-scanner" -version = "0.31.5" +version = "0.31.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "597f2001b2e5fc1121e3d5b9791d3e78f05ba6bfa4641053846248e3a13661c3" +checksum = "896fdafd5d28145fce7958917d69f2fd44469b1d4e861cb5961bcbeebc6d1484" dependencies = [ "proc-macro2", "quick-xml", @@ -5464,9 +5959,9 @@ dependencies = [ [[package]] name = "wayland-sys" -version = "0.31.5" +version = "0.31.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "efa8ac0d8e8ed3e3b5c9fc92c7881406a268e11555abe36493efabe649a29e09" +checksum = "dbcebb399c77d5aa9fa5db874806ee7b4eba4e73650948e8f93963f128896615" dependencies = [ "dlib", "log", @@ -5496,9 +5991,9 @@ dependencies = [ [[package]] name = "webpki-roots" -version = "0.26.7" +version = "0.26.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5d642ff16b7e79272ae451b7322067cdc17cadf68c23264be9d94a32319efe7e" +checksum = "2210b291f7ea53617fbafcc4939f10914214ec15aace5ba62293a668f322c5c9" dependencies = [ "rustls-pki-types", ] @@ -5509,6 +6004,31 @@ version = "0.1.8" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "53a85b86a771b1c87058196170769dd264f66c0782acf1ae6cc51bfd64b39082" +[[package]] +name = "wgpu" +version = "22.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e1d1c4ba43f80542cf63a0a6ed3134629ae73e8ab51e4b765a67f3aa062eb433" +dependencies = [ + "arrayvec", + "cfg_aliases 0.1.1", + "document-features", + "js-sys", + "log", + "naga 22.1.0", + "parking_lot", + "profiling", + "raw-window-handle", + "smallvec", + "static_assertions", + "wasm-bindgen", + "wasm-bindgen-futures", + "web-sys", + "wgpu-core 22.1.0", + "wgpu-hal 22.0.0", + "wgpu-types 22.0.0", +] + [[package]] name = "wgpu" version = "23.0.1" @@ -5529,9 +6049,34 @@ dependencies = [ "wasm-bindgen", "wasm-bindgen-futures", "web-sys", - "wgpu-core", - "wgpu-hal", - "wgpu-types", + "wgpu-core 23.0.1", + "wgpu-hal 23.0.1", + "wgpu-types 23.0.0", +] + +[[package]] +name = "wgpu-core" +version = "22.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0348c840d1051b8e86c3bcd31206080c5e71e5933dabd79be1ce732b0b2f089a" +dependencies = [ + "arrayvec", + "bit-vec 0.7.0", + "bitflags 2.8.0", + "cfg_aliases 0.1.1", + "document-features", + "indexmap", + "log", + "naga 22.1.0", + "once_cell", + "parking_lot", + "profiling", + "raw-window-handle", + "rustc-hash", + "smallvec", + "thiserror 1.0.69", + "wgpu-hal 22.0.0", + "wgpu-types 22.0.0", ] [[package]] @@ -5542,7 +6087,7 @@ checksum = "d63c3c478de8e7e01786479919c8769f62a22eec16788d8c2ac77ce2c132778a" dependencies = [ "arrayvec", "bit-vec 0.8.0", - "bitflags 2.6.0", + "bitflags 2.8.0", "cfg_aliases 0.1.1", "document-features", "indexmap", @@ -5555,8 +6100,53 @@ dependencies = [ "rustc-hash", "smallvec", "thiserror 1.0.69", - "wgpu-hal", - "wgpu-types", + "wgpu-hal 23.0.1", + "wgpu-types 23.0.0", +] + +[[package]] +name = "wgpu-hal" +version = "22.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f6bbf4b4de8b2a83c0401d9e5ae0080a2792055f25859a02bf9be97952bbed4f" +dependencies = [ + "android_system_properties", + "arrayvec", + "ash", + "bit-set 0.6.0", + "bitflags 2.8.0", + "block", + "cfg_aliases 0.1.1", + "core-graphics-types", + "d3d12", + "glow 0.13.1", + "glutin_wgl_sys", + "gpu-alloc", + "gpu-allocator 0.26.0", + "gpu-descriptor", + "hassle-rs", + "js-sys", + "khronos-egl", + "libc", + "libloading", + "log", + "metal", + "naga 22.1.0", + "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 1.0.69", + "wasm-bindgen", + "web-sys", + "wgpu-types 22.0.0", + "winapi", ] [[package]] @@ -5569,15 +6159,15 @@ dependencies = [ "arrayvec", "ash", "bit-set 0.8.0", - "bitflags 2.6.0", + "bitflags 2.8.0", "block", "bytemuck", "cfg_aliases 0.1.1", "core-graphics-types", - "glow", + "glow 0.14.2", "glutin_wgl_sys", "gpu-alloc", - "gpu-allocator", + "gpu-allocator 0.27.0", "gpu-descriptor", "js-sys", "khronos-egl", @@ -5599,9 +6189,20 @@ dependencies = [ "thiserror 1.0.69", "wasm-bindgen", "web-sys", - "wgpu-types", - "windows", - "windows-core", + "wgpu-types 23.0.0", + "windows 0.58.0", + "windows-core 0.58.0", +] + +[[package]] +name = "wgpu-types" +version = "22.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bc9d91f0e2c4b51434dfa6db77846f2793149d8e73f800fa2e41f52b8eac3c5d" +dependencies = [ + "bitflags 2.8.0", + "js-sys", + "web-sys", ] [[package]] @@ -5610,7 +6211,7 @@ version = "23.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "610f6ff27778148c31093f3b03abc4840f9636d58d597ca2f5977433acfe0068" dependencies = [ - "bitflags 2.6.0", + "bitflags 2.8.0", "js-sys", "web-sys", ] @@ -5670,13 +6271,32 @@ 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 = "e48a53791691ab099e5e2ad123536d0fff50652600abaf43bbf952894110d0be" +dependencies = [ + "windows-core 0.52.0", + "windows-targets 0.52.6", +] + [[package]] name = "windows" version = "0.58.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "dd04d41d93c4992d421894c18c8b43496aa748dd4c081bac0dc93eb0489272b6" dependencies = [ - "windows-core", + "windows-core 0.58.0", + "windows-targets 0.52.6", +] + +[[package]] +name = "windows-core" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "33ab640c8d7e35bf8ba19b884ba838ceb4fba93a4e8c65a9059d08afcfc683d9" +dependencies = [ "windows-targets 0.52.6", ] @@ -5701,7 +6321,7 @@ checksum = "2bbd5b46c938e506ecbce286b6628a02171d56153ba733b6c741fc627ec9579b" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.98", ] [[package]] @@ -5712,7 +6332,7 @@ checksum = "053c4c462dc91d3b1504c6fe5a726dd15e216ba718e84a0e46a88fbe5ded3515" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.98", ] [[package]] @@ -5950,14 +6570,14 @@ checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec" [[package]] name = "winit" -version = "0.30.8" +version = "0.30.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f5d74280aabb958072864bff6cfbcf9025cf8bfacdde5e32b5e12920ef703b0f" +checksum = "a809eacf18c8eca8b6635091543f02a5a06ddf3dad846398795460e6e0ae3cc0" dependencies = [ "ahash", "android-activity", "atomic-waker", - "bitflags 2.6.0", + "bitflags 2.8.0", "block2", "bytemuck", "calloop", @@ -6002,9 +6622,9 @@ dependencies = [ [[package]] name = "winnow" -version = "0.6.20" +version = "0.7.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "36c1fec1a2bb5866f07c25f68c26e565c4c200aebb96d7e55710c19d3e8ac49b" +checksum = "86e376c75f4f43f44db463cf729e0d3acbf954d13e22c51e26e4c264b4ab545f" dependencies = [ "memchr", ] @@ -6025,13 +6645,22 @@ version = "0.0.19" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d135d17ab770252ad95e9a872d365cf3090e3be864a34ab46f48555993efc904" +[[package]] +name = "wio" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5d129932f4644ac2396cb456385cbf9e63b5b30c6e8dc4820bdca4eb082037a5" +dependencies = [ + "winapi", +] + [[package]] name = "wit-bindgen-rt" version = "0.33.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3268f3d866458b787f390cf61f4bbb563b922d091359f9608842999eaee3943c" dependencies = [ - "bitflags 2.6.0", + "bitflags 2.8.0", ] [[package]] @@ -6090,7 +6719,7 @@ version = "0.4.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d039de8032a9a8856a6be89cea3e5d12fdd82306ab7c94d74e6deab2460651c5" dependencies = [ - "bitflags 2.6.0", + "bitflags 2.8.0", "dlib", "log", "once_cell", @@ -6105,9 +6734,9 @@ checksum = "b9cc00251562a284751c9973bace760d86c0276c471b4be569fe6b068ee97a56" [[package]] name = "xml-rs" -version = "0.8.24" +version = "0.8.25" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ea8b391c9a790b496184c29f7f93b9ed5b16abb306c05415b68bcc16e4d06432" +checksum = "c5b940ebc25896e71dd073bad2dbaa2abfe97b0a391415e22ad1326d9c54e3c4" [[package]] name = "xmlwriter" @@ -6115,6 +6744,23 @@ version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ec7a2a501ed189703dba8b08142f057e887dfc4b2cc4db2d343ac6376ba3e0b9" +[[package]] +name = "yazi" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c94451ac9513335b5e23d7a8a2b61a7102398b8cca5160829d313e84c9d98be1" + +[[package]] +name = "yeslogic-fontconfig-sys" +version = "6.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "503a066b4c037c440169d995b869046827dbc71263f6e8f3be6d77d4f3229dbd" +dependencies = [ + "dlib", + "once_cell", + "pkg-config", +] + [[package]] name = "yoke" version = "0.7.5" @@ -6135,10 +6781,16 @@ checksum = "2380878cad4ac9aac1e2435f3eb4020e8374b5f13c296cb75b4620ff8e229154" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.98", "synstructure", ] +[[package]] +name = "zeno" +version = "0.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dd15f8e0dbb966fd9245e7498c7e9e5055d9e5c8b676b95bd67091cd11a1e697" + [[package]] name = "zerocopy" version = "0.7.35" @@ -6151,11 +6803,11 @@ dependencies = [ [[package]] name = "zerocopy" -version = "0.8.14" +version = "0.8.17" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a367f292d93d4eab890745e75a778da40909cab4d6ff8173693812f79c4a2468" +checksum = "aa91407dacce3a68c56de03abe2760159582b846c6a4acd2f456618087f12713" dependencies = [ - "zerocopy-derive 0.8.14", + "zerocopy-derive 0.8.17", ] [[package]] @@ -6166,18 +6818,18 @@ checksum = "fa4f8080344d4671fb4e831a13ad1e68092748387dfc4f55e356242fae12ce3e" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.98", ] [[package]] name = "zerocopy-derive" -version = "0.8.14" +version = "0.8.17" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d3931cb58c62c13adec22e38686b559c86a30565e16ad6e8510a337cedc611e1" +checksum = "06718a168365cad3d5ff0bb133aad346959a2074bd4a85c121255a11304a8626" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.98", ] [[package]] @@ -6197,7 +6849,7 @@ checksum = "595eed982f7d355beb85837f651fa22e90b3c044842dc7f2c2842c086f295808" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.98", "synstructure", ] @@ -6226,7 +6878,7 @@ checksum = "6eafa6dfb17584ea3e2bd6e76e0cc15ad7af12b09abdd1ca55961bed9b1063c6" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.98", ] [[package]] diff --git a/Cargo.toml b/Cargo.toml index e9a0a4d97..426263ca4 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -48,6 +48,7 @@ gosub_cairo = { version = "0.1.1", path = "./crates/gosub_cairo", features = [], gosub_taffy = { version = "0.1.1", path = "./crates/gosub_taffy", features = [], registry = "gosub" } gosub_net = { version = "0.1.1", path = "./crates/gosub_net", features = [], registry = "gosub" } gosub_instance = { version = "0.1.0", path = "./crates/gosub_instance", features = [], registry = "gosub" } +gosub_fontmanager = { version = "0.1.0", path = "./crates/gosub_fontmanager", registry = "gosub" } # Dependencies are needed for gosub_engine itself, and some of the binaries in src/bin. cookie = { version = "0.18.1", features = ["secure", "private"] } url = "2.5.4" @@ -62,7 +63,7 @@ walkdir = "2.5.0" [dev-dependencies] criterion = { version = "0.5", features = ["html_reports"] } test-case = "3.3.1" -gtk4 = "0.9.5" +gtk4 = "0.8.0" winit = "0.30.8" cookie = { version = "0.18.1", features = ["secure", "private"] } url = "2.5.4" diff --git a/cloc-it.sh b/cloc-it.sh new file mode 100644 index 000000000..b1b05e69d --- /dev/null +++ b/cloc-it.sh @@ -0,0 +1,36 @@ +#!/bin/bash + +mkdir -p weekly_loc +> loc_per_filetype.csv # Create output CSV file + +echo "Week,FileType,LOC" >> loc_per_filetype.csv + +while read week; do + + git checkout main --quiet + + # Get the last commit of the week$ + commit=$(git log --before="$week 23:59:59" --pretty=format:"%H" -n 1) + echo "Commit: $commit $week" + + if [ ! -z "$commit" ]; then + # Check out the commit + git checkout $commit --quiet + + # Run cloc and get LoC per file type + files=$(git ls-files) + cloc_output=$(cloc --quiet --json $files) + echo "$cloc_output" > weekly_loc/$week.json + + # Parse JSON to get LoC per file type + file_types=$(echo "$cloc_output" | jq -r 'to_entries[] | select(.key != "header" and .key != "SUM") | .key') + for file_type in $file_types; do + loc=$(echo "$cloc_output" | jq -r ".\"$file_type\".code") + echo "$week,$file_type,$loc" >> loc_per_filetype.csv + done + fi +done < weeks.txt + +# Return to the main branch +git checkout main --quiet + diff --git a/crates/gosub_cairo/Cargo.toml b/crates/gosub_cairo/Cargo.toml index 622fb6b86..6db79bb91 100644 --- a/crates/gosub_cairo/Cargo.toml +++ b/crates/gosub_cairo/Cargo.toml @@ -10,17 +10,20 @@ license = "MIT" gosub_shared = { version = "0.1.1", registry = "gosub", path = "../gosub_shared" } gosub_interface = { version = "0.1.2", registry = "gosub", path = "../gosub_interface", features = [] } gosub_svg = { version = "0.1.1", registry = "gosub", path = "../gosub_svg", features = ["resvg"] } +gosub_renderer = { version = "0.1.1", registry = "gosub", path = "../gosub_renderer" } +gosub_fontmanager = { version = "0.1.0", path = "../gosub_fontmanager", registry = "gosub" } image = "0.25.2" smallvec = "1.13.2" anyhow = "1.0.89" futures = "0.3.30" -cairo-rs = "0.20.5" -cairo-sys-rs = { version = "0.20.0", features = ["freetype"] } + +cairo-rs = { version = "0.19.4", features = ["use_glib", "freetype"] } kurbo = "0.11.1" peniko = "0.3.1" skrifa = "0.26.5" log = "0.4.22" uuid = { version = "1.12.1", features = ["v4"] } -freetype-rs = "0.38.0" parley = "0.2.0" once_cell = "1.20.2" +pango = "0.19.8" +pangocairo = "0.19.8" \ No newline at end of file diff --git a/crates/gosub_cairo/src/debug/text.rs b/crates/gosub_cairo/src/debug/text.rs index 6666efce6..92e2f6cc6 100644 --- a/crates/gosub_cairo/src/debug/text.rs +++ b/crates/gosub_cairo/src/debug/text.rs @@ -2,31 +2,37 @@ use crate::elements::brush::GsBrush; use crate::elements::color::GsColor; use crate::elements::transform::GsTransform; use crate::Scene; +use gosub_interface::font::{FontInfo, FontStyle}; use gosub_interface::render_backend::{Brush as _, Color as _, Transform as _}; use gosub_shared::types::Point; -use gosub_shared::ROBOTO_FONT; use peniko::{Blob, Font}; use std::sync::{Arc, LazyLock}; -static FONT: LazyLock = LazyLock::new(|| Font::new(Blob::new(Arc::new(ROBOTO_FONT)), 0)); - pub fn render_text_simple(scene: &mut Scene, text: &str, point: Point, font_size: f32) { - render_text(scene, text, point, font_size, &FONT, &GsBrush::color(GsColor::BLACK)); + // render_text(scene, text, point, font_size, &font_info, &GsBrush::color(GsColor::BLACK)); } -pub fn render_text(scene: &mut Scene, text: &str, point: Point, font_size: f32, font: &Font, brush: &GsBrush) { +pub fn render_text( + scene: &mut Scene, + text: &str, + point: Point, + font_size: f32, + font_info: &impl FontInfo, + brush: &GsBrush, +) { let transform = GsTransform::translate(point.x.into(), point.y.into()); - render_text_var( - scene, - text, - font_size, - font, - brush, - transform, - GsTransform::IDENTITY, - &[], - ) + // render_text_var( + // scene, + // text, + // font_size, + // + // font_info, + // brush, + // transform, + // GsTransform::IDENTITY, + // &[], + // ) } #[allow(clippy::too_many_arguments)] diff --git a/crates/gosub_cairo/src/elements/rect.rs b/crates/gosub_cairo/src/elements/rect.rs index 10ca85977..bd3fd78b3 100644 --- a/crates/gosub_cairo/src/elements/rect.rs +++ b/crates/gosub_cairo/src/elements/rect.rs @@ -31,6 +31,8 @@ impl GsRect { } pub(crate) fn render(obj: &RenderRect, cr: &cairo::Context) { + // info!(target: "cairo", "GsRect::render"); + let x = obj.rect.x; let y = obj.rect.y; let width = obj.rect.width; diff --git a/crates/gosub_cairo/src/elements/text.rs b/crates/gosub_cairo/src/elements/text.rs index 336b5c4df..7cdcc9566 100644 --- a/crates/gosub_cairo/src/elements/text.rs +++ b/crates/gosub_cairo/src/elements/text.rs @@ -1,4 +1,14 @@ use crate::CairoBackend; +use cairo::{freetype, FontFace}; +use std::borrow::Borrow; +use std::cell::LazyCell; +use std::rc::Rc; +use std::sync::Arc; + +use crate::elements::brush::GsBrush; +use crate::elements::color::GsColor; +use cairo::freetype::{Face, Library}; +use gosub_interface::font::FontBlob; use gosub_interface::layout::{Decoration, TextLayout}; use gosub_interface::render_backend::{RenderText, Text as TText}; use gosub_shared::font::{Glyph, GlyphID}; @@ -7,114 +17,21 @@ use gosub_shared::geo::{NormalizedCoord, Point, FP}; use std::cell::RefCell; use std::collections::HashMap; use std::rc::Rc; - -use crate::elements::brush::GsBrush; -use crate::elements::color::GsColor; -use freetype::{Face, Library}; -use gosub_shared::ROBOTO_FONT; -use kurbo::Stroke; -use log::info; -use once_cell::sync::Lazy; -use parley::fontique::{FamilyId, SourceKind}; -use parley::{FontContext, GenericFamily}; - -/// Font manager that keeps track of fonts and faces -struct GosubFontContext { - /// Freetype library. Should be kept alive as long as any face is alive. - _library: Library, - /// Font context for parley to find fonts - font_ctx: FontContext, - /// Cache of any loaded font faces - face_cache: HashMap, - /// Default font face to use when a font cannot be found - default_face: Face, -} - -impl GosubFontContext { - /// Finds the face for the given family name, or returns the default face if no font is found. - fn find_face_family(&mut self, family: &str) -> &mut Face { - info!("Finding face for family: {}", family); - - // See if we already got the face in cache - if self.face_cache.contains_key(family) { - info!("Face found in cache"); - return self.face_cache.get_mut(family).expect("Face not found in cache"); - } - - // Parse the family name into a GenericFamily enum - let gf = GenericFamily::parse(family).unwrap_or(GenericFamily::SansSerif); - - // Find all the fonts for this family - let fids: Vec = self.font_ctx.collection.generic_families(gf).collect(); - if fids.is_empty() { - info!("No family found for family: {}", family); - return &mut self.default_face; - } - - // We only use the first font in the family - match self.font_ctx.collection.family(fids[0]) { - Some(f) => { - info!("Face found for family: {:?}", f.fonts()); - - // This first font can have multiple fonts (e.g. regular, bold, italic, etc.) - for font in f.fonts() { - match &font.source().kind { - SourceKind::Memory(blob) => { - info!("Loading font face from memory"); - let rc = Rc::new(blob.data().to_vec()); - - let face = self._library.new_memory_face(rc, 0).expect("Failed to load font face"); - self.face_cache.insert(family.to_string(), face); - } - SourceKind::Path(path) => { - info!("Loading font face from path {}", path.to_str().expect("path to string")); - - let face = self - ._library - .new_face(path.to_str().expect("path to string"), 0) - .expect("Failed to load font face"); - self.face_cache.insert(family.to_string(), face); - } - } - } - } - None => { - info!("No face found for family: {}", family); - } - } - - &mut self.default_face - } -} +use log::{info, warn}; thread_local! { - /// We use a thread-local lazy static to ensure the font context is initialized once per thread - /// and is dropped when the thread exits. We need this because the FreeType library cannot be dropped - /// while any faces are still alive, so all is managed within this struct. - static LIB_FONT_FACE: Lazy> = Lazy::new(|| { - let lib = Library::init().expect("Failed to initialize FreeType"); - let rc = Rc::new(ROBOTO_FONT.to_vec()); - let default_face = lib.new_memory_face(rc, 0).expect("Failed to load font face"); - - // The FontContext struct holds the lib, ensuring it lives as long as all loaded faces - RefCell::new(GosubFontContext { - _library: lib, - font_ctx: FontContext::new(), - face_cache: HashMap::new(), - default_face, - }) + static LIB_FONT_FACE: LazyCell = LazyCell::new(|| { + Library::init().expect("Failed to initialize FreeType") }); } #[allow(unused)] #[derive(Clone, Debug)] pub struct GsText { - // List of glyphs we need to show + // List of positioned glyphs with font info glyphs: Vec, - // Actual utf-8 text (we don't have this yet) - text: String, - // // Font we need to display (we need to have more info, like font familty, weight, etc.) - // font: peniko::Font, + // Font we need to display + font: FontBlob, // Font size fs: FP, // List of coordinates for each glyph (?) @@ -126,8 +43,9 @@ pub struct GsText { } impl TText for GsText { - fn new(layout: &TL) -> Self { - // let font = layout.font().clone().into(); + // fn new(layout: &TL) -> Self { + fn new(layout: &impl TextLayout) -> Self { + let font = layout.font().clone(); let fs = layout.font_size(); let glyphs = layout @@ -142,8 +60,7 @@ impl TText for GsText { Self { glyphs, - text: String::new(), - // font, + font, fs, coords: layout.coords().to_vec(), decoration: layout.decorations().clone(), @@ -154,32 +71,17 @@ impl TText for GsText { impl GsText { pub(crate) fn render(obj: &RenderText, cr: &cairo::Context) { - // let brush = &render.brush; - // let style: StyleRef = Fill::NonZero.into(); - // - // let transform = render.transform.map(|t| t).unwrap_or(Transform::IDENTITY); - // let brush_transform = render.brush_transform.map(|t| t); - let base_x = obj.rect.x; let base_y = obj.rect.y; cr.move_to(base_x, base_y); - // Setup brush for rendering text - - // This should be moved to the GosubFontContext::get_cairo_font_face(family: &str) method) - let font_face = unsafe { - LIB_FONT_FACE.with(|ctx_ref| { - let mut ctx = ctx_ref.borrow_mut(); - - let ft_face = ctx.find_face_family("sans-serif"); - let ft_face_ptr = ft_face.raw_mut() as *mut _ as *mut std::ffi::c_void; - let ff = cairo::ffi::cairo_ft_font_face_create_for_ft_face(ft_face_ptr, 0); - cairo::FontFace::from_raw_full(ff) - }) - }; - cr.set_font_face(&font_face); - for text in &obj.text { + let Ok(font_face) = create_memory_font_face(&text.font) else { + warn!("Could not convert memory face"); + continue; + }; + cr.set_font_face(&font_face); + GsBrush::render(&obj.brush, cr); cr.move_to(base_x + text.offset.x as f64, base_y + text.offset.y as f64); cr.set_font_size(text.fs.into()); @@ -201,7 +103,7 @@ impl GsText { // Set decoration (underline, overline, line-through) { let decoration = &text.decoration; - let _stroke = Stroke::new(decoration.width as f64); + let _stroke = kurbo::Stroke::new(decoration.width as f64); let c = decoration.color; let brush = GsBrush::solid(GsColor::rgba32(c.0, c.1, c.2, 1.0)); @@ -234,3 +136,42 @@ impl GsText { } } } + +#[derive(Clone)] +struct BlobWrapper(Arc>); + +impl Borrow<[u8]> for BlobWrapper { + fn borrow(&self) -> &[u8] { + self.0.as_ref().as_ref() + } +} + +/// Creates a cairo font-face from the font data (blob of raw fontdata). We do this by converting +/// the blob into an in-memory freetype face and then into a cairo font face. +fn create_memory_font_face(font: &FontBlob) -> Result { + static FT_FACE_KEY: cairo::UserDataKey> = cairo::UserDataKey::new(); + + // Create an in-memory font face from the font data + let face = LIB_FONT_FACE.with(|lib| { + lib.new_memory_face2(BlobWrapper(font.data.clone()), font.index as isize) + .expect("Failed to create memory face") + }); + let mut face = face.clone(); + + // SAFETY: The user data entry keeps `freetype::face::Face` alive + // until the FontFace is dropped. + let font_face = unsafe { + FontFace::from_raw_full(cairo::ffi::cairo_ft_font_face_create_for_ft_face( + face.raw_mut() as freetype::ffi::FT_Face as *mut _, + 0, + )) + }; + font_face.set_user_data(&FT_FACE_KEY, Rc::new(face))?; + let status = unsafe { cairo::ffi::cairo_font_face_status(font_face.to_raw_none()) }; + match status { + cairo::ffi::STATUS_SUCCESS => {} + err => return Err(err.into()), + }; + + Ok(font_face) +} diff --git a/crates/gosub_cairo/src/lib.rs b/crates/gosub_cairo/src/lib.rs index cda5baaa2..56886668a 100644 --- a/crates/gosub_cairo/src/lib.rs +++ b/crates/gosub_cairo/src/lib.rs @@ -36,13 +36,14 @@ impl RenderBackend for CairoBackend { type BorderSide = GsBorderSide; type BorderRadius = GsBorderRadius; type Transform = GsTransform; - type Text = GsText; type Gradient = GsGradient; type Color = GsColor; type Image = GsImage; type Brush = GsBrush; type Scene = Scene; + type Text = GsText; type SVGRenderer = gosub_svg::resvg::Resvg; + type FontManager = gosub_fontmanager::FontManager; type ActiveWindowData<'a> = ActiveWindowData; type WindowData<'a> = WindowData; diff --git a/crates/gosub_cairo/src/scene.rs b/crates/gosub_cairo/src/scene.rs index 6cc1c1dcd..04e0c64a2 100644 --- a/crates/gosub_cairo/src/scene.rs +++ b/crates/gosub_cairo/src/scene.rs @@ -3,6 +3,7 @@ use crate::elements::rect::GsRect; use crate::elements::text::GsText; use crate::elements::transform::GsTransform; use crate::CairoBackend; +use gosub_interface::font::FontBlob; use gosub_interface::render_backend::{ Point, Radius, RenderBackend, RenderRect, RenderText, Scene as TScene, Transform as TTransform, FP, }; @@ -18,8 +19,8 @@ pub enum SceneCommand { // Draw a simple text without too much decoration and in a single font / color SimpleText { text: String, + font: FontBlob, pos: Point, - size: FP, }, // Group a list of commands together on a certain transform (translation, rotation, scale) Group { @@ -36,8 +37,17 @@ impl SceneCommand { } } - fn simple_text(text: String, pos: Point, size: FP) -> SceneCommand { - SceneCommand::SimpleText { text, pos, size } + fn simple_text(_text: String, _pos: Point, _size: FP) -> SceneCommand { + // let mut f = GsRenderFont::default(); + // f.set_size(size as f32); + + todo!() + + // SceneCommand::SimpleText { + // text, + // font: f, + // pos, + // } } } @@ -51,11 +61,11 @@ impl Debug for SceneCommand { .field("children", &children) .field("transform", &transform) .finish(), - SceneCommand::SimpleText { text, pos, size } => f + SceneCommand::SimpleText { text, font, pos } => f .debug_struct("SimpleText") .field("text", text) + .field("font", font) .field("pos", pos) - .field("size", size) .finish(), } } @@ -100,18 +110,21 @@ impl Scene { SceneCommand::Text(text) => { GsText::render(text, cr); } - SceneCommand::SimpleText { text, pos, size } => { - let face = - &cairo::FontFace::toy_create("sans-serif", cairo::FontSlant::Normal, cairo::FontWeight::Bold) - .unwrap(); - cr.set_font_face(face); - let fs: f32 = *size; - cr.set_font_size(fs as f64); - - cr.move_to(pos.x.into(), pos.y.into()); - cr.set_source_rgb(0.0, 0.0, 1.0); - - _ = cr.show_text(text); + SceneCommand::SimpleText { + text: _, + font: _, + pos: _, + } => { + // let pango_ctx = pangocairo::functions::create_context(cr); + // let layout = Layout::new(&pango_ctx); + // + // let font_desc = font.get_font_description(); + // layout.set_font_description(Some(&font_desc)); + // layout.set_text(text); + // + // cr.move_to(pos.x.into(), pos.y.into()); + // cr.set_source_rgb(0.0, 0.0, 1.0); + // pangocairo::functions::show_layout(cr, &layout); } } } diff --git a/crates/gosub_fontmanager/Cargo.toml b/crates/gosub_fontmanager/Cargo.toml new file mode 100644 index 000000000..2a7f64695 --- /dev/null +++ b/crates/gosub_fontmanager/Cargo.toml @@ -0,0 +1,51 @@ +[package] +name = "gosub_fontmanager" +version = "0.1.0" +edition = "2021" +authors = ["Gosub Community "] +description = "Generic font manager" +license = "MIT" + +[[bin]] +name = "display-fonts" +path = "src/bin/display-fonts.rs" + +[[bin]] +name = "generate-svg" +path = "src/bin/generate-svg.rs" + +[[bin]] +name = "gtk-test" +path = "src/bin/gtk-test.rs" + +[[bin]] +name = "gtk2-test" +path = "src/bin/gtk2-test.rs" + +[[bin]] +name = "vello-test" +path = "src/bin/vello-test.rs" + +[dependencies] +gosub_interface = { version = "0.1.1", registry = "gosub", path = "../gosub_interface", features = [] } +colog = "^1.3" +log = "0.4.22" +anyhow = "1.0.95" +prettytable = "0.10.0" +vello = "0.3.0" +winit = "0.30.7" +pollster = "0.4.0" +image = "0.25.5" +swash = "0.1.19" +lazy_static = "1.5.0" +rand = "0.9.0-beta.1" +cow-utils = "0.1.3" + +gtk4 = { version = "0.8.0", features = ["v4_6"] } +parley = "0.2.0" +font-kit = { version = "0.14.2" } +freetype-rs = "0.36.0" +# 19.4 is the latest version that works with the current version of font-kit (0.14.2) +cairo-rs = { version = "0.19.4", features = ["use_glib", "freetype"] } + +pangocairo = "0.19.8" \ No newline at end of file diff --git a/crates/gosub_fontmanager/src/bin/display-fonts.rs b/crates/gosub_fontmanager/src/bin/display-fonts.rs new file mode 100644 index 000000000..c6b881a90 --- /dev/null +++ b/crates/gosub_fontmanager/src/bin/display-fonts.rs @@ -0,0 +1,55 @@ +use cow_utils::CowUtils; +use gosub_fontmanager::FontManager; +use prettytable::{Attr, Cell, Row, Table}; + +fn main() { + colog::init(); + + let arg = std::env::args().nth(1); + let binding = arg.unwrap_or("".into()); + let pattern = binding.as_str(); + + let manager = FontManager::new(); + render_table(&manager, pattern); +} + +fn render_table(manager: &FontManager, family: &str) { + let mut table = Table::new(); + table.set_format(*prettytable::format::consts::FORMAT_NO_BORDER_LINE_SEPARATOR); + table.set_titles(Row::new(vec![ + Cell::new("Family").with_style(Attr::Bold), + Cell::new("Style").with_style(Attr::Bold), + Cell::new("Weight").with_style(Attr::Bold), + Cell::new("Stretch").with_style(Attr::Bold), + Cell::new("Monospaced").with_style(Attr::Bold), + Cell::new("Path").with_style(Attr::Bold), + Cell::new("Index").with_style(Attr::Bold), + ])); + + for info in manager.available_fonts() { + if !family.is_empty() { + let fam = info.family.cow_to_ascii_lowercase(); + let family_lower = family.cow_to_ascii_lowercase(); + if !fam.contains(&*family_lower) { + continue; + } + } + + table.add_row(Row::new(vec![ + Cell::new(&info.family), + Cell::new(&format!("{}", &info.style)), + Cell::new(&info.weight.to_string()), + Cell::new(&info.stretch.to_string()), + Cell::new(&info.monospaced.to_string()), + if info.path.is_some() { + Cell::new(info.path.clone().unwrap().to_str().unwrap()) + } else { + Cell::new("N/A") + }, + Cell::new(&info.index.unwrap_or(0).to_string()), + ])); + } + + table.printstd(); + println!("\n\n\n"); +} diff --git a/crates/gosub_fontmanager/src/bin/generate-svg.rs b/crates/gosub_fontmanager/src/bin/generate-svg.rs new file mode 100644 index 000000000..65491ca34 --- /dev/null +++ b/crates/gosub_fontmanager/src/bin/generate-svg.rs @@ -0,0 +1,131 @@ +use anyhow::anyhow; +use freetype::Library; +use gosub_fontmanager::FontManager; +use gosub_interface::font::FontStyle; + +const TEST_STRING: &str = r"A B C D E F G H I J K L M N O P Q R S T U V W X Y Z +a b c d e f g h i j k l m n o p q r s t u v w x y z +0 1 2 3 4 5 6 7 8 9 ( ) $ % @ & ¢ € [ \ ] ^ _ ` { | } ~ < > # = + - * / : ; , . ! ? +¡ ¿ ˆ ˜ ¨ ´ ` ˘ ˙ ˚ ˝ ˛ ˇ ˆ ˇ ˘ ˙ ˚ ˛ ˜ ˝ ˇ ˘ ˙ ˚ ˛ ˜ ˝ ˇ ˘ ˙ ˚ ˛ ˜ ˝ +À Á Â Ã Ä Å Æ Ç È É Ê Ë Ì Í Î Ï Ð Ñ Ò Ó Ô Õ Ö Ø Ù Ú Û Ü Ý Þ ß +à á â ã ä å æ ç è é ê ë ì í î ï ð ñ ò ó ô õ ö ø ù ú û ü ý þ ÿ +Ā ā Ă ă Ą ą Ć ć Ĉ ĉ Ċ ċ Č č Ď ď Đ đ Ē ē Ĕ ĕ Ė ė Ę ę Ě ě Ĝ ĝ Ğ ğ +Ġ ġ Ģ ģ Ĥ ĥ Ħ ħ Ĩ ĩ Ī ī Ĭ ĭ Į į İ ı IJ ij Ĵ ĵ Ķ ķ ĸ Ĺ ĺ Ļ ļ Ľ ľ +Ŀ ŀ Ł ł Ń ń Ņ ņ Ň ň ʼn Ŋ ŋ Ō ō Ŏ ŏ Ő ő Œ œ Ŕ ŕ Ŗ ŗ Ř ř Ś ś Ŝ ŝ +Ş ş Š š Ţ ţ Ť ť Ŧ ŧ Ũ ũ Ū ū Ŭ ŭ Ů ů Ű ű Ų ų Ŵ ŵ Ŷ ŷ Ÿ Ź ź Ż ż Ž ž +ſ ƀ Ɓ Ƃ ƃ Ƅ ƅ Ɔ Ƈ ƈ Ɖ Ɗ Ƌ ƌ ƍ Ǝ Ə Ɛ Ƒ ƒ Ɠ Ɣ ƕ Ɩ Ɨ Ƙ ƙ ƚ ƛ Ɯ Ɲ ƞ Ɵ +Ơ ơ Ƣ ƣ Ƥ ƥ Ʀ Ƨ ƨ Ʃ ƪ ƫ Ƭ ƭ Ʈ Ư ư Ʊ Ʋ Ƴ ƴ Ƶ ƶ Ʒ Ƹ ƹ ƺ ƻ Ƽ ƽ ƾ ƿ +ǀ ǁ ǂ ǃ DŽ Dž dž LJ Lj lj NJ Nj nj Ǎ ǎ Ǐ ǐ Ǒ ǒ Ǔ ǔ Ǖ ǖ Ǘ ǘ Ǚ ǚ Ǜ ǜ ǝ +Ǟ ǟ Ǡ ǡ Ǣ ǣ Ǥ ǥ Ǧ ǧ Ǩ ǩ Ǫ ǫ Ǭ ǭ Ǯ ǯ ǰ DZ Dz dz Ǵ ǵ Ƕ Ƿ Ǹ ǹ Ǻ ǻ +Ǽ ǽ Ǿ ǿ Ȁ ȁ Ȃ ȃ Ȅ ȅ Ȇ ȇ Ȉ ȉ Ȋ ȋ Ȍ ȍ Ȏ ȏ Ȑ ȑ Ȓ ȓ Ȕ ȕ Ȗ ȗ Ș ș Ț ț +Ȝ ȝ Ȟ ȟ Ƞ ȡ Ȣ ȣ Ȥ ȥ Ȧ ȧ Ȩ ȩ Ȫ ȫ Ȭ ȭ Ȯ ȯ Ȱ ȱ Ȳ ȳ ȴ ȵ ȶ ȷ ȸ ȹ Ⱥ +Ȼ ȼ Ƚ Ⱦ ȿ ɀ Ɂ ɂ Ƀ Ʉ Ʌ Ɇ ɇ Ɉ ɉ Ɋ ɋ Ɍ ɍ Ɏ ɏ ɐ ɑ ɒ ɓ ɔ ɕ ɖ ɗ ɘ ə ɚ +ɜ ɝ ɞ ɟ ɠ ɡ ɢ ɣ ɤ ɥ ɦ ɧ ɨ ɩ ɪ ɫ ɬ ɭ ɮ ɯ ɰ ɱ ɲ ɳ ɴ ɵ ɶ ɷ ɸ ɹ +\u{EA84} \u{EA84} \u{EA84} \u{EA84} \u{EA84} +Hello world from the Gosub FontManager system! +"; + +fn main() { + colog::init(); + + let manager = FontManager::new(); + + let arg = std::env::args().nth(1); + let binding = arg.unwrap_or("arial".into()); + let font = binding.as_str(); + + let Some(font_info) = manager.find(&[font], FontStyle::Normal) else { + eprintln!("Font not found: {}", font); + return; + }; + + let library = Library::init().expect("unable to init freetype library"); + let path = font_info + .path + .ok_or_else(|| anyhow!("No path in font info")) + .expect("No path in font info"); + let face = library + .new_face(path, font_info.index.unwrap_or(0) as isize) + .expect("unable to create face"); + + char_to_svg(face, TEST_STRING); +} + +fn char_to_svg(face: freetype::Face, content: &str) { + face.set_char_size(10 * 64, 0, 10, 0).unwrap(); + + println!(""); + println!(""); + println!(""); + + let mut x_pos = -155.0; + let mut y_pos = 10.0; + + for c in content.chars() { + if c == '\n' { + x_pos = -155.0; + y_pos += 10.0; + continue; + } + + if c == ' ' { + x_pos += 2.5; + continue; + } + + let result = face.load_char(c as usize, freetype::face::LoadFlag::NO_SCALE); + if result.is_err() { + continue; + } + + let glyph = face.glyph(); + let metrics = glyph.metrics(); + // let xmin = metrics.horiBearingX - 5; + let width = metrics.width + 10; + // let ymin = -metrics.horiBearingY - 5; + // let height = metrics.height + 10; + // let scale_factor = 10.0 / width as f32; + let scale_factor = 0.0056; + + let outline = glyph.outline().unwrap(); + + for contour in outline.contours_iter() { + let start = contour.start(); + + // dbg!(x_pos, y_pos); + println!( + "", + x_pos - 1.0, + y_pos - 1.0, + scale_factor + ); + + println!( + ""); + println!(""); + } + + x_pos += width as f32 * scale_factor; + x_pos += 1.0; + } + println!(""); +} + +fn draw_curve(curve: freetype::outline::Curve) { + match curve { + freetype::outline::Curve::Line(pt) => println!("L {} {}", pt.x, -pt.y), + freetype::outline::Curve::Bezier2(pt1, pt2) => { + println!("Q {} {} {} {}", pt1.x, -pt1.y, pt2.x, -pt2.y) + } + freetype::outline::Curve::Bezier3(pt1, pt2, pt3) => { + println!("C {} {} {} {} {} {}", pt1.x, -pt1.y, pt2.x, -pt2.y, pt3.x, -pt3.y) + } + } +} diff --git a/crates/gosub_fontmanager/src/bin/gtk-test.rs b/crates/gosub_fontmanager/src/bin/gtk-test.rs new file mode 100644 index 000000000..989139a94 --- /dev/null +++ b/crates/gosub_fontmanager/src/bin/gtk-test.rs @@ -0,0 +1,187 @@ +use gosub_interface::font::{FontInfo, FontManager, FontStyle}; +use gtk4::pango::FontDescription; +use gtk4::prelude::{ + ApplicationExt, ApplicationExtManual, DrawingAreaExt, DrawingAreaExtManual, GtkWindowExt, WidgetExt, +}; +use gtk4::{glib, Application, ApplicationWindow, DrawingArea}; +use pangocairo::functions::{create_layout, show_layout}; +use pangocairo::pango; +use std::cell::RefCell; +use std::rc::Rc; + +const APP_ID: &str = "io.gosub.font-manager.gtk-test"; + +fn main() -> glib::ExitCode { + colog::init(); + + let app = Application::builder().application_id(APP_ID).build(); + app.connect_activate(build_ui); + + app.connect_startup(|_app| { + println!("Setting default icon"); + gtk4::Window::set_default_icon_name(APP_ID); + }); + + app.run() +} + +struct FontContext { + font_manager: gosub_fontmanager::FontManager, + // font_map: pango::FontMap, +} + +fn build_ui(app: &Application) { + let font_manager = gosub_fontmanager::FontManager::new(); + // let font_map = pangocairo::FontMap::new(); + let font_context = FontContext { + font_manager, + // font_map, + }; + + // See if fonts actually exists + let _ = font_context + .font_manager + .find(&["comic sans ms"], FontStyle::Normal) + .expect("Failed to find font Comic Sans MS"); + let _ = font_context + .font_manager + .find(&["Arial"], FontStyle::Normal) + .expect("Failed to find font Arial"); + + let fonts = ["arial", "verdana", "comic sans ms", "webdings"]; + let current_font_idx = Rc::new(RefCell::new(0)); + let current_font_size = Rc::new(RefCell::new(24.0)); + + let font_size_clone = Rc::clone(¤t_font_size); + let font_idx_clone = Rc::clone(¤t_font_idx); + + // Create a window and set the title + let window = ApplicationWindow::builder() + .application(app) + .title("GTK Font Renderer") + .build(); + + let area = DrawingArea::default(); + area.set_hexpand(true); + area.set_vexpand(true); + area.set_draw_func(move |area, gtk_cr, width, _height| { + // Red square to indicate stuff is being drawn on screen + gtk_cr.set_source_rgba(1.0, 0.0, 0.0, 1.0); + gtk_cr.rectangle(0.0, 0.0, 100.0, 100.0); + let _ = gtk_cr.fill(); + + // Layout works nicely with bounding boxes and alignment, but I can't seem to get the font face to render + let layout = create_layout(gtk_cr); + + let idx1 = *font_idx_clone.clone().borrow() % fonts.len(); + let idx2 = (*font_idx_clone.clone().borrow() + 1) % fonts.len(); + let fs = *font_size_clone.borrow(); + + let fi = font_context + .font_manager + .find_font(&[fonts[idx1]], FontStyle::Normal) + .unwrap(); + let desc = FontDescription::from_string(fi.to_description(fs).as_str()); + layout.set_font_description(Some(&desc)); + + layout.set_text(gosub_fontmanager::flatland::TEXT); + layout.set_width(width * pango::SCALE); + layout.set_alignment(pango::Alignment::Center); + + let cur_y = 200; + let mut max_y = cur_y; + + // Create layout + gtk_cr.set_source_rgba(1.0, 0.0, 1.0, 1.0); + gtk_cr.move_to(0.0, cur_y as f64); + show_layout(gtk_cr, &layout); + max_y += layout.pixel_size().1; + + // Nice bounding rectangle around the text + gtk_cr.set_source_rgba(0.0, 0.0, 0.0, 1.0); + gtk_cr.set_line_width(1.0); + gtk_cr.rectangle(0.0, cur_y as f64, width as f64, max_y as f64 - cur_y as f64); + let _ = gtk_cr.stroke(); + + // Add a little bit of padding + max_y += 25; + let cur_y = max_y; + + // Display the next text in a different font + let fi = font_context + .font_manager + .find_font(&[fonts[idx2]], FontStyle::Normal) + .unwrap(); + let desc = FontDescription::from_string(fi.to_description(fs).as_str()); + layout.set_font_description(Some(&desc)); + gtk_cr.set_source_rgba(0.7, 0.2, 0.5, 1.0); + gtk_cr.move_to(0.0, cur_y as f64); + show_layout(gtk_cr, &layout); + max_y += layout.pixel_size().1; + + // Bounding box around the text again + gtk_cr.set_source_rgba(0.0, 1.0, 1.0, 1.0); + gtk_cr.set_line_width(3.0); + gtk_cr.rectangle(0.0, cur_y as f64, width as f64, max_y as f64 - cur_y as f64); + let _ = gtk_cr.stroke(); + + // Get current position and add the layout height. This is the new height of the canvas in this drawing area so + // we can scroll. + area.set_content_height(max_y + 50); + }); + + // Of course, scrolling doesn't work... need to figure out why it doesn't work. + let scroll = gtk4::ScrolledWindow::builder() + .hscrollbar_policy(gtk4::PolicyType::Automatic) + .vscrollbar_policy(gtk4::PolicyType::Automatic) + .child(&area) + .build(); + window.set_child(Some(&scroll)); + + let controller = gtk4::EventControllerKey::new(); + + let font_size_clone = Rc::clone(¤t_font_size); + let font_idx_clone = Rc::new(current_font_idx); + + controller.connect_key_pressed(move |_controller, keyval, _keycode, _state| { + // Check which key was pressed + match keyval { + key if key == gtk4::gdk::Key::a => { + *font_size_clone.borrow_mut() -= 2.0; + if *font_size_clone.borrow() < 2.0 { + *font_size_clone.borrow_mut() = 2.0; + } + println!("Font size: {}", *font_size_clone.borrow()); + area.queue_draw(); + } + key if key == gtk4::gdk::Key::s => { + *font_size_clone.borrow_mut() += 2.0; + println!("Font size: {}", *font_size_clone.borrow()); + area.queue_draw(); + } + key if key == gtk4::gdk::Key::z => { + *font_idx_clone.borrow_mut() += 1; + if *font_idx_clone.borrow() >= fonts.len() { + *font_idx_clone.borrow_mut() = 0; + } + area.queue_draw(); + } + key if key == gtk4::gdk::Key::x => { + if *font_idx_clone.borrow() == 0 { + *font_idx_clone.borrow_mut() = fonts.len() - 1; + } else { + *font_idx_clone.borrow_mut() -= 1; + } + area.queue_draw(); + } + _ => (), + } + + glib::Propagation::Proceed + }); + window.add_controller(controller); + + window.set_default_width(800); + window.set_default_height(600); + window.present(); +} diff --git a/crates/gosub_fontmanager/src/bin/gtk2-test.rs b/crates/gosub_fontmanager/src/bin/gtk2-test.rs new file mode 100644 index 000000000..ce78a3f2f --- /dev/null +++ b/crates/gosub_fontmanager/src/bin/gtk2-test.rs @@ -0,0 +1,317 @@ +use freetype::Library; +use gosub_fontmanager::{FontInfo, FontManager}; +use gosub_interface::font::{FontInfo as _, FontManager as _, FontStyle}; +use gtk4::cairo::{FontFace, Glyph}; +use gtk4::prelude::*; +use gtk4::{glib, Application, ApplicationWindow, DrawingArea}; +use image::Rgba; +use parley::fontique::Weight; +use parley::layout::{Alignment, Layout, PositionedLayoutItem}; +use parley::style::StyleProperty; +use parley::{Font, InlineBox, LayoutContext}; +use std::borrow::Cow; +use std::cell::RefCell; +use std::collections::HashMap; +use std::rc::Rc; + +use rand::Rng; + +#[derive(Clone, Copy, Debug, PartialEq)] +struct ColorBrush { + color: Rgba, +} + +impl Default for ColorBrush { + fn default() -> Self { + Self { + color: Rgba([0, 0, 0, 255]), + } + } +} + +const APP_ID: &str = "io.gosub.font-manager.gtk-test"; + +fn main() -> glib::ExitCode { + colog::init(); + + let app = Application::builder().application_id(APP_ID).build(); + app.connect_activate(build_ui); + + app.connect_startup(|_app| { + println!("Setting default icon"); + gtk4::Window::set_default_icon_name(APP_ID); + }); + + app.run() +} + +struct FontContext { + ft_lib: Rc, + font_face_cache: HashMap, + font_manager: Rc, + parley_context: parley::FontContext, +} + +fn build_ui(app: &Application) { + // Create a window and set the title + let window = ApplicationWindow::builder() + .application(app) + .title("GTK Font Renderer") + .build(); + + // Setup font context so we initialize these things only once and reuse them for each drawing + let ft_lib = Rc::new(Library::init().unwrap()); + let font_face_cache = HashMap::new(); + let font_manager = Rc::new(FontManager::new()); + let parley_context = parley::FontContext::new(); + + let mut font_context = FontContext { + ft_lib, + font_face_cache, + font_manager, + parley_context, + }; + + let fonts = ["arial", "verdana", "comic sans ms", "webdings"]; + let current_font_idx = Rc::new(RefCell::new(0)); + let current_font_size = Rc::new(RefCell::new(24.0)); + + // let text = "Some text here. Let's make it a bit longer so that line wrapping kicks in 😊. And also some اللغة العربية arabic text.\nThis is underline and strikethrough text"; + let text = gosub_fontmanager::flatland::TEXT; + + let font_size_clone = Rc::clone(¤t_font_size); + let font_idx_clone = Rc::clone(¤t_font_idx); + + let area = DrawingArea::default(); + area.set_hexpand(true); + area.set_vexpand(true); + area.set_draw_func(move |area, cr, width, _height| { + let font_info = font_context + .font_manager + .find_font(&[fonts[*font_idx_clone.clone().borrow()]], FontStyle::Normal) + .unwrap(); + + // Draw a random colored square to indicate stuff is being (re)drawn on screen + let mut rng = rand::rng(); + cr.set_source_rgba(rng.random(), rng.random(), rng.random(), 0.5); + cr.rectangle(0.0, 0.0, 100.0, 100.0); + let _ = cr.fill(); + + let mut offset_y = 100.0; + + let layout = create_layout( + &mut font_context, + &font_info, + text, + width as f64, + *font_size_clone.borrow(), + ); + let layout_height = layout.height(); + + draw(&mut font_context, cr, layout, 100.0, offset_y); + + // Add some padding between the different font sizes + offset_y += layout_height + 25.0; + + // The height is now the total height of all the text. We can se the content height to + // be the height of this. Since we are drawing this inside a scrollable window, the scroll + // will kick in when this content height is larger than the window height. + area.set_content_height(offset_y as i32 + 50); + }); + + let scroll = gtk4::ScrolledWindow::builder() + .hscrollbar_policy(gtk4::PolicyType::Automatic) + .vscrollbar_policy(gtk4::PolicyType::Automatic) + .child(&area) + .build(); + window.set_child(Some(&scroll)); + + let controller = gtk4::EventControllerKey::new(); + + let font_size_clone = Rc::clone(¤t_font_size); + let font_idx_clone = Rc::new(current_font_idx); + + controller.connect_key_pressed(move |_controller, keyval, _keycode, _state| { + // Check which key was pressed + match keyval { + key if key == gtk4::gdk::Key::a => { + *font_size_clone.borrow_mut() -= 2.0; + if *font_size_clone.borrow() < 2.0 { + *font_size_clone.borrow_mut() = 2.0; + } + println!("Font size: {}", *font_size_clone.borrow()); + area.queue_draw(); + } + key if key == gtk4::gdk::Key::s => { + *font_size_clone.borrow_mut() += 2.0; + println!("Font size: {}", *font_size_clone.borrow()); + area.queue_draw(); + } + key if key == gtk4::gdk::Key::z => { + *font_idx_clone.borrow_mut() += 1; + if *font_idx_clone.borrow() >= fonts.len() { + *font_idx_clone.borrow_mut() = 0; + } + area.queue_draw(); + } + key if key == gtk4::gdk::Key::x => { + if *font_idx_clone.borrow() == 0 { + *font_idx_clone.borrow_mut() = fonts.len() - 1; + } else { + *font_idx_clone.borrow_mut() -= 1; + } + area.queue_draw(); + } + _ => (), + } + + glib::Propagation::Proceed + }); + window.add_controller(controller); + + // Create a small enough window so we can see scrollbars in the scroll window + window.set_default_width(800); + window.set_default_height(600); + window.present(); +} + +// Draw the layout onto the cairo context +fn draw(fctx: &mut FontContext, cr: >k4::cairo::Context, layout: Layout, offset_x: f32, offset_y: f32) { + // The layouter has cut the text into different lines for us. + for line in layout.lines() { + // Each item is either a run of glyps or an inline box. + for item in line.items() { + match item { + PositionedLayoutItem::GlyphRun(glyph_run) => { + let grun = glyph_run.run(); + + // Find the font that is accompanied by this glyph run, or generate it if it does not exist yet. + let font_id = grun.font().data.id(); + + let font_face = match fctx.font_face_cache.get(&font_id) { + Some(font_face) => font_face, + None => { + let font_face = create_memory_font_face(fctx.ft_lib.clone(), grun.font()); + fctx.font_face_cache.insert(font_id, font_face); + fctx.font_face_cache.get(&font_id).unwrap() + } + }; + cr.set_font_face(font_face); + + cr.set_font_size(glyph_run.run().font_size() as f64); + + // Render per glyph + cr.set_source_rgba(0.0, 0.0, 0.0, 1.0); + + // Glyphs are already positioned by the layouter. However, we must take into account + // that our offset is not 0,0 but offset_x, offset_y. + let glyphs: Vec = glyph_run + .positioned_glyphs() + .map(|g| Glyph::new(g.id as u64, offset_x as f64 + g.x as f64, offset_y as f64 + g.y as f64)) + .collect(); + + // We can show the set of glyphs as a whole now + cr.show_glyphs(glyphs.as_slice()).unwrap(); + } + PositionedLayoutItem::InlineBox(inline_box) => { + cr.rectangle( + (offset_x + inline_box.x) as f64, + (offset_y + inline_box.y) as f64, + inline_box.width as f64, + inline_box.height as f64, + ); + cr.set_source_rgba(0.0, 0.0, 0.0, 1.0); + let _ = cr.stroke(); + + cr.rectangle( + (offset_x + inline_box.x) as f64, + (offset_y + inline_box.y) as f64, + inline_box.width as f64, + inline_box.height as f64, + ); + cr.set_source_rgba(0.0, 0.0, 1.0, 0.25); + let _ = cr.fill(); + } + }; + } + } +} + +/// Creates a cairo font-face from the font data (blob of raw fontdata). We do this by converting +/// the blob into an in-memory freetype face and then into a cairo font face. +fn create_memory_font_face(ft_lib: Rc, font: &Font) -> FontFace { + // Create an in-memory font face from the font data + let face = ft_lib.new_memory_face2(font.data.data(), font.index as isize).unwrap(); + let mut face = face.clone(); + + // SAFETY: The user data entry keeps `freetype::face::Face` alive + // until the FontFace is dropped. + unsafe { + FontFace::from_raw_full(cairo::ffi::cairo_ft_font_face_create_for_ft_face( + face.raw_mut() as cairo::freetype::ffi::FT_Face as *mut _, + 0, + )) + } +} + +fn create_layout( + fctx: &mut FontContext, + font_info: &FontInfo, + text: &str, + width: f64, + font_size: f64, +) -> Layout { + let display_scale = 1.0_f32; + + // Max_advance is the maximum width of a line. We can use this to break lines. We use the width of the window - minus some padding. + let max_advance = Some((width - 100.0) as f32 * display_scale); + + let mut layout_cx = LayoutContext::new(); + + // I'm not 100% clear why the layouter needs a text brush or color? + let text_color = Rgba([0, 0, 0, 255]); + let text_brush = ColorBrush { color: text_color }; + let brush_style = StyleProperty::Brush(text_brush); + let bold_style = StyleProperty::FontWeight(Weight::BOLD); + // let underline_style = StyleProperty::Underline(true); + // let strikethrough_style = StyleProperty::Strikethrough(true); + + // Fetch parley from the font manager. Notice that we ask parley for a context and font stack based on the font-family we + // requested through our font_info. + + let font_stack = parley::FontStack::Single(parley::style::FontFamily::Named(Cow::Borrowed(font_info.family()))); + + let mut builder = layout_cx.ranged_builder(&mut fctx.parley_context, text, display_scale); + builder.push_default(brush_style); + builder.push_default(font_stack); + builder.push_default(StyleProperty::LineHeight(1.0)); + builder.push_default(StyleProperty::FontSize(font_size as f32)); + builder.push_default(StyleProperty::LetterSpacing(1.0)); + + builder.push(bold_style, 6..11); // From index 6 to 11, the text will be bold + // builder.push(underline_style, 141..150); + // builder.push(strikethrough_style, 155..168); + + // Add some inline boxes. They can represent inline images for instance. + builder.push_inline_box(InlineBox { + id: 0, + index: 5, + width: 100.0, + height: 100.0, + }); + + builder.push_inline_box(InlineBox { + id: 1, + index: 50, + width: 100.0, + height: 30.0, + }); + + let mut layout: Layout = builder.build(text); + + // We can now break the lines and align them. + layout.break_all_lines(max_advance); + layout.align(max_advance, Alignment::Start); + + layout +} diff --git a/crates/gosub_fontmanager/src/bin/parley.rs b/crates/gosub_fontmanager/src/bin/parley.rs new file mode 100644 index 000000000..dfc57e61b --- /dev/null +++ b/crates/gosub_fontmanager/src/bin/parley.rs @@ -0,0 +1,265 @@ +use gosub_fontmanager::FontManager; +use gosub_interface::font::FontStyle; +use image::codecs::png::PngEncoder; +use image::{self, Pixel, Rgba, RgbaImage}; +use parley::layout::{Alignment, Glyph, GlyphRun, Layout, PositionedLayoutItem}; +use parley::style::{FontWeight, StyleProperty}; +use parley::{InlineBox, LayoutContext}; +use std::borrow::Cow; +use std::fs::File; +use swash::scale::image::Content; +use swash::scale::{Render, ScaleContext, Scaler, Source, StrikeWith}; +use swash::zeno; +use swash::FontRef; +use zeno::{Format, Vector}; + +#[derive(Clone, Copy, Debug, PartialEq)] +struct ColorBrush { + color: Rgba, +} + +impl Default for ColorBrush { + fn default() -> Self { + Self { + color: Rgba([0, 0, 0, 255]), + } + } +} + +fn main() { + colog::init(); + + let display_scale = 1.0; + let max_advance = Some(400.0 * display_scale); + + let text_color = Rgba([0, 0, 0, 255]); + let bg_color = Rgba([255, 255, 255, 255]); + + let padding = 20; + + let manager = FontManager::new(); + let font_info = manager + .find(&["consolas", "verdana", "comic sans ms", "arial"], FontStyle::Normal) + .expect("font not found"); + + let mut font_context = parley::FontContext::new(); + let font_stack = parley::FontStack::Single(parley::style::FontFamily::Named(Cow::Owned(font_info.family.clone()))); + + let mut layout_cx = LayoutContext::new(); + let mut scale_cx = ScaleContext::new(); + + let text_brush = ColorBrush { color: text_color }; + let brush_style = StyleProperty::Brush(text_brush); + let bold_style = StyleProperty::FontWeight(FontWeight::EXTRA_BLACK); + let underline_style = StyleProperty::Underline(true); + let strikethrough_style = StyleProperty::Strikethrough(true); + + let text = "Some text here. Let's make it a bit longer so that line wrapping kicks in 😊. And also some اللغة العربية arabic text.\nThis is underline and strikethrough text"; + // let text = gosub_fontmanager::flatland::TEXT; + + let mut builder = layout_cx.ranged_builder(&mut font_context, text, display_scale); + builder.push_default(brush_style); + builder.push_default(font_stack); + builder.push_default(StyleProperty::LineHeight(1.3)); + builder.push_default(StyleProperty::FontSize(16.0)); + + builder.push(bold_style, 4..8); + builder.push(underline_style, 141..150); + builder.push(strikethrough_style, 155..168); + + builder.push_inline_box(InlineBox { + id: 0, + index: 0, + width: 50.0, + height: 50.0, + }); + + builder.push_inline_box(InlineBox { + id: 1, + index: 50, + width: 50.0, + height: 30.0, + }); + + let mut layout: Layout = builder.build(text); + + layout.break_all_lines(max_advance); + layout.align(max_advance, Alignment::Start); + + let width = layout.width().ceil() as u32 + (padding * 2); + let height = layout.height().ceil() as u32 + (padding * 2); + let mut img = RgbaImage::from_pixel(width, height, bg_color); + + for line in layout.lines() { + for item in line.items() { + match item { + PositionedLayoutItem::GlyphRun(glyph_run) => { + render_glyph_run(&mut scale_cx, &glyph_run, &mut img, padding); + } + PositionedLayoutItem::InlineBox(inline_box) => { + for x_off in 0..(inline_box.width.floor() as u32) { + for y_off in 0..(inline_box.height.floor() as u32) { + let x = inline_box.x as u32 + x_off + padding; + let y = inline_box.y as u32 + y_off + padding; + img.put_pixel(x, y, Rgba([0, 0, 0, 64])); + } + } + } + }; + } + } + + // Write image to PNG file in examples/_output dir + let output_path = { + let path = std::path::PathBuf::from(file!()); + let mut path = std::fs::canonicalize(path).unwrap(); + path.pop(); + path.pop(); + path.pop(); + path.push("_output"); + drop(std::fs::create_dir(path.clone())); + path.push("swash_render.png"); + path + }; + let output_file = File::create(output_path.clone()).unwrap(); + let png_encoder = PngEncoder::new(output_file); + img.write_with_encoder(png_encoder).unwrap(); + println!("Image written to: {:?}", output_path); +} + +fn render_glyph_run( + context: &mut ScaleContext, + glyph_run: &GlyphRun<'_, ColorBrush>, + img: &mut RgbaImage, + padding: u32, +) { + // Resolve properties of the GlyphRun + let mut run_x = glyph_run.offset(); + let run_y = glyph_run.baseline(); + let style = glyph_run.style(); + let color = style.brush; + + // Get the "Run" from the "GlyphRun" + let run = glyph_run.run(); + + // Resolve properties of the Run + let font = run.font(); + let font_size = run.font_size(); + let normalized_coords = run.normalized_coords(); + + // Convert from parley::Font to swash::FontRef + let font_ref = FontRef::from_index(font.data.as_ref(), font.index as usize).unwrap(); + + // Build a scaler. As the font properties are constant across an entire run of glyphs + // we can build one scaler for the run and reuse it for each glyph. + let mut scaler = context + .builder(font_ref) + .size(font_size) + .hint(true) + .normalized_coords(normalized_coords) + .build(); + + // Iterates over the glyphs in the GlyphRun + for glyph in glyph_run.glyphs() { + let glyph_x = run_x + glyph.x + (padding as f32); + let glyph_y = run_y - glyph.y + (padding as f32); + run_x += glyph.advance; + + render_glyph(img, &mut scaler, color, glyph, glyph_x, glyph_y); + } + + // Draw decorations: underline & strikethrough + let style = glyph_run.style(); + let run_metrics = run.metrics(); + if let Some(decoration) = &style.underline { + let offset = decoration.offset.unwrap_or(run_metrics.underline_offset); + let size = decoration.size.unwrap_or(run_metrics.underline_size); + render_decoration(img, glyph_run, decoration.brush, offset, size, padding); + } + if let Some(decoration) = &style.strikethrough { + let offset = decoration.offset.unwrap_or(run_metrics.strikethrough_offset); + let size = decoration.size.unwrap_or(run_metrics.strikethrough_size); + render_decoration(img, glyph_run, decoration.brush, offset, size, padding); + } +} + +fn render_decoration( + img: &mut RgbaImage, + glyph_run: &GlyphRun<'_, ColorBrush>, + brush: ColorBrush, + offset: f32, + width: f32, + padding: u32, +) { + let y = glyph_run.baseline() - offset; + for pixel_y in y as u32..(y + width) as u32 { + for pixel_x in glyph_run.offset() as u32..(glyph_run.offset() + glyph_run.advance()) as u32 { + img.get_pixel_mut(pixel_x + padding, pixel_y + padding) + .blend(&brush.color); + } + } +} + +fn render_glyph( + img: &mut RgbaImage, + scaler: &mut Scaler<'_>, + brush: ColorBrush, + glyph: Glyph, + glyph_x: f32, + glyph_y: f32, +) { + // Compute the fractional offset + // You'll likely want to quantize this in a real renderer + let offset = Vector::new(glyph_x.fract(), glyph_y.fract()); + + // Render the glyph using swash + let rendered_glyph = Render::new( + // Select our source order + &[ + Source::ColorOutline(0), + Source::ColorBitmap(StrikeWith::BestFit), + Source::Outline, + ], + ) + // Select the simple alpha (non-subpixel) format + .format(Format::Alpha) + // Apply the fractional offset + .offset(offset) + // Render the image + .render(scaler, glyph.id) + .unwrap(); + + let glyph_width = rendered_glyph.placement.width; + let glyph_height = rendered_glyph.placement.height; + let glyph_x = (glyph_x.floor() as i32 + rendered_glyph.placement.left) as u32; + let glyph_y = (glyph_y.floor() as i32 - rendered_glyph.placement.top) as u32; + + match rendered_glyph.content { + Content::Mask => { + let mut i = 0; + let bc = brush.color; + for pixel_y in 0..glyph_height { + for pixel_x in 0..glyph_width { + let x = glyph_x + pixel_x; + let y = glyph_y + pixel_y; + let alpha = rendered_glyph.data[i]; + let color = Rgba([bc[0], bc[1], bc[2], alpha]); + img.get_pixel_mut(x, y).blend(&color); + i += 1; + } + } + } + Content::SubpixelMask => unimplemented!(), + Content::Color => { + let row_size = glyph_width as usize * 4; + for (pixel_y, row) in rendered_glyph.data.chunks_exact(row_size).enumerate() { + for (pixel_x, pixel) in row.chunks_exact(4).enumerate() { + let x = glyph_x + pixel_x as u32; + let y = glyph_y + pixel_y as u32; + let color = Rgba(pixel.try_into().expect("Not RGBA")); + img.get_pixel_mut(x, y).blend(&color); + } + } + } + }; +} diff --git a/crates/gosub_fontmanager/src/bin/vello-test.rs b/crates/gosub_fontmanager/src/bin/vello-test.rs new file mode 100644 index 000000000..f7b5066db --- /dev/null +++ b/crates/gosub_fontmanager/src/bin/vello-test.rs @@ -0,0 +1,178 @@ +use gosub_interface::font::FontStyle; +use std::num::NonZeroUsize; +use std::sync::Arc; +use vello::kurbo::{Affine, Circle, Ellipse, Line, RoundedRect, Stroke}; +use vello::peniko::Color; +use vello::util::{DeviceHandle, RenderContext, RenderSurface}; +use vello::{wgpu, AaConfig, RenderParams, Renderer, RendererOptions, Scene}; +use winit::application::ApplicationHandler; +use winit::event::WindowEvent; +use winit::event_loop::{ActiveEventLoop, ControlFlow, EventLoop}; +use winit::window::{Window, WindowId}; + +const AA_CONFIGS: [AaConfig; 3] = [AaConfig::Area, AaConfig::Msaa8, AaConfig::Msaa16]; + +fn main() { + colog::init(); + + let event_loop = EventLoop::new().unwrap(); + event_loop.set_control_flow(ControlFlow::Poll); + // event_loop.set_control_flow(ControlFlow::Wait); + + let mut app = App::new(); + let _ = event_loop.run_app(&mut app); +} + +struct App<'s> { + render_ctx: RenderContext, + renderer: Option, + surface: Option>, // Surface must be before window for safety during cleanup + window: Option>, +} + +impl App<'_> { + fn new() -> Self { + App { + window: None, + render_ctx: RenderContext::new(), + renderer: None, + surface: None, + } + } +} + +impl ApplicationHandler for App<'_> { + fn resumed(&mut self, event_loop: &ActiveEventLoop) { + if self.window.is_some() { + return; + } + + let fontmanager = gosub_fontmanager::FontManager::new(); + let _ = fontmanager.find(&["Arial"], FontStyle::Normal).unwrap(); + + let mut attribs = Window::default_attributes(); + attribs.title = "Vello Font Test".to_string(); + let window = Arc::new(event_loop.create_window(attribs).unwrap()); + + let size = window.inner_size(); + let surface_future = + self.render_ctx + .create_surface(window.clone(), size.width, size.height, wgpu::PresentMode::AutoVsync); + let surface = pollster::block_on(surface_future).expect("Failed to create surface"); + + let dev_handle = &self.render_ctx.devices[surface.dev_id]; + + let renderer = Renderer::new( + &dev_handle.device, + RendererOptions { + surface_format: Some(surface.format), + use_cpu: false, + antialiasing_support: AA_CONFIGS.iter().copied().collect(), + num_init_threads: NonZeroUsize::new(0), + }, + ); + + // let size = window.inner_size(); + // let config = wgpu::SurfaceConfiguration { + // usage: wgpu::TextureUsages::RENDER_ATTACHMENT, + // format: surface.get_supported_formats(&adapter)[0], + // width: size.width, + // height: size.height, + // present_mode: wgpu::PresentMode::Fifo, + // alpha_mode: wgpu::CompositeAlphaMode::Auto, + // view_formats: vec![], + // }; + // surface.configure(&device, &config); + + // STEP 2: Create a scene + + // let mut scene = Scene::default(); + // scene.append(&Draw::Fill(Fill::new( + // FillStyle::default(), + // Transform::identity(), + // Rect::new(100.0, 100.0, 300.0, 300.0).into_path(), + // None, + // ))); + + self.window = Some(window); + self.surface = Some(surface); + self.renderer = Some(renderer.unwrap()); + } + + fn window_event(&mut self, event_loop: &ActiveEventLoop, _id: WindowId, event: WindowEvent) { + match event { + WindowEvent::CloseRequested => { + event_loop.exit(); + } + WindowEvent::Resized(size) => { + self.render_ctx + .resize_surface(self.surface.as_mut().unwrap(), size.width, size.height); + } + WindowEvent::RedrawRequested => { + let surface = self.surface.as_ref().unwrap(); + + let dev_id = surface.dev_id; + let DeviceHandle { device, queue, .. } = &self.render_ctx.devices[dev_id]; + + let width = surface.config.width; + let height = surface.config.height; + + let surface_texture = surface + .surface + .get_current_texture() + .expect("Failed to get current texture"); + let render_params = RenderParams { + base_color: Color::YELLOW_GREEN, + width, + height, + antialiasing_method: AaConfig::Area, + }; + + let mut scene = Scene::new(); + // Draw an outlined rectangle + let stroke = Stroke::new(6.0); + let rect = RoundedRect::new(10.0, 10.0, 240.0, 240.0, 20.0); + let rect_stroke_color = Color::YELLOW_GREEN; + scene.stroke(&stroke, Affine::IDENTITY, rect_stroke_color, None, &rect); + + // Draw a filled circle + let circle = Circle::new((420.0, 200.0), 120.0); + let circle_fill_color = Color::REBECCA_PURPLE; + scene.fill( + vello::peniko::Fill::NonZero, + Affine::IDENTITY, + circle_fill_color, + None, + &circle, + ); + + // Draw a filled ellipse + let ellipse = Ellipse::new((250.0, 420.0), (100.0, 160.0), -90.0); + let ellipse_fill_color = Color::BLUE_VIOLET; + scene.fill( + vello::peniko::Fill::NonZero, + Affine::IDENTITY, + ellipse_fill_color, + None, + &ellipse, + ); + + // Draw a straight line + let line = Line::new((260.0, 20.0), (620.0, 100.0)); + let line_stroke_color = Color::FIREBRICK; + scene.stroke(&stroke, Affine::IDENTITY, line_stroke_color, None, &line); + + let _ = self.renderer.as_mut().unwrap().render_to_surface( + device, + queue, + &scene, + &surface_texture, + &render_params, + ); + + surface_texture.present(); + } + _ => (), + } + } +} diff --git a/crates/gosub_fontmanager/src/flatland.rs b/crates/gosub_fontmanager/src/flatland.rs new file mode 100644 index 000000000..f84f9b946 --- /dev/null +++ b/crates/gosub_fontmanager/src/flatland.rs @@ -0,0 +1,69 @@ +pub const TEXT: &str = r"§ 1 Of the Nature of Flatland + +I call our world Flatland, not because we call it so, but to make its nature clearer to you, my happy readers, who are privileged to live in Space. + +Imagine a vast sheet of paper on which straight Lines, Triangles, Squares, Pentagons, Hexagons, and other figures, instead of remaining fixed in their places, move freely about, on or in the surface, but without the power of rising above or sinking below it, very much like shadows—only hard with luminous edges—and you will then have a pretty correct notion of my country and countrymen. Alas, a few years ago, I should have said “my universe:” but now my mind has been opened to higher views of things. + +In such a country, you will perceive at once that it is impossible that there should be anything of what you call a “solid” kind; but I dare say you will suppose that we could at least distinguish by sight the Triangles, Squares, and other figures, moving about as I have described them. On the contrary, we could see nothing of the kind, not at least so as to distinguish one figure from another. Nothing was visible, nor could be visible, to us, except Straight Lines; and the necessity of this I will speedily demonstrate. + +Place a penny on the middle of one of your tables in Space; and leaning over it, look down upon it. It will appear a circle. + +But now, drawing back to the edge of the table, gradually lower your eye (thus bringing yourself more and more into the condition of the inhabitants of Flatland), and you will find the penny becoming more and more oval to your view, and at last when you have placed your eye exactly on the edge of the table (so that you are, as it were, actually a Flatlander) the penny will then have ceased to appear oval at all, and will have become, so far as you can see, a straight line. + +The same thing would happen if you were to treat in the same way a Triangle, or a Square, or any other figure cut out from pasteboard. As soon as you look at it with your eye on the edge of the table, you will find that it ceases to appear to you as a figure, and that it becomes in appearance a straight line. Take for example an equilateral Triangle—who represents with us a Tradesman of the respectable class. Figure 1 represents the Tradesman as you would see him while you were bending over him from above; figures 2 and 3 represent the Tradesman, as you would see him if your eye were close to the level, or all but on the level of the table; and if your eye were quite on the level of the table (and that is how we see him in Flatland) you would see nothing but a straight line. + + When I was in Spaceland I heard that your sailors have very similar experiences while they traverse your seas and discern some distant island or coast lying on the horizon. The far-off land may have bays, forelands, angles in and out to any number and extent; yet at a distance you see none of these (unless indeed your sun shines bright upon them revealing the projections and retirements by means of light and shade), nothing but a grey unbroken line upon the water. + +Well, that is just what we see when one of our triangular or other acquaintances comes towards us in Flatland. As there is neither sun with us, nor any light of such a kind as to make shadows, we have none of the helps to the sight that you have in Spaceland. If our friend comes closer to us we see his line becomes larger; if he leaves us it becomes smaller; but still he looks like a straight line; be he a Triangle, Square, Pentagon, Hexagon, Circle, what you will—a straight Line he looks and nothing else. + +You may perhaps ask how under these disadvantagous circumstances we are able to distinguish our friends from one another: but the answer to this very natural question will be more fitly and easily given when I come to describe the inhabitants of Flatland. For the present let me defer this subject, and say a word or two about the climate and houses in our country. + +§ 2 Of the Climate and Houses in Flatland + +As with you, so also with us, there are four points of the compass North, South, East, and West. + +There being no sun nor other heavenly bodies, it is impossible for us to determine the North in the usual way; but we have a method of our own. By a Law of Nature with us, there is a constant attraction to the South; and, although in temperate climates this is very slight—so that even a Woman in reasonable health can journey several furlongs northward without much difficulty—yet the hampering effort of the southward attraction is quite sufficient to serve as a compass in most parts of our earth. Moreover, the rain (which falls at stated intervals) coming always from the North, is an additional assistance; and in the towns we have the guidance of the houses, which of course have their side-walls running for the most part North and South, so that the roofs may keep off the rain from the North. In the country, where there are no houses, the trunks of the trees serve as some sort of guide. Altogether, we have not so much difficulty as might be expected in determining our bearings. + +Yet in our more temperate regions, in which the southward attraction is hardly felt, walking sometimes in a perfectly desolate plain where there have been no houses nor trees to guide me, I have been occasionally compelled to remain stationary for hours together, waiting till the rain came before continuing my journey. On the weak and aged, and especially on delicate Females, the force of attraction tells much more heavily than on the robust of the Male Sex, so that it is a point of breeding, if you meet a Lady on the street, always to give her the North side of the way—by no means an easy thing to do always at short notice when you are in rude health and in a climate where it is difficult to tell your North from your South. + +Windows there are none in our houses: for the light comes to us alike in our homes and out of them, by day and by night, equally at all times and in all places, whence we know not. It was in old days, with our learned men, an interesting and oft-investigate question, “What is the origin of light?” and the solution of it has been repeatedly attempted, with no other result than to crowd our lunatic asylums with the would-be solvers. Hence, after fruitless attempts to suppress such investigations indirectly by making them liable to a heavy tax, the Legislature, in comparatively recent times, absolutely prohibited them. I—alas, I alone in Flatland—know now only too well the true solution of this mysterious problem; but my knowledge cannot be made intelligible to a single one of my countrymen; and I am mocked at—I, the sole possessor of the truths of Space and of the theory of the introduction of Light from the world of three Dimensions—as if I were the maddest of the mad! But a truce to these painful digressions: let me return to our homes. + +The most common form for the construction of a house is five-sided or pentagonal, as in the annexed figure. The two Northern sides RO, OF, constitute the roof, and for the most part have no doors; on the East is a small door for the Women; on the West a much larger one for the Men; the South side or floor is usually doorless. + + Square and triangular houses are not allowed, and for this reason. The angles of a Square (and still more those of an equilateral Triangle,) being much more pointed than those of a Pentagon, and the lines of inanimate objects (such as houses) being dimmer than the lines of Men and Women, it follows that there is no little danger lest the points of a square or triangular house residence might do serious injury to an inconsiderate or perhaps absentminded traveller suddenly running against them: and therefore, as early as the eleventh century of our era, triangular houses were universally forbidden by Law, the only exceptions being fortifications, powder-magazines, barracks, and other state buildings, which is not desirable that the general public should approach without circumspection. + +At this period, square houses were still everywhere permitted, though discouraged by a special tax. But, about three centuries afterwards, the Law decided that in all towns containing a population above ten thousand, the angle of a Pentagon was the smallest house-angle that could be allowed consistently with the public safety. The good sense of the community has seconded the efforts of the Legislature; and now, even in the country, the pentagonal construction has superseded every other. It is only now and then in some very remote and backward agricultural district that an antiquarian may still discover a square house. + +§ 3 Concerning the Inhabitants of Flatland + +The greatest length or breadth of a full grown inhabitant of Flatland may be estimated at about eleven of your inches. Twelve inches may be regarded as a maximum. + +Our Women are Straight Lines. + +Our Soldiers and Lowest Class of Workmen are Triangles with two equal sides, each about eleven inches long, and a base or third side so short (often not exceeding half an inch) that they form at their vertices a very sharp and formidable angle. Indeed when their bases are of the most degraded type (not more than the eighth part of an inch in size), they can hardly be distinguished from Straight lines or Women; so extremely pointed are their vertices. With us, as with you, these Triangles are distinguished from others by being called Isosceles; and by this name I shall refer to them in the following pages. + +Our Middle Class consists of Equilateral or Equal-Sided Triangles. + +Our Professional Men and Gentlemen are Squares (to which class I myself belong) and Five-Sided Figures or Pentagons. + +Next above these come the Nobility, of whom there are several degrees, beginning at Six-Sided Figures, or Hexagons, and from thence rising in the number of their sides till they receive the honourable title of Polygonal, or many-Sided. Finally when the number of the sides becomes so numerous, and the sides themselves so small, that the figure cannot be distinguished from a circle, he is included in the Circular or Priestly order; and this is the highest class of all. + +It is a Law of Nature with us that a male child shall have one more side than his father, so that each generation shall rise (as a rule) one step in the scale of development and nobility. Thus the son of a Square is a Pentagon; the son of a Pentagon, a Hexagon; and so on. + +But this rule applies not always to the Tradesman, and still less often to the Soldiers, and to the Workmen; who indeed can hardly be said to deserve the name of human Figures, since they have not all their sides equal. With them therefore the Law of Nature does not hold; and the son of an Isosceles (i.e. a Triangle with two sides equal) remains Isosceles still. Nevertheless, all hope is not such out, even from the Isosceles, that his posterity may ultimately rise above his degraded condition. For, after a long series of military successes, or diligent and skillful labours, it is generally found that the more intelligent among the Artisan and Soldier classes manifest a slight increase of their third side or base, and a shrinkage of the two other sides. Intermarriages (arranged by the Priests) between the sons and daughters of these more intellectual members of the lower classes generally result in an offspring approximating still more to the type of the Equal-Sided Triangle. + +Rarely—in proportion to the vast numbers of Isosceles births—is a genuine and certifiable Equal-Sided Triangle produced from Isosceles parents.[1] Such a birth requires, as its antecedents, not only a series of carefully arranged intermarriages, but also a long-continued exercise of frugality and self-control on the part of the would-be ancestors of the coming Equilateral, and a patient, systematic, and continuous development of the Isosceles intellect through many generations. + +[1] “What need of a certificate?” a Spaceland critic may ask: “Is not the procreation of a Square Son a certificate from Nature herself, proving the Equal-sidedness of the Father?” I reply that no Lady of any position will mary an uncertified Triangle. Square offspring has sometimes resulted from a slightly Irregular Triangle; but in almost every such case the Irregularity of the first generation is visited on the third; which either fails to attain the Pentagonal rank, or relapses to the Triangular. + +The birth of a True Equilateral Triangle from Isosceles parents is the subject of rejoicing in our country for many furlongs round. After a strict examination conducted by the Sanitary and Social Board, the infant, if certified as Regular, is with solemn ceremonial admitted into the class of Equilaterals. He is then immediately taken from his proud yet sorrowing parents and adopted by some childless Equilateral, who is bound by oath never to permit the child henceforth to enter his former home or so much as to look upon his relations again, for fear lest the freshly developed organism may, by force of unconscious imitation, fall back again into his hereditary level. + +The occasional emergence of an Equilateral from the ranks of his serf-born ancestors is welcomed, not only by the poor serfs themselves, as a gleam of light and hope shed upon the monotonous squalor of their existence, but also by the Aristocracy at large; for all the higher classes are well aware that these rare phenomena, while they do little or nothing to vulgarize their own privileges, serve as almost useful barrier against revolution from below. + +Had the acute-angled rabble been all, without exception, absolutely destitute of hope and of ambition, they might have found leaders in some of their many seditious outbreaks, so able as to render their superior numbers and strength too much even for the wisdom of the Circles. But a wise ordinance of Nature has decreed that in proportion as the working-classes increase in intelligence, knowledge, and all virtue, in that same proportion their acute angle (which makes them physically terrible) shall increase also and approximate to their comparatively harmless angle of the Equilateral Triangle. Thus, in the most brutal and formidable off the soldier class—creatures almost on a level with women in their lack of intelligence—it is found that, as they wax in the mental ability necessary to employ their tremendous penetrating power to advantage, so do they wane in the power of penetration itself. + +How admirable is the Law of Compensation! And how perfect a proof of the natural fitness and, I may almost say, the divine origin of the aristocratic constitution of the States of Flatland! By a judicious use of this Law of Nature, the Polygons and Circles are almost always able to stifle sedition in its very cradle, taking advantage of the irrepressible and boundless hopefulness of the human mind. Art also comes to the aid of Law and Order. It is generally found possible—by a little artificial compression or expansion on the part of the State physicians—to make some of the more intelligent leaders of a rebellion perfectly Regular, and to admit them at once into the privileged classes; a much larger number, who are still below the standard, allured by the prospect of being ultimately ennobled, are induced to enter the State Hospitals, where they are kept in honourable confinement for life; one or two alone of the most obstinate, foolish, and hopelessly irregular are led to execution. + +Then the wretched rabble of the Isosceles, planless and leaderless, are either transfixed without resistance by the small body of their brethren whom the Chief Circle keeps in pay for emergencies of this kind; or else more often, by means of jealousies and suspicious skillfully fomented among them by the Circular party, they are stirred to mutual warfare, and perish by one another’s angles. No less than one hundred and twenty rebellions are recorded in our annals, besides minor outbreaks numbered at two hundred and thirty-five; and they have all ended thus. + +-= The End =-"; diff --git a/crates/gosub_fontmanager/src/font_manager.rs b/crates/gosub_fontmanager/src/font_manager.rs new file mode 100644 index 000000000..9611369f8 --- /dev/null +++ b/crates/gosub_fontmanager/src/font_manager.rs @@ -0,0 +1,3 @@ +mod cache; +pub mod font_info; +pub mod manager; diff --git a/crates/gosub_fontmanager/src/font_manager/cache.rs b/crates/gosub_fontmanager/src/font_manager/cache.rs new file mode 100644 index 000000000..d8b92b05e --- /dev/null +++ b/crates/gosub_fontmanager/src/font_manager/cache.rs @@ -0,0 +1,49 @@ +use crate::font_manager::font_info::FontInfo; +use gosub_interface::font::FontStyle; +use lazy_static::lazy_static; +use std::collections::HashMap; +use std::sync::{Arc, Mutex}; + +lazy_static! { + pub static ref FONT_CACHE: Arc> = Arc::new(Mutex::new(MemoryCache::new())); +} + +#[allow(unused)] +pub(crate) trait Cache { + fn get(&self, family: &str, style: FontStyle) -> Option; + fn set(&mut self, family: &str, style: FontStyle, font_info: &FontInfo); + fn clear(&mut self); + fn remove(&mut self, family: &str, style: FontStyle); +} + +// Some kind of caching strategy for font_info stuff +pub struct MemoryCache { + cache: HashMap, +} + +impl MemoryCache { + pub fn new() -> Self { + Self { cache: HashMap::new() } + } +} + +impl Cache for MemoryCache { + fn get(&self, family: &str, style: FontStyle) -> Option { + let key = format!("{}-{}", family, style); + self.cache.get(&key).cloned() + } + + fn set(&mut self, family: &str, style: FontStyle, font_info: &FontInfo) { + let key = format!("{}-{}", family, style); + self.cache.insert(key, font_info.clone()); + } + + fn clear(&mut self) { + self.cache.clear(); + } + + fn remove(&mut self, family: &str, style: FontStyle) { + let key = format!("{}-{}", family, style); + self.cache.remove(&key); + } +} diff --git a/crates/gosub_fontmanager/src/font_manager/font_info.rs b/crates/gosub_fontmanager/src/font_manager/font_info.rs new file mode 100644 index 000000000..31493edfe --- /dev/null +++ b/crates/gosub_fontmanager/src/font_manager/font_info.rs @@ -0,0 +1,139 @@ +use std::path::PathBuf; + +use gosub_interface::font::{FontError, FontInfo as TFontInfo, FontStyle}; + +#[derive(Clone, Debug)] +pub struct FontInfo { + /// Family name of the font (e.g. "Arial") + pub family: String, + /// Style of the font + pub style: FontStyle, + /// Weight (400 normal, 700 bold) + pub weight: i32, + /// Stretch (1.0 normal, < 1.0 condensed) + pub stretch: f32, + /// Font is monospaced + pub monospaced: bool, + /// Path to the font file + pub path: Option, + /// Index of the face in the font-file + pub index: Option, +} + +impl TFontInfo for FontInfo { + fn family(&self) -> &str { + self.family.as_str() + } + + fn style(&self) -> FontStyle { + self.style + } + + fn weight(&self) -> i32 { + self.weight + } + + fn stretch(&self) -> f32 { + self.stretch + } + + fn monospaced(&self) -> bool { + self.monospaced + } + + fn path(&self) -> Option { + self.path.clone() + } + + fn index(&self) -> Option { + self.index + } + + fn new(family: &str) -> Result { + Ok(Self { + family: family.to_string(), + style: FontStyle::Normal, + weight: 400, + stretch: 1.0, + monospaced: false, + path: None, + index: None, + }) + } + + fn with_family(&self, family: &str) -> Self { + Self { + family: family.to_string(), + style: self.style, + weight: self.weight, + stretch: self.stretch, + monospaced: self.monospaced, + path: self.path.clone(), + index: self.index, + } + } + + fn with_style(&self, style: FontStyle) -> Self { + Self { + family: self.family.clone(), + style, + weight: self.weight, + stretch: self.stretch, + monospaced: self.monospaced, + path: self.path.clone(), + index: self.index, + } + } + + fn with_weight(&self, weight: i32) -> Self { + Self { + family: self.family.clone(), + style: self.style, + weight, + stretch: self.stretch, + monospaced: self.monospaced, + path: self.path.clone(), + index: self.index, + } + } + + fn with_stretch(&self, stretch: f32) -> Self { + Self { + family: self.family.clone(), + style: self.style, + weight: self.weight, + stretch, + monospaced: self.monospaced, + path: self.path.clone(), + index: self.index, + } + } + + fn with_monospaced(&self, monospaced: bool) -> Self { + Self { + family: self.family.clone(), + style: self.style, + weight: self.weight, + stretch: self.stretch, + monospaced, + path: self.path.clone(), + index: self.index, + } + } + + fn with_path(&self, path: PathBuf, index: Option) -> Self { + Self { + family: self.family.clone(), + style: self.style, + weight: self.weight, + stretch: self.stretch, + monospaced: self.monospaced, + path: Some(path), + index, + } + } + + fn to_description(&self, size: f32) -> String { + format!("{} {} {}", self.family, self.style, size) + } +} diff --git a/crates/gosub_fontmanager/src/font_manager/manager.rs b/crates/gosub_fontmanager/src/font_manager/manager.rs new file mode 100644 index 000000000..cc58ac36e --- /dev/null +++ b/crates/gosub_fontmanager/src/font_manager/manager.rs @@ -0,0 +1,135 @@ +use crate::font_manager::font_info::FontInfo; +use anyhow::anyhow; +use font_kit::handle::Handle; +use gosub_interface::font::FontManager as TFontManager; +use gosub_interface::font::FontStyle; +use log::error; +use std::collections::HashSet; +use std::path::{Path, PathBuf}; +use std::sync::{Arc, RwLock}; + +#[allow(dead_code)] +pub const LOG_TARGET: &str = "font-manager"; + +thread_local! { + static FONT_MANAGER: Arc> = Arc::new(RwLock::new(FontManager::new())); +} + +pub struct FontManager { + /// Vec of all font-info structures found + available_fonts: Vec, +} + +impl Default for FontManager { + fn default() -> Self { + Self::new() + } +} + +impl FontManager { + pub fn new() -> Self { + let source = font_kit::source::SystemSource::new(); + let handles = source.all_fonts().unwrap(); + + let mut seen_paths: HashSet = HashSet::new(); + + let mut font_info_list = Vec::new(); + for handle in &handles { + if let Ok(info) = handle_to_info(&mut seen_paths, handle) { + font_info_list.push(info) + } + } + + font_info_list.sort_by_key(|fi| fi.family.clone()); + + Self { + available_fonts: font_info_list, + } + } + + /// Returns all available fonts for given source-type + pub fn available_fonts(&self) -> &Vec { + &self.available_fonts + } + + pub fn find(&self, families: &[&str], style: FontStyle) -> Option { + for &fam in families { + for fi in self.available_fonts() { + if fi.family.eq_ignore_ascii_case(fam) && fi.style == style { + return Some(fi.clone()); + } + } + } + + None + } +} + +impl TFontManager for FontManager { + type FontInfo = FontInfo; + + fn instance() -> Arc> { + FONT_MANAGER.with(|f| f.clone()) + } + + fn find_font(&self, families: &[&str], style: FontStyle) -> Option { + for &fam in families { + for fi in &self.available_fonts { + if fi.family.eq_ignore_ascii_case(fam) && fi.style == style { + return Some(fi.clone()); + } + } + } + None + } +} + +fn handle_to_info(seen_paths: &mut HashSet, handle: &Handle) -> Result { + let font = handle.load().unwrap(); + + let family = font.family_name(); + let props = font.properties(); + + let style = match props.style { + font_kit::properties::Style::Normal => FontStyle::Normal, + font_kit::properties::Style::Italic => FontStyle::Italic, + font_kit::properties::Style::Oblique => FontStyle::Oblique, + }; + + let Handle::Path { ref path, font_index } = handle else { + error!(target: LOG_TARGET, "Expected a path handle. Got: {:?}", handle); + return Err(anyhow!("Expected a path handle")); + }; + + // Check if the path is symlinked + let resolved_path = resolve_symlink(path.to_path_buf()); + if seen_paths.contains(&resolved_path) { + return Err(anyhow!("Path already seen")); + } + seen_paths.insert(resolved_path.clone()); + + Ok(FontInfo { + family, + style, + weight: props.weight.0 as i32, + stretch: props.stretch.0, + monospaced: font.is_monospace(), + path: Some(resolved_path.clone()), + index: Some(*font_index as i32), + }) +} + +/// Resolves a symlinked path +fn resolve_symlink(path: PathBuf) -> PathBuf { + let mut resolved_path = path.clone(); + + while let Ok(target) = std::fs::read_link(&resolved_path) { + resolved_path = if target.is_relative() { + path.parent().unwrap_or(Path::new("/")).join(target) + } else { + target + }; + } + + resolved_path +} diff --git a/crates/gosub_fontmanager/src/lib.rs b/crates/gosub_fontmanager/src/lib.rs new file mode 100644 index 000000000..c7cd2b898 --- /dev/null +++ b/crates/gosub_fontmanager/src/lib.rs @@ -0,0 +1,5 @@ +pub mod flatland; +pub mod font_manager; + +pub use font_manager::font_info::FontInfo; +pub use font_manager::manager::FontManager; diff --git a/crates/gosub_interface/src/config.rs b/crates/gosub_interface/src/config.rs index d9425d82a..3fe9994d3 100644 --- a/crates/gosub_interface/src/config.rs +++ b/crates/gosub_interface/src/config.rs @@ -6,6 +6,7 @@ mod render; mod render_tree; mod tree_drawer; +use crate::font::HasFontManager; pub use chrome::*; pub use css_system::*; pub use document::*; @@ -19,6 +20,7 @@ pub trait ModuleConfiguration: + HasCssSystem + HasDocument + HasHtmlParser + + HasFontManager + HasLayouter + HasRenderTree + HasTreeDrawer diff --git a/crates/gosub_interface/src/config/layouter.rs b/crates/gosub_interface/src/config/layouter.rs index b50836144..5a6ab5a03 100644 --- a/crates/gosub_interface/src/config/layouter.rs +++ b/crates/gosub_interface/src/config/layouter.rs @@ -1,8 +1,9 @@ use crate::config::HasCssSystem; +use crate::font::HasFontManager; use crate::layout::{LayoutTree, Layouter}; use std::fmt::Debug; -pub trait HasLayouter: HasCssSystem + Debug + 'static { - type Layouter: Layouter; +pub trait HasLayouter: HasFontManager + HasCssSystem + Debug + 'static { + type Layouter: Layouter; type LayoutTree: LayoutTree; } diff --git a/crates/gosub_interface/src/config/render_tree.rs b/crates/gosub_interface/src/config/render_tree.rs index da9412e36..747588cfb 100644 --- a/crates/gosub_interface/src/config/render_tree.rs +++ b/crates/gosub_interface/src/config/render_tree.rs @@ -1,6 +1,7 @@ use crate::config::HasLayouter; +use crate::font::HasFontManager; use crate::render_tree::RenderTree; -pub trait HasRenderTree: HasLayouter { +pub trait HasRenderTree: HasLayouter + HasFontManager { type RenderTree: RenderTree; } diff --git a/crates/gosub_interface/src/css3.rs b/crates/gosub_interface/src/css3.rs index 1028bb153..37752b944 100644 --- a/crates/gosub_interface/src/css3.rs +++ b/crates/gosub_interface/src/css3.rs @@ -69,6 +69,7 @@ pub trait CssPropertyMap: Default + Debug + WasmNotSend { fn make_clean(&mut self); fn is_dirty(&self) -> bool; } + pub trait CssProperty: Debug + Display + Sized + From { fn compute_value(&mut self); // this should probably be removed diff --git a/crates/gosub_interface/src/document.rs b/crates/gosub_interface/src/document.rs index 2c0f43c16..ab3a3e993 100644 --- a/crates/gosub_interface/src/document.rs +++ b/crates/gosub_interface/src/document.rs @@ -32,12 +32,6 @@ pub trait Document>: Sized + Display + Debug + P #[allow(clippy::new_ret_no_self)] fn new(document_type: DocumentType, url: Option, root_node: Option) -> C::Document; - // /// Creates a new document with an optional document root node - // fn new_with_handle(document_type: DocumentType, url: Option, location: &Location, root_node: Option<&Self::Node>) -> DocumentHandle; - - // /// Returns the document handle for this document - // fn handle(&self) -> DocumentHandle; - /// Location of the document (URL, file path, etc.) fn url(&self) -> Option; @@ -52,16 +46,11 @@ pub trait Document>: Sized + Display + Debug + P // Return an element node by the "id" attribute fn node_by_named_id(&self, id: &str) -> Option<&Self::Node>; - // fn add_named_id(&mut self, id: &str, node_id: NodeId); - // /// Remove a named ID from the document - // fn remove_named_id(&mut self, id: &str); - fn stylesheets(&self) -> &Vec; fn add_stylesheet(&mut self, stylesheet: C::Stylesheet); /// Return the root node of the document fn get_root(&self) -> &Self::Node; - // fn get_root_mut(&mut self) -> &mut Self::Node; fn attach_node(&mut self, node_id: NodeId, parent_id: NodeId, position: Option); fn detach_node(&mut self, node_id: NodeId); @@ -73,9 +62,6 @@ pub trait Document>: Sized + Display + Debug + P // Updates a node that is referenced into the document. This is useful for instance when a node is fetched with node_by_id() for instance. fn update_node_ref(&mut self, node: &Self::Node); - // /// Return the parent node from a given ID - // fn parent_node(&self, node: &Self::Node) -> Option<&Self::Node>; - /// Removes a node from the document fn delete_node_by_id(&mut self, node_id: NodeId); diff --git a/crates/gosub_interface/src/draw.rs b/crates/gosub_interface/src/draw.rs index 6b52156d2..e8cd9a201 100644 --- a/crates/gosub_interface/src/draw.rs +++ b/crates/gosub_interface/src/draw.rs @@ -1,4 +1,4 @@ -use crate::config::{HasDocument, HasDrawComponents}; +use crate::config::{HasDocument, HasDrawComponents, HasHtmlParser}; use crate::eventloop::EventLoopHandle; use crate::layout::LayoutTree; use crate::render_backend::{ImgCache, NodeDesc, RenderBackend}; @@ -24,7 +24,7 @@ pub trait TreeDrawer { ) -> impl Future> where Self: Sized, - C: HasDocument; + C: HasDocument + HasHtmlParser; fn from_source( // The initial url that the source was loaded from @@ -38,7 +38,7 @@ pub trait TreeDrawer { ) -> Result<(Self, C::Document)> where Self: Sized, - C: HasDocument; + C: HasDocument + HasHtmlParser; fn with_fetcher( // The initial url that the source was loaded from @@ -52,7 +52,7 @@ pub trait TreeDrawer { ) -> impl Future> where Self: Sized, - C: HasDocument; + C: HasDocument + HasHtmlParser; fn clear_buffers(&mut self); fn toggle_debug(&mut self); @@ -73,7 +73,7 @@ pub trait TreeDrawer { fn reload(&mut self, el: impl EventLoopHandle) -> impl Future> + 'static where - C: HasDocument; + C: HasDocument + HasHtmlParser; fn navigate( &mut self, @@ -81,7 +81,7 @@ pub trait TreeDrawer { el: impl EventLoopHandle, ) -> impl Future> + 'static where - C: HasDocument; + C: HasDocument + HasHtmlParser; fn reload_from(&mut self, tree: C::RenderTree); } diff --git a/crates/gosub_interface/src/font.rs b/crates/gosub_interface/src/font.rs new file mode 100644 index 000000000..d3d8363d5 --- /dev/null +++ b/crates/gosub_interface/src/font.rs @@ -0,0 +1,77 @@ +use std::fmt::{Debug, Formatter}; +use std::path::PathBuf; +use std::sync::{Arc, RwLock}; + +#[derive(Clone)] +pub struct FontBlob { + pub data: Arc + Send + Sync>, + pub index: u32, +} + +impl FontBlob { + pub fn new(data: Arc + Send + Sync>, index: u32) -> Self { + Self { data, index } + } +} + +impl Debug for FontBlob { + fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { + f.debug_struct("Font").finish() + } +} + +#[derive(Debug)] +pub enum FontError { + FontNotFound(String), // Font not found for a family + InvalidFont(String), // Font is invalid or corrupted + UnsupportedFeature(String), // Unsupported features +} + +#[derive(Clone, Debug, Copy, PartialEq)] +pub enum FontStyle { + Normal, + Italic, + Oblique, +} + +impl std::fmt::Display for FontStyle { + fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { + match self { + FontStyle::Normal => write!(f, "Normal"), + FontStyle::Italic => write!(f, "Italic"), + FontStyle::Oblique => write!(f, "Oblique"), + } + } +} + +pub trait HasFontManager: Sized + Debug { + type FontManager: FontManager; +} + +pub trait FontInfo: Sized + Clone + Debug + Send { + fn family(&self) -> &str; + fn style(&self) -> FontStyle; + fn weight(&self) -> i32; + fn stretch(&self) -> f32; + fn monospaced(&self) -> bool; + fn path(&self) -> Option; + fn index(&self) -> Option; + + fn new(family: &str) -> Result; + fn with_family(&self, family: &str) -> Self; + fn with_style(&self, style: FontStyle) -> Self; + fn with_weight(&self, weight: i32) -> Self; + fn with_stretch(&self, stretch: f32) -> Self; + fn with_monospaced(&self, monospaced: bool) -> Self; + fn with_path(&self, path: PathBuf, index: Option) -> Self; + + /// Converts this font info to a font description usable by Pango + fn to_description(&self, size: f32) -> String; +} + +pub trait FontManager: Sized + 'static { + type FontInfo: FontInfo; + + fn instance() -> Arc>; + fn find_font(&self, families: &[&str], style: FontStyle) -> Option; +} diff --git a/crates/gosub_interface/src/layout.rs b/crates/gosub_interface/src/layout.rs index 6d05c7287..f482b682f 100644 --- a/crates/gosub_interface/src/layout.rs +++ b/crates/gosub_interface/src/layout.rs @@ -1,9 +1,9 @@ -use std::fmt::Debug; - use crate::config::HasLayouter; +use crate::font::{FontBlob, HasFontManager}; use gosub_shared::font::Glyph; use gosub_shared::geo::{Point, Rect, Size, SizeU32}; use gosub_shared::types::Result; +use std::fmt::Debug; #[derive(Clone, Debug)] pub struct FontData { @@ -28,32 +28,46 @@ impl FontData { } } +/// LayoutTree is a combined structure of a RenderTree and a LayoutTree. The RenderTree part contains all the +/// nodes that can be rendered or have any effect of visual layout. The layout part holds all the information +/// about how the nodes are laid out on the screen. pub trait LayoutTree>: Sized + Debug + 'static { type NodeId: Debug + Copy + Clone + From + Into + PartialEq; type Node: LayoutNode; + /// Returns all NodeIds of the children of the given NodeId fn children(&self, id: Self::NodeId) -> Option>; + /// Returns true when the given NodeId is a child of the root node fn contains(&self, id: &Self::NodeId) -> bool; + /// Returns the count of the children fn child_count(&self, id: Self::NodeId) -> usize; + /// Returns the parent of the given NodeId, or None when the node is a root node fn parent_id(&self, id: Self::NodeId) -> Option; - fn get_cache(&self, id: Self::NodeId) -> Option<&::Cache>; - fn get_layout(&self, id: Self::NodeId) -> Option<&::Layout>; - fn get_cache_mut(&mut self, id: Self::NodeId) -> Option<&mut ::Cache>; - fn get_layout_mut(&mut self, id: Self::NodeId) -> Option<&mut ::Layout>; - fn set_cache(&mut self, id: Self::NodeId, cache: ::Cache); - fn set_layout(&mut self, id: Self::NodeId, layout: ::Layout); - fn style_dirty(&self, id: Self::NodeId) -> bool; + /// Returns the Layout cache which holds all the style and display information of a node + fn get_cache(&self, id: Self::NodeId) -> Option<&>::Cache>; + /// Returns the layout data of the node. + fn get_layout(&self, id: Self::NodeId) -> Option<&>::Layout>; + + fn get_cache_mut(&mut self, id: Self::NodeId) -> Option<&mut >::Cache>; + fn get_layout_mut(&mut self, id: Self::NodeId) -> Option<&mut >::Layout>; + fn set_cache(&mut self, id: Self::NodeId, cache: >::Cache); + fn set_layout(&mut self, id: Self::NodeId, layout: >::Layout); + fn style_dirty(&self, id: Self::NodeId) -> bool; fn clean_style(&mut self, id: Self::NodeId); + /// Get node functionality fn get_node_mut(&mut self, id: Self::NodeId) -> Option<&mut Self::Node>; fn get_node(&self, id: Self::NodeId) -> Option<&Self::Node>; + /// Returns the root node of the tree fn root(&self) -> Self::NodeId; } -pub trait Layouter: Sized + Clone + Send + 'static { +/// Main layout trait that will convert a RenderTree into a LayoutTree (or in our case, it will +/// update the LayoutTree with new layout information) +pub trait Layouter: Sized + Clone + Send + 'static { type Cache: LayoutCache; type Layout: Layout + Send; @@ -61,18 +75,24 @@ pub trait Layouter: Sized + Clone + Send + 'static { const COLLAPSE_INLINE: bool; - fn layout>( + fn layout( &self, + // Rendertree, probably not filled with layout information. This rendertree will be updated by this function. tree: &mut C::LayoutTree, + // The root node of the tree. This is not really needed as the LayoutTree also contains this information root: >::NodeId, + // Dimensions of the viewport that we layout in space: SizeU32, ) -> Result<()>; } +/// Cache that holds all the style and display information of a node pub trait LayoutCache: Default + Send + Debug { fn invalidate(&mut self); } +/// Trait that defines all layout information of a node. Currently residing in the same tree that also +/// holds the RenderTree. This is not ideal, but it is a start. pub trait Layout: Default + Debug { /// Returns the relative upper left pos of the content box fn rel_pos(&self) -> Point; @@ -82,16 +102,13 @@ pub trait Layout: Default + Debug { /// Size of the scroll box (content box without overflow), including scrollbars (if any) fn size(&self) -> Size; - fn size_or(&self) -> Option; fn set_size_and_content(&mut self, size: SizeU32) { self.set_size(size); self.set_content(size); } - fn set_size(&mut self, size: SizeU32); - fn set_content(&mut self, size: SizeU32); /// Size of the content box (content without scrollbars, but with overflow) @@ -149,33 +166,34 @@ pub trait Layout: Default + Debug { pub trait LayoutNode: HasTextLayout { fn get_property(&self, name: &str) -> Option<&C::CssProperty>; fn text_data(&self) -> Option<&str>; - + fn text_size(&self) -> Option; /// This can only return true if the `Layout::COLLAPSE_INLINE` is set true for the layouter - /// fn is_anon_inline_parent(&self) -> bool; } pub trait HasTextLayout { fn clear_text_layout(&mut self); - fn add_text_layout(&mut self, layout: ::TextLayout); - fn get_text_layouts(&self) -> Option<&[::TextLayout]>; - fn get_text_layouts_mut(&mut self) -> Option<&mut Vec<::TextLayout>>; + fn add_text_layout(&mut self, layout: >::TextLayout); + fn get_text_layouts(&self) -> Option<&[>::TextLayout]>; + fn get_text_layouts_mut(&mut self) -> Option<&mut Vec<>::TextLayout>>; } +/// Text layout that keeps all information on how a part of text is laid out pub trait TextLayout { - fn size(&self) -> Size; - + /// Returns a list of glyphs for the text fn glyphs(&self) -> &[Glyph]; - - fn font_data(&self) -> &FontData; - + /// Font data + fn font(&self) -> &FontBlob; + // Size of the font in pixels fn font_size(&self) -> f32; - - fn coords(&self) -> &[i16]; - + /// Additional font decorations fn decorations(&self) -> &Decoration; - + // Offset? fn offset(&self) -> Point; + /// Coordinates of the font + fn coords(&self) -> &[i16]; + /// Size of the text + fn size(&self) -> Size; } #[derive(Debug, Clone, Default)] diff --git a/crates/gosub_interface/src/lib.rs b/crates/gosub_interface/src/lib.rs index d26fd079a..fbfb584e4 100644 --- a/crates/gosub_interface/src/lib.rs +++ b/crates/gosub_interface/src/lib.rs @@ -4,6 +4,7 @@ pub mod css3; pub mod document; pub mod draw; pub mod eventloop; +pub mod font; pub mod html5; pub mod input; pub mod instance; diff --git a/crates/gosub_interface/src/render_backend.rs b/crates/gosub_interface/src/render_backend.rs index 5d869ceef..116f3c1ed 100644 --- a/crates/gosub_interface/src/render_backend.rs +++ b/crates/gosub_interface/src/render_backend.rs @@ -1,3 +1,4 @@ +use crate::font::{FontManager, HasFontManager}; use crate::layout::TextLayout; use crate::svg::SvgRenderer; pub use gosub_shared::geo::*; @@ -18,19 +19,22 @@ pub trait RenderBackend: Sized + Debug { type BorderSide: BorderSide; type BorderRadius: BorderRadius; type Transform: Transform; - type Text: Text + Clone; type Gradient: Gradient; type Color: Color; type Image: Image; type Brush: Brush; type Scene: Scene; + type Text: Text; type SVGRenderer: SvgRenderer; + type FontManager: FontManager; type ActiveWindowData<'a>; type WindowData<'a>; fn draw_rect(&mut self, data: &mut Self::WindowData<'_>, rect: &RenderRect); - fn draw_text(&mut self, data: &mut Self::WindowData<'_>, text: &RenderText); + fn draw_text(&mut self, data: &mut Self::WindowData<'_>, text: &RenderText) + where + Self: HasFontManager; fn apply_scene(&mut self, data: &mut Self::WindowData<'_>, scene: &Self::Scene, transform: Option); fn reset(&mut self, data: &mut Self::WindowData<'_>); @@ -76,6 +80,10 @@ pub trait Scene: Clone + Debug + Send { fn new() -> Self; } +pub trait Text: Clone + Debug + Sized { + fn new(layout: &impl TextLayout) -> Self; +} + #[derive(Clone, Debug)] pub struct RenderRect { pub rect: B::Rect, @@ -479,10 +487,6 @@ pub trait Transform: Sized + Mul + MulAssign + Clone + Send + Debug { } } -pub trait Text { - fn new(node: &TL) -> Self; -} - pub struct ColorStop { pub offset: FP, pub color: B::Color, diff --git a/crates/gosub_interface/src/render_tree.rs b/crates/gosub_interface/src/render_tree.rs index ba0afb7eb..f9c892028 100644 --- a/crates/gosub_interface/src/render_tree.rs +++ b/crates/gosub_interface/src/render_tree.rs @@ -17,22 +17,26 @@ pub trait RenderTree: Send + 'static { fn get_children(&self, id: Self::NodeId) -> Option>; - fn get_layout(&self, id: Self::NodeId) -> Option<&::Layout>; + fn get_layout(&self, id: Self::NodeId) -> Option<&>::Layout>; fn from_document(doc: &C::Document) -> Self where C: HasDocument; } +pub type TextLayoutRef<'a, C> = &'a [<::Layouter as Layouter>::TextLayout]; + pub trait RenderTreeNode: Debug { fn props(&self) -> &::PropertyMap; fn props_mut(&mut self) -> &mut ::PropertyMap; - fn layout(&self) -> &::Layout; - fn layout_mut(&mut self) -> &mut ::Layout; + fn layout(&self) -> &>::Layout; + fn layout_mut(&mut self) -> &mut >::Layout; fn element_attributes(&self) -> Option<&HashMap>; - fn text_data(&self) -> Option<(&str, &[::TextLayout])>; + + fn text_data(&self) -> Option<(&str, TextLayoutRef<'_, C>)>; + fn name(&self) -> &str; } diff --git a/crates/gosub_renderer/Cargo.toml b/crates/gosub_renderer/Cargo.toml index 41620e29b..39a11d639 100644 --- a/crates/gosub_renderer/Cargo.toml +++ b/crates/gosub_renderer/Cargo.toml @@ -11,10 +11,12 @@ gosub_rendering = { version = "0.1.1", registry = "gosub", path = "../gosub_rend gosub_interface = { version = "0.1.1", registry = "gosub", path = "../gosub_interface", features = [] } gosub_shared = { version = "0.1.1", registry = "gosub", path = "../gosub_shared" } gosub_net = { version = "0.1.1", registry = "gosub", path = "../gosub_net" } +gosub_fontmanager = { version = "0.1.0", registry = "gosub", path = "../gosub_fontmanager" } anyhow = "1.0.94" image = "0.25.5" url = "2.5.4" log = "0.4.22" +pango = "0.20.7" [target.'cfg(target_arch = "wasm32")'.dependencies] wasm-bindgen-futures = "0.4.47" diff --git a/crates/gosub_renderer/src/draw.rs b/crates/gosub_renderer/src/draw.rs index ac14728f7..2319c8fbd 100644 --- a/crates/gosub_renderer/src/draw.rs +++ b/crates/gosub_renderer/src/draw.rs @@ -4,16 +4,16 @@ use crate::draw::img_cache::ImageCache; use crate::draw::testing::{test_add_element, test_restyle_element}; use crate::render_tree::{load_html_rendertree, load_html_rendertree_fetcher, load_html_rendertree_source}; use anyhow::anyhow; -use gosub_interface::config::{HasDrawComponents, HasHtmlParser}; +use gosub_interface::config::{HasDocument, HasDrawComponents, HasHtmlParser}; use gosub_interface::css3::{CssProperty, CssPropertyMap, CssValue}; use gosub_interface::draw::TreeDrawer; use gosub_interface::eventloop::EventLoopHandle; -use gosub_interface::layout::{Layout, LayoutTree, Layouter}; use gosub_interface::render_backend::{ Border, BorderSide, BorderStyle, Brush, Color, ImageBuffer, ImgCache, NodeDesc, Rect, RenderBackend, RenderBorder, RenderRect, RenderText, Scene as TScene, Text, Transform, }; +use gosub_interface::layout::{Layout, LayoutTree, Layouter, TextLayout}; use gosub_interface::render_tree; use gosub_interface::render_tree::RenderTreeNode as _; use gosub_interface::svg::SvgRenderer; @@ -36,7 +36,6 @@ mod testing; const DEBUG_CONTENT_COLOR: (u8, u8, u8) = (0, 192, 255); const DEBUG_PADDING_COLOR: (u8, u8, u8) = (0, 255, 192); const DEBUG_BORDER_COLOR: (u8, u8, u8) = (255, 72, 72); -// const DEBUG_MARGIN_COLOR: (u8, u8, u8) = (255, 192, 0); type Point = gosub_shared::types::Point; @@ -77,8 +76,8 @@ impl TreeDrawerImpl { } } -impl, LayoutTree = RenderTree> + HasHtmlParser> TreeDrawer - for TreeDrawerImpl +impl, LayoutTree = RenderTree> + HasHtmlParser + HasDocument> + TreeDrawer for TreeDrawerImpl { type ImgCache = ImageCache; @@ -162,7 +161,6 @@ impl, LayoutTree = RenderTree if self.dirty { self.dirty = false; - // el.redraw(); } @@ -362,13 +360,11 @@ impl< { pub(crate) fn render(&mut self, size: SizeU32) { let root = self.drawer.tree.root(); - if let Err(e) = self.drawer.layouter.layout::(&mut self.drawer.tree, root, size) { + if let Err(e) = self.drawer.layouter.layout(&mut self.drawer.tree, root, size) { eprintln!("Failed to compute layout: {:?}", e); return; } - // print_tree(&self.taffy, self.root, &self.style); - self.drawer.position = PositionTree::::from_tree(&self.drawer.tree); self.render_node_with_children(self.drawer.tree.root(), Point::ZERO); @@ -476,9 +472,7 @@ fn render_text( let text = layout .iter() .map(|layout| { - let text: ::Text = - Text::new::<::TextLayout>(layout); - + let text: ::Text = Text::new(layout); text }) .collect::>(); diff --git a/crates/gosub_rendering/src/lib.rs b/crates/gosub_rendering/src/lib.rs index 949b917a5..89c2ab21c 100644 --- a/crates/gosub_rendering/src/lib.rs +++ b/crates/gosub_rendering/src/lib.rs @@ -6,4 +6,3 @@ pub mod position; // pub mod macos_render_tree; pub mod render_tree; -pub mod text; diff --git a/crates/gosub_rendering/src/render_tree.rs b/crates/gosub_rendering/src/render_tree.rs index 7f3513e80..f2b5f4d4c 100644 --- a/crates/gosub_rendering/src/render_tree.rs +++ b/crates/gosub_rendering/src/render_tree.rs @@ -4,10 +4,13 @@ use gosub_interface::config::{HasDocument, HasLayouter, HasRenderTree}; use gosub_interface::css3::{CssProperty, CssPropertyMap, CssSystem}; use gosub_interface::document::Document; -use gosub_interface::layout::{HasTextLayout, Layout, LayoutCache, LayoutNode, LayoutTree, Layouter}; +use gosub_interface::font::HasFontManager; +use gosub_interface::layout::{HasTextLayout, Layout, LayoutCache, LayoutNode, LayoutTree, Layouter, TextLayout}; use gosub_interface::node::NodeData; use gosub_interface::node::{ElementDataType, Node as DocumentNode, TextDataType}; use gosub_interface::render_tree; +use gosub_interface::render_tree::TextLayoutRef; +use gosub_shared::geo::Size; use gosub_shared::node::NodeId; use gosub_shared::types::Result; use log::info; @@ -24,7 +27,7 @@ const INLINE_ELEMENTS: [&str; 31] = [ /// Map of all declared values for all nodes in the document #[derive(Debug)] -pub struct RenderTree { +pub struct RenderTree { pub nodes: HashMap>, pub root: NodeId, pub dirty: bool, @@ -52,29 +55,29 @@ impl> LayoutTree for RenderTree { self.get_node(id).and_then(|node| node.parent) } - fn get_cache(&self, id: Self::NodeId) -> Option<&::Cache> { + fn get_cache(&self, id: Self::NodeId) -> Option<&>::Cache> { self.get_node(id).map(|node| &node.cache) } - fn get_layout(&self, id: Self::NodeId) -> Option<&::Layout> { + fn get_layout(&self, id: Self::NodeId) -> Option<&>::Layout> { self.get_node(id).map(|node| &node.layout) } - fn get_cache_mut(&mut self, id: Self::NodeId) -> Option<&mut ::Cache> { + fn get_cache_mut(&mut self, id: Self::NodeId) -> Option<&mut >::Cache> { self.get_node_mut(id).map(|node| &mut node.cache) } - fn get_layout_mut(&mut self, id: Self::NodeId) -> Option<&mut ::Layout> { + fn get_layout_mut(&mut self, id: Self::NodeId) -> Option<&mut >::Layout> { self.get_node_mut(id).map(|node| &mut node.layout) } - fn set_cache(&mut self, id: Self::NodeId, cache: ::Cache) { + fn set_cache(&mut self, id: Self::NodeId, cache: >::Cache) { if let Some(node) = self.get_node_mut(id) { node.cache = cache; } } - fn set_layout(&mut self, id: Self::NodeId, layout: ::Layout) { + fn set_layout(&mut self, id: Self::NodeId, layout: >::Layout) { if let Some(node) = self.get_node_mut(id) { node.layout = layout; } @@ -105,6 +108,8 @@ impl> LayoutTree for RenderTree { } } +impl> RenderTree {} + impl> RenderTree { // Generates a new render tree with a root node pub fn with_capacity(capacity: usize) -> Self { @@ -124,9 +129,9 @@ impl> RenderTree { parent: None, name: String::from("root"), namespace: None, - data: RenderNodeData::Document, - cache: ::Cache::default(), - layout: ::Layout::default(), + data: RenderNodeData::::Document, + cache: >::Cache::default(), + layout: >::Layout::default(), }, ); @@ -155,11 +160,11 @@ impl> RenderTree { parent: Some(parent), name, namespace, - data: RenderNodeData::Element { + data: RenderNodeData::::Element { attributes: HashMap::new(), }, - cache: ::Cache::default(), - layout: ::Layout::default(), + cache: >::Cache::default(), + layout: >::Layout::default(), }; self.attach_node(node); @@ -171,7 +176,7 @@ impl> RenderTree { &mut self, parent: NodeId, name: String, - data: RenderNodeData, + data: RenderNodeData, properties: C::CssPropertyMap, ) -> NodeId { let id = self.reserve_id(); @@ -184,8 +189,8 @@ impl> RenderTree { name, namespace: None, data, - cache: ::Cache::default(), - layout: ::Layout::default(), + cache: >::Cache::default(), + layout: >::Layout::default(), }; self.attach_node(node); @@ -352,9 +357,9 @@ impl> RenderTree { parent: Some(node_id), name: "#anonymous".to_string(), namespace: None, - data: RenderNodeData::AnonymousInline, - cache: ::Cache::default(), - layout: ::Layout::default(), + data: RenderNodeData::::AnonymousInline, + cache: >::Cache::default(), + layout: >::Layout::default(), }; let id = wrapper_node.id; @@ -504,8 +509,8 @@ impl + HasDocument> Rende name, // We might be able to move node into render_tree_node namespace, data: render_data, - cache: ::Cache::default(), - layout: ::Layout::default(), + cache: >::Cache::default(), + layout: >::Layout::default(), }; self.nodes.insert(current_node_id, render_tree_node); @@ -517,7 +522,7 @@ impl + HasDocument> Rende ::inheritance::(self); - if ::COLLAPSE_INLINE { + if >::COLLAPSE_INLINE { self.collapse_inline(self.root); } } @@ -543,7 +548,7 @@ impl> render_tree::Render self.get_children(id).cloned() } - fn get_layout(&self, id: Self::NodeId) -> Option<&::Layout> { + fn get_layout(&self, id: Self::NodeId) -> Option<&>::Layout> { Some(&LayoutTree::get_node(self, id)?.layout) } @@ -564,11 +569,11 @@ impl render_tree::RenderTreeNode for RenderTreeNode { &mut self.properties } - fn layout(&self) -> &::Layout { + fn layout(&self) -> &>::Layout { &self.layout } - fn layout_mut(&mut self) -> &mut ::Layout { + fn layout_mut(&mut self) -> &mut >::Layout { &mut self.layout } @@ -580,9 +585,9 @@ impl render_tree::RenderTreeNode for RenderTreeNode { None } - fn text_data(&self) -> Option<(&str, &[::TextLayout])> { + fn text_data(&self) -> Option<(&str, TextLayoutRef)> { if let RenderNodeData::Text(data) = &self.data { - return Some((&data.text, &data.layout)); + return Some((&data.text, data.layout.as_slice())); } None @@ -595,22 +600,20 @@ impl render_tree::RenderTreeNode for RenderTreeNode { // Generates a declaration property and adds it to the css_map_entry -pub enum RenderNodeData { +pub enum RenderNodeData { Document, Element { attributes: HashMap }, - Text(Box>), + Text(Box>), AnonymousInline, } -impl Debug for RenderNodeData { +impl Debug for RenderNodeData { fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { match self { - RenderNodeData::Document => f.write_str("Document"), - RenderNodeData::Element { attributes } => { - f.debug_struct("Element").field("attributes", attributes).finish() - } - RenderNodeData::Text(data) => f.debug_struct("TextData").field("data", data).finish(), - RenderNodeData::AnonymousInline => f.write_str("AnonymousInline"), + Self::Document => f.write_str("Document"), + Self::Element { attributes } => f.debug_struct("Element").field("attributes", attributes).finish(), + Self::Text(data) => f.debug_struct("TextData").field("data", data).finish(), + Self::AnonymousInline => f.write_str("AnonymousInline"), } } } @@ -629,12 +632,12 @@ impl Debug for RenderNodeData { // } // -pub struct TextData { +pub struct TextData { pub text: String, - pub layout: Vec, + pub layout: Vec<>::TextLayout>, } -impl Debug for TextData { +impl Debug for TextData { fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { f.debug_struct("TextData") .field("text", &self.text) @@ -649,8 +652,8 @@ pub enum ControlFlow { Error(anyhow::Error), } -impl RenderNodeData { - pub fn from_node_data(node: &NodeData) -> ControlFlow { +impl RenderNodeData { + pub fn from_node_data(node: &NodeData) -> ControlFlow { ControlFlow::Ok(match node { NodeData::Element(d) => RenderNodeData::Element { attributes: d.attributes().clone(), @@ -688,16 +691,16 @@ fn pre_transform_text(text: String) -> String { new_text } -pub struct RenderTreeNode { +pub struct RenderTreeNode { pub id: NodeId, pub properties: C::CssPropertyMap, pub children: Vec, pub parent: Option, pub name: String, pub namespace: Option, - pub data: RenderNodeData, - pub cache: ::Cache, - pub layout: ::Layout, + pub data: RenderNodeData, + pub cache: >::Cache, + pub layout: >::Layout, } impl Debug for RenderTreeNode { @@ -766,13 +769,13 @@ impl HasTextLayout for RenderTreeNode { } } - fn add_text_layout(&mut self, layout: ::TextLayout) { + fn add_text_layout(&mut self, layout: >::TextLayout) { if let RenderNodeData::Text(text) = &mut self.data { text.layout.push(layout); } } - fn get_text_layouts(&self) -> Option<&[::TextLayout]> { + fn get_text_layouts(&self) -> Option<&[>::TextLayout]> { if let RenderNodeData::Text(text) = &self.data { Some(&text.layout) } else { @@ -780,7 +783,7 @@ impl HasTextLayout for RenderTreeNode { } } - fn get_text_layouts_mut(&mut self) -> Option<&mut Vec<::TextLayout>> { + fn get_text_layouts_mut(&mut self) -> Option<&mut Vec<>::TextLayout>> { if let RenderNodeData::Text(text) = &mut self.data { Some(&mut text.layout) } else { @@ -801,8 +804,24 @@ impl LayoutNode for RenderTreeNode { } } + fn text_size(&self) -> Option { + if let RenderNodeData::Text(text) = &self.data { + Some(text.layout.iter().fold(Size::ZERO, |cur, layout| { + let size = layout.size(); + let offset = layout.offset(); + + Size { + width: cur.width.max(size.width + offset.x), + height: cur.height.max(size.height + offset.y), + } + })) + } else { + None + } + } + fn is_anon_inline_parent(&self) -> bool { - matches!(self.data, RenderNodeData::AnonymousInline) + matches!(self.data, RenderNodeData::::AnonymousInline) } } @@ -812,56 +831,3 @@ pub fn generate_render_tree(document: &C::Docume Ok(render_tree) } - -// pub fn walk_render_tree(tree: &RenderTree, visitor: &mut Box>) { -// let root = tree.get_root(); -// internal_walk_render_tree(tree, root, visitor); -// } -// -// fn internal_walk_render_tree( -// tree: &RenderTree, -// node: &RenderTreeNode, -// visitor: &mut Box>, -// ) { -// // Enter node -// match &node.data { -// RenderNodeData::Document(document) => visitor.document_enter(tree, node, document), -// RenderNodeData::DocType(doctype) => visitor.doctype_enter(tree, node, doctype), -// RenderNodeData::Text(text) => visitor.text_enter(tree, node, &text.into()), -// RenderNodeData::Comment(comment) => visitor.comment_enter(tree, node, comment), -// RenderNodeData::Element(element) => visitor.element_enter(tree, node, element), -// } -// -// for child_id in &node.children { -// if tree.nodes.contains_key(child_id) { -// let child_node = tree.nodes.get(child_id).expect("node"); -// internal_walk_render_tree(tree, child_node, visitor); -// } -// } -// -// // Leave node -// match &node.data { -// RenderNodeData::Document(document) => visitor.document_leave(tree, node, document), -// RenderNodeData::DocType(doctype) => visitor.doctype_leave(tree, node, doctype), -// RenderNodeData::Text(text) => visitor.text_leave(tree, node, &text.into()), -// RenderNodeData::Comment(comment) => visitor.comment_leave(tree, node, comment), -// RenderNodeData::Element(element) => visitor.element_leave(tree, node, element), -// } -// } -// -// pub trait TreeVisitor { -// fn document_enter(&mut self, tree: &RenderTree, node: &RenderTreeNode, data: &DocumentData); -// fn document_leave(&mut self, tree: &RenderTree, node: &RenderTreeNode, data: &DocumentData); -// -// fn doctype_enter(&mut self, tree: &RenderTree, node: &RenderTreeNode, data: &DocTypeData); -// fn doctype_leave(&mut self, tree: &RenderTree, node: &RenderTreeNode, data: &DocTypeData); -// -// fn text_enter(&mut self, tree: &RenderTree, node: &RenderTreeNode, data: &TextData); -// fn text_leave(&mut self, tree: &RenderTree, node: &RenderTreeNode, data: &TextData); -// -// fn comment_enter(&mut self, tree: &RenderTree, node: &RenderTreeNode, data: &CommentData); -// fn comment_leave(&mut self, tree: &RenderTree, node: &RenderTreeNode, data: &CommentData); -// -// fn element_enter(&mut self, tree: &RenderTree, node: &RenderTreeNode, data: &ElementData); -// fn element_leave(&mut self, tree: &RenderTree, node: &RenderTreeNode, data: &ElementData); -// } diff --git a/crates/gosub_rendering/src/text.rs b/crates/gosub_rendering/src/text.rs deleted file mode 100644 index 39fd36fb2..000000000 --- a/crates/gosub_rendering/src/text.rs +++ /dev/null @@ -1 +0,0 @@ -// fn measure_text() diff --git a/crates/gosub_taffy/Cargo.toml b/crates/gosub_taffy/Cargo.toml index 79fe0ec17..b54e79944 100644 --- a/crates/gosub_taffy/Cargo.toml +++ b/crates/gosub_taffy/Cargo.toml @@ -9,6 +9,7 @@ description = "Taffy layouter for Gosub" [dependencies] gosub_shared = { version = "0.1.1", registry = "gosub", path = "../gosub_shared" } gosub_interface = { version = "0.1.1", registry = "gosub", path = "../gosub_interface" } +gosub_fontmanager = { version = "0.1.0", path = "../gosub_fontmanager", registry = "gosub" } taffy = "0.7.5" anyhow = "1.0.94" regex = "1.11.1" diff --git a/crates/gosub_taffy/src/compute/inline.rs b/crates/gosub_taffy/src/compute/inline.rs index b8e84892f..2a506e59e 100644 --- a/crates/gosub_taffy/src/compute/inline.rs +++ b/crates/gosub_taffy/src/compute/inline.rs @@ -1,10 +1,6 @@ +use parley::fontique::{FallbackKey, Script, Weight}; +use parley::FontContext; use std::sync::{LazyLock, Mutex}; - -use log::warn; -use parley::fontique::{FallbackKey, Script}; -use parley::layout::{Alignment, PositionedLayoutItem}; -use parley::style::{FontSettings, FontStack, FontStyle, FontVariation, FontWeight, StyleProperty}; -use parley::{FontContext, InlineBox, LayoutContext}; use taffy::{ AvailableSpace, CollapsibleMarginSet, Layout, LayoutInput, LayoutOutput, LayoutPartialTree, NodeId, Point, Rect, RunMode, Size, @@ -12,11 +8,11 @@ use taffy::{ use gosub_interface::config::HasLayouter; use gosub_interface::css3::{CssProperty, CssValue}; -use gosub_interface::layout::{Decoration, DecorationStyle, FontData, HasTextLayout, LayoutNode, LayoutTree}; +use gosub_interface::layout::{Decoration, DecorationStyle, HasTextLayout, LayoutNode, LayoutTree}; +use gosub_interface::font::{FontBlob, FontInfo, FontManager, FontStyle, HasFontManager}; use gosub_shared::font::Glyph; -use gosub_shared::geo; use gosub_shared::geo::FP; -use gosub_shared::ROBOTO_FONT; +use gosub_shared::{geo, ROBOTO_FONT}; use crate::text::TextLayout; use crate::{Display, LayoutDocument, TaffyLayouter}; @@ -32,44 +28,62 @@ static FONT_CX: LazyLock> = LazyLock::new(|| { Mutex::new(ctx) }); +/// Computes the layout for inline elements. pub fn compute_inline_layout>( tree: &mut LayoutDocument, - nod_id: >::NodeId, + node_id: >::NodeId, mut layout_input: LayoutInput, ) -> LayoutOutput { layout_input.known_dimensions = Size::NONE; layout_input.run_mode = RunMode::PerformLayout; //TODO: We should respect the run mode // layout_input.sizing_mode = SizingMode::ContentSize; - let Some(children) = tree.0.children(nod_id) else { + // If there are no children, the node is hidden + let Some(children) = tree.0.children(node_id) else { return LayoutOutput::HIDDEN; }; + // Either a node is an inline element (for instance, an image aligned or inside a text), or an + // actual text node. + + // The buffer that holds the text data of the node let mut str_buf = String::new(); - let mut text_node_data = Vec::new(); + // Text node data holds the information about the text nodes. There can be multiple text node data elements for + // a single text, for instance, if there are different font sizes or weights inside the text. + let mut text_node_data: Vec> = Vec::new(); + // List of any inline boxes that are inside the node let mut inline_boxes = Vec::new(); + // Generate the text data and inline boxes. A text can consist of multiple text nodes, for instance, if there are + // different font sizes or weights inside the text. For instance: "This is a bold text". In this example + // there will be three text nodes: "This is a ", "bold" and " text". The first and last will have no bold font, + // but the second has a bold font. for child in &children { - let node_id = NodeId::from((*child).into()); + let child_node_id = NodeId::from((*child).into()); + // If the child is not a node, we skip it let Some(node) = tree.0.get_node_mut(*child) else { continue; }; node.clear_text_layout(); if let Some(text) = node.text_data() { + // We found a text node if text.is_empty() { continue; } + // Empty or whitespace only text nodes are ignored let only_whitespace = text.chars().all(|c| c.is_whitespace()); if only_whitespace { continue; } + // We add a space between the text nodes, so that the text is not glued together str_buf.push(' '); str_buf.push_str(text); + // @TODO: default font family can be different per platform let font_family = node .get_property("font-family") .and_then(|s| s.as_string()) @@ -77,21 +91,19 @@ pub fn compute_inline_layout>( .unwrap_or("sans-serif".to_string()); let font_size = node.get_property("font-size").map(|s| s.unit_to_px()).unwrap_or(16.0); - let alignment = parse_alignment(node); - let font_weight = parse_font_weight(node); - let font_style = parse_font_style(node); - let var_axes = parse_font_axes(node); - let line_height = node.get_property("line-height").and_then(|s| s.as_number()); - let word_spacing = node.get_property("word-spacing").map(|s| s.unit_to_px()); - let letter_spacing = node.get_property("letter-spacing").map(|s| s.unit_to_px()); + let font_info = ::FontInfo::new(&font_family) + .unwrap() + .with_weight(font_weight.value() as i32) + .with_style(font_style); + let mut underline = false; let mut overline = false; let mut line_through = false; @@ -99,10 +111,10 @@ pub fn compute_inline_layout>( let mut decoration_color = (0.0, 0.0, 0.0, 1.0); let mut style = DecorationStyle::Solid; let mut underline_offset = 4.0; - let color = node.get_property("color").and_then(|s| s.parse_color()); - if let Some(actual_parent) = tree.0.parent_id(nod_id) { + // Generate decoration styles + if let Some(actual_parent) = tree.0.parent_id(node_id) { if let Some(node) = tree.0.get_node_mut(actual_parent) { let decoration_line = node.get_property("text-decoration-line"); @@ -159,14 +171,12 @@ pub fn compute_inline_layout>( } text_node_data.push(TextNodeData { - font_family, + font_info, font_size, line_height, word_spacing, letter_spacing, alignment, - font_weight, - font_style, var_axes, decoration: Decoration { @@ -181,17 +191,17 @@ pub fn compute_inline_layout>( }, to: str_buf.len(), - id: node_id, + id: child_node_id, }); } else { - let out = tree.compute_child_layout(node_id, layout_input); + // We found an inline box + let out = tree.compute_child_layout(child_node_id, layout_input); tree.update_style(*child); let size = if let Some(cache) = tree.0.get_cache(*child) { if cache.display == Display::Inline { //TODO: handle margins here - out.content_size } else { out.size @@ -200,8 +210,8 @@ pub fn compute_inline_layout>( out.content_size }; - inline_boxes.push(InlineBox { - id: node_id.into(), + inline_boxes.push(parley::InlineBox { + id: child_node_id.into(), index: str_buf.len(), height: size.height, width: size.width, @@ -209,134 +219,150 @@ pub fn compute_inline_layout>( } } + // No inline boxes or text data, so the node is hidden if inline_boxes.is_empty() && str_buf.is_empty() { return LayoutOutput::HIDDEN; } + // We we don't have a text node, we add an empty character to the buffer if str_buf.is_empty() { str_buf.push(0 as char); } - let mut layout_cx: LayoutContext = LayoutContext::new(); + // We use the parley layout engine to generate the text layout + let mut layout_cx: parley::LayoutContext = parley::LayoutContext::new(); // let mut scale_cx = ScaleContext::new(); - let Ok(mut lock) = FONT_CX.lock() else { - warn!("Failed to get font context"); - return LayoutOutput::HIDDEN; - }; - let mut builder = layout_cx.ranged_builder(&mut lock, &str_buf, 1.0); - let mut align = Alignment::default(); + let mut font_context = FONT_CX.lock().unwrap(); - println!("============================="); - - for d in &text_node_data { - dbg!(d); - } + let mut builder = layout_cx.ranged_builder(&mut font_context, &str_buf, 1.0); + let mut align = parley::Alignment::default(); + // The first text node is the default style for the text. This is why this is treated separately. if let Some(default) = text_node_data.first() { - builder.push_default(StyleProperty::FontStack(FontStack::Source( - (&default.font_family).into(), + let info: &<::FontManager as FontManager>::FontInfo = default.font_info(); + + builder.push_default(parley::StyleProperty::FontStack(parley::FontStack::Single( + parley::FontFamily::Named(info.family().into()), ))); - builder.push_default(StyleProperty::FontSize(default.font_size)); + builder.push_default(parley::StyleProperty::FontSize(default.font_size)); if let Some(line_height) = default.line_height { - builder.push_default(StyleProperty::LineHeight(line_height)); + builder.push_default(parley::StyleProperty::LineHeight(line_height)); } if let Some(word_spacing) = default.word_spacing { - builder.push_default(StyleProperty::WordSpacing(word_spacing)); + builder.push_default(parley::StyleProperty::WordSpacing(word_spacing)); } if let Some(letter_spacing) = default.letter_spacing { - builder.push_default(StyleProperty::LetterSpacing(letter_spacing)); + builder.push_default(parley::StyleProperty::LetterSpacing(letter_spacing)); } - builder.push_default(StyleProperty::FontWeight(default.font_weight)); - builder.push_default(StyleProperty::FontStyle(default.font_style)); - builder.push_default(StyleProperty::FontVariations(FontSettings::List( + builder.push_default(parley::StyleProperty::FontWeight(Weight::new(info.weight() as f32))); + builder.push_default(parley::StyleProperty::FontStyle(match info.style() { + FontStyle::Normal => parley::FontStyle::Normal, + FontStyle::Italic => parley::FontStyle::Italic, + FontStyle::Oblique => parley::FontStyle::Oblique(None), + })); + builder.push_default(parley::StyleProperty::FontVariations(parley::FontSettings::List( default.var_axes.as_slice().into(), ))); if default.decoration.overline && default.decoration.underline { - builder.push_default(StyleProperty::Underline(true)); + builder.push_default(parley::StyleProperty::Underline(true)); - builder.push_default(StyleProperty::UnderlineSize(Some(default.decoration.width * 2.0))); - builder.push_default(StyleProperty::UnderlineOffset(Some( + builder.push_default(parley::StyleProperty::UnderlineSize(Some( + default.decoration.width * 2.0, + ))); + builder.push_default(parley::StyleProperty::UnderlineOffset(Some( default.decoration.underline_offset, ))); } else if default.decoration.overline { - builder.push_default(StyleProperty::Underline(true)); + builder.push_default(parley::StyleProperty::Underline(true)); - builder.push_default(StyleProperty::UnderlineSize(Some(default.decoration.width))); + builder.push_default(parley::StyleProperty::UnderlineSize(Some(default.decoration.width))); } else if default.decoration.underline { - builder.push_default(StyleProperty::Underline(true)); + builder.push_default(parley::StyleProperty::Underline(true)); - builder.push_default(StyleProperty::UnderlineSize(Some(default.decoration.width))); - builder.push_default(StyleProperty::UnderlineOffset(Some( + builder.push_default(parley::StyleProperty::UnderlineSize(Some(default.decoration.width))); + builder.push_default(parley::StyleProperty::UnderlineOffset(Some( default.decoration.underline_offset, ))); } - builder.push_default(StyleProperty::Brush(0)); + builder.push_default(parley::StyleProperty::Brush(0)); align = default.alignment; let mut from = default.to; for (idx, text_node) in text_node_data.get(1..).unwrap_or_default().iter().enumerate() { + let info: &<::FontManager as FontManager>::FontInfo = text_node.font_info(); + builder.push( - StyleProperty::FontStack(FontStack::Source(text_node.font_family.as_str().into())), + parley::StyleProperty::FontStack(parley::FontStack::Source(info.family().into())), from..text_node.to, ); - builder.push(StyleProperty::FontSize(text_node.font_size), from..text_node.to); + builder.push(parley::StyleProperty::FontSize(text_node.font_size), from..text_node.to); if let Some(line_height) = text_node.line_height { - builder.push(StyleProperty::LineHeight(line_height), from..text_node.to); + builder.push(parley::StyleProperty::LineHeight(line_height), from..text_node.to); } if let Some(word_spacing) = text_node.word_spacing { - builder.push(StyleProperty::WordSpacing(word_spacing), from..text_node.to); + builder.push(parley::StyleProperty::WordSpacing(word_spacing), from..text_node.to); } if let Some(letter_spacing) = text_node.letter_spacing { - builder.push(StyleProperty::LetterSpacing(letter_spacing), from..text_node.to); + builder.push(parley::StyleProperty::LetterSpacing(letter_spacing), from..text_node.to); } - builder.push(StyleProperty::FontWeight(text_node.font_weight), from..text_node.to); - builder.push(StyleProperty::FontStyle(text_node.font_style), from..text_node.to); builder.push( - StyleProperty::FontVariations(FontSettings::List(text_node.var_axes.as_slice().into())), + parley::StyleProperty::FontWeight(Weight::new(info.weight() as f32)), + from..text_node.to, + ); + builder.push( + parley::StyleProperty::FontStyle(match info.style() { + FontStyle::Normal => parley::FontStyle::Normal, + FontStyle::Italic => parley::FontStyle::Italic, + FontStyle::Oblique => parley::FontStyle::Oblique(None), + }), + from..text_node.to, + ); + builder.push( + parley::StyleProperty::FontVariations(parley::FontSettings::List(text_node.var_axes.as_slice().into())), from..text_node.to, ); - builder.push(StyleProperty::Brush(idx), from..text_node.to); + builder.push(parley::StyleProperty::Brush(idx), from..text_node.to); if default.decoration.overline && default.decoration.underline { - builder.push(StyleProperty::Underline(true), from..text_node.to); + builder.push(parley::StyleProperty::Underline(true), from..text_node.to); builder.push( - StyleProperty::UnderlineSize(Some(default.decoration.width * 2.0)), + parley::StyleProperty::UnderlineSize(Some(default.decoration.width * 2.0)), from..text_node.to, ); builder.push( - StyleProperty::UnderlineOffset(Some(default.decoration.underline_offset + 4.0)), + parley::StyleProperty::UnderlineOffset(Some(default.decoration.underline_offset + 4.0)), from..text_node.to, ); } else if default.decoration.overline { - builder.push(StyleProperty::Underline(true), from..text_node.to); + builder.push(parley::StyleProperty::Underline(true), from..text_node.to); builder.push( - StyleProperty::UnderlineSize(Some(default.decoration.width)), + parley::StyleProperty::UnderlineSize(Some(default.decoration.width)), from..text_node.to, ); - builder.push(StyleProperty::UnderlineOffset(Some(4.0)), from..text_node.to); + builder.push(parley::StyleProperty::UnderlineOffset(Some(4.0)), from..text_node.to); } else if default.decoration.underline { - builder.push(StyleProperty::Underline(true), from..text_node.to); + builder.push(parley::StyleProperty::Underline(true), from..text_node.to); builder.push( - StyleProperty::UnderlineSize(Some(default.decoration.width)), + parley::StyleProperty::UnderlineSize(Some(default.decoration.width)), from..text_node.to, ); builder.push( - StyleProperty::UnderlineOffset(Some(default.decoration.underline_offset)), + parley::StyleProperty::UnderlineOffset(Some(default.decoration.underline_offset)), from..text_node.to, ); } builder.push( - StyleProperty::Underline(default.decoration.underline || default.decoration.overline), + parley::StyleProperty::Underline(default.decoration.underline || default.decoration.overline), from..text_node.to, ); @@ -350,6 +376,8 @@ pub fn compute_inline_layout>( let mut layout = builder.build(&str_buf); + drop(font_context); + let max_width = match layout_input.available_space.width { AvailableSpace::Definite(width) => Some(width), AvailableSpace::MinContent => Some(0.0), @@ -385,7 +413,7 @@ pub fn compute_inline_layout>( for item in line.items() { match item { - PositionedLayoutItem::GlyphRun(run) => { + parley::PositionedLayoutItem::GlyphRun(run) => { let mut offset = 0.0; let grun = run.run(); @@ -447,11 +475,16 @@ pub fn compute_inline_layout>( } } + let font = grun.font().clone(); + let (font_data, _) = font.data.into_raw_parts(); + let text_layout = TextLayout { + glyphs, size, font_size: fs, - font_data: FontData::new(grun.font().data.data(), grun.font().index), glyphs, + // Actual font that is resolved by the layouter which is used for these set of glyphs + resolved_font: FontBlob::new(font_data, font.index), coords, decoration, offset: geo::Point { @@ -472,7 +505,7 @@ pub fn compute_inline_layout>( node.add_text_layout(text_layout); } - PositionedLayoutItem::InlineBox(inline_box) => { + parley::PositionedLayoutItem::InlineBox(inline_box) => { let id = NodeId::from(inline_box.id); let size = Size { @@ -562,67 +595,85 @@ pub fn compute_inline_layout>( } } +/// Structure that holds information for a (partial) text that consists of a single font size, weight, etc. +/// If a string consists of multiple font sizes, weights, etc., there will be multiple TextNodeData elements. +/// For instance: "This is a bold text". In this example there will be three text nodes: "This is a ", +/// "bold" and " text" with different font weights. #[derive(Debug)] -struct TextNodeData { - font_family: String, +struct TextNodeData { + /// Start index of the text node in the complete string (str_buf) + to: usize, + /// Node identifier that holds the text + id: NodeId, + /// Actual font for rendering and layouting + font_info: <::FontManager as FontManager>::FontInfo, + /// Font size font_size: f32, + /// Line height in case of multiple lines line_height: Option, + /// Spacing between words word_spacing: Option, + /// Spacing between letters (glyphs?) letter_spacing: Option, - alignment: Alignment, - font_weight: FontWeight, // Axis: WGHT - font_style: FontStyle, // Axis: ITAL - var_axes: Vec, + /// Alignment of the text + alignment: parley::Alignment, + /// Unknown + var_axes: Vec, + /// Decoration of the font (strikethrough, underline etc) decoration: Decoration, +} - to: usize, - id: NodeId, +impl TextNodeData { + /// Returns the font info of the text node + pub fn font_info(&self) -> &<::FontManager as FontManager>::FontInfo { + &self.font_info + } } -fn parse_alignment(node: &mut impl LayoutNode) -> Alignment { +fn parse_alignment(node: &mut impl LayoutNode) -> parley::Alignment { let Some(prop) = node.get_property("text-align") else { - return Alignment::Start; + return parley::Alignment::Start; }; let Some(s) = prop.as_string() else { - return Alignment::Start; + return parley::Alignment::Start; }; match s { - "left" => Alignment::Start, - "center" => Alignment::Middle, - "right" => Alignment::End, - "justify" => Alignment::Justified, - _ => Alignment::Start, + "left" => parley::Alignment::Start, + "center" => parley::Alignment::Middle, + "right" => parley::Alignment::End, + "justify" => parley::Alignment::Justified, + _ => parley::Alignment::Start, } } -fn parse_font_weight(node: &mut impl LayoutNode) -> FontWeight { +fn parse_font_weight(node: &mut impl LayoutNode) -> parley::FontWeight { let Some(prop) = node.get_property("font-weight") else { - return FontWeight::NORMAL; + return parley::FontWeight::NORMAL; }; let Some(s) = prop.as_string() else { if let Some(v) = prop.as_number() { - return FontWeight::new(v); + return parley::FontWeight::new(v); }; - return FontWeight::NORMAL; + return parley::FontWeight::NORMAL; }; match s { - "thin" => FontWeight::THIN, - "extra-light" => FontWeight::EXTRA_LIGHT, - "light" => FontWeight::LIGHT, - "semi-light" => FontWeight::SEMI_LIGHT, - "normal" => FontWeight::NORMAL, - "medium" => FontWeight::MEDIUM, - "semi-bold" => FontWeight::SEMI_BOLD, - "bold" => FontWeight::BOLD, - "extra-bold" => FontWeight::EXTRA_BOLD, - "black" => FontWeight::BLACK, - "extra-black" => FontWeight::EXTRA_BLACK, - _ => FontWeight::NORMAL, + "thin" => parley::FontWeight::THIN, + "extra-light" => parley::FontWeight::EXTRA_LIGHT, + "light" => parley::FontWeight::LIGHT, + "semi-light" => parley::FontWeight::SEMI_LIGHT, + "normal" => parley::FontWeight::NORMAL, + "medium" => parley::FontWeight::MEDIUM, + "semi-bold" => parley::FontWeight::SEMI_BOLD, + "bold" => parley::FontWeight::BOLD, + "extra-bold" => parley::FontWeight::EXTRA_BOLD, + "black" => parley::FontWeight::BLACK, + "extra-black" => parley::FontWeight::EXTRA_BLACK, + _ => parley::FontWeight::NORMAL, } } @@ -633,7 +684,6 @@ fn parse_font_style(node: &mut impl LayoutNode) -> FontStyle let Some(s) = prop.as_string() else { //TODO handle font-style: oblique - return FontStyle::Normal; }; @@ -645,7 +695,7 @@ fn parse_font_style(node: &mut impl LayoutNode) -> FontStyle } } -fn parse_font_axes(n: &mut impl LayoutNode) -> Vec { +fn parse_font_axes(n: &mut impl LayoutNode) -> Vec { let prop = n.get_property("font-variation-settings"); let Some(s) = prop else { @@ -678,6 +728,9 @@ fn parse_font_axes(n: &mut impl LayoutNode) -> Vec(n: &mut impl LayoutNode) -> Vec + HasFontManager> Layouter for TaffyLayouter { type Cache = Cache; type Layout = Layout; type TextLayout = TextLayout; const COLLAPSE_INLINE: bool = true; - fn layout>( + fn layout( &self, - tree: &mut C::LayoutTree, - root: >::NodeId, + tree: &mut B::LayoutTree, + root: >::NodeId, space: SizeU32, ) -> Result<()> { let size = taffy::Size { @@ -148,8 +150,15 @@ impl Layouter for TaffyLayouter { height: AvailableSpace::Definite(space.height as f32), }; - let mut tree: LayoutDocument = LayoutDocument(tree); + // We need to convert our tree into a LayoutDocument. This document can be used by Taffy to layout the tree + // throughout the LayoutPartialTree trait that our LayoutDocument implements. + let mut tree: LayoutDocument = LayoutDocument(tree); + + // Precompute the styles for all nodes in the layout tree. This will convert all the CSS properties we need + // for layouting into Taffy properties that are stored in a cache. Self::precompute_style(&mut tree, root); + + // Now let taffy compute the layout of the tree. compute_root_layout(&mut tree, TaffyId::from(root.into()), size); Ok(()) @@ -161,12 +170,14 @@ impl TaffyLayouter { tree: &mut LayoutDocument, root: >::NodeId, ) { + // Convert our CSS properties into Taffy properties and store them in a cache. tree.update_style(root); let Some(children) = tree.0.children(root) else { return; }; + // Recursively precompute the style for all children of the current node. for child in children { Self::precompute_style(tree, >::NodeId::from(child.into())); } @@ -221,6 +232,7 @@ impl> TraversePartialTree for LayoutDoc } impl> LayoutDocument<'_, C> { + /// Get the CSS properties for the given node, and store it inside the cache fn update_style(&mut self, node_id: >::NodeId) { let Some(node) = self.0.get_node_mut(node_id) else { return; @@ -234,9 +246,9 @@ impl> LayoutDocument<'_, C> { } } + /// Get the taffy style properties for a given node. If the style is dirty, we will update the style first. fn get_taffy_style(&mut self, node_id: >::NodeId) -> &Style { let dirty_style = self.0.style_dirty(node_id); - if dirty_style { self.update_style(node_id); } @@ -249,6 +261,7 @@ impl> LayoutDocument<'_, C> { &cache.style } + /// Force the taffy style from the cache. Do not care about dirty styles fn get_taffy_style_no_update(&self, node_id: >::NodeId) -> &Style { if let Some(cache) = self.0.get_cache(node_id) { return &cache.style; @@ -308,6 +321,7 @@ impl> CacheTree for LayoutDocument<'_, } } +/// Implementation of taffy's LayoutPartialTree impl> LayoutPartialTree for LayoutDocument<'_, C> { type CoreContainerStyle<'a> = &'a Style @@ -332,7 +346,10 @@ impl> LayoutPartialTree for LayoutDocum let node_id = >::NodeId::from(node_id_taffy.into()); if let Some(node) = tree.0.get_node_mut(node_id) { + // If we are an inline parent, we should compute the inline layout if node.is_anon_inline_parent() { + println!("Node: {:?} is inline parent", node_id); + // Any text nodes are always inline, so they are handled in this function return compute_inline_layout(tree, node_id, inputs); } } @@ -340,6 +357,8 @@ impl> LayoutPartialTree for LayoutDocum // let has_children = tree.0.child_count(node_id) > 0; //TODO: this isn't optimal, since we are now requesting the same node twice (up in get_cache and here) let style = tree.get_taffy_style(node_id); + // @TODO: somehow we should implement table layout here as well. This could be doable with a Grid layout aparently. + match style.display { TaffyDisplay::None => compute_hidden_layout(tree, node_id_taffy), TaffyDisplay::Block => compute_block_layout(tree, node_id_taffy, inputs), @@ -350,6 +369,7 @@ impl> LayoutPartialTree for LayoutDocum } } +/// Implementation of taffy's LayoutBLockContainer impl> LayoutBlockContainer for LayoutDocument<'_, C> { type BlockContainerStyle<'a> = &'a Style @@ -369,6 +389,7 @@ impl> LayoutBlockContainer for LayoutDo } } +/// Implementation of taffy's LayoutFlexboxContainer impl> LayoutFlexboxContainer for LayoutDocument<'_, C> { type FlexboxContainerStyle<'a> = &'a Style @@ -388,6 +409,7 @@ impl> LayoutFlexboxContainer for Layout } } +/// Implementation of taffy's LayoutGridContainer impl> LayoutGridContainer for LayoutDocument<'_, C> { type GridContainerStyle<'a> = &'a Style diff --git a/crates/gosub_taffy/src/style.rs b/crates/gosub_taffy/src/style.rs index cc78d96e8..e509dc096 100644 --- a/crates/gosub_taffy/src/style.rs +++ b/crates/gosub_taffy/src/style.rs @@ -9,8 +9,9 @@ mod parse_properties; const SCROLLBAR_WIDTH: f32 = 16.0; +// This function will convert a node into a Style object with Taffy properties. pub fn get_style_from_node(node: &mut impl LayoutNode) -> (Style, Display) { - //TODO: theoretically we should limit this to the taffy layouter, since it doesn't make any sense otherweise + //TODO: theoretically we should limit this to the taffy layouter, since it doesn't make any sense otherwise let (display, disp) = parse_properties::parse_display(node); let overflow = parse_properties::parse_overflow(node); let position = parse_properties::parse_position(node); diff --git a/crates/gosub_taffy/src/style/parse.rs b/crates/gosub_taffy/src/style/parse.rs index 78fc6f8bb..804052e69 100644 --- a/crates/gosub_taffy/src/style/parse.rs +++ b/crates/gosub_taffy/src/style/parse.rs @@ -8,6 +8,10 @@ use gosub_interface::config::HasLayouter; use gosub_interface::css3::CssProperty; use gosub_interface::layout::LayoutNode; +// Parse functions that will parse a CSS property and converts it into a Taffy type so it can be used +// in the taffy layout engine. This step is needed since our CSS properties are not directly compatible +// with the Taffy layout engine. + pub fn parse_len(node: &mut impl LayoutNode, name: &str) -> LengthPercentage { let Some(property) = node.get_property(name) else { return LengthPercentage::Length(0.0); diff --git a/crates/gosub_taffy/src/text.rs b/crates/gosub_taffy/src/text.rs index 222f9b11f..3da6d5abb 100644 --- a/crates/gosub_taffy/src/text.rs +++ b/crates/gosub_taffy/src/text.rs @@ -1,17 +1,26 @@ -use gosub_interface::layout::{Decoration, FontData, TextLayout as TLayout}; +use gosub_interface::font::FontBlob; +use gosub_interface::layout::{Decoration, TextLayout as TLayout}; use gosub_shared::font::Glyph; use gosub_shared::geo::{Point, Size}; use std::fmt; use std::fmt::{Debug, Formatter}; pub struct TextLayout { + /// Glyphs of the text that needs to be rendered. Note that glyph-ids are based on the font stored + /// in the font_info field. pub glyphs: Vec, - pub font_data: FontData, + /// Actual font used for layouting (and thus rendering) of the text. + pub resolved_font: FontBlob, + // Font size of the text pub font_size: f32, - pub size: Size, - pub coords: Vec, + /// Font decorations of the text pub decoration: Decoration, + /// Offset pub offset: Point, + /// Size of the text (?) + pub size: Size, + /// Coordinates of the text + pub coords: Vec, } impl Debug for TextLayout { @@ -26,26 +35,18 @@ impl Debug for TextLayout { } impl TLayout for TextLayout { - fn size(&self) -> Size { - self.size - } - fn glyphs(&self) -> &[Glyph] { &self.glyphs } - fn font_data(&self) -> &FontData { - &self.font_data + fn font(&self) -> &FontBlob { + &self.resolved_font } fn font_size(&self) -> f32 { self.font_size } - fn coords(&self) -> &[i16] { - &self.coords - } - fn decorations(&self) -> &Decoration { &self.decoration } @@ -53,4 +54,12 @@ impl TLayout for TextLayout { fn offset(&self) -> Point { self.offset } + + fn coords(&self) -> &[i16] { + &self.coords + } + + fn size(&self) -> Size { + self.size + } } diff --git a/crates/gosub_vello/Cargo.toml b/crates/gosub_vello/Cargo.toml index eeb912d88..c5885bc11 100644 --- a/crates/gosub_vello/Cargo.toml +++ b/crates/gosub_vello/Cargo.toml @@ -11,6 +11,7 @@ gosub_shared = { version = "0.1.1", registry = "gosub", path = "../gosub_shared" gosub_interface = { version = "0.1.1", registry = "gosub", path = "../gosub_interface", features = [] } gosub_html5 = { version = "0.1.1", registry = "gosub", path = "../gosub_html5", optional = true } gosub_svg = { version = "0.1.1", registry = "gosub", path = "../gosub_svg" } +gosub_fontmanager = { version = "0.1.0", path = "../gosub_fontmanager", registry = "gosub" } vello = "0.4.0" vello_encoding = "0.4.0" image = "0.25.5" diff --git a/crates/gosub_vello/src/lib.rs b/crates/gosub_vello/src/lib.rs index 7cd1c11f7..8a2a63df4 100644 --- a/crates/gosub_vello/src/lib.rs +++ b/crates/gosub_vello/src/lib.rs @@ -1,6 +1,7 @@ use std::fmt::Debug; use anyhow::anyhow; +use gosub_fontmanager::FontManager; use log::info; use vello::kurbo::Point as VelloPoint; use vello::peniko::Color as VelloColor; @@ -9,6 +10,7 @@ use vello::{AaConfig, RenderParams, Scene as VelloScene}; pub use border::*; pub use brush::*; pub use color::*; +use gosub_interface::font::HasFontManager; use gosub_interface::render_backend::{RenderBackend, RenderRect, RenderText, Scene as TScene, WindowHandle}; use gosub_shared::geo::{Point, SizeU32}; use gosub_shared::types::Result; @@ -48,27 +50,32 @@ impl Debug for VelloBackend { } } +impl HasFontManager for VelloBackend { + type FontManager = FontManager; +} + impl RenderBackend for VelloBackend { type Rect = Rect; type Border = Border; type BorderSide = BorderSide; type BorderRadius = BorderRadius; type Transform = Transform; - type Text = Text; type Gradient = Gradient; type Color = Color; type Image = Image; type Brush = Brush; type Scene = Scene; + type Text = Text; #[cfg(feature = "resvg")] type SVGRenderer = gosub_svg::resvg::Resvg; #[cfg(all(feature = "vello_svg", not(feature = "resvg")))] type SVGRenderer = vello_svg::VelloSVG; type ActiveWindowData<'a> = ActiveWindowData<'a>; - type WindowData<'a> = WindowData; + type FontManager = FontManager; + fn draw_rect(&mut self, data: &mut Self::WindowData<'_>, rect: &RenderRect) { data.scene.draw_rect(rect); } diff --git a/crates/gosub_vello/src/text.rs b/crates/gosub_vello/src/text.rs index c3973c09a..27dc573c1 100644 --- a/crates/gosub_vello/src/text.rs +++ b/crates/gosub_vello/src/text.rs @@ -1,13 +1,13 @@ use crate::VelloBackend; -use gosub_interface::layout::{Decoration, FontData, TextLayout}; +use gosub_interface::layout::{Decoration, TextLayout}; use gosub_interface::render_backend::{RenderText, Text as TText}; use gosub_shared::geo::{NormalizedCoord, Point, FP}; use vello::kurbo::{Affine, Line, Stroke}; -use vello::peniko::{Brush, Color, Fill, Font, StyleRef}; +use vello::peniko::{Blob, Brush, Color, Fill, Font, StyleRef}; use vello::Scene; use vello_encoding::Glyph; -#[derive(Clone)] +#[derive(Clone, Debug)] pub struct Text { glyphs: Vec, fs: FP, @@ -108,3 +108,34 @@ impl Text { } } } + +impl TText for Text { + fn new(layout: &impl TextLayout) -> Self { + let font = layout.font(); + + let font = Font::new(Blob::new(font.data.clone()), font.index); + + let fs = layout.font_size(); + + let glyphs = layout + .glyphs() + .iter() + .map(|g| Glyph { + id: g.id as u32, + x: g.x, + y: g.y, + }) + .collect(); + + let coords = layout.coords().iter().map(|c| NormalizedCoord::from_bits(*c)).collect(); + + Self { + glyphs, + font, + fs, + coords, + decoration: layout.decorations().clone(), + offset: layout.offset(), + } + } +} diff --git a/crates/gosub_web_platform/src/event_listeners.rs b/crates/gosub_web_platform/src/event_listeners.rs index 95480d742..1c7549b7a 100644 --- a/crates/gosub_web_platform/src/event_listeners.rs +++ b/crates/gosub_web_platform/src/event_listeners.rs @@ -1,7 +1,6 @@ use crate::callback::{Callback, FutureExecutor}; use gosub_interface::input::{InputEvent, MouseButton}; use gosub_shared::geo::Point; -use log::info; use std::fmt::Debug; pub enum Listeners { @@ -39,7 +38,6 @@ pub struct EventListener { impl EventListener { pub fn handle_event(&mut self, event: D, e: &mut E) { - info!("Handling event: {:?}", event); for listener in self.listeners.iter_mut() { listener.execute(e, event.clone()); } diff --git a/examples/gtk-renderer/main.rs b/examples/gtk-renderer/main.rs index 74a0a3598..9e6be169d 100644 --- a/examples/gtk-renderer/main.rs +++ b/examples/gtk-renderer/main.rs @@ -4,6 +4,7 @@ use futures::executor::block_on; use futures::StreamExt; use gosub_cairo::{CairoBackend, Scene}; use gosub_css3::system::Css3System; +use gosub_fontmanager::FontManager; use gosub_html5::document::builder::DocumentBuilderImpl; use gosub_html5::document::document_impl::DocumentImpl; use gosub_html5::document::fragment::DocumentFragmentImpl; @@ -11,6 +12,7 @@ use gosub_html5::parser::Html5Parser; use gosub_instance::{EngineInstance, InstanceMessage}; use gosub_interface::chrome::ChromeHandle; use gosub_interface::config::*; +use gosub_interface::font::HasFontManager; use gosub_interface::instance::{Handles, InstanceId}; use gosub_interface::render_backend::RenderBackend; use gosub_interface::render_backend::SizeU32; @@ -25,6 +27,7 @@ use log::{info, LevelFilter}; use simple_logger::SimpleLogger; use std::sync::{Arc, Mutex}; use url::Url; + const APP_ID: &str = "io.gosub.gtk-renderer"; #[derive(Clone, Debug, PartialEq)] @@ -64,6 +67,10 @@ impl HasChrome for Config { type ChromeHandle = GtkChromeHandle; } +impl HasFontManager for Config { + type FontManager = FontManager; +} + impl ModuleConfiguration for Config {} #[derive(Clone)] @@ -94,7 +101,7 @@ fn build_ui(app: &Application, cl: &ApplicationCommandLine) -> i32 { let url = args .get(1) .and_then(|url| url.to_str()) - .unwrap_or("https://example.com") + .unwrap_or("https://gosub.io/tests/gopher.html") .to_string(); // Create a window and set the title @@ -146,9 +153,15 @@ fn build_ui(app: &Application, cl: &ApplicationCommandLine) -> i32 { }); }); + let scroll = gtk4::ScrolledWindow::builder() + .hscrollbar_policy(gtk4::PolicyType::Automatic) + .vscrollbar_policy(gtk4::PolicyType::Automatic) + .child(&area) + .build(); + window.set_child(Some(&scroll)); + window.set_default_width(800); window.set_default_height(600); - window.set_child(Some(&area)); window.present(); 1 diff --git a/examples/vello-renderer/main.rs b/examples/vello-renderer/main.rs index 253fcad7a..f9c354db4 100644 --- a/examples/vello-renderer/main.rs +++ b/examples/vello-renderer/main.rs @@ -28,6 +28,7 @@ pub mod tabs; pub mod window; use gosub_instance::DebugEvent; use gosub_interface::chrome::ChromeHandle; +use gosub_interface::font::HasFontManager; use gosub_interface::instance::InstanceId; use gosub_interface::render_backend::RenderBackend; use gosub_shared::geo::SizeU32; @@ -71,6 +72,10 @@ impl HasChrome for Config { type ChromeHandle = WinitEventLoopHandle; } +impl HasFontManager for Config { + type FontManager = gosub_fontmanager::FontManager; +} + impl ModuleConfiguration for Config {} pub struct WinitEventLoopHandle { From 9afe616a2563ecef1e8f01db2578a6e45130deb9 Mon Sep 17 00:00:00 2001 From: Joshua Thijssen Date: Sat, 8 Feb 2025 12:21:28 +0100 Subject: [PATCH 2/2] Finalized font-manager integration --- crates/gosub_cairo/src/elements/text.rs | 17 +++------ crates/gosub_interface/src/font.rs | 8 +++++ crates/gosub_interface/src/layout.rs | 2 +- crates/gosub_renderer/src/draw.rs | 2 +- crates/gosub_taffy/src/compute/inline.rs | 5 ++- crates/gosub_taffy/src/text.rs | 6 ++-- crates/gosub_vello/src/text.rs | 46 +++++------------------- 7 files changed, 29 insertions(+), 57 deletions(-) diff --git a/crates/gosub_cairo/src/elements/text.rs b/crates/gosub_cairo/src/elements/text.rs index 7cdcc9566..241fda023 100644 --- a/crates/gosub_cairo/src/elements/text.rs +++ b/crates/gosub_cairo/src/elements/text.rs @@ -13,11 +13,7 @@ use gosub_interface::layout::{Decoration, TextLayout}; use gosub_interface::render_backend::{RenderText, Text as TText}; use gosub_shared::font::{Glyph, GlyphID}; use gosub_shared::geo::{NormalizedCoord, Point, FP}; - -use std::cell::RefCell; -use std::collections::HashMap; -use std::rc::Rc; -use log::{info, warn}; +use log::warn; thread_local! { static LIB_FONT_FACE: LazyCell = LazyCell::new(|| { @@ -31,7 +27,7 @@ pub struct GsText { // List of positioned glyphs with font info glyphs: Vec, // Font we need to display - font: FontBlob, + font_data: FontBlob, // Font size fs: FP, // List of coordinates for each glyph (?) @@ -45,9 +41,6 @@ pub struct GsText { impl TText for GsText { // fn new(layout: &TL) -> Self { fn new(layout: &impl TextLayout) -> Self { - let font = layout.font().clone(); - let fs = layout.font_size(); - let glyphs = layout .glyphs() .iter() @@ -60,8 +53,8 @@ impl TText for GsText { Self { glyphs, - font, - fs, + font_data: layout.font_data().clone(), + fs: layout.font_size(), coords: layout.coords().to_vec(), decoration: layout.decorations().clone(), offset: layout.offset(), @@ -76,7 +69,7 @@ impl GsText { cr.move_to(base_x, base_y); for text in &obj.text { - let Ok(font_face) = create_memory_font_face(&text.font) else { + let Ok(font_face) = create_memory_font_face(&text.font_data) else { warn!("Could not convert memory face"); continue; }; diff --git a/crates/gosub_interface/src/font.rs b/crates/gosub_interface/src/font.rs index d3d8363d5..d3aa81267 100644 --- a/crates/gosub_interface/src/font.rs +++ b/crates/gosub_interface/src/font.rs @@ -12,6 +12,14 @@ impl FontBlob { pub fn new(data: Arc + Send + Sync>, index: u32) -> Self { Self { data, index } } + + pub fn data(&self) -> &Arc + Send + Sync> { + &self.data + } + + pub fn as_u8(&self) -> &[u8] { + self.data.as_ref().as_ref() + } } impl Debug for FontBlob { diff --git a/crates/gosub_interface/src/layout.rs b/crates/gosub_interface/src/layout.rs index f482b682f..6b3f1954b 100644 --- a/crates/gosub_interface/src/layout.rs +++ b/crates/gosub_interface/src/layout.rs @@ -183,7 +183,7 @@ pub trait TextLayout { /// Returns a list of glyphs for the text fn glyphs(&self) -> &[Glyph]; /// Font data - fn font(&self) -> &FontBlob; + fn font_data(&self) -> &FontBlob; // Size of the font in pixels fn font_size(&self) -> f32; /// Additional font decorations diff --git a/crates/gosub_renderer/src/draw.rs b/crates/gosub_renderer/src/draw.rs index 2319c8fbd..5404bd16b 100644 --- a/crates/gosub_renderer/src/draw.rs +++ b/crates/gosub_renderer/src/draw.rs @@ -9,11 +9,11 @@ use gosub_interface::css3::{CssProperty, CssPropertyMap, CssValue}; use gosub_interface::draw::TreeDrawer; use gosub_interface::eventloop::EventLoopHandle; +use gosub_interface::layout::{Layout, LayoutTree, Layouter}; use gosub_interface::render_backend::{ Border, BorderSide, BorderStyle, Brush, Color, ImageBuffer, ImgCache, NodeDesc, Rect, RenderBackend, RenderBorder, RenderRect, RenderText, Scene as TScene, Text, Transform, }; -use gosub_interface::layout::{Layout, LayoutTree, Layouter, TextLayout}; use gosub_interface::render_tree; use gosub_interface::render_tree::RenderTreeNode as _; use gosub_interface::svg::SvgRenderer; diff --git a/crates/gosub_taffy/src/compute/inline.rs b/crates/gosub_taffy/src/compute/inline.rs index 2a506e59e..702c590cb 100644 --- a/crates/gosub_taffy/src/compute/inline.rs +++ b/crates/gosub_taffy/src/compute/inline.rs @@ -8,8 +8,8 @@ use taffy::{ use gosub_interface::config::HasLayouter; use gosub_interface::css3::{CssProperty, CssValue}; -use gosub_interface::layout::{Decoration, DecorationStyle, HasTextLayout, LayoutNode, LayoutTree}; use gosub_interface::font::{FontBlob, FontInfo, FontManager, FontStyle, HasFontManager}; +use gosub_interface::layout::{Decoration, DecorationStyle, HasTextLayout, LayoutNode, LayoutTree}; use gosub_shared::font::Glyph; use gosub_shared::geo::FP; use gosub_shared::{geo, ROBOTO_FONT}; @@ -482,9 +482,8 @@ pub fn compute_inline_layout>( glyphs, size, font_size: fs, - glyphs, // Actual font that is resolved by the layouter which is used for these set of glyphs - resolved_font: FontBlob::new(font_data, font.index), + font_data: FontBlob::new(font_data, font.index), coords, decoration, offset: geo::Point { diff --git a/crates/gosub_taffy/src/text.rs b/crates/gosub_taffy/src/text.rs index 3da6d5abb..08d765bc1 100644 --- a/crates/gosub_taffy/src/text.rs +++ b/crates/gosub_taffy/src/text.rs @@ -10,7 +10,7 @@ pub struct TextLayout { /// in the font_info field. pub glyphs: Vec, /// Actual font used for layouting (and thus rendering) of the text. - pub resolved_font: FontBlob, + pub font_data: FontBlob, // Font size of the text pub font_size: f32, /// Font decorations of the text @@ -39,8 +39,8 @@ impl TLayout for TextLayout { &self.glyphs } - fn font(&self) -> &FontBlob { - &self.resolved_font + fn font_data(&self) -> &FontBlob { + &self.font_data } fn font_size(&self) -> f32 { diff --git a/crates/gosub_vello/src/text.rs b/crates/gosub_vello/src/text.rs index 27dc573c1..595b24ca9 100644 --- a/crates/gosub_vello/src/text.rs +++ b/crates/gosub_vello/src/text.rs @@ -1,9 +1,10 @@ use crate::VelloBackend; +use gosub_interface::font::FontBlob; use gosub_interface::layout::{Decoration, TextLayout}; use gosub_interface::render_backend::{RenderText, Text as TText}; use gosub_shared::geo::{NormalizedCoord, Point, FP}; use vello::kurbo::{Affine, Line, Stroke}; -use vello::peniko::{Blob, Brush, Color, Fill, Font, StyleRef}; +use vello::peniko::{Blob, Brush, Color, Fill, Font as PenikoFont, StyleRef}; use vello::Scene; use vello_encoding::Glyph; @@ -11,35 +12,12 @@ use vello_encoding::Glyph; pub struct Text { glyphs: Vec, fs: FP, - font_data: FontData, + font_data: FontBlob, coords: Vec, decoration: Decoration, offset: Point, } -impl TText for Text { - fn new(layout: &TL) -> Self { - let glyphs = layout - .glyphs() - .iter() - .map(|g| Glyph { - id: g.id as u32, - x: g.x, - y: g.y, - }) - .collect(); - - Self { - glyphs, - fs: layout.font_size(), - font_data: layout.font_data().clone(), - coords: layout.coords().to_vec(), - decoration: layout.decorations().clone(), - offset: layout.offset(), - } - } -} - impl Text { pub(crate) fn show(scene: &mut Scene, render: &RenderText) { let brush = &render.brush.0; @@ -56,10 +34,10 @@ impl Text { for text in &render.text { let transform = transform.then_translate((text.offset.x as f64, text.offset.y as f64).into()); - let font = Font::new(text.font_data.to_bytes().to_vec().into(), text.font_data.index()); + let peniko_font = PenikoFont::new(Blob::new(text.font_data.data.clone()), text.font_data.index); scene - .draw_glyphs(&font) + .draw_glyphs(&peniko_font) .font_size(text.fs) .transform(transform) .glyph_transform(brush_transform) @@ -111,12 +89,6 @@ impl Text { impl TText for Text { fn new(layout: &impl TextLayout) -> Self { - let font = layout.font(); - - let font = Font::new(Blob::new(font.data.clone()), font.index); - - let fs = layout.font_size(); - let glyphs = layout .glyphs() .iter() @@ -127,13 +99,13 @@ impl TText for Text { }) .collect(); - let coords = layout.coords().iter().map(|c| NormalizedCoord::from_bits(*c)).collect(); + // let coords = layout.coords().iter().map(|c| NormalizedCoord::from(*c)).collect(); Self { glyphs, - font, - fs, - coords, + font_data: layout.font_data().clone(), + fs: layout.font_size(), + coords: layout.coords().to_vec(), decoration: layout.decorations().clone(), offset: layout.offset(), }