fix: detect SVG icons by content

This commit is contained in:
Frederic Laing 2026-09-07 12:42:21 +02:00 • committed by Michael Murphy
parent d4d71fd53e
commit 0304f1d92e
3 changed files with 55 additions and 11 deletions

View file

@ -166,6 +166,7 @@ zbus = { workspace = true, optional = true }
float-cmp = "0.10.0" float-cmp = "0.10.0"
ron = { workspace = true, optional = true } ron = { workspace = true, optional = true }
enumflags2 = "0.7.12" enumflags2 = "0.7.12"
roxmltree = "0.20"
# Enable DBus feature on Linux targets # 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] [target.'cfg(all(unix, not(any(target_os = "redox", target_family = "wasm", target_os = "android", target_vendor = "apple"))))'.dependencies]

View file

@ -5,8 +5,10 @@ use super::Icon;
use crate::widget::{image, svg}; use crate::widget::{image, svg};
use std::borrow::Cow; use std::borrow::Cow;
use std::ffi::OsStr; use std::ffi::OsStr;
use std::fs::File;
use std::hash::Hash; use std::hash::Hash;
use std::path::PathBuf; use std::io::Read;
use std::path::{Path, PathBuf};
#[must_use] #[must_use]
#[derive(Clone, Debug, Hash, derive_setters::Setters)] #[derive(Clone, Debug, Hash, derive_setters::Setters)]
@ -31,6 +33,52 @@ pub enum Data {
Svg(svg::Handle), Svg(svg::Handle),
} }
enum SvgSource {
Path,
Bytes(Vec<u8>),
}
fn svg_source(path: &Path) -> Option<SvgSource> {
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. /// Create an icon handle from its path.
pub fn from_path(path: PathBuf) -> Handle { pub fn from_path(path: PathBuf) -> Handle {
Handle { Handle {
@ -38,10 +86,10 @@ pub fn from_path(path: PathBuf) -> Handle {
.file_stem() .file_stem()
.and_then(OsStr::to_str) .and_then(OsStr::to_str)
.is_some_and(|name| name.ends_with("-symbolic")), .is_some_and(|name| name.ends_with("-symbolic")),
data: if path.extension().is_some_and(|ext| ext == OsStr::new("svg")) { data: match svg_source(&path) {
Data::Svg(svg::Handle::from_path(path)) Some(SvgSource::Path) => Data::Svg(svg::Handle::from_path(path)),
} else { Some(SvgSource::Bytes(bytes)) => Data::Svg(svg::Handle::from_memory(bytes)),
Data::Image(image::Handle::from_path(path)) None => Data::Image(image::Handle::from_path(path)),
}, },
} }
} }

View file

@ -3,7 +3,6 @@
use super::{Handle, Icon}; use super::{Handle, Icon};
use std::borrow::Cow; use std::borrow::Cow;
use std::ffi::OsStr;
use std::path::PathBuf; use std::path::PathBuf;
use std::sync::Arc; use std::sync::Arc;
@ -138,11 +137,7 @@ impl Named {
Handle { Handle {
symbolic: self.symbolic, symbolic: self.symbolic,
data: if let Some(path) = self.path() { data: if let Some(path) = self.path() {
if path.extension().is_some_and(|ext| ext == OsStr::new("svg")) { super::from_path(path).data
super::Data::Svg(iced_core::svg::Handle::from_path(path))
} else {
super::Data::Image(iced_core::image::Handle::from_path(path))
}
} else { } else {
super::bundle::get(&name).unwrap_or_else(|| { super::bundle::get(&name).unwrap_or_else(|| {
let bytes: &'static [u8] = &[]; let bytes: &'static [u8] = &[];