diff --git a/Cargo.toml b/Cargo.toml index 5268098d..0ab3aaa6 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -166,6 +166,7 @@ zbus = { workspace = true, optional = true } float-cmp = "0.10.0" ron = { workspace = true, optional = true } enumflags2 = "0.7.12" +roxmltree = "0.20" # Enable DBus feature on Linux targets [target.'cfg(all(unix, not(any(target_os = "redox", target_family = "wasm", target_os = "android", target_vendor = "apple"))))'.dependencies] diff --git a/src/widget/icon/handle.rs b/src/widget/icon/handle.rs index 7e0bab02..1fe334f7 100644 --- a/src/widget/icon/handle.rs +++ b/src/widget/icon/handle.rs @@ -5,8 +5,10 @@ use super::Icon; use crate::widget::{image, svg}; use std::borrow::Cow; use std::ffi::OsStr; +use std::fs::File; use std::hash::Hash; -use std::path::PathBuf; +use std::io::Read; +use std::path::{Path, PathBuf}; #[must_use] #[derive(Clone, Debug, Hash, derive_setters::Setters)] @@ -31,6 +33,52 @@ pub enum Data { Svg(svg::Handle), } +enum SvgSource { + Path, + Bytes(Vec), +} + +fn svg_source(path: &Path) -> Option { + if path + .extension() + .and_then(OsStr::to_str) + .is_some_and(|extension| extension.eq_ignore_ascii_case("svg")) + { + return Some(SvgSource::Path); + } + + let Ok(mut file) = File::open(path) else { + return None; + }; + + let Ok(metadata) = file.metadata() else { + return None; + }; + const MAX_SVG_SIZE: u64 = 16 * 1024 * 1024; + if !metadata.file_type().is_file() || metadata.len() > MAX_SVG_SIZE { + return None; + } + + let mut prefix = [0; 32]; + let Ok(length) = file.read(&mut prefix) else { + return None; + }; + let prefix = &prefix[..length]; + + if ::image::guess_format(prefix).is_ok() { + return None; + } + + let mut bytes = Vec::with_capacity(metadata.len() as usize); + bytes.extend_from_slice(prefix); + if file.read_to_end(&mut bytes).is_err() { + return None; + } + + let document = roxmltree::Document::parse(std::str::from_utf8(&bytes).ok()?).ok()?; + (document.root_element().tag_name().name() == "svg").then_some(SvgSource::Bytes(bytes)) +} + /// Create an icon handle from its path. pub fn from_path(path: PathBuf) -> Handle { Handle { @@ -38,10 +86,10 @@ pub fn from_path(path: PathBuf) -> Handle { .file_stem() .and_then(OsStr::to_str) .is_some_and(|name| name.ends_with("-symbolic")), - data: if path.extension().is_some_and(|ext| ext == OsStr::new("svg")) { - Data::Svg(svg::Handle::from_path(path)) - } else { - Data::Image(image::Handle::from_path(path)) + data: match svg_source(&path) { + Some(SvgSource::Path) => Data::Svg(svg::Handle::from_path(path)), + Some(SvgSource::Bytes(bytes)) => Data::Svg(svg::Handle::from_memory(bytes)), + None => Data::Image(image::Handle::from_path(path)), }, } } diff --git a/src/widget/icon/named.rs b/src/widget/icon/named.rs index 89050301..118b4f12 100644 --- a/src/widget/icon/named.rs +++ b/src/widget/icon/named.rs @@ -3,7 +3,6 @@ use super::{Handle, Icon}; use std::borrow::Cow; -use std::ffi::OsStr; use std::path::PathBuf; use std::sync::Arc; @@ -138,11 +137,7 @@ impl Named { Handle { symbolic: self.symbolic, data: if let Some(path) = self.path() { - if path.extension().is_some_and(|ext| ext == OsStr::new("svg")) { - super::Data::Svg(iced_core::svg::Handle::from_path(path)) - } else { - super::Data::Image(iced_core::image::Handle::from_path(path)) - } + super::from_path(path).data } else { super::bundle::get(&name).unwrap_or_else(|| { let bytes: &'static [u8] = &[];