From 0d862296ecf807a911dea45a148339498cc587c4 Mon Sep 17 00:00:00 2001 From: Shark Date: Thu, 20 Jun 2024 21:26:33 +0200 Subject: [PATCH] implement svg renderers (resvg and vello_svg) --- Cargo.lock | 342 +++++++++++++++++++++- crates/gosub_html5/src/node/arena.rs | 1 - crates/gosub_html5/src/parser/document.rs | 1 - crates/gosub_html5/src/writer.rs | 53 ++-- crates/gosub_render_backend/Cargo.toml | 1 + crates/gosub_render_backend/src/lib.rs | 48 ++- crates/gosub_render_backend/src/svg.rs | 13 +- crates/gosub_renderer/src/draw.rs | 142 +++++---- crates/gosub_renderer/src/draw/img.rs | 50 ++++ crates/gosub_svg/Cargo.toml | 18 ++ crates/gosub_svg/src/lib.rs | 27 ++ crates/gosub_svg/src/resvg.rs | 58 ++++ crates/gosub_vello/Cargo.toml | 9 + crates/gosub_vello/src/image.rs | 19 +- crates/gosub_vello/src/lib.rs | 10 +- crates/gosub_vello/src/scene.rs | 15 +- crates/gosub_vello/src/vello_svg.rs | 33 +++ src/bin/document-writer.rs | 6 - src/bin/resources/gosub.html | 2 +- 19 files changed, 709 insertions(+), 139 deletions(-) create mode 100644 crates/gosub_renderer/src/draw/img.rs create mode 100644 crates/gosub_svg/Cargo.toml create mode 100644 crates/gosub_svg/src/lib.rs create mode 100644 crates/gosub_svg/src/resvg.rs create mode 100644 crates/gosub_vello/src/vello_svg.rs diff --git a/Cargo.lock b/Cargo.lock index 219a6d656..d9da034ad 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -953,6 +953,12 @@ version = "2.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7e962a19be5cfc3f3bf6dd8f61eb50107f356ad6270fbb3ed41476571db78be5" +[[package]] +name = "data-url" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5c297a1c74b71ae29df00c3e22dd9534821d60eb9af5a0192823fa2acea70c2a" + [[package]] name = "deranged" version = "0.3.11" @@ -1115,6 +1121,12 @@ dependencies = [ "miniz_oxide", ] +[[package]] +name = "float-cmp" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "98de4bbd547a563b716d8dfa9aad1cb19bfab00f4fa09a6a4ed21dbcf44ce9c4" + [[package]] name = "flume" version = "0.11.0" @@ -1130,6 +1142,12 @@ 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 = "font-types" version = "0.5.5" @@ -1139,6 +1157,43 @@ dependencies = [ "bytemuck", ] +[[package]] +name = "fontconfig-parser" +version = "0.5.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6a595cb550439a117696039dfc69830492058211b771a2a165379f2a1a53d84d" +dependencies = [ + "roxmltree 0.19.0", +] + +[[package]] +name = "fontdb" +version = "0.16.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b0299020c3ef3f60f526a4f64ab4a3d4ce116b1acbf24cdd22da0068e5d81dc3" +dependencies = [ + "fontconfig-parser", + "log", + "memmap2", + "slotmap", + "tinyvec", + "ttf-parser 0.20.0", +] + +[[package]] +name = "fontdb" +version = "0.18.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e32eac81c1135c1df01d4e6d4233c47ba11f6a6d07f33e0bba09d18797077770" +dependencies = [ + "fontconfig-parser", + "log", + "memmap2", + "slotmap", + "tinyvec", + "ttf-parser 0.21.1", +] + [[package]] name = "foreign-types" version = "0.5.0" @@ -1514,6 +1569,7 @@ dependencies = [ name = "gosub_render_backend" version = "0.1.0" dependencies = [ + "gosub_html5", "gosub_shared", "image", "raw-window-handle", @@ -1584,6 +1640,19 @@ dependencies = [ "rust-fontconfig", ] +[[package]] +name = "gosub_svg" +version = "0.1.0" +dependencies = [ + "anyhow", + "gosub_html5", + "gosub_render_backend", + "gosub_shared", + "resvg", + "tiny-skia", + "usvg 0.42.0", +] + [[package]] name = "gosub_testing" version = "0.1.0" @@ -1639,13 +1708,16 @@ version = "0.1.0" dependencies = [ "anyhow", "futures", + "gosub_html5", "gosub_render_backend", "gosub_shared", + "gosub_svg", "gosub_typeface", "image", "raw-window-handle", "smallvec", - "vello", + "vello 0.2.0", + "vello_svg", "wgpu", ] @@ -1979,6 +2051,12 @@ dependencies = [ "thiserror", ] +[[package]] +name = "imagesize" +version = "0.12.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "029d73f573d8e8d63e6d5020011d3255b28c3ba85d6cf870a07184ed23de9284" + [[package]] name = "imgref" version = "1.10.1" @@ -2718,7 +2796,7 @@ version = "0.20.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d4586edfe4c648c71797a74c84bacb32b52b212eff5dfe2bb9f2c599844023e7" dependencies = [ - "ttf-parser", + "ttf-parser 0.20.0", ] [[package]] @@ -2805,9 +2883,15 @@ version = "0.11.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "90fcb95eef784c2ac79119d1dd819e162b5da872ce6f3c3abe1e8ca1c082f72b" dependencies = [ - "siphasher", + "siphasher 0.3.11", ] +[[package]] +name = "pico-args" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5be167a7af36ee22fe3115051bc51f6e6c7054c9348e28deb4f49bd6f705a315" + [[package]] name = "pin-project" version = "1.1.5" @@ -3176,6 +3260,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 0.4.3", +] + [[package]] name = "read-fonts" version = "0.19.3" @@ -3183,7 +3276,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e8b8af39d1f23869711ad4cea5e7835a20daa987f80232f7f2a2374d648ca64d" dependencies = [ "bytemuck", - "font-types", + "font-types 0.5.5", ] [[package]] @@ -3240,6 +3333,22 @@ dependencies = [ "quick-error 1.2.3", ] +[[package]] +name = "resvg" +version = "0.42.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "944d052815156ac8fa77eaac055220e95ba0b01fa8887108ca710c03805d9051" +dependencies = [ + "gif", + "jpeg-decoder", + "log", + "pico-args", + "rgb", + "svgtypes", + "tiny-skia", + "usvg 0.42.0", +] + [[package]] name = "rgb" version = "0.8.37" @@ -3264,6 +3373,18 @@ dependencies = [ "windows-sys 0.52.0", ] +[[package]] +name = "roxmltree" +version = "0.19.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3cd14fd5e3b777a7422cca79358c57a8f6e3a703d9ac187448d0daf220c2407f" + +[[package]] +name = "roxmltree" +version = "0.20.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6c20b6793b5c2fa6553b250154b78d6d0db37e72700ae35fad9387a46f487c97" + [[package]] name = "rstar" version = "0.12.0" @@ -3352,6 +3473,38 @@ dependencies = [ "untrusted", ] +[[package]] +name = "rustybuzz" +version = "0.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "88117946aa1bfb53c2ae0643ceac6506337f44887f8c9fbfb43587b1cc52ba49" +dependencies = [ + "bitflags 2.5.0", + "bytemuck", + "smallvec", + "ttf-parser 0.20.0", + "unicode-bidi-mirroring", + "unicode-ccc", + "unicode-properties", + "unicode-script", +] + +[[package]] +name = "rustybuzz" +version = "0.14.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cfb9cf8877777222e4a3bc7eb247e398b56baba500c38c1c46842431adc8b55c" +dependencies = [ + "bitflags 2.5.0", + "bytemuck", + "smallvec", + "ttf-parser 0.21.1", + "unicode-bidi-mirroring", + "unicode-ccc", + "unicode-properties", + "unicode-script", +] + [[package]] name = "ryu" version = "1.0.17" @@ -3477,12 +3630,36 @@ dependencies = [ "windows-sys 0.48.0", ] +[[package]] +name = "simplecss" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a11be7c62927d9427e9f40f3444d5499d868648e2edbc4e2116de69e7ec0e89d" +dependencies = [ + "log", +] + [[package]] name = "siphasher" version = "0.3.11" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "38b58827f4464d87d377d175e90bf58eb00fd8716ff0a62f80356b5e61555d0d" +[[package]] +name = "siphasher" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "56199f7ddabf13fe5074ce809e7d3f42b42ae711800501b5b16ea82ad029c39d" + +[[package]] +name = "skrifa" +version = "0.15.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eff28ee3b66d43060ef9a327e0f18e4c1813f194120156b4d4524fac3ba8ce22" +dependencies = [ + "read-fonts 0.15.6", +] + [[package]] name = "skrifa" version = "0.19.3" @@ -3490,7 +3667,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0ab45fb68b53576a43d4fc0e9ec8ea64e29a4d2cc7f44506964cb75f288222e9" dependencies = [ "bytemuck", - "read-fonts", + "read-fonts 0.19.3", ] [[package]] @@ -3624,6 +3801,9 @@ name = "strict-num" version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6637bab7722d379c8b41ba849228d680cc12d0a45ba1fa2b48f2a30577a06731" +dependencies = [ + "float-cmp", +] [[package]] name = "strsim" @@ -3643,6 +3823,16 @@ version = "0.4.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f83ba502a3265efb76efb89b0a2f7782ad6f2675015d4ce37e4b547dda42b499" +[[package]] +name = "svgtypes" +version = "0.15.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fae3064df9b89391c9a76a0425a69d124aee9c5c28455204709e72c39868a43c" +dependencies = [ + "kurbo", + "siphasher 1.0.1", +] + [[package]] name = "syn" version = "1.0.109" @@ -3823,6 +4013,7 @@ dependencies = [ "bytemuck", "cfg-if", "log", + "png", "tiny-skia-path", ] @@ -3960,6 +4151,12 @@ version = "0.20.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "17f77d76d837a7830fe1d4f12b7b4ba4192c1888001c7164257e4bc6d21d96b4" +[[package]] +name = "ttf-parser" +version = "0.21.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2c591d83f69777866b9126b24c6dd9a18351f177e49d625920d19f989fd31cf8" + [[package]] name = "typenum" version = "1.17.0" @@ -3978,12 +4175,24 @@ version = "0.3.15" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "08f95100a766bf4f8f28f90d77e0a5461bbdb219042e7679bebe79004fed8d75" +[[package]] +name = "unicode-bidi-mirroring" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "23cb788ffebc92c5948d0e997106233eeb1d8b9512f93f41651f52b6c5f5af86" + [[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-ccc" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1df77b101bcc4ea3d78dafc5ad7e4f58ceffe0b2b16bf446aeb50b6cb4157656" + [[package]] name = "unicode-general-category" version = "0.6.0" @@ -4011,12 +4220,30 @@ dependencies = [ "tinyvec", ] +[[package]] +name = "unicode-properties" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e4259d9d4425d9f0661581b804cb85fe66a4c631cadd8f490d1c13a35d5d9291" + +[[package]] +name = "unicode-script" +version = "0.5.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ad8d71f5726e5f285a935e9fe8edfd53f0491eb6e9a5774097fdabee7cd8c9cd" + [[package]] name = "unicode-segmentation" version = "1.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d4c87d22b6e3f4a18d4d40ef354e97c90fcb14dd91d7dc0aa9d8a1172ebf7202" +[[package]] +name = "unicode-vo" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b1d386ff53b415b7fe27b50bb44679e2cc4660272694b7b6f3326d8480823a94" + [[package]] name = "unicode-width" version = "0.1.11" @@ -4073,6 +4300,60 @@ dependencies = [ "percent-encoding", ] +[[package]] +name = "usvg" +version = "0.41.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5c704361d822337cfc00387672c7b59eaa72a1f0744f62b2a68aa228a0c6927d" +dependencies = [ + "base64", + "data-url", + "flate2", + "fontdb 0.16.2", + "imagesize", + "kurbo", + "log", + "pico-args", + "roxmltree 0.19.0", + "rustybuzz 0.13.0", + "simplecss", + "siphasher 1.0.1", + "strict-num", + "svgtypes", + "tiny-skia-path", + "unicode-bidi", + "unicode-script", + "unicode-vo", + "xmlwriter", +] + +[[package]] +name = "usvg" +version = "0.42.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b84ea542ae85c715f07b082438a4231c3760539d902e11d093847a0b22963032" +dependencies = [ + "base64", + "data-url", + "flate2", + "fontdb 0.18.0", + "imagesize", + "kurbo", + "log", + "pico-args", + "roxmltree 0.20.0", + "rustybuzz 0.14.1", + "simplecss", + "siphasher 1.0.1", + "strict-num", + "svgtypes", + "tiny-skia-path", + "unicode-bidi", + "unicode-script", + "unicode-vo", + "xmlwriter", +] + [[package]] name = "utf8parse" version = "0.2.1" @@ -4114,6 +4395,20 @@ dependencies = [ "wasm-bindgen", ] +[[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 0.15.5", + "vello_encoding 0.1.0", +] + [[package]] name = "vello" version = "0.2.0" @@ -4125,14 +4420,26 @@ dependencies = [ "log", "peniko", "raw-window-handle", - "skrifa", + "skrifa 0.19.3", "static_assertions", "thiserror", - "vello_encoding", + "vello_encoding 0.2.0", "vello_shaders", "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 0.15.5", +] + [[package]] name = "vello_encoding" version = "0.2.0" @@ -4142,7 +4449,7 @@ dependencies = [ "bytemuck", "guillotiere", "peniko", - "skrifa", + "skrifa 0.19.3", "smallvec", ] @@ -4155,7 +4462,18 @@ dependencies = [ "bytemuck", "naga", "thiserror", - "vello_encoding", + "vello_encoding 0.2.0", +] + +[[package]] +name = "vello_svg" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "14c537fcaf660d8e16f971e819450db72cbdeec97d36be1bc6fb4bc7e916084c" +dependencies = [ + "image", + "usvg 0.41.0", + "vello 0.1.0", ] [[package]] @@ -4955,6 +5273,12 @@ version = "0.13.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "66fee0b777b0f5ac1c69bb06d361268faafa61cd4682ae064a171c16c433e9e4" +[[package]] +name = "xmlwriter" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ec7a2a501ed189703dba8b08142f057e887dfc4b2cc4db2d343ac6376ba3e0b9" + [[package]] name = "zerocopy" version = "0.7.32" diff --git a/crates/gosub_html5/src/node/arena.rs b/crates/gosub_html5/src/node/arena.rs index d96cec0f1..6791e88b2 100644 --- a/crates/gosub_html5/src/node/arena.rs +++ b/crates/gosub_html5/src/node/arena.rs @@ -62,7 +62,6 @@ impl NodeArena { id } - pub fn nodes(&self) -> &HashMap { &self.nodes } diff --git a/crates/gosub_html5/src/parser/document.rs b/crates/gosub_html5/src/parser/document.rs index 26095e69f..fc47bf5d4 100755 --- a/crates/gosub_html5/src/parser/document.rs +++ b/crates/gosub_html5/src/parser/document.rs @@ -496,7 +496,6 @@ impl Document { false } - pub fn nodes(&self) -> &HashMap { self.arena.nodes() } diff --git a/crates/gosub_html5/src/writer.rs b/crates/gosub_html5/src/writer.rs index c23691729..8cfe2219c 100644 --- a/crates/gosub_html5/src/writer.rs +++ b/crates/gosub_html5/src/writer.rs @@ -1,29 +1,24 @@ -use crate::{node::{Node, NodeData, NodeId}, parser::document::Document, visit::Visitor}; - - - - +use crate::{ + node::{Node, NodeData, NodeId}, + parser::document::Document, + visit::Visitor, +}; impl Document { pub fn write_document(&self) -> String { Writer::write_from_node(NodeId::root(), self) } - pub fn write_from_node(&self, node: NodeId) -> String { Writer::write_from_node(node, self) } } - - - struct Writer { buffer: String, comments: bool, } - impl Writer { pub fn write_from_node(node: NodeId, doc: &Document) -> String { let mut w = Self { @@ -37,7 +32,7 @@ impl Writer { } pub fn visit_node(&mut self, id: NodeId, doc: &Document) { - let Some(node)= doc.get_node_by_id(id) else { + let Some(node) = doc.get_node_by_id(id) else { return; }; @@ -83,7 +78,6 @@ impl Writer { } } - pub fn visit_children(&mut self, children: &Vec, doc: &Document) { for child in children { self.visit_node(*child, doc); @@ -91,25 +85,20 @@ impl Writer { } } - impl Visitor for Writer { fn text_enter(&mut self, _node: &Node, data: &crate::node::data::text::TextData) { self.buffer.push_str(&data.value); } - fn text_leave(&mut self, _node: &Node, _data: &crate::node::data::text::TextData) { - - } + fn text_leave(&mut self, _node: &Node, _data: &crate::node::data::text::TextData) {} fn doctype_enter(&mut self, _node: &Node, data: &crate::node::data::doctype::DocTypeData) { self.buffer.push_str(""); + self.buffer.push('>'); } - fn doctype_leave(&mut self, _node: &Node, _data: &crate::node::data::doctype::DocTypeData) { - - } + fn doctype_leave(&mut self, _node: &Node, _data: &crate::node::data::doctype::DocTypeData) {} fn comment_enter(&mut self, _node: &Node, data: &crate::node::data::comment::CommentData) { if self.comments { @@ -119,35 +108,29 @@ impl Visitor for Writer { } } - fn comment_leave(&mut self, _node: &Node, _data: &crate::node::data::comment::CommentData) { - - } + fn comment_leave(&mut self, _node: &Node, _data: &crate::node::data::comment::CommentData) {} fn element_enter(&mut self, _node: &Node, data: &crate::node::data::element::ElementData) { - self.buffer.push_str("<"); + self.buffer.push('<'); self.buffer.push_str(&data.name); for (name, value) in &data.attributes { - self.buffer.push_str(" "); + self.buffer.push(' '); self.buffer.push_str(name); self.buffer.push_str("=\""); self.buffer.push_str(value); - self.buffer.push_str("\""); + self.buffer.push('"'); } - self.buffer.push_str(">"); + self.buffer.push('>'); } - fn element_leave(&mut self, node: &Node, data: &crate::node::data::element::ElementData) { + fn element_leave(&mut self, _node: &Node, data: &crate::node::data::element::ElementData) { self.buffer.push_str(""); + self.buffer.push('>'); } - fn document_enter(&mut self, _node: &Node, _data: &crate::node::data::document::DocumentData) { - - } + fn document_enter(&mut self, _node: &Node, _data: &crate::node::data::document::DocumentData) {} - fn document_leave(&mut self, _node: &Node, _data: &crate::node::data::document::DocumentData) { - - } + fn document_leave(&mut self, _node: &Node, _data: &crate::node::data::document::DocumentData) {} } diff --git a/crates/gosub_render_backend/Cargo.toml b/crates/gosub_render_backend/Cargo.toml index 9b20a3418..52c8b49aa 100644 --- a/crates/gosub_render_backend/Cargo.toml +++ b/crates/gosub_render_backend/Cargo.toml @@ -8,4 +8,5 @@ smallvec = "1.13.2" image = "0.25.1" raw-window-handle = "0.6.2" gosub_shared = { path = "../gosub_shared" } +gosub_html5 = { path = "../gosub_html5" } diff --git a/crates/gosub_render_backend/src/lib.rs b/crates/gosub_render_backend/src/lib.rs index 2d1c86170..4abdf5c7f 100644 --- a/crates/gosub_render_backend/src/lib.rs +++ b/crates/gosub_render_backend/src/lib.rs @@ -1,13 +1,16 @@ -use raw_window_handle::{HasDisplayHandle, HasWindowHandle}; use std::fmt::Debug; use std::ops::{Div, Mul, MulAssign}; +use raw_window_handle::{HasDisplayHandle, HasWindowHandle}; use smallvec::SmallVec; use gosub_shared::types::Result; - use gosub_shared::types::Size as SizeT; +use crate::svg::SvgRenderer; + +pub mod svg; + pub type Size = SizeT; pub type SizeU32 = SizeT; @@ -28,6 +31,7 @@ pub trait RenderBackend: Sized + Debug { type Image: Image; type Brush: Brush; type Scene: Scene; + type SVGRenderer: SvgRenderer; type ActiveWindowData<'a>; type WindowData<'a>; @@ -583,7 +587,10 @@ pub trait Color { pub trait Image { fn new(size: (FP, FP), data: Vec) -> Self; - fn from_img(img: &image::DynamicImage) -> Self; + fn from_img(img: image::DynamicImage) -> Self; + + fn width(&self) -> u32; + fn height(&self) -> u32; } pub trait Brush: Clone { @@ -593,3 +600,38 @@ pub trait Brush: Clone { fn image(image: B::Image) -> Self; } + +pub enum ImageBuffer { + Image(B::Image), + Scene(B::Scene, SizeU32), +} + +impl ImageBuffer { + pub fn width(&self) -> u32 { + match self { + ImageBuffer::Image(img) => img.width(), + ImageBuffer::Scene(_, size) => size.width, + } + } + + pub fn height(&self) -> u32 { + match self { + ImageBuffer::Image(img) => img.height(), + ImageBuffer::Scene(_, size) => size.height, + } + } + + pub fn size(&self) -> SizeU32 { + match self { + ImageBuffer::Image(img) => SizeU32::new(img.width(), img.height()), + ImageBuffer::Scene(_, size) => *size, + } + } + + pub fn size_tuple(&self) -> (FP, FP) { + match self { + ImageBuffer::Image(img) => (img.width() as FP, img.height() as FP), + ImageBuffer::Scene(_, size) => (size.width as FP, size.height as FP), + } + } +} diff --git a/crates/gosub_render_backend/src/svg.rs b/crates/gosub_render_backend/src/svg.rs index 5250ed70d..59ff05973 100644 --- a/crates/gosub_render_backend/src/svg.rs +++ b/crates/gosub_render_backend/src/svg.rs @@ -1,7 +1,16 @@ -use crate::RenderBackend; +use gosub_html5::node::NodeId; +use gosub_html5::parser::document::DocumentHandle; +use gosub_shared::types::Result; +use crate::{ImageBuffer, RenderBackend}; +pub trait SvgRenderer { + type SvgDocument; + fn new(wd: &mut B::WindowData<'_>) -> Self; -pub trait SvgRenderer { + fn parse_external(data: String) -> Result; + fn parse_internal(tree: DocumentHandle, id: NodeId) -> Result; + + fn render(&mut self, doc: &Self::SvgDocument) -> Result>; } diff --git a/crates/gosub_renderer/src/draw.rs b/crates/gosub_renderer/src/draw.rs index 25d41f2a1..44a3c3202 100644 --- a/crates/gosub_renderer/src/draw.rs +++ b/crates/gosub_renderer/src/draw.rs @@ -1,15 +1,13 @@ -use std::io::Read; - use anyhow::anyhow; -use image::DynamicImage; use taffy::{ AvailableSpace, Layout, NodeId, PrintTree, Size as TSize, TaffyTree, TraversePartialTree, }; use url::Url; use gosub_html5::node::NodeId as GosubId; +use gosub_render_backend::svg::SvgRenderer; use gosub_render_backend::{ - Border, BorderSide, BorderStyle, Brush, Color, Image, PreRenderText, Rect, RenderBackend, + Border, BorderSide, BorderStyle, Brush, Color, ImageBuffer, PreRenderText, Rect, RenderBackend, RenderBorder, RenderRect, RenderText, Scene as TScene, SizeU32, Text, Transform, FP, }; use gosub_rendering::layout::generate_taffy_tree; @@ -19,8 +17,11 @@ use gosub_styling::css_colors::RgbColor; use gosub_styling::css_values::CssValue; use gosub_styling::render_tree::{RenderNodeData, RenderTree, RenderTreeNode}; +use crate::draw::img::request_img; use crate::render_tree::{load_html_rendertree, NodeID, TreeDrawer}; +mod img; + pub trait SceneDrawer { fn draw(&mut self, backend: &mut B, data: &mut B::WindowData<'_>, size: SizeU32); fn mouse_move(&mut self, backend: &mut B, data: &mut B::WindowData<'_>, x: f64, y: f64) @@ -52,7 +53,13 @@ impl SceneDrawer for TreeDrawer { .take() .unwrap_or_else(|| B::Scene::new(data)); - self.render(&mut scene, size); + let mut drawer = Drawer { + scene: &mut scene, + drawer: self, + svg: B::SVGRenderer::new(data), + }; + + drawer.render(size); self.tree_scene = Some(scene); @@ -222,30 +229,36 @@ impl SceneDrawer for TreeDrawer { } } -impl TreeDrawer { - pub(crate) fn render(&mut self, scene: &mut B::Scene, size: SizeU32) { +struct Drawer<'s, 't, B: RenderBackend> { + scene: &'s mut B::Scene, + drawer: &'t mut TreeDrawer, + svg: B::SVGRenderer, +} + +impl Drawer<'_, '_, B> { + pub(crate) fn render(&mut self, size: SizeU32) { let space = TSize { width: AvailableSpace::Definite(size.width as f32), height: AvailableSpace::Definite(size.height as f32), }; - if let Err(e) = self.taffy.compute_layout(self.root, space) { + if let Err(e) = self.drawer.taffy.compute_layout(self.drawer.root, space) { eprintln!("Failed to compute layout: {:?}", e); return; } - self.position = PositionTree::from_taffy(&self.taffy, self.root); + self.drawer.position = PositionTree::from_taffy(&self.drawer.taffy, self.drawer.root); - self.render_node_with_children(self.root, scene, Point::ZERO); + self.render_node_with_children(self.drawer.root, Point::ZERO); } - fn render_node_with_children(&mut self, id: NodeID, scene: &mut B::Scene, mut pos: Point) { - let err = self.render_node(id, scene, &mut pos); + fn render_node_with_children(&mut self, id: NodeID, mut pos: Point) { + let err = self.render_node(id, &mut pos); if let Err(e) = err { eprintln!("Error rendering node: {:?}", e); } - let children = match self.taffy.children(id) { + let children = match self.drawer.taffy.children(id) { Ok(children) => children, Err(e) => { eprintln!("Error rendering node children: {e}"); @@ -254,24 +267,21 @@ impl TreeDrawer { }; for child in children { - self.render_node_with_children(child, scene, pos); + self.render_node_with_children(child, pos); } } - fn render_node( - &mut self, - id: NodeID, - scene: &mut B::Scene, - pos: &mut Point, - ) -> anyhow::Result<()> { + fn render_node(&mut self, id: NodeID, pos: &mut Point) -> anyhow::Result<()> { let gosub_id = *self + .drawer .taffy .get_node_context(id) .ok_or(anyhow!("Failed to get style id"))?; - let layout = self.taffy.get_final_layout(id); + let layout = self.drawer.taffy.get_final_layout(id); let node = self + .drawer .style .get_node_mut(gosub_id) .ok_or(anyhow!("Node not found"))?; @@ -279,7 +289,14 @@ impl TreeDrawer { pos.x += layout.location.x as FP; pos.y += layout.location.y as FP; - let border_radius = render_bg(node, scene, layout, pos, &self.url); + let border_radius = render_bg( + node, + self.scene, + layout, + pos, + &self.drawer.url, + &mut self.svg, + ); if let RenderNodeData::Element(element) = &node.data { if element.name() == "img" { @@ -288,28 +305,10 @@ impl TreeDrawer { .get("src") .ok_or(anyhow!("Image element has no src attribute"))?; - let url = Url::parse(src.as_str()).or_else(|_| self.url.join(src.as_str()))?; - - let img = if url.scheme() == "file" { - let path = url.as_str().trim_start_matches("file://"); - - println!("Loading image from: {:?}", path); - - image::open(path)? - } else { - let res = gosub_net::http::ureq::get(url.as_str()).call()?; - - let mut img = Vec::with_capacity( - res.header("Content-Length") - .unwrap_or("1024") - .parse::()?, - ); - - res.into_reader().read_to_end(&mut img)?; - - image::load_from_memory(&img)? - }; + let url = + Url::parse(src.as_str()).or_else(|_| self.drawer.url.join(src.as_str()))?; + let img = request_img(&mut self.svg, &url)?; let fit = element .attributes .get("object-fit") @@ -319,11 +318,11 @@ impl TreeDrawer { println!("Rendering image at: {:?}", pos); println!("with size: {:?}", layout.size); - render_image::(img, scene, *pos, layout.size, border_radius, fit)?; + render_image::(img, self.scene, *pos, layout.size, border_radius, fit)?; } } - render_text(node, scene, pos, layout); + render_text(node, self.scene, pos, layout); Ok(()) } } @@ -377,6 +376,7 @@ fn render_bg( layout: &Layout, pos: &Point, root_url: &Url, + svg: &mut B::SVGRenderer, ) -> (FP, FP, FP, FP) { let bg_color = node .properties @@ -490,21 +490,12 @@ fn render_bg( return border_radius; }; - let Ok(res) = gosub_net::http::ureq::get(url.as_str()).call() else { - return border_radius; - }; - - let mut img = Vec::with_capacity( - res.header("Content-Length") - .unwrap_or("1024") - .parse::() - .unwrap_or(1024), - ); - - let _ = res.into_reader().read_to_end(&mut img); //TODO: handle error - - let Ok(img) = image::load_from_memory(&img) else { - return border_radius; + let img = match request_img(svg, &url) { + Ok(img) => img, + Err(e) => { + eprintln!("Error loading image: {:?}", e); + return border_radius; + } }; let _ = @@ -535,7 +526,7 @@ impl Side { } fn render_image( - img: DynamicImage, + img: ImageBuffer, scene: &mut B::Scene, pos: Point, size: TSize, @@ -547,7 +538,7 @@ fn render_image( let rect = Rect::new(pos.x, pos.y, pos.x + width, pos.y + height); - let img_size = (img.width() as FP, img.height() as FP); + let img_size = img.size_tuple(); let transform = match fit { "fill" => { @@ -586,16 +577,23 @@ fn render_image( let transform = transform.with_translation(pos); - let rect = RenderRect { - rect, - transform: None, - radius: Some(B::BorderRadius::from(radii)), - brush: Brush::image(Image::new(img_size, img.into_rgba8().into_raw())), - brush_transform: Some(transform), - border: None, - }; - - scene.draw_rect(&rect); + match img { + ImageBuffer::Image(img) => { + let rect = RenderRect { + rect, + transform: None, + radius: Some(B::BorderRadius::from(radii)), + brush: Brush::image(img), + brush_transform: Some(transform), + border: None, + }; + + scene.draw_rect(&rect); + } + ImageBuffer::Scene(s, _size) => { + scene.apply_scene(&s, Some(transform)); //TODO we probably want to use a clip layer here + } + } Ok(()) } diff --git a/crates/gosub_renderer/src/draw/img.rs b/crates/gosub_renderer/src/draw/img.rs new file mode 100644 index 000000000..5a5194fe2 --- /dev/null +++ b/crates/gosub_renderer/src/draw/img.rs @@ -0,0 +1,50 @@ +use std::fs; +use std::io::Cursor; + +use url::Url; + +use gosub_render_backend::svg::SvgRenderer; +use gosub_render_backend::{Image as _, ImageBuffer, RenderBackend}; +use gosub_shared::types::Result; + +pub fn request_img( + svg_renderer: &mut B::SVGRenderer, + url: &Url, +) -> Result> { + let img = if url.scheme() == "file" { + let path = url.as_str().trim_start_matches("file://"); + + println!("Loading image from: {:?}", path); + + fs::read(path)? + } else { + let res = gosub_net::http::ureq::get(url.as_str()).call()?; + + let mut img = Vec::with_capacity( + res.header("Content-Length") + .and_then(|x| x.parse::().ok()) + .unwrap_or(1024), + ); + + res.into_reader().read_to_end(&mut img)?; + + img + }; + + let is_svg = img.starts_with(b">::parse_external(svg)?; + + svg_renderer.render(&svg)? + } else { + let format = image::guess_format(&img)?; + let img = image::load(Cursor::new(img), format)?; //In that way we don't need to copy the image data + + let img = B::Image::from_img(img); + + ImageBuffer::Image(img) + }) +} diff --git a/crates/gosub_svg/Cargo.toml b/crates/gosub_svg/Cargo.toml new file mode 100644 index 000000000..858973d13 --- /dev/null +++ b/crates/gosub_svg/Cargo.toml @@ -0,0 +1,18 @@ +[package] +name = "gosub_svg" +version = "0.1.0" +edition = "2021" + +[dependencies] +tiny-skia = "0.11.4" +usvg = { version = "0.42.0", default-features = false, features = [] } +gosub_shared = { path = "../gosub_shared" } +gosub_html5 = { path = "../gosub_html5" } +gosub_render_backend = { path = "../gosub_render_backend" } +resvg = { version = "0.42.0", optional = true } +anyhow = "1.0.86" + + + +[features] +resvg = ["dep:resvg"] diff --git a/crates/gosub_svg/src/lib.rs b/crates/gosub_svg/src/lib.rs new file mode 100644 index 000000000..1a9c46f60 --- /dev/null +++ b/crates/gosub_svg/src/lib.rs @@ -0,0 +1,27 @@ +use gosub_html5::{node::NodeId, parser::document::DocumentHandle}; +use gosub_shared::types::Result; + +#[cfg(feature = "resvg")] +pub mod resvg; + +pub struct SVGDocument { + tree: usvg::Tree, +} + +impl SVGDocument { + #[allow(clippy::should_implement_trait)] + pub fn from_str(svg: &str) -> Result { + let opts = usvg::Options { + ..Default::default() + }; + + let tree = usvg::Tree::from_str(svg, &opts)?; + Ok(Self { tree }) + } + + pub fn from_html_doc(id: NodeId, doc: DocumentHandle) -> Result { + let str = doc.get().write_from_node(id); + + Self::from_str(&str) + } +} diff --git a/crates/gosub_svg/src/resvg.rs b/crates/gosub_svg/src/resvg.rs new file mode 100644 index 000000000..094a5eae4 --- /dev/null +++ b/crates/gosub_svg/src/resvg.rs @@ -0,0 +1,58 @@ +use anyhow::anyhow; +use tiny_skia::Pixmap; + +use gosub_html5::node::NodeId; +use gosub_html5::parser::document::DocumentHandle; +use gosub_render_backend::svg::SvgRenderer; +use gosub_render_backend::{Image, ImageBuffer, RenderBackend, FP}; +use gosub_shared::types::Result; + +use crate::SVGDocument; + +pub struct Resvg; + +impl SvgRenderer for Resvg { + type SvgDocument = SVGDocument; + + fn new(_: &mut B::WindowData<'_>) -> Self { + Self + } + + fn parse_external(data: String) -> Result { + SVGDocument::from_str(&data) + } + + fn parse_internal(tree: DocumentHandle, id: NodeId) -> Result { + SVGDocument::from_html_doc(id, tree) + } + + fn render(&mut self, doc: &SVGDocument) -> Result> { + let img: B::Image = Self::render_to_image::(self, doc)?; + + Ok(ImageBuffer::Image(img)) + } +} + +impl Resvg { + pub fn render_to_image(&mut self, doc: &SVGDocument) -> Result { + let size = doc.tree.size().to_int_size(); + + let mut pixmap = Pixmap::new(size.width(), size.height()) + .ok_or_else(|| anyhow!("Failed to create pixmap"))?; + + resvg::render( + &doc.tree, + tiny_skia::Transform::default(), + &mut pixmap.as_mut(), + ); + + Ok(tiny_skia_pixmap_to_img::(pixmap)) + } +} + +fn tiny_skia_pixmap_to_img(pixmap: Pixmap) -> B::Image { + let w = pixmap.width(); + let h = pixmap.height(); + + Image::new((w as FP, h as FP), pixmap.take()) +} diff --git a/crates/gosub_vello/Cargo.toml b/crates/gosub_vello/Cargo.toml index a5cd2ac1b..efd605e77 100644 --- a/crates/gosub_vello/Cargo.toml +++ b/crates/gosub_vello/Cargo.toml @@ -7,6 +7,7 @@ edition = "2021" gosub_shared = { path = "../gosub_shared" } gosub_render_backend = { path = "../gosub_render_backend" } gosub_typeface = { path = "../gosub_typeface" } +gosub_svg = { path = "../gosub_svg" } vello = "0.2.0" image = "0.25.1" smallvec = "1.13.2" @@ -14,3 +15,11 @@ anyhow = "1.0.82" wgpu = "0.20.1" raw-window-handle = "0.6.2" futures = "0.3.30" +vello_svg = { version = "0.2.0", optional = true } +gosub_html5 = { path = "../gosub_html5", optional = true } + + +[features] +default = ["resvg"] +resvg = ["gosub_svg/resvg"] +vello_svg = ["dep:vello_svg", "dep:gosub_html5"] # resvg has higher priority, so if both are enabled, resvg will be used diff --git a/crates/gosub_vello/src/image.rs b/crates/gosub_vello/src/image.rs index e33bb449b..e0899fabe 100644 --- a/crates/gosub_vello/src/image.rs +++ b/crates/gosub_vello/src/image.rs @@ -1,8 +1,10 @@ -use gosub_render_backend::{Image as TImage, FP}; -use image::{DynamicImage, GenericImageView}; use std::sync::Arc; + +use image::{DynamicImage, GenericImageView}; use vello::peniko::{Blob, Format, Image as VelloImage}; +use gosub_render_backend::{Image as TImage, FP}; + pub struct Image(pub(crate) VelloImage); impl From for Image { @@ -23,11 +25,20 @@ impl TImage for Image { )) } - fn from_img(img: &DynamicImage) -> Self { + fn from_img(img: DynamicImage) -> Self { let (width, height) = img.dimensions(); - let data = img.to_rgba8().into_raw(); + + let data = img.into_rgba8().into_raw(); let blob = Blob::new(Arc::new(data)); Image(VelloImage::new(blob, Format::Rgba8, width, height)) } + + fn width(&self) -> u32 { + self.0.width + } + + fn height(&self) -> u32 { + self.0.height + } } diff --git a/crates/gosub_vello/src/lib.rs b/crates/gosub_vello/src/lib.rs index b9bb5212e..ff7f05255 100644 --- a/crates/gosub_vello/src/lib.rs +++ b/crates/gosub_vello/src/lib.rs @@ -33,6 +33,8 @@ mod scene; mod text; mod transform; +#[cfg(feature = "vello_svg")] +mod vello_svg; pub struct VelloBackend; impl Debug for VelloBackend { @@ -53,9 +55,15 @@ impl RenderBackend for VelloBackend { type Color = Color; type Image = Image; type Brush = Brush; + type Scene = Scene; + #[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 Scene = Scene; fn draw_rect(&mut self, data: &mut Self::WindowData<'_>, rect: &RenderRect) { data.scene.draw_rect(rect); diff --git a/crates/gosub_vello/src/scene.rs b/crates/gosub_vello/src/scene.rs index 5242505b1..0777ff6ab 100644 --- a/crates/gosub_vello/src/scene.rs +++ b/crates/gosub_vello/src/scene.rs @@ -1,17 +1,20 @@ -use gosub_render_backend::{RenderBackend, RenderRect, RenderText, Scene as TScene}; use vello::kurbo::RoundedRect; use vello::peniko::Fill; use vello::Scene as VelloScene; +use gosub_render_backend::{RenderBackend, RenderRect, RenderText, Scene as TScene}; + use crate::{Border, BorderRenderOptions, Text, Transform, VelloBackend}; pub struct Scene(pub(crate) VelloScene); -impl TScene for Scene { - fn reset(&mut self) { - self.0.reset() +impl Scene { + pub fn inner(&mut self) -> &mut VelloScene { + &mut self.0 } +} +impl TScene for Scene { fn draw_rect(&mut self, rect: &RenderRect) { let affine = rect.transform.as_ref().map(|t| t.0).unwrap_or_default(); @@ -51,6 +54,10 @@ impl TScene for Scene { self.0.append(&scene.0, transform.map(|t| t.0)); } + fn reset(&mut self) { + self.0.reset() + } + fn new(_data: &mut ::WindowData<'_>) -> Self { VelloScene::new().into() } diff --git a/crates/gosub_vello/src/vello_svg.rs b/crates/gosub_vello/src/vello_svg.rs new file mode 100644 index 000000000..83eb96253 --- /dev/null +++ b/crates/gosub_vello/src/vello_svg.rs @@ -0,0 +1,33 @@ +use gosub_html5::node::NodeId; +use gosub_html5::parser::document::DocumentHandle; +use gosub_render_backend::svg::SvgRenderer; +use gosub_render_backend::ImageBuffer; +use gosub_shared::types::Result; + +use crate::render::window::WindowData; +use crate::VelloBackend; +use gosub_svg::SVGDocument; + +pub struct VelloSVG; + +impl SvgRenderer for VelloSVG { + type SvgDocument = SVGDocument; + + fn new(_: &mut WindowData) -> Self { + Self + } + + fn parse_external(data: String) -> Result { + SVGDocument::from_str(&data) + } + + fn parse_internal(tree: DocumentHandle, id: NodeId) -> Result { + SVGDocument::from_html_doc(id, tree) + } + + fn render(&mut self, _doc: &SVGDocument) -> Result> { + // vello_svg::render_tree(scene.inner(), &doc.tree); //TODO: too old versions that vello_svg uses + + todo!(); + } +} diff --git a/src/bin/document-writer.rs b/src/bin/document-writer.rs index df099afdd..fd5841886 100644 --- a/src/bin/document-writer.rs +++ b/src/bin/document-writer.rs @@ -6,7 +6,6 @@ use gosub_shared::bytes::{CharIterator, Confidence, Encoding}; use gosub_shared::timing::Scale; use gosub_shared::timing_display; use gosub_shared::types::Result; -use std::borrow::Borrow; use std::fs; use std::process::exit; use std::str::FromStr; @@ -84,10 +83,8 @@ fn main() -> Result<()> { timing_display!(true, Scale::Auto); - let doc = handle.get(); - let mut body = NodeId::root(); for (id, node) in doc.nodes() { @@ -96,12 +93,9 @@ fn main() -> Result<()> { } } - - let wrote = doc.write_from_node(body); println!("{wrote}"); - Ok(()) } diff --git a/src/bin/resources/gosub.html b/src/bin/resources/gosub.html index ec06ee3a0..907ffa536 100644 --- a/src/bin/resources/gosub.html +++ b/src/bin/resources/gosub.html @@ -138,7 +138,7 @@

Gosub

The gateway to optimized searching and browsing

- +
Join us on the journey to a new web browser