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"
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]

View file

@ -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<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.
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)),
},
}
}

View file

@ -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] = &[];