From 427f454bfa4aaba791b8d7d20aa939d389476619 Mon Sep 17 00:00:00 2001 From: Shark Date: Sat, 23 Mar 2024 20:41:01 +0100 Subject: [PATCH] calculate layout with taffy --- Cargo.lock | 996 +++++++++++++++++- crates/gosub_rendering/Cargo.toml | 4 + crates/gosub_rendering/src/layout.rs | 56 + crates/gosub_rendering/src/lib.rs | 2 + crates/gosub_rendering/src/style.rs | 74 ++ crates/gosub_rendering/src/style/parse.rs | 188 ++++ .../src/style/parse_properties.rs | 346 ++++++ crates/gosub_styling/Cargo.toml | 4 +- crates/gosub_styling/src/css_values.rs | 39 +- crates/gosub_styling/src/lib.rs | 1 + crates/gosub_styling/src/prerender_text.rs | 317 ++++++ crates/gosub_styling/src/render_tree.rs | 152 ++- 12 files changed, 2150 insertions(+), 29 deletions(-) create mode 100644 crates/gosub_rendering/src/layout.rs create mode 100644 crates/gosub_rendering/src/style.rs create mode 100644 crates/gosub_rendering/src/style/parse.rs create mode 100644 crates/gosub_rendering/src/style/parse_properties.rs create mode 100644 crates/gosub_styling/src/prerender_text.rs diff --git a/Cargo.lock b/Cargo.lock index 749587fb0..6af8ee794 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -52,6 +52,18 @@ dependencies = [ "subtle", ] +[[package]] +name = "ahash" +version = "0.8.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e89da841a80418a9b391ebaea17f5c112ffaaa96f621d2c285b5174da76b9011" +dependencies = [ + "cfg-if", + "once_cell", + "version_check", + "zerocopy 0.7.32", +] + [[package]] name = "aho-corasick" version = "1.1.2" @@ -61,6 +73,69 @@ dependencies = [ "memchr", ] +[[package]] +name = "aliasable" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "250f629c0161ad8107cf89319e990051fae62832fd343083bea452d93e2205fd" + +[[package]] +name = "alloc-no-stdlib" +version = "2.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cc7bb162ec39d46ab1ca8c77bf72e890535becd1751bb45f64c597edb4c8c6b3" + +[[package]] +name = "alloc-stdlib" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "94fb8275041c72129eb51b7d0322c29b8387a0386127718b096429201a5d6ece" +dependencies = [ + "alloc-no-stdlib", +] + +[[package]] +name = "allocator-api2" +version = "0.2.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0942ffc6dcaadf03badf6e6a2d0228460359d5e34b57ccdc720b7382dfbd5ec5" + +[[package]] +name = "allsorts" +version = "0.14.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "256467e8518f46b4ddbebd7cb8df2add04a720e3a6bdd0800c3896a5fc97ae3a" +dependencies = [ + "bitflags 1.3.2", + "bitreader", + "brotli-decompressor", + "byteorder", + "encoding_rs", + "flate2", + "glyph-names", + "itertools", + "lazy_static", + "libc", + "log", + "num-traits", + "ouroboros", + "rustc-hash", + "tinyvec", + "ucd-trie", + "unicode-canonical-combining-class", + "unicode-general-category", + "unicode-joining-type", +] + +[[package]] +name = "android_system_properties" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "819e7219dbd41043ac279b19830f2efc897156490d7fd6ea916720117ee66311" +dependencies = [ + "libc", +] + [[package]] name = "anes" version = "0.1.6" @@ -121,6 +196,21 @@ version = "1.0.81" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0952808a6c2afd1aa8947271f3a60f1a6763c7b912d210184c5149b5cf147247" +[[package]] +name = "arrayvec" +version = "0.7.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "96d30a06541fbafbc7f82ed10c06164cfbd2c401138f6addd8404629c4b16711" + +[[package]] +name = "ash" +version = "0.37.3+1.3.251" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "39e9c3835d686b0a6084ab4234fcd1b07dbf6e4767dce60874b12356a25ecd4a" +dependencies = [ + "libloading 0.7.4", +] + [[package]] name = "async-trait" version = "0.1.77" @@ -159,6 +249,21 @@ version = "0.21.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9d297deb1925b89f2ccc13d7635fa0714f12c87adce1c75356b39ca9b7178567" +[[package]] +name = "bit-set" +version = "0.5.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0700ddab506f33b20a03b13996eccd309a48e5ff77d0d95926aa0210fb4e95f1" +dependencies = [ + "bit-vec", +] + +[[package]] +name = "bit-vec" +version = "0.6.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "349f9b6a179ed607305526ca489b34ad0a41aed5f7980fa90eb03160b69598fb" + [[package]] name = "bitflags" version = "1.3.2" @@ -171,6 +276,21 @@ version = "2.4.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ed570934406eb16438a4e976b1b4500774099c13b8cb96eec99f620f05090ddf" +[[package]] +name = "bitreader" +version = "0.3.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bdd859c9d97f7c468252795b35aeccc412bdbb1e90ee6969c4fa6328272eaeff" +dependencies = [ + "cfg-if", +] + +[[package]] +name = "block" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0d8c1fef690941d3e7788d328517591fecc684c084084702d6ff1641e993699a" + [[package]] name = "block-buffer" version = "0.10.4" @@ -180,6 +300,16 @@ dependencies = [ "generic-array", ] +[[package]] +name = "brotli-decompressor" +version = "2.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4e2e4afe60d7dd600fdd3de8d0f08c2b7ec039712e3b6137ff98b7004e82de4f" +dependencies = [ + "alloc-no-stdlib", + "alloc-stdlib", +] + [[package]] name = "bumpalo" version = "3.15.3" @@ -192,6 +322,32 @@ version = "0.6.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e1e5f035d16fc623ae5f74981db80a439803888314e3a555fd6f04acd51a3205" +[[package]] +name = "bytemuck" +version = "1.15.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5d6d68c57235a3a081186990eca2867354726650f42f7516ca50c28d6281fd15" +dependencies = [ + "bytemuck_derive", +] + +[[package]] +name = "bytemuck_derive" +version = "1.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4da9a32f3fed317401fa3c862968128267c3106685286e15d5aaa3d7389c2f60" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.55", +] + +[[package]] +name = "byteorder" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b" + [[package]] name = "bytes" version = "1.5.0" @@ -216,6 +372,12 @@ version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" +[[package]] +name = "cfg_aliases" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fd16c4719339c4530435d38e511904438d07cce7950afa3718a84ac36c10e89e" + [[package]] name = "chardet" version = "0.2.4" @@ -299,6 +461,16 @@ version = "0.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "98cc8fbded0c607b7ba9dd60cd98df59af97e84d24e49c8557331cfc26d301ce" +[[package]] +name = "codespan-reporting" +version = "0.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3538270d33cc669650c4b093848450d380def10c331d38c768e34cac80576e6e" +dependencies = [ + "termcolor", + "unicode-width", +] + [[package]] name = "colorchoice" version = "1.0.0" @@ -321,6 +493,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 = "convert_case" version = "0.4.0" @@ -344,6 +547,33 @@ dependencies = [ "version_check", ] +[[package]] +name = "core-foundation" +version = "0.9.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "91e195e091a93c46f7102ec7818a2aa394e1e1771c3ab4825963fa03e45afb8f" +dependencies = [ + "core-foundation-sys", + "libc", +] + +[[package]] +name = "core-foundation-sys" +version = "0.8.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "06ea2b9bc92be3c2baa9334a323ebca2d6f074ff852cd1d7b11064035cd3868f" + +[[package]] +name = "core-graphics-types" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "45390e6114f68f718cc7a830514a96f903cccd70d02a8f6d9f643ac4ba45afaf" +dependencies = [ + "bitflags 1.3.2", + "core-foundation", + "libc", +] + [[package]] name = "cpufeatures" version = "0.2.12" @@ -449,6 +679,17 @@ dependencies = [ "cipher", ] +[[package]] +name = "d3d12" +version = "0.19.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3e3d747f100290a1ca24b752186f61f6637e1deffe3bf6320de6fcb29510a307" +dependencies = [ + "bitflags 2.4.2", + "libloading 0.8.3", + "winapi", +] + [[package]] name = "data-encoding" version = "2.5.0" @@ -500,6 +741,15 @@ version = "1.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "11157ac094ffbdde99aa67b23417ebdd801842852b500e395a45a9c0aac03e4a" +[[package]] +name = "encoding_rs" +version = "0.8.33" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7268b386296a025e474d5140678f75d6de9493ae55a5d709eeb9dd08149945e1" +dependencies = [ + "cfg-if", +] + [[package]] name = "enum-as-inner" version = "0.6.0" @@ -528,6 +778,15 @@ dependencies = [ "windows-sys 0.52.0", ] +[[package]] +name = "euclid" +version = "0.22.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "87f253bc5c813ca05792837a0ff4b3a580336b224512d48f7eda1d7dd9210787" +dependencies = [ + "num-traits", +] + [[package]] name = "flate2" version = "1.0.28" @@ -544,6 +803,39 @@ version = "1.0.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" +[[package]] +name = "font-types" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5b7f6040d337bd44434ab21fc6509154edf2cece88b23758d9d64654c4e7730b" + +[[package]] +name = "foreign-types" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d737d9aa519fb7b749cbc3b962edcf310a8dd1f4b67c91c4f83975dbdd17d965" +dependencies = [ + "foreign-types-macros", + "foreign-types-shared", +] + +[[package]] +name = "foreign-types-macros" +version = "0.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1a5c6c585bc94aaf2c7b51dd4c2ba22680844aba4c687be581871a6f518c5742" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.55", +] + +[[package]] +name = "foreign-types-shared" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "aa9a19cbb55df58761df49b23516a86d432839add4af60fc256da840f66ed35b" + [[package]] name = "form_urlencoded" version = "1.2.1" @@ -578,6 +870,17 @@ version = "0.3.30" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "dfc6580bb841c5a68e9ef15c77ccc837b40a7504914d52e47b8b0e9bbda25a1d" +[[package]] +name = "futures-intrusive" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1d930c203dd0b6ff06e0201a4a2fe9149b43c684fd4420555b26d21b1a02956f" +dependencies = [ + "futures-core", + "lock_api", + "parking_lot", +] + [[package]] name = "futures-io" version = "0.3.30" @@ -640,6 +943,44 @@ version = "0.28.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4271d37baee1b8c7e4b708028c57d816cf9d2434acb33a549475f78c181f6253" +[[package]] +name = "gl_generator" +version = "0.14.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1a95dfc23a2b4a9a2f5ab41d194f8bfda3cabec42af4e39f08c339eb2a0c124d" +dependencies = [ + "khronos_api", + "log", + "xml-rs", +] + +[[package]] +name = "glow" +version = "0.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bd348e04c43b32574f2de31c8bb397d96c9fcfa1371bd4ca6d8bdc464ab121b1" +dependencies = [ + "js-sys", + "slotmap", + "wasm-bindgen", + "web-sys", +] + +[[package]] +name = "glutin_wgl_sys" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6c8098adac955faa2d31079b65dc48841251f69efd3ac25477903fc424362ead" +dependencies = [ + "gl_generator", +] + +[[package]] +name = "glyph-names" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c3531d702d6c1a3ba92a5fb55a404c7b8c476c8e7ca249951077afcbe4bc807f" + [[package]] name = "gosub_bindings" version = "0.1.0" @@ -756,7 +1097,11 @@ dependencies = [ name = "gosub_rendering" version = "0.1.0" dependencies = [ + "anyhow", "gosub_html5", + "gosub_styling", + "regex", + "taffy", ] [[package]] @@ -783,6 +1128,8 @@ dependencies = [ "gosub_shared", "lazy_static", "regex", + "rust-fontconfig", + "vello", ] [[package]] @@ -840,6 +1187,74 @@ dependencies = [ "syn 2.0.55", ] +[[package]] +name = "gpu-alloc" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fbcd2dba93594b227a1f57ee09b8b9da8892c34d55aa332e034a228d0fe6a171" +dependencies = [ + "bitflags 2.4.2", + "gpu-alloc-types", +] + +[[package]] +name = "gpu-alloc-types" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "98ff03b468aa837d70984d55f5d3f846f6ec31fe34bbb97c4f85219caeee1ca4" +dependencies = [ + "bitflags 2.4.2", +] + +[[package]] +name = "gpu-allocator" +version = "0.25.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6f56f6318968d03c18e1bcf4857ff88c61157e9da8e47c5f29055d60e1228884" +dependencies = [ + "log", + "presser", + "thiserror", + "winapi", + "windows", +] + +[[package]] +name = "gpu-descriptor" +version = "0.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cc11df1ace8e7e564511f53af41f3e42ddc95b56fd07b3f4445d2a6048bc682c" +dependencies = [ + "bitflags 2.4.2", + "gpu-descriptor-types", + "hashbrown", +] + +[[package]] +name = "gpu-descriptor-types" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6bf0b36e6f090b7e1d8a4b49c0cb81c1f8376f72198c65dd3ad9ff3556b8b78c" +dependencies = [ + "bitflags 2.4.2", +] + +[[package]] +name = "grid" +version = "0.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d196ffc1627db18a531359249b2bf8416178d84b729f3cebeb278f285fb9b58c" + +[[package]] +name = "guillotiere" +version = "0.6.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b62d5865c036cb1393e23c50693df631d3f5d7bcca4c04fe4cc0fd592e74a782" +dependencies = [ + "euclid", + "svg_fmt", +] + [[package]] name = "gzip-header" version = "1.0.0" @@ -864,6 +1279,25 @@ name = "hashbrown" version = "0.14.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "290f1a1d9242c78d09ce40a5e87e7554ee637af1351968159f4952f028f75604" +dependencies = [ + "ahash", + "allocator-api2", +] + +[[package]] +name = "hassle-rs" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "af2a7e73e1f34c48da31fb668a907f250794837e08faa144fd24f0b8b741e890" +dependencies = [ + "bitflags 2.4.2", + "com", + "libc", + "libloading 0.8.3", + "thiserror", + "widestring", + "winapi", +] [[package]] name = "heck" @@ -883,6 +1317,12 @@ version = "0.3.9" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d231dfb89cfffdbc30e7fc41579ed6066ad03abda9e567ccafae602b97ec5024" +[[package]] +name = "hexf-parse" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dfa686283ad6dd069f105e5ab091b04c62850d3e4cf5d67debad1933f55023df" + [[package]] name = "hickory-proto" version = "0.24.0" @@ -1060,6 +1500,12 @@ version = "1.0.10" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b1a46d1a171d865aa5f83f92695765caa047a9b4cbae2cbf37dbd613a793fd4c" +[[package]] +name = "jni-sys" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8eaf4bc02d17cbdd7ff4c7438cafcdf7fb9a4613313ad11b4f8fefe7d3fa0130" + [[package]] name = "js-sys" version = "0.3.69" @@ -1069,6 +1515,33 @@ dependencies = [ "wasm-bindgen", ] +[[package]] +name = "khronos-egl" +version = "6.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6aae1df220ece3c0ada96b8153459b67eebe9ae9212258bb0134ae60416fdf76" +dependencies = [ + "libc", + "libloading 0.8.3", + "pkg-config", +] + +[[package]] +name = "khronos_api" +version = "3.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e2db585e1d738fc771bf08a151420d3ed193d9d895a36df7f6f8a9456b911ddc" + +[[package]] +name = "kurbo" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6e5aa9f0f96a938266bdb12928a67169e8d22c6a786fda8ed984b85e6ba93c3c" +dependencies = [ + "arrayvec", + "smallvec", +] + [[package]] name = "lazy_static" version = "1.4.0" @@ -1081,6 +1554,26 @@ version = "0.2.153" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9c198f91728a82281a64e1f4f9eeb25d82cb32a5de251c6bd1b5154d63a8e7bd" +[[package]] +name = "libloading" +version = "0.7.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b67380fd3b2fbe7527a606e18729d21c6f3951633d0500574c4dc22d2d638b9f" +dependencies = [ + "cfg-if", + "winapi", +] + +[[package]] +name = "libloading" +version = "0.8.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0c2a198fb6b0eada2a8df47933734e6d35d350665a33a3593d7164fa52c75c19" +dependencies = [ + "cfg-if", + "windows-targets 0.52.4", +] + [[package]] name = "linked-hash-map" version = "0.5.6" @@ -1118,6 +1611,15 @@ dependencies = [ "linked-hash-map", ] +[[package]] +name = "malloc_buf" +version = "0.0.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "62bb907fe88d54d8d9ce32a3cceab4218ed2f6b7d35617cafe9adf84e43919cb" +dependencies = [ + "libc", +] + [[package]] name = "match_cfg" version = "0.1.0" @@ -1130,6 +1632,21 @@ version = "2.7.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "523dc4f511e55ab87b694dc30d0f820d60906ef06413f93d4d7a1385599cc149" +[[package]] +name = "metal" +version = "0.27.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c43f73953f8cbe511f021b58f18c3ce1c3d1ae13fe953293e13345bf83217f25" +dependencies = [ + "bitflags 2.4.2", + "block", + "core-graphics-types", + "foreign-types", + "log", + "objc", + "paste", +] + [[package]] name = "minimal-lexical" version = "0.2.1" @@ -1151,9 +1668,48 @@ version = "0.8.11" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a4a650543ca06a924e8b371db273b2756685faae30f8487da1b56505a8f78b0c" dependencies = [ - "libc", - "wasi", - "windows-sys 0.48.0", + "libc", + "wasi", + "windows-sys 0.48.0", +] + +[[package]] +name = "mmapio" +version = "0.9.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0204e2cac68f5b2e35b7ec8cb5d906f6e58e78dad8066a30b6ee54da99bb03dd" +dependencies = [ + "libc", + "winapi", +] + +[[package]] +name = "naga" +version = "0.19.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "50e3524642f53d9af419ab5e8dd29d3ba155708267667c2f3f06c88c9e130843" +dependencies = [ + "bit-set", + "bitflags 2.4.2", + "codespan-reporting", + "hexf-parse", + "indexmap", + "log", + "num-traits", + "rustc-hash", + "spirv", + "termcolor", + "thiserror", + "unicode-xid", +] + +[[package]] +name = "ndk-sys" +version = "0.5.0+25.2.9519653" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8c196769dd60fd4f363e11d948139556a344e79d451aeb2fa2fd040738ef7691" +dependencies = [ + "jni-sys", ] [[package]] @@ -1211,6 +1767,25 @@ dependencies = [ "libc", ] +[[package]] +name = "objc" +version = "0.2.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "915b1b472bc21c53464d6c8461c9d3af805ba1ef837e1cac254428f4a77177b1" +dependencies = [ + "malloc_buf", + "objc_exception", +] + +[[package]] +name = "objc_exception" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ad970fb455818ad6cba4c122ad012fae53ae8b4795f86378bce65e4f6bab2ca4" +dependencies = [ + "cc", +] + [[package]] name = "object" version = "0.32.2" @@ -1238,6 +1813,30 @@ version = "0.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c08d65885ee38876c4f86fa503fb49d7b507c2b62552df7c70b2fce627e06381" +[[package]] +name = "ouroboros" +version = "0.17.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e2ba07320d39dfea882faa70554b4bd342a5f273ed59ba7c1c6b4c840492c954" +dependencies = [ + "aliasable", + "ouroboros_macro", + "static_assertions", +] + +[[package]] +name = "ouroboros_macro" +version = "0.17.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ec4c6225c69b4ca778c0aea097321a64c421cf4577b331c61b229267edabb6f8" +dependencies = [ + "heck 0.4.1", + "proc-macro-error", + "proc-macro2", + "quote", + "syn 2.0.55", +] + [[package]] name = "parking_lot" version = "0.12.1" @@ -1267,6 +1866,16 @@ version = "1.0.14" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "de3145af08024dea9fa9914f381a17b8fc6034dfb00f3a84013f7ff43f29ed4c" +[[package]] +name = "peniko" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "caaf7fec601d640555d9a4cab7343eba1e1c7a5a71c9993ff63b4c26bc5d50c5" +dependencies = [ + "kurbo", + "smallvec", +] + [[package]] name = "percent-encoding" version = "2.3.1" @@ -1385,6 +1994,36 @@ version = "0.2.17" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5b40af805b3121feab8a3c29f04d8ad262fa8e0561883e7653e024ae4479e6de" +[[package]] +name = "presser" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e8cf8e6a8aa66ce33f63993ffc4ea4271eb5b0530a9002db8455ea6050c77bfa" + +[[package]] +name = "proc-macro-error" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "da25490ff9892aab3fcf7c36f08cfb902dd3e71ca0f9f9517bea02a73a5ce38c" +dependencies = [ + "proc-macro-error-attr", + "proc-macro2", + "quote", + "syn 1.0.109", + "version_check", +] + +[[package]] +name = "proc-macro-error-attr" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a1be40180e52ecc98ad80b184934baf3d0d29f979574e439af5a55274b35f869" +dependencies = [ + "proc-macro2", + "quote", + "version_check", +] + [[package]] name = "proc-macro2" version = "1.0.79" @@ -1394,6 +2033,12 @@ dependencies = [ "unicode-ident", ] +[[package]] +name = "profiling" +version = "1.0.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "43d84d1d7a6ac92673717f9f6d1518374ef257669c24ebc5ac25d5033828be58" + [[package]] name = "quick-error" version = "1.2.3" @@ -1428,7 +2073,7 @@ checksum = "8d31e63ea85be51c423e52ba8f2e68a3efd53eed30203ee029dd09947333693e" dependencies = [ "rand_chacha 0.9.0-alpha.1", "rand_core 0.9.0-alpha.1", - "zerocopy", + "zerocopy 0.8.0-alpha.6", ] [[package]] @@ -1467,9 +2112,21 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "cc89dffba8377c5ec847d12bb41492bda235dba31a25e8b695cd0fe6589eb8c9" dependencies = [ "getrandom", - "zerocopy", + "zerocopy 0.8.0-alpha.6", ] +[[package]] +name = "range-alloc" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9c8a99fddc9f0ba0a85884b8d14e3592853e787d581ca1816c91349b10e4eeab" + +[[package]] +name = "raw-window-handle" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "42a9830a0e1b9fb145ebb365b8bc4ccd75f290f98c0247deafbbe2c75cefb544" + [[package]] name = "rayon" version = "1.9.0" @@ -1490,6 +2147,15 @@ dependencies = [ "crossbeam-utils", ] +[[package]] +name = "read-fonts" +version = "0.15.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "17ea23eedb4d938031b6d4343222444608727a6aa68ec355e13588d9947ffe92" +dependencies = [ + "font-types", +] + [[package]] name = "redox_syscall" version = "0.4.1" @@ -1528,6 +2194,12 @@ version = "0.8.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c08c74e62047bb2de4ff487b251e4a92e24f48745648451635cec7d591162d9f" +[[package]] +name = "renderdoc-sys" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "19b30a45b0cd0bcca8037f3d0dc3421eaf95327a17cad11964fb8179b4fc4832" + [[package]] name = "resolv-conf" version = "0.7.0" @@ -1553,12 +2225,30 @@ dependencies = [ "windows-sys 0.52.0", ] +[[package]] +name = "rust-fontconfig" +version = "0.1.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ea6002602a06c22f5f4e5ea2ed68854bfce071b48e1092c03874e34d0b59eb4b" +dependencies = [ + "allsorts", + "mmapio", + "rayon", + "xmlparser", +] + [[package]] name = "rustc-demangle" version = "0.1.23" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d626bb9dae77e28219937af045c257c28bfd3f69333c512553507f5f9798cb76" +[[package]] +name = "rustc-hash" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "08d43f7aa6b08d49f382cde6a7982047c3426db949b1424bc4b7ec9ae12c6ce2" + [[package]] name = "rustc_version" version = "0.4.0" @@ -1700,6 +2390,15 @@ version = "0.3.11" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "38b58827f4464d87d377d175e90bf58eb00fd8716ff0a62f80356b5e61555d0d" +[[package]] +name = "skrifa" +version = "0.15.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eff28ee3b66d43060ef9a327e0f18e4c1813f194120156b4d4524fac3ba8ce22" +dependencies = [ + "read-fonts", +] + [[package]] name = "slab" version = "0.4.9" @@ -1709,6 +2408,15 @@ dependencies = [ "autocfg", ] +[[package]] +name = "slotmap" +version = "1.0.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dbff4acf519f630b3a3ddcfaea6c06b42174d9a44bc70c620e9ed1649d58b82a" +dependencies = [ + "version_check", +] + [[package]] name = "smallvec" version = "1.13.1" @@ -1731,6 +2439,15 @@ version = "0.9.8" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6980e8d7511241f8acf4aebddbb1ff938df5eebe98691418c4468d0b72a96a67" +[[package]] +name = "spirv" +version = "0.3.0+sdk-1.3.268.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eda41003dc44290527a59b13432d4a0379379fa074b70174882adfbdfd917844" +dependencies = [ + "bitflags 2.4.2", +] + [[package]] name = "sqlite" version = "0.34.0" @@ -1761,6 +2478,12 @@ dependencies = [ "sqlite3-src", ] +[[package]] +name = "static_assertions" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f" + [[package]] name = "strsim" version = "0.11.0" @@ -1773,6 +2496,12 @@ version = "2.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "81cdd64d312baedb58e21336b31bc043b77e01cc99033ce76ef539f78e965ebc" +[[package]] +name = "svg_fmt" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f83ba502a3265efb76efb89b0a2f7782ad6f2675015d4ce37e4b547dda42b499" + [[package]] name = "syn" version = "1.0.109" @@ -1795,6 +2524,28 @@ dependencies = [ "unicode-ident", ] +[[package]] +name = "taffy" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1ebe07d79b1554c75926c01a8431a365d61dd9661096b9b493cb0adba712445e" +dependencies = [ + "arrayvec", + "grid", + "num-traits", + "serde", + "slotmap", +] + +[[package]] +name = "termcolor" +version = "1.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "06794f8f6c5c898b3275aebefa6b8a1cb24cd2c6c79397ab15774837a0bc5755" +dependencies = [ + "winapi-util", +] + [[package]] name = "test-case" version = "3.3.1" @@ -1968,18 +2719,42 @@ version = "1.17.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "42ff0bf0c66b8238c6f3b578df37d0b7848e55df8577b3f74f92a69acceeb825" +[[package]] +name = "ucd-trie" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ed646292ffc8188ef8ea4d1e0e0150fb15a5c2e12ad9b8fc191ae7a8a7f3c4b9" + [[package]] name = "unicode-bidi" version = "0.3.15" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "08f95100a766bf4f8f28f90d77e0a5461bbdb219042e7679bebe79004fed8d75" +[[package]] +name = "unicode-canonical-combining-class" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6925586af9268182c711e47c0853ed84131049efaca41776d0ca97f983865c32" + +[[package]] +name = "unicode-general-category" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2281c8c1d221438e373249e065ca4989c4c36952c211ff21a0ee91c44a3869e7" + [[package]] name = "unicode-ident" version = "1.0.12" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3354b9ac3fae1ff6755cb6db53683adb661634f67557942dea4facebec0fee4b" +[[package]] +name = "unicode-joining-type" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "22f8cb47ccb8bc750808755af3071da4a10dcd147b68fc874b7ae4b12543f6f5" + [[package]] name = "unicode-normalization" version = "0.1.23" @@ -1989,6 +2764,18 @@ dependencies = [ "tinyvec", ] +[[package]] +name = "unicode-width" +version = "0.1.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e51733f11c9c4f72aa0c160008246859e340b00807569a0da0e7a1079b27ba85" + +[[package]] +name = "unicode-xid" +version = "0.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f962df74c8c05a667b5ee8bcf162993134c104e96440b663c8daa176dc772d8c" + [[package]] name = "universal-hash" version = "0.5.1" @@ -2063,6 +2850,33 @@ dependencies = [ "which", ] +[[package]] +name = "vello" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e9a4b96a2d6d6effa67868b4436560e3a767f71f0e043df007587c5d6b2e8b7a" +dependencies = [ + "bytemuck", + "futures-intrusive", + "peniko", + "raw-window-handle", + "skrifa", + "vello_encoding", + "wgpu", +] + +[[package]] +name = "vello_encoding" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0c5b6c6ec113c9b6ee1e1894ccef1b5559373aead718b7442811f2fefff7d423" +dependencies = [ + "bytemuck", + "guillotiere", + "peniko", + "skrifa", +] + [[package]] name = "version_check" version = "0.9.4" @@ -2110,6 +2924,18 @@ dependencies = [ "wasm-bindgen-shared", ] +[[package]] +name = "wasm-bindgen-futures" +version = "0.4.42" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "76bc14366121efc8dbb487ab05bcc9d346b3b5ec0eaa76e46594cabbe51762c0" +dependencies = [ + "cfg-if", + "js-sys", + "wasm-bindgen", + "web-sys", +] + [[package]] name = "wasm-bindgen-macro" version = "0.2.92" @@ -2158,6 +2984,113 @@ dependencies = [ "rustls-pki-types", ] +[[package]] +name = "wgpu" +version = "0.19.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a4b1213b52478a7631d6e387543ed8f642bc02c578ef4e3b49aca2a29a7df0cb" +dependencies = [ + "arrayvec", + "cfg-if", + "cfg_aliases", + "js-sys", + "log", + "naga", + "parking_lot", + "profiling", + "raw-window-handle", + "smallvec", + "static_assertions", + "wasm-bindgen", + "wasm-bindgen-futures", + "web-sys", + "wgpu-core", + "wgpu-hal", + "wgpu-types", +] + +[[package]] +name = "wgpu-core" +version = "0.19.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f9f6b033c2f00ae0bc8ea872c5989777c60bc241aac4e58b24774faa8b391f78" +dependencies = [ + "arrayvec", + "bit-vec", + "bitflags 2.4.2", + "cfg_aliases", + "codespan-reporting", + "indexmap", + "log", + "naga", + "once_cell", + "parking_lot", + "profiling", + "raw-window-handle", + "rustc-hash", + "smallvec", + "thiserror", + "web-sys", + "wgpu-hal", + "wgpu-types", +] + +[[package]] +name = "wgpu-hal" +version = "0.19.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "49f972c280505ab52ffe17e94a7413d9d54b58af0114ab226b9fc4999a47082e" +dependencies = [ + "android_system_properties", + "arrayvec", + "ash", + "bit-set", + "bitflags 2.4.2", + "block", + "cfg_aliases", + "core-graphics-types", + "d3d12", + "glow", + "glutin_wgl_sys", + "gpu-alloc", + "gpu-allocator", + "gpu-descriptor", + "hassle-rs", + "js-sys", + "khronos-egl", + "libc", + "libloading 0.8.3", + "log", + "metal", + "naga", + "ndk-sys", + "objc", + "once_cell", + "parking_lot", + "profiling", + "range-alloc", + "raw-window-handle", + "renderdoc-sys", + "rustc-hash", + "smallvec", + "thiserror", + "wasm-bindgen", + "web-sys", + "wgpu-types", + "winapi", +] + +[[package]] +name = "wgpu-types" +version = "0.19.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b671ff9fb03f78b46ff176494ee1ebe7d603393f42664be55b64dc8d53969805" +dependencies = [ + "bitflags 2.4.2", + "js-sys", + "web-sys", +] + [[package]] name = "which" version = "5.0.0" @@ -2214,6 +3147,25 @@ 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", + "windows-targets 0.52.4", +] + +[[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.4", +] + [[package]] name = "windows-sys" version = "0.48.0" @@ -2356,13 +3308,45 @@ dependencies = [ "windows-sys 0.48.0", ] +[[package]] +name = "xml-rs" +version = "0.8.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0fcb9cbac069e033553e8bb871be2fbdffcab578eb25bd0f7c508cedc6dcd75a" + +[[package]] +name = "xmlparser" +version = "0.13.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "66fee0b777b0f5ac1c69bb06d361268faafa61cd4682ae064a171c16c433e9e4" + +[[package]] +name = "zerocopy" +version = "0.7.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "74d4d3961e53fa4c9a25a8637fc2bfaf2595b3d3ae34875568a5cf64787716be" +dependencies = [ + "zerocopy-derive 0.7.32", +] + [[package]] name = "zerocopy" version = "0.8.0-alpha.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "db678a6ee512bd06adf35c35be471cae2f9c82a5aed2b5d15e03628c98bddd57" dependencies = [ - "zerocopy-derive", + "zerocopy-derive 0.8.0-alpha.6", +] + +[[package]] +name = "zerocopy-derive" +version = "0.7.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9ce1b18ccd8e73a9321186f97e46f9f04b778851177567b1975109d26a08d2a6" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.55", ] [[package]] diff --git a/crates/gosub_rendering/Cargo.toml b/crates/gosub_rendering/Cargo.toml index 4de50c8e0..905562abf 100644 --- a/crates/gosub_rendering/Cargo.toml +++ b/crates/gosub_rendering/Cargo.toml @@ -7,3 +7,7 @@ license = "MIT" [dependencies] gosub_html5 = { path = "../gosub_html5" } +gosub_styling = { path = "../gosub_styling" } +taffy = "0.4.1" +anyhow = "1.0.81" +regex = "1.10.4" diff --git a/crates/gosub_rendering/src/layout.rs b/crates/gosub_rendering/src/layout.rs new file mode 100644 index 000000000..c2a8c21b4 --- /dev/null +++ b/crates/gosub_rendering/src/layout.rs @@ -0,0 +1,56 @@ +use taffy::prelude::*; + +use gosub_html5::node::NodeId as GosubID; +use gosub_styling::render_tree::{RenderNodeData, RenderTree}; + +use crate::style::get_style_from_node; + +pub fn generate_taffy_tree(rt: &mut RenderTree) -> anyhow::Result<(TaffyTree, NodeId)> { + let mut tree: TaffyTree = TaffyTree::with_capacity(rt.nodes.len()); + + rt.get_root(); + + let root = add_children_to_tree(rt, &mut tree, rt.root)?; + + Ok((tree, root)) +} + +fn add_children_to_tree( + rt: &mut RenderTree, + tree: &mut TaffyTree, + node_id: GosubID, +) -> anyhow::Result { + let Some(node_children) = rt.get_children(node_id) else { + return Err(anyhow::anyhow!("Node not found {:?}", node_id)); + }; + + let mut children = Vec::with_capacity(node_children.len()); + + //clone, so we can drop the borrow of RT, we would be copying the NodeID anyway, so it's not a big deal (only a few bytes) + for child in node_children.clone() { + match add_children_to_tree(rt, tree, child) { + Ok(node) => children.push(node), + Err(e) => eprintln!("Error adding child to tree: {:?}", e), + } + } + + let Some(node) = rt.get_node_mut(node_id) else { + return Err(anyhow::anyhow!("Node not found")); + }; + + let style = get_style_from_node(node); + + let node = rt.get_node(node_id).unwrap(); + if let RenderNodeData::Text(text) = &node.data { + println!("Text: {:?}", text.text); + println!("Style: {:?}", style.size); + } + + let node = tree + .new_with_children(style, &children) + .map_err(|e| anyhow::anyhow!(e.to_string()))?; + + tree.set_node_context(node, Some(node_id))?; + + Ok(node) +} diff --git a/crates/gosub_rendering/src/lib.rs b/crates/gosub_rendering/src/lib.rs index d5ee9e438..e817e266a 100644 --- a/crates/gosub_rendering/src/lib.rs +++ b/crates/gosub_rendering/src/lib.rs @@ -3,4 +3,6 @@ //! This crate supplies functionality to render CSSOM and DOM trees into a viewable display. //! +pub mod layout; pub mod render_tree; +pub mod style; diff --git a/crates/gosub_rendering/src/style.rs b/crates/gosub_rendering/src/style.rs new file mode 100644 index 000000000..70e002d35 --- /dev/null +++ b/crates/gosub_rendering/src/style.rs @@ -0,0 +1,74 @@ +mod parse; +mod parse_properties; + +use gosub_styling::render_tree::RenderTreeNode; +use taffy::Style; + +const SCROLLBAR_WIDTH: f32 = 16.0; + +pub fn get_style_from_node(node: &mut RenderTreeNode) -> Style { + let display = parse_properties::parse_display(node); + let overflow = parse_properties::parse_overflow(node); + let position = parse_properties::parse_position(node); + let inset = parse_properties::parse_inset(node); + let size = parse_properties::parse_size(node); + let min_size = parse_properties::parse_min_size(node); + let max_size = parse_properties::parse_max_size(node); + let aspect_ratio = parse_properties::parse_aspect_ratio(node); + let margin = parse_properties::parse_margin(node); + let padding = parse_properties::parse_padding(node); + let border = parse_properties::parse_border(node); + let align_items = parse_properties::parse_align_items(node); + let align_self = parse_properties::parse_align_self(node); + let justify_items = parse_properties::parse_justify_items(node); + let justify_self = parse_properties::parse_justify_self(node); + let align_content = parse_properties::parse_align_content(node); + let justify_content = parse_properties::parse_justify_content(node); + let gap = parse_properties::parse_gap(node); + let flex_direction = parse_properties::parse_flex_direction(node); + let flex_wrap = parse_properties::parse_flex_wrap(node); + let flex_basis = parse_properties::parse_flex_basis(node); + let flex_grow = parse_properties::parse_flex_grow(node); + let flex_shrink = parse_properties::parse_flex_shrink(node); + let grid_template_rows = parse_properties::parse_grid_template_rows(node); + let grid_template_columns = parse_properties::parse_grid_template_columns(node); + let grid_auto_rows = parse_properties::parse_grid_auto_rows(node); + let grid_auto_columns = parse_properties::parse_grid_auto_columns(node); + let grid_auto_flow = parse_properties::parse_grid_auto_flow(node); + let grid_row = parse_properties::parse_grid_row(node); + let grid_column = parse_properties::parse_grid_column(node); + + Style { + display, + overflow, + scrollbar_width: SCROLLBAR_WIDTH, + position, + inset, + size, + min_size, + max_size, + aspect_ratio, + margin, + padding, + border, + align_items, + align_self, + justify_items, + justify_self, + align_content, + justify_content, + gap, + flex_direction, + flex_wrap, + flex_basis, + flex_grow, + flex_shrink, + grid_template_rows, + grid_template_columns, + grid_auto_rows, + grid_auto_columns, + grid_auto_flow, + grid_row, + grid_column, + } +} diff --git a/crates/gosub_rendering/src/style/parse.rs b/crates/gosub_rendering/src/style/parse.rs new file mode 100644 index 000000000..ab5a2b141 --- /dev/null +++ b/crates/gosub_rendering/src/style/parse.rs @@ -0,0 +1,188 @@ +use taffy::prelude::*; +use taffy::{ + AlignContent, AlignItems, Dimension, GridPlacement, LengthPercentage, LengthPercentageAuto, + TrackSizingFunction, +}; + +use gosub_styling::css_values::CssValue; +use gosub_styling::render_tree::{RenderNodeData, RenderTreeNode}; + +pub(crate) fn parse_len(node: &mut RenderTreeNode, name: &str) -> LengthPercentage { + let Some(property) = node.get_property(name) else { + return LengthPercentage::Length(0.0); + }; + + property.compute_value(); + + match &property.actual { + CssValue::Percentage(value) => LengthPercentage::Percent(*value), + CssValue::Unit(..) => LengthPercentage::Length(property.actual.unit_to_px()), + CssValue::String(_) => LengthPercentage::Length(property.actual.unit_to_px()), //HACK + _ => LengthPercentage::Length(0.0), + } +} + +pub(crate) fn parse_len_auto(node: &mut RenderTreeNode, name: &str) -> LengthPercentageAuto { + let Some(property) = node.get_property(name) else { + return LengthPercentageAuto::Auto; + }; + + property.compute_value(); + + match &property.actual { + CssValue::String(value) => match value.as_str() { + "auto" => LengthPercentageAuto::Auto, + _ => LengthPercentageAuto::Length(property.actual.unit_to_px()), //HACK + }, + CssValue::Percentage(value) => LengthPercentageAuto::Percent(*value), + CssValue::Unit(..) => LengthPercentageAuto::Length(property.actual.unit_to_px()), + _ => LengthPercentageAuto::Auto, + } +} + +pub(crate) fn parse_dimension(node: &mut RenderTreeNode, name: &str) -> Dimension { + let mut auto = Dimension::Auto; + if let RenderNodeData::Text(text) = &node.data { + if name == "width" { + auto = Dimension::Length(text.width); + } else if name == "height" { + auto = Dimension::Length(text.height); + } + } + + let Some(property) = node.get_property(name) else { + return auto; + }; + + property.compute_value(); + + if name == "width" { + println!("Width: {:?}", property.actual); + } + + match &property.actual { + CssValue::String(value) => match value.as_str() { + "auto" => auto, + s if s.ends_with('%') => { + let value = s.trim_end_matches('%').parse::().unwrap_or(0.0); + Dimension::Percent(value) + } + _ => Dimension::Length(property.actual.unit_to_px()), //HACK + }, + CssValue::Percentage(value) => Dimension::Percent(*value), + CssValue::Unit(..) => Dimension::Length(property.actual.unit_to_px()), + _ => auto, + } +} + +pub(crate) fn parse_align_i(node: &mut RenderTreeNode, name: &str) -> Option { + let display = node.get_property(name)?; + display.compute_value(); + + let CssValue::String(ref value) = display.actual else { + return None; + }; + + match value.as_str() { + "start" => Some(AlignItems::Start), + "end" => Some(AlignItems::End), + "flex-start" => Some(AlignItems::FlexStart), + "flex-end" => Some(AlignItems::FlexEnd), + "center" => Some(AlignItems::Center), + "baseline" => Some(AlignItems::Baseline), + "stretch" => Some(AlignItems::Stretch), + _ => None, + } +} + +pub(crate) fn parse_align_c(node: &mut RenderTreeNode, name: &str) -> Option { + let display = node.get_property(name)?; + + display.compute_value(); + + let CssValue::String(ref value) = display.actual else { + return None; + }; + + match value.as_str() { + "start" => Some(AlignContent::Start), + "end" => Some(AlignContent::End), + "flex-start" => Some(AlignContent::FlexStart), + "flex-end" => Some(AlignContent::FlexEnd), + "center" => Some(AlignContent::Center), + "stretch" => Some(AlignContent::Stretch), + "space-between" => Some(AlignContent::SpaceBetween), + "space-around" => Some(AlignContent::SpaceAround), + _ => None, + } +} + +pub(crate) fn parse_tracking_sizing_function( + node: &mut RenderTreeNode, + name: &str, +) -> Vec { + let Some(display) = node.get_property(name) else { + return Vec::new(); + }; + + display.compute_value(); + + let CssValue::String(ref _value) = display.actual else { + return Vec::new(); + }; + + Vec::new() //TODO: Implement this +} + +#[allow(dead_code)] +pub(crate) fn parse_non_repeated_tracking_sizing_function( + _node: &mut RenderTreeNode, + _name: &str, +) -> NonRepeatedTrackSizingFunction { + todo!("implement parse_non_repeated_tracking_sizing_function") +} + +pub(crate) fn parse_grid_auto( + node: &mut RenderTreeNode, + name: &str, +) -> Vec { + let Some(display) = node.get_property(name) else { + return Vec::new(); + }; + + display.compute_value(); + + let CssValue::String(ref _value) = display.actual else { + return Vec::new(); + }; + + Vec::new() //TODO: Implement this +} + +pub(crate) fn parse_grid_placement(node: &mut RenderTreeNode, name: &str) -> GridPlacement { + let Some(display) = node.get_property(name) else { + return GridPlacement::Auto; + }; + + display.compute_value(); + + match &display.actual { + CssValue::String(value) => { + if value.starts_with("span") { + let value = value.trim_start_matches("span").trim(); + + if let Ok(value) = value.parse::() { + GridPlacement::from_span(value) + } else { + GridPlacement::Auto + } + } else if let Ok(value) = value.parse::() { + GridPlacement::from_line_index(value) + } else { + GridPlacement::Auto + } + } + CssValue::Number(value) => GridPlacement::from_line_index(*value as i16), + _ => GridPlacement::Auto, + } +} diff --git a/crates/gosub_rendering/src/style/parse_properties.rs b/crates/gosub_rendering/src/style/parse_properties.rs new file mode 100644 index 000000000..26b8f1bed --- /dev/null +++ b/crates/gosub_rendering/src/style/parse_properties.rs @@ -0,0 +1,346 @@ +use regex::Regex; +use taffy::prelude::*; +use taffy::{Overflow, Point}; + +use gosub_styling::css_values::CssValue; +use gosub_styling::render_tree::RenderTreeNode; + +use crate::style::parse::{ + parse_align_c, parse_align_i, parse_dimension, parse_grid_auto, parse_grid_placement, + parse_len, parse_len_auto, parse_tracking_sizing_function, +}; + +pub(crate) fn parse_display(node: &mut RenderTreeNode) -> Display { + let Some(display) = node.get_property("display") else { + return Display::Block; + }; + + display.compute_value(); + + let CssValue::String(ref value) = display.actual else { + return Display::Block; + }; + + match value.as_str() { + "none" => Display::None, + "block" => Display::Block, + "flex" => Display::Flex, + "grid" => Display::Grid, + _ => Display::Block, + } +} + +pub(crate) fn parse_overflow(node: &mut RenderTreeNode) -> Point { + fn parse(str: &str) -> Overflow { + match str { + "visible" => Overflow::Visible, + "hidden" => Overflow::Hidden, + "scroll" => Overflow::Scroll, + _ => Overflow::Visible, + } + } + + let mut overflow = Point { + x: Overflow::Visible, + y: Overflow::Visible, + }; + + if let Some(display) = node.get_property("overflow-x") { + display.compute_value(); + + if let CssValue::String(ref value) = display.actual { + let x = parse(value); + overflow.x = x; + }; + }; + + if let Some(display) = node.get_property("overflow-y") { + display.compute_value(); + + if let CssValue::String(ref value) = display.actual { + let y = parse(value); + overflow.y = y; + }; + }; + + overflow +} + +pub(crate) fn parse_position(node: &mut RenderTreeNode) -> Position { + let Some(position) = node.get_property("position") else { + return Position::Relative; + }; + + position.compute_value(); + + let CssValue::String(ref value) = position.actual else { + return Position::Relative; + }; + + match value.as_str() { + "relative" => Position::Relative, + "absolute" => Position::Absolute, + _ => Position::Relative, + } +} + +pub(crate) fn parse_inset(node: &mut RenderTreeNode) -> Rect { + Rect { + top: parse_len_auto(node, "inset-top"), + right: parse_len_auto(node, "inset-right"), + bottom: parse_len_auto(node, "inset-bottom"), + left: parse_len_auto(node, "inset-left"), + } +} + +pub(crate) fn parse_size(node: &mut RenderTreeNode) -> Size { + Size { + width: parse_dimension(node, "width"), + height: parse_dimension(node, "height"), + } +} + +pub(crate) fn parse_min_size(node: &mut RenderTreeNode) -> Size { + Size { + width: parse_dimension(node, "min-width"), + height: parse_dimension(node, "min-height"), + } +} + +pub(crate) fn parse_max_size(node: &mut RenderTreeNode) -> Size { + Size { + width: parse_dimension(node, "max-width"), + height: parse_dimension(node, "max-height"), + } +} + +pub(crate) fn parse_aspect_ratio(node: &mut RenderTreeNode) -> Option { + let aspect_ratio = node.get_property("aspect-ratio")?; + + aspect_ratio.compute_value(); + + match &aspect_ratio.actual { + CssValue::Number(value) => Some(*value), + CssValue::String(value) => { + if value == "auto" { + None + } else { + //expecting: number / number + let Ok(regex) = Regex::new(r"(\d+\.?\d*)\s*/\s*(\d+\.?\d*)") else { + return None; + }; + let captures = regex.captures(value)?; + + if captures.len() != 3 { + return None; + } + + let Ok(numerator) = captures[1].parse::() else { + return None; + }; + let Ok(denominator) = captures[2].parse::() else { + return None; + }; + + Some(numerator / denominator) + } + } + + _ => None, + } +} + +pub(crate) fn parse_margin(node: &mut RenderTreeNode) -> Rect { + Rect { + top: parse_len_auto(node, "margin-top"), + right: parse_len_auto(node, "margin-right"), + bottom: parse_len_auto(node, "margin-bottom"), + left: parse_len_auto(node, "margin-left"), + } +} + +pub(crate) fn parse_padding(node: &mut RenderTreeNode) -> Rect { + Rect { + top: parse_len(node, "padding-top"), + right: parse_len(node, "padding-right"), + bottom: parse_len(node, "padding-bottom"), + left: parse_len(node, "padding-left"), + } +} + +pub(crate) fn parse_border(node: &mut RenderTreeNode) -> Rect { + Rect { + top: parse_len(node, "border-top-width"), + right: parse_len(node, "border-right-width"), + bottom: parse_len(node, "border-bottom-width"), + left: parse_len(node, "border-left-width"), + } +} + +pub(crate) fn parse_align_items(node: &mut RenderTreeNode) -> Option { + let display = node.get_property("align-items")?; + + display.compute_value(); + + let CssValue::String(ref value) = display.actual else { + return None; + }; + + match value.as_str() { + "start" => Some(AlignItems::Start), + "end" => Some(AlignItems::End), + "flex-start" => Some(AlignItems::FlexStart), + "flex-end" => Some(AlignItems::FlexEnd), + "center" => Some(AlignItems::Center), + "baseline" => Some(AlignItems::Baseline), + "stretch" => Some(AlignItems::Stretch), + _ => None, + } +} + +pub(crate) fn parse_align_self(node: &mut RenderTreeNode) -> Option { + parse_align_i(node, "align-self") +} + +pub(crate) fn parse_justify_items(node: &mut RenderTreeNode) -> Option { + parse_align_i(node, "justify-items") +} + +pub(crate) fn parse_justify_self(node: &mut RenderTreeNode) -> Option { + parse_align_i(node, "justify-self") +} + +pub(crate) fn parse_align_content(node: &mut RenderTreeNode) -> Option { + parse_align_c(node, "align-content") +} + +pub(crate) fn parse_justify_content(node: &mut RenderTreeNode) -> Option { + parse_align_c(node, "justify-content") +} + +pub(crate) fn parse_gap(node: &mut RenderTreeNode) -> Size { + Size { + width: parse_len(node, "column-gap"), + height: parse_len(node, "row-gap"), + } +} + +pub(crate) fn parse_flex_direction(node: &mut RenderTreeNode) -> FlexDirection { + let Some(property) = node.get_property("flex-direction") else { + return FlexDirection::Row; + }; + + property.compute_value(); + + match &property.actual { + CssValue::String(value) => match value.as_str() { + "row" => FlexDirection::Row, + "row-reverse" => FlexDirection::RowReverse, + "column" => FlexDirection::Column, + "column-reverse" => FlexDirection::ColumnReverse, + _ => FlexDirection::Row, + }, + _ => FlexDirection::Row, + } +} + +pub(crate) fn parse_flex_wrap(node: &mut RenderTreeNode) -> FlexWrap { + let Some(property) = node.get_property("flex-wrap") else { + return FlexWrap::NoWrap; + }; + + property.compute_value(); + + match &property.actual { + CssValue::String(value) => match value.as_str() { + "nowrap" => FlexWrap::NoWrap, + "wrap" => FlexWrap::Wrap, + "wrap-reverse" => FlexWrap::WrapReverse, + _ => FlexWrap::NoWrap, + }, + _ => FlexWrap::NoWrap, + } +} + +pub(crate) fn parse_flex_basis(node: &mut RenderTreeNode) -> Dimension { + parse_dimension(node, "flex-basis") +} + +pub(crate) fn parse_flex_grow(node: &mut RenderTreeNode) -> f32 { + let Some(property) = node.get_property("flex-grow") else { + return 0.0; + }; + + property.compute_value(); + + match &property.actual { + CssValue::Number(value) => *value, + _ => 0.0, + } +} + +pub(crate) fn parse_flex_shrink(node: &mut RenderTreeNode) -> f32 { + let Some(property) = node.get_property("flex-shrink") else { + return 1.0; + }; + + property.compute_value(); + + match &property.actual { + CssValue::Number(value) => *value, + _ => 1.0, + } +} + +pub(crate) fn parse_grid_template_rows(node: &mut RenderTreeNode) -> Vec { + parse_tracking_sizing_function(node, "grid-template-rows") +} + +pub(crate) fn parse_grid_template_columns(node: &mut RenderTreeNode) -> Vec { + parse_tracking_sizing_function(node, "grid-template-columns") +} + +pub(crate) fn parse_grid_auto_rows( + node: &mut RenderTreeNode, +) -> Vec { + parse_grid_auto(node, "grid-auto-rows") +} + +pub(crate) fn parse_grid_auto_columns( + node: &mut RenderTreeNode, +) -> Vec { + parse_grid_auto(node, "grid-auto-columns") +} + +pub(crate) fn parse_grid_auto_flow(node: &mut RenderTreeNode) -> GridAutoFlow { + let Some(property) = node.get_property("grid-auto-flow") else { + return GridAutoFlow::Row; + }; + + property.compute_value(); + + match &property.actual { + CssValue::String(value) => match value.as_str() { + "row" => GridAutoFlow::Row, + "column" => GridAutoFlow::Column, + "row dense" => GridAutoFlow::RowDense, + "column dense" => GridAutoFlow::ColumnDense, + _ => GridAutoFlow::Row, + }, + _ => GridAutoFlow::Row, + } +} + +pub(crate) fn parse_grid_row(node: &mut RenderTreeNode) -> Line { + Line { + start: parse_grid_placement(node, "grid-row-start"), + end: parse_grid_placement(node, "grid-row-end"), + } +} + +pub(crate) fn parse_grid_column(node: &mut RenderTreeNode) -> Line { + Line { + start: parse_grid_placement(node, "grid-column-start"), + end: parse_grid_placement(node, "grid-column-end"), + } +} diff --git a/crates/gosub_styling/Cargo.toml b/crates/gosub_styling/Cargo.toml index 0b6ecfcd5..a9057d895 100644 --- a/crates/gosub_styling/Cargo.toml +++ b/crates/gosub_styling/Cargo.toml @@ -12,4 +12,6 @@ gosub_html5 = { path = "../gosub_html5" } lazy_static = "1.4" anyhow = "1.0.81" regex = "1.10.4" -colors-transform = "0.2.11" \ No newline at end of file +colors-transform = "0.2.11" +vello = "0.1.0" +rust-fontconfig = "0.1.7" diff --git a/crates/gosub_styling/src/css_values.rs b/crates/gosub_styling/src/css_values.rs index ae56ca585..25f099217 100644 --- a/crates/gosub_styling/src/css_values.rs +++ b/crates/gosub_styling/src/css_values.rs @@ -47,16 +47,25 @@ fn match_selector_part( // '*' always matches any selector } CssSelectorType::Type => { + if !current_node.is_element() { + return false; + } if part.value != current_node.as_element().name { return false; } } CssSelectorType::Class => { + if !current_node.is_element() { + return false; + } if !current_node.as_element().classes.contains(&part.value) { return false; } } CssSelectorType::Id => { + if !current_node.is_element() { + return false; + } if current_node .as_element() .attributes @@ -383,7 +392,7 @@ impl CssProperty { /// the non-existing properties. #[derive(Debug)] pub struct CssProperties { - pub(crate) properties: HashMap, + pub properties: HashMap, } impl Default for CssProperties { @@ -398,6 +407,10 @@ impl CssProperties { properties: HashMap::new(), } } + + pub fn get(&mut self, name: &str) -> Option<&mut CssProperty> { + self.properties.get_mut(name) + } } /// Actual CSS value, can be a color, length, percentage, string or unit. Some relative values will be computed @@ -439,6 +452,30 @@ impl CssValue { _ => None, } } + + pub fn unit_to_px(&self) -> f32 { + //TODO: Implement the rest of the units + match self { + CssValue::Unit(val, unit) => match unit.as_str() { + "px" => *val, + "em" => *val * 16.0, + "rem" => *val * 16.0, + _ => *val, + }, + CssValue::String(value) => { + if value.ends_with("px") { + value.trim_end_matches("px").parse::().unwrap() + } else if value.ends_with("rem") { + value.trim_end_matches("rem").parse::().unwrap() * 16.0 + } else if value.ends_with("em") { + value.trim_end_matches("em").parse::().unwrap() * 16.0 + } else { + 0.0 + } + } + _ => 0.0, + } + } } #[cfg(test)] diff --git a/crates/gosub_styling/src/lib.rs b/crates/gosub_styling/src/lib.rs index 3017daf79..1ee6dd510 100644 --- a/crates/gosub_styling/src/lib.rs +++ b/crates/gosub_styling/src/lib.rs @@ -12,6 +12,7 @@ use gosub_css3::Css3; pub mod css_colors; pub mod css_values; +pub mod prerender_text; mod property_list; pub mod render_tree; diff --git a/crates/gosub_styling/src/prerender_text.rs b/crates/gosub_styling/src/prerender_text.rs new file mode 100644 index 000000000..6aa68d646 --- /dev/null +++ b/crates/gosub_styling/src/prerender_text.rs @@ -0,0 +1,317 @@ +use std::sync::{Arc, Mutex}; + +use lazy_static::lazy_static; +use rust_fontconfig::{FcFontCache, FcPattern}; +use vello::glyph::Glyph; +use vello::peniko::{Blob, Font}; +use vello::skrifa::instance::Size; +use vello::skrifa::{FontRef, MetadataProvider}; + +use gosub_html5::node::data::text::TextData; + +const BACKUP_FONT_NAME: &str = "Roboto"; + +lazy_static! { + static ref FONT_PATH_CACHE: FcFontCache = FcFontCache::build(); + + static ref FONT_RENDERER_CACHE: Mutex = { + // we look for the backup font first, then we look for sans-serif, then we just take the first font we find + // if we can't find any fonts, we panic + let mut pattern = FcPattern { + name: Some(BACKUP_FONT_NAME.to_string()), + ..Default::default() + }; + + let font_path = FONT_PATH_CACHE.query(&pattern).unwrap_or_else(|| { + pattern = FcPattern { + name: Some("sans-serif".to_string()), + ..Default::default() + }; + + FONT_PATH_CACHE.query(&pattern).unwrap_or_else(|| { + FONT_PATH_CACHE.query_all(&Default::default()).first().unwrap_or_else(|| { + panic!("No fonts found") + }) + }) + }); + + //TODO: remove expect here and use a different query + let font_bytes = std::fs::read(&font_path.path).expect("Failed to read font file"); + let font = Font::new(Blob::new(Arc::new(font_bytes)), 0); + + let backup = TextRenderer { + pattern, + font, + sizing: Vec::new(), + }; + + Mutex::new(FontRendererCache::new(backup)) + + }; +} + +pub struct FontRendererCache { + renderers: Vec, + backup: TextRenderer, +} + +enum Index { + Some(usize), + Backup, +} + +impl Index { + fn is_backup(&self) -> bool { + matches!(self, Self::Backup) + } +} + +impl From> for Index { + fn from(index: Option) -> Self { + match index { + Some(index) => Self::Some(index), + None => Self::Backup, + } + } +} + +enum IndexNoBackup { + None, + Some(usize), + Insert(String), +} + +impl IndexNoBackup { + fn is_none(&self) -> bool { + matches!(self, Self::None) + } +} + +impl From> for IndexNoBackup { + fn from(index: Option) -> Self { + match index { + Some(index) => Self::Some(index), + None => Self::None, + } + } +} + +impl FontRendererCache { + fn new(backup: TextRenderer) -> Self { + Self { + renderers: Vec::new(), + backup, + } + } + + fn query_no_backup(&mut self, pattern: FcPattern) -> IndexNoBackup { + let index: IndexNoBackup = self + .renderers + .iter() + .position(|r| r.pattern == pattern) + .into(); + + if index.is_none() { + let Some(font_path) = FONT_PATH_CACHE.query(&pattern) else { + return IndexNoBackup::None; + }; + + return IndexNoBackup::Insert(font_path.path.clone()); + } + + index + } + + pub fn query(&mut self, pattern: FcPattern) -> &mut TextRenderer { + if self.backup.pattern == pattern { + return &mut self.backup; + } + + // we need to do this with an index value because of https://github.com/rust-lang/rust/issues/21906 + let mut index: Index = self + .renderers + .iter() + .position(|r| r.pattern == pattern) + .into(); + + if index.is_backup() { + let Some(font_path) = FONT_PATH_CACHE.query(&pattern) else { + return &mut self.backup; + }; + + let Ok(font_bytes) = std::fs::read(&font_path.path) else { + return &mut self.backup; + }; + + let font = Font::new(Blob::new(Arc::new(font_bytes)), 0); + + let r = TextRenderer { + pattern, + font, + sizing: Vec::new(), + }; + + self.renderers.push(r); + index = Index::Some(self.renderers.len() - 1); + } + + match index { + Index::Some(index) => &mut self.renderers[index], + Index::Backup => &mut self.backup, + } + } + + fn query_ff(&mut self, font_family: Vec) -> &mut TextRenderer { + let mut renderer = IndexNoBackup::None; + for f in font_family { + let pattern = FcPattern { + name: Some(f), + ..Default::default() + }; + + let rend = self.query_no_backup(pattern); + + match rend { + IndexNoBackup::Some(index) => { + return &mut self.renderers[index]; + } + IndexNoBackup::Insert(path) => { + renderer = IndexNoBackup::Insert(path); + } + IndexNoBackup::None => {} + } + } + + match renderer { + IndexNoBackup::Some(index) => &mut self.renderers[index], //unreachable, but we handle it just in case + IndexNoBackup::Insert(path) => { + let font_bytes = std::fs::read(&path).expect("Failed to read font file"); + let font = Font::new(Blob::new(Arc::new(font_bytes)), 0); + + let r = TextRenderer { + pattern: FcPattern { + name: Some(path), + ..Default::default() + }, + font, + sizing: Vec::new(), + }; + + let idx = self.renderers.len(); + self.renderers.push(r); + &mut self.renderers[idx] + } + IndexNoBackup::None => &mut self.backup, + } + } +} + +#[derive(Clone)] +pub struct TextRenderer { + pattern: FcPattern, + font: Font, + sizing: Vec, +} + +#[derive(Clone)] +pub struct FontSizing { + pub font_size: f32, + pub line_height: f32, +} + +#[derive(Debug)] +pub struct PrerenderText { + pub text: String, + pub width: f32, + pub height: f32, + pub line_height: f32, + pub font_size: f32, + pub glyphs: Vec, +} + +#[allow(clippy::from_over_into)] +impl Into for PrerenderText { + fn into(self) -> TextData { + TextData { value: self.text } + } +} + +#[allow(clippy::from_over_into)] +impl Into for &PrerenderText { + fn into(self) -> TextData { + TextData { + value: self.text.clone(), + } + } +} + +impl PrerenderText { + pub fn new(text: String, font_size: f32, font_family: Vec) -> anyhow::Result { + let mut renderers_cache = FONT_RENDERER_CACHE + .lock() + .map_err(|_| anyhow::anyhow!("Failed to lock font renderer cache"))?; + let renderer = renderers_cache.query_ff(font_family); + + let font_ref = + to_font_ref(&renderer.font).ok_or_else(|| anyhow::anyhow!("Failed to get font ref"))?; + + let axes = font_ref.axes(); + let char_map = font_ref.charmap(); + let fs = Size::new(font_size); + let variations: &[(&str, f32)] = &[]; // if we have more than an empty slice here we need to change the rendering to the scene + let var_loc = axes.location(variations.iter().copied()); + let glyph_metrics = font_ref.glyph_metrics(fs, &var_loc); + let metrics = font_ref.metrics(fs, &var_loc); + let line_height = metrics.ascent - metrics.descent + metrics.leading; + + let sizing = FontSizing { + font_size, + line_height, + }; + + let mut width: f32 = 0.0; + let mut pen_x: f32 = 0.0; + + let glyphs = text + .chars() + .filter_map(|c| { + if c == '\n' { + return None; + } + + let gid = char_map.map(c).unwrap_or_default(); + let advance = glyph_metrics.advance_width(gid).unwrap_or_default(); + let x = pen_x; + pen_x += advance; + + Some(Glyph { + id: gid.to_u16() as u32, + x, + y: 0.0, + }) + }) + .collect(); + + width = width.max(pen_x); + + renderer.sizing.push(sizing); + + Ok(Self { + text, + width, + height: line_height, + line_height, + font_size, + glyphs, + }) + } +} + +fn to_font_ref(font: &Font) -> Option> { + use vello::skrifa::raw::FileRef; + let file_ref = FileRef::new(font.data.as_ref()).ok()?; + match file_ref { + FileRef::Font(font) => Some(font), + FileRef::Collection(collection) => collection.get(font.index).ok(), + } +} diff --git a/crates/gosub_styling/src/render_tree.rs b/crates/gosub_styling/src/render_tree.rs index 98c22c807..123dc53ae 100644 --- a/crates/gosub_styling/src/render_tree.rs +++ b/crates/gosub_styling/src/render_tree.rs @@ -12,12 +12,13 @@ use gosub_shared::types::Result; use crate::css_values::{ match_selector, CssProperties, CssProperty, CssValue, DeclarationProperty, }; +use crate::prerender_text::PrerenderText; /// Map of all declared values for all nodes in the document #[derive(Default)] pub struct RenderTree { - nodes: HashMap, - root: NodeId, + pub nodes: HashMap, + pub root: NodeId, } impl RenderTree { @@ -37,7 +38,7 @@ impl RenderTree { parent: None, name: String::from("root"), namespace: None, - data: NodeData::Document(DocumentData::default()), + data: RenderNodeData::Document(DocumentData::default()), }, ); @@ -49,6 +50,21 @@ impl RenderTree { self.nodes.get(&self.root).expect("root node") } + /// Returns the node with the given id + pub fn get_node(&self, id: NodeId) -> Option<&RenderTreeNode> { + self.nodes.get(&id) + } + + /// Returns a mutable reference to the node with the given id + pub fn get_node_mut(&mut self, id: NodeId) -> Option<&mut RenderTreeNode> { + self.nodes.get_mut(&id) + } + + /// Returns the children of the given node + pub fn get_children(&self, id: NodeId) -> Option<&Vec> { + self.nodes.get(&id).map(|node| &node.children) + } + /// Inserts a new node into the render tree, note that you are responsible for the node id /// and the children of the node pub fn insert_node(&mut self, id: NodeId, node: RenderTreeNode) { @@ -136,9 +152,6 @@ impl RenderTree { let node = binding .get_node_by_id(current_node_id) .expect("node not found"); - // if !node.is_element() { - // continue; - // } for sheet in document.get().stylesheets.iter() { for rule in sheet.rules.iter() { @@ -180,6 +193,28 @@ impl RenderTree { let binding = document.get(); let current_node = binding.get_node_by_id(current_node_id).unwrap(); + + let mut data = || { + if let Some(parent_id) = current_node.parent { + if let Some(parent) = self.nodes.get_mut(&parent_id) { + let parent_props = Some(&mut parent.properties); + + return RenderNodeData::from_node_data( + current_node.data.clone(), + parent_props, + ) + .ok(); + }; + }; + + RenderNodeData::from_node_data(current_node.data.clone(), None).ok() + }; + + let Some(data) = data() else { + eprintln!("Failed to create node data for node: {:?}", current_node_id); + continue; + }; + let render_tree_node = RenderTreeNode { id: current_node_id, properties: css_map_entry, @@ -187,7 +222,7 @@ impl RenderTree { parent: node.parent, name: node.name.clone(), // We might be able to move node into render_tree_node namespace: node.namespace.clone(), - data: node.data.clone(), + data, }; self.nodes.insert(current_node_id, render_tree_node); @@ -202,7 +237,7 @@ impl RenderTree { let mut delete_list = Vec::new(); for (id, node) in &self.nodes { - if let NodeData::Element(element) = &node.data { + if let RenderNodeData::Element(element) = &node.data { if removable_elements.contains(&element.name.as_str()) { delete_list.append(&mut self.get_child_node_ids(*id)); delete_list.push(*id); @@ -226,6 +261,68 @@ impl RenderTree { } } +#[derive(Debug)] +pub enum RenderNodeData { + Document(DocumentData), + Element(Box), + Text(PrerenderText), + Comment(CommentData), + //are these really needed in the render tree? + DocType(DocTypeData), +} + +impl RenderNodeData { + pub fn from_node_data(node: NodeData, props: Option<&mut CssProperties>) -> Result { + Ok(match node { + NodeData::Document(data) => RenderNodeData::Document(data), + NodeData::Element(data) => RenderNodeData::Element(data), + NodeData::Text(data) => { + let props = props.ok_or(anyhow::anyhow!("No properties found"))?; + let ff; + if let Some(prop) = props.get("font-family") { + prop.compute_value(); + ff = if let CssValue::String(ref font_family) = prop.actual { + font_family.clone() + } else { + String::from("Arial") + }; + } else { + ff = String::from("Arial") + }; + + let ff = ff + .trim() + .split(',') + .map(|ff| ff.to_string()) + .collect::>(); + + let fs; + + if let Some(prop) = props.get("font-size") { + prop.compute_value(); + + fs = if let CssValue::String(ref fs) = prop.actual { + if fs.ends_with("px") { + fs.trim_end_matches("px").parse::().unwrap_or(12.0) + } else { + 12.01 + } + } else { + 12.02 + }; + } else { + fs = 12.03 + }; + + let text = PrerenderText::new(data.value.clone(), fs, ff)?; + RenderNodeData::Text(text) + } + NodeData::Comment(data) => RenderNodeData::Comment(data), + NodeData::DocType(data) => RenderNodeData::DocType(data), + }) + } +} + #[derive(Debug)] pub struct RenderTreeNode { pub id: NodeId, @@ -234,18 +331,31 @@ pub struct RenderTreeNode { pub parent: Option, pub name: String, pub namespace: Option, - pub data: NodeData, + pub data: RenderNodeData, } impl RenderTreeNode { /// Returns true if the node is an element node pub fn is_element(&self) -> bool { - matches!(self.data, NodeData::Element(_)) + matches!(self.data, RenderNodeData::Element(_)) } /// Returns true if the node is a text node pub fn is_text(&self) -> bool { - matches!(self.data, NodeData::Text(_)) + matches!(self.data, RenderNodeData::Text(_)) + } + + /// Returns the requested property for the node + pub fn get_property(&mut self, prop_name: &str) -> Option<&mut CssProperty> { + self.properties.properties.get_mut(prop_name) + } + + /// Returns the requested attribute for the node + pub fn get_attribute(&self, attr_name: &str) -> Option<&String> { + match &self.data { + RenderNodeData::Element(element) => element.attributes.get(attr_name), + _ => None, + } } } @@ -268,11 +378,11 @@ fn internal_walk_render_tree( ) { // Enter node match &node.data { - NodeData::Document(document) => visitor.document_enter(tree, node, document), - NodeData::DocType(doctype) => visitor.doctype_enter(tree, node, doctype), - NodeData::Text(text) => visitor.text_enter(tree, node, text), - NodeData::Comment(comment) => visitor.comment_enter(tree, node, comment), - NodeData::Element(element) => visitor.element_enter(tree, node, element), + 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 { @@ -284,11 +394,11 @@ fn internal_walk_render_tree( // Leave node match &node.data { - NodeData::Document(document) => visitor.document_leave(tree, node, document), - NodeData::DocType(doctype) => visitor.doctype_leave(tree, node, doctype), - NodeData::Text(text) => visitor.text_leave(tree, node, text), - NodeData::Comment(comment) => visitor.comment_leave(tree, node, comment), - NodeData::Element(element) => visitor.element_leave(tree, node, element), + 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), } }