diff --git a/Cargo.lock b/Cargo.lock index e9df7a2e..6d1d01b1 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -959,6 +959,15 @@ dependencies = [ "libc", ] +[[package]] +name = "core_maths" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "77745e017f5edba1a9c1d854f6f3a52dac8a12dd5af5d2f54aecf61e43d80d30" +dependencies = [ + "libm", +] + [[package]] name = "crc32fast" version = "1.4.2" @@ -1662,6 +1671,28 @@ version = "0.1.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a0d2fde1f7b3d48b8395d5f2de76c18a528bd6a9cdde438df747bfcba3e05d6f" +[[package]] +name = "fontconfig-parser" +version = "0.5.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c1fcfcd44ca6e90c921fee9fa665d530b21ef1327a4c1a6c5250ea44b776ada7" +dependencies = [ + "roxmltree", +] + +[[package]] +name = "fontdb" +version = "0.23.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "457e789b3d1202543297a350643cf459f836cade38934e7a4cf6a39e7cde2905" +dependencies = [ + "fontconfig-parser", + "log", + "slotmap", + "tinyvec", + "ttf-parser", +] + [[package]] name = "foreign-types" version = "0.5.0" @@ -2261,9 +2292,9 @@ dependencies = [ [[package]] name = "imagesize" -version = "0.12.0" +version = "0.13.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "029d73f573d8e8d63e6d5020011d3255b28c3ba85d6cf870a07184ed23de9284" +checksum = "edcd27d72f2f071c64249075f42e205ff93c9a4c5f6c6da53e79ed9f9832c285" [[package]] name = "immutable-chunkmap" @@ -2394,11 +2425,12 @@ dependencies = [ [[package]] name = "kurbo" -version = "0.9.5" +version = "0.11.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bd85a5776cd9500c2e2059c8c76c3b01528566b7fcbaf8098b55a33fc298849b" +checksum = "89234b2cc610a7dd927ebde6b41dd1a5d4214cffaef4cf1fb2195d592f92518f" dependencies = [ "arrayvec", + "smallvec", ] [[package]] @@ -2423,6 +2455,12 @@ dependencies = [ "windows-targets 0.52.6", ] +[[package]] +name = "libm" +version = "0.2.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8355be11b20d696c8f18f6cc018c4e372165b1fa8126cef092399c9951984ffa" + [[package]] name = "libredox" version = "0.1.3" @@ -3366,12 +3404,6 @@ dependencies = [ "crossbeam-utils", ] -[[package]] -name = "rctree" -version = "0.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3b42e27ef78c35d3998403c1d26f3efd9e135d3e5121b0a4845cc5cc27547f4f" - [[package]] name = "redox_syscall" version = "0.4.1" @@ -3438,9 +3470,9 @@ checksum = "19b30a45b0cd0bcca8037f3d0dc3421eaf95327a17cad11964fb8179b4fc4832" [[package]] name = "resvg" -version = "0.37.0" +version = "0.45.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cadccb3d99a9efb8e5e00c16fbb732cbe400db2ec7fc004697ee7d97d86cf1f4" +checksum = "dd43d1c474e9dadf09a8fdf22d713ba668b499b5117b9b9079500224e26b5b29" dependencies = [ "log", "pico-args", @@ -3511,9 +3543,9 @@ dependencies = [ [[package]] name = "roxmltree" -version = "0.19.0" +version = "0.20.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3cd14fd5e3b777a7422cca79358c57a8f6e3a703d9ac187448d0daf220c2407f" +checksum = "6c20b6793b5c2fa6553b250154b78d6d0db37e72700ae35fad9387a46f487c97" [[package]] name = "rustc-demangle" @@ -3578,6 +3610,24 @@ version = "1.0.19" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f7c45b9784283f1b2e7fb61b42047c2fd678ef0960d4f6f1eba131594cc369d4" +[[package]] +name = "rustybuzz" +version = "0.20.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fd3c7c96f8a08ee34eff8857b11b49b07d71d1c3f4e88f8a88d4c9e9f90b1702" +dependencies = [ + "bitflags 2.8.0", + "bytemuck", + "core_maths", + "log", + "smallvec", + "ttf-parser", + "unicode-bidi-mirroring", + "unicode-ccc", + "unicode-properties", + "unicode-script", +] + [[package]] name = "ryu" version = "1.0.18" @@ -3731,9 +3781,9 @@ dependencies = [ [[package]] name = "siphasher" -version = "0.3.11" +version = "1.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "38b58827f4464d87d377d175e90bf58eb00fd8716ff0a62f80356b5e61555d0d" +checksum = "56199f7ddabf13fe5074ce809e7d3f42b42ae711800501b5b16ea82ad029c39d" [[package]] name = "slab" @@ -3864,9 +3914,9 @@ checksum = "13c2bddecc57b384dee18652358fb23172facb8a2c51ccc10d74c157bdea3292" [[package]] name = "svgtypes" -version = "0.13.0" +version = "0.15.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6e44e288cd960318917cbd540340968b90becc8bc81f171345d706e7a89d9d70" +checksum = "68c7541fff44b35860c1a7a47a7cadf3e4a304c457b58f9870d9706ece028afc" dependencies = [ "kurbo", "siphasher", @@ -4107,6 +4157,21 @@ dependencies = [ "serde_json", ] +[[package]] +name = "tinyvec" +version = "1.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "09b3661f17e86524eccd4371ab0429194e0d7c008abb45f7a7495b1719463c71" +dependencies = [ + "tinyvec_macros", +] + +[[package]] +name = "tinyvec_macros" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20" + [[package]] name = "toml_datetime" version = "0.6.8" @@ -4160,6 +4225,9 @@ name = "ttf-parser" version = "0.25.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5902c5d130972a0000f60860bfbf46f7ca3db5391eddfedd1b8728bd9dc96c0e" +dependencies = [ + "core_maths", +] [[package]] name = "type-map" @@ -4187,18 +4255,54 @@ version = "2.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7e51b68083f157f853b6379db119d1c1be0e6e4dec98101079dec41f6f5cf6df" +[[package]] +name = "unicode-bidi" +version = "0.3.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5c1cb5db39152898a79168971543b1cb5020dff7fe43c8dc468b0885f5e29df5" + +[[package]] +name = "unicode-bidi-mirroring" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5dfa6e8c60bb66d49db113e0125ee8711b7647b5579dc7f5f19c42357ed039fe" + +[[package]] +name = "unicode-ccc" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ce61d488bcdc9bc8b5d1772c404828b17fc481c0a582b5581e95fb233aef503e" + [[package]] name = "unicode-ident" version = "1.0.13" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e91b56cd4cadaeb79bbf1a5645f6b4f8dc5bde8834ad5894a8db35fda9efa1fe" +[[package]] +name = "unicode-properties" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e70f2a8b45122e719eb623c01822704c4e0907e7e426a05927e1a1cfff5b75d0" + +[[package]] +name = "unicode-script" +version = "0.5.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9fb421b350c9aff471779e262955939f565ec18b86c15364e6bdf0d662ca7c1f" + [[package]] name = "unicode-segmentation" version = "1.12.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f6ccf251212114b54433ec949fd6a7841275f9ada20dddd2f29e9ceea4501493" +[[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.14" @@ -4267,46 +4371,29 @@ dependencies = [ [[package]] name = "usvg" -version = "0.37.0" +version = "0.45.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "38b0a51b72ab80ca511d126b77feeeb4fb1e972764653e61feac30adc161a756" -dependencies = [ - "base64 0.21.7", - "log", - "pico-args", - "usvg-parser", - "usvg-tree", - "xmlwriter", -] - -[[package]] -name = "usvg-parser" -version = "0.37.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9bd4e3c291f45d152929a31f0f6c819245e2921bfd01e7bd91201a9af39a2bdc" +checksum = "2ac8e0e3e4696253dc06167990b3fe9a2668ab66270adf949a464db4088cb354" dependencies = [ + "base64 0.22.1", "data-url", "flate2", + "fontdb", "imagesize", "kurbo", "log", + "pico-args", "roxmltree", + "rustybuzz", "simplecss", "siphasher", - "svgtypes", - "usvg-tree", -] - -[[package]] -name = "usvg-tree" -version = "0.37.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8ee3d202ebdb97a6215604b8f5b4d6ef9024efd623cf2e373a6416ba976ec7d3" -dependencies = [ - "rctree", "strict-num", "svgtypes", "tiny-skia-path", + "unicode-bidi", + "unicode-script", + "unicode-vo", + "xmlwriter", ] [[package]] diff --git a/crates/egui_extras/Cargo.toml b/crates/egui_extras/Cargo.toml index 7099d95d..2e7b1bd2 100644 --- a/crates/egui_extras/Cargo.toml +++ b/crates/egui_extras/Cargo.toml @@ -62,6 +62,9 @@ serde = ["egui/serde", "enum-map/serde", "dep:serde"] ## Support loading svg images. svg = ["resvg"] +## Support rendering text in svg images. +svg_text = ["svg", "resvg/text", "resvg/system-fonts"] + ## Enable better syntax highlighting using [`syntect`](https://docs.rs/syntect). syntect = ["dep:syntect"] @@ -101,7 +104,7 @@ syntect = { version = "5", optional = true, default-features = false, features = ] } # svg feature -resvg = { version = "0.37", optional = true, default-features = false } +resvg = { version = "0.45", optional = true, default-features = false } # http feature ehttp = { version = "0.5", optional = true, default-features = false } diff --git a/crates/egui_extras/src/image.rs b/crates/egui_extras/src/image.rs index 46d9df17..d7aa3126 100644 --- a/crates/egui_extras/src/image.rs +++ b/crates/egui_extras/src/image.rs @@ -63,8 +63,12 @@ impl RetainedImage { /// # Errors /// On invalid image #[cfg(feature = "svg")] - pub fn from_svg_bytes(debug_name: impl Into, svg_bytes: &[u8]) -> Result { - Self::from_svg_bytes_with_size(debug_name, svg_bytes, None) + pub fn from_svg_bytes( + debug_name: impl Into, + svg_bytes: &[u8], + options: &resvg::usvg::Options<'_>, + ) -> Result { + Self::from_svg_bytes_with_size(debug_name, svg_bytes, None, options) } /// Pass in the str of an SVG that you've loaded. @@ -72,8 +76,12 @@ impl RetainedImage { /// # Errors /// On invalid image #[cfg(feature = "svg")] - pub fn from_svg_str(debug_name: impl Into, svg_str: &str) -> Result { - Self::from_svg_bytes(debug_name, svg_str.as_bytes()) + pub fn from_svg_str( + debug_name: impl Into, + svg_str: &str, + options: &resvg::usvg::Options<'_>, + ) -> Result { + Self::from_svg_bytes(debug_name, svg_str.as_bytes(), options) } /// Pass in the bytes of an SVG that you've loaded @@ -86,10 +94,11 @@ impl RetainedImage { debug_name: impl Into, svg_bytes: &[u8], size_hint: Option, + options: &resvg::usvg::Options<'_>, ) -> Result { Ok(Self::from_color_image( debug_name, - load_svg_bytes_with_size(svg_bytes, size_hint)?, + load_svg_bytes_with_size(svg_bytes, size_hint, options)?, )) } @@ -227,8 +236,11 @@ pub fn load_image_bytes(image_bytes: &[u8]) -> Result Result { - load_svg_bytes_with_size(svg_bytes, None) +pub fn load_svg_bytes( + svg_bytes: &[u8], + options: &resvg::usvg::Options<'_>, +) -> Result { + load_svg_bytes_with_size(svg_bytes, None, options) } /// Load an SVG and rasterize it into an egui image with a scaling parameter. @@ -241,48 +253,47 @@ pub fn load_svg_bytes(svg_bytes: &[u8]) -> Result { pub fn load_svg_bytes_with_size( svg_bytes: &[u8], size_hint: Option, + options: &resvg::usvg::Options<'_>, ) -> Result { use resvg::tiny_skia::{IntSize, Pixmap}; - use resvg::usvg::{Options, Tree, TreeParsing}; + use resvg::usvg::{Transform, Tree}; profiling::function_scope!(); - let opt = Options::default(); + let rtree = Tree::from_data(svg_bytes, options).map_err(|err| err.to_string())?; - let mut rtree = Tree::from_data(svg_bytes, &opt).map_err(|err| err.to_string())?; - - let mut size = rtree.size.to_int_size(); - match size_hint { - None => (), - Some(SizeHint::Size(w, h)) => { - size = size.scale_to( - IntSize::from_wh(w, h).ok_or_else(|| format!("Failed to scale SVG to {w}x{h}"))?, - ); - } - Some(SizeHint::Height(h)) => { - size = size - .scale_to_height(h) - .ok_or_else(|| format!("Failed to scale SVG to height {h}"))?; - } - Some(SizeHint::Width(w)) => { - size = size - .scale_to_width(w) - .ok_or_else(|| format!("Failed to scale SVG to width {w}"))?; - } + let size = rtree.size().to_int_size(); + let scaled_size = match size_hint { + None => size, + Some(SizeHint::Size(w, h)) => size.scale_to( + IntSize::from_wh(w, h).ok_or_else(|| format!("Failed to scale SVG to {w}x{h}"))?, + ), + Some(SizeHint::Height(h)) => size + .scale_to_height(h) + .ok_or_else(|| format!("Failed to scale SVG to height {h}"))?, + Some(SizeHint::Width(w)) => size + .scale_to_width(w) + .ok_or_else(|| format!("Failed to scale SVG to width {w}"))?, Some(SizeHint::Scale(z)) => { let z_inner = z.into_inner(); - size = size - .scale_by(z_inner) - .ok_or_else(|| format!("Failed to scale SVG by {z_inner}"))?; + size.scale_by(z_inner) + .ok_or_else(|| format!("Failed to scale SVG by {z_inner}"))? } }; - let (w, h) = (size.width(), size.height()); + + let (w, h) = (scaled_size.width(), scaled_size.height()); let mut pixmap = Pixmap::new(w, h).ok_or_else(|| format!("Failed to create SVG Pixmap of size {w}x{h}"))?; - rtree.size = size.to_size(); - resvg::Tree::from_usvg(&rtree).render(Default::default(), &mut pixmap.as_mut()); + resvg::render( + &rtree, + Transform::from_scale( + w as f32 / size.width() as f32, + h as f32 / size.height() as f32, + ), + &mut pixmap.as_mut(), + ); let image = egui::ColorImage::from_rgba_unmultiplied([w as _, h as _], pixmap.data()); diff --git a/crates/egui_extras/src/loaders/svg_loader.rs b/crates/egui_extras/src/loaders/svg_loader.rs index 2a3f1556..4c33281f 100644 --- a/crates/egui_extras/src/loaders/svg_loader.rs +++ b/crates/egui_extras/src/loaders/svg_loader.rs @@ -10,9 +10,9 @@ use egui::{ type Entry = Result, String>; -#[derive(Default)] pub struct SvgLoader { cache: Mutex, SizeHint), Entry>>, + options: resvg::usvg::Options<'static>, } impl SvgLoader { @@ -27,6 +27,22 @@ fn is_supported(uri: &str) -> bool { ext == "svg" } +impl Default for SvgLoader { + fn default() -> Self { + // opt is mutated when `svg_text` feature flag is enabled + #[allow(unused_mut)] + let mut options = resvg::usvg::Options::default(); + + #[cfg(feature = "svg_text")] + options.fontdb_mut().load_system_fonts(); + + Self { + cache: Mutex::new(HashMap::default()), + options, + } + } +} + impl ImageLoader for SvgLoader { fn id(&self) -> &str { Self::ID @@ -48,8 +64,12 @@ impl ImageLoader for SvgLoader { match ctx.try_load_bytes(uri) { Ok(BytesPoll::Ready { bytes, .. }) => { log::trace!("started loading {uri:?}"); - let result = crate::image::load_svg_bytes_with_size(&bytes, Some(size_hint)) - .map(Arc::new); + let result = crate::image::load_svg_bytes_with_size( + &bytes, + Some(size_hint), + &self.options, + ) + .map(Arc::new); log::trace!("finished loading {uri:?}"); cache.insert((Cow::Owned(uri.to_owned()), size_hint), result.clone()); match result {