From 546c81624ae587ce355f0cbcdd2b2123b0b3aa15 Mon Sep 17 00:00:00 2001 From: Gert Pellin Date: Tue, 13 Oct 2026 05:23:25 +0200 Subject: [PATCH] SVG import: an unsupported filter's warning names the filter and the object A filter that can't become a live effect warned "filter on a group ignored", even on a plain : usvg wraps every filtered element in a group of its own, which the message named. The warning now names the filter and its primitives and the element (by id, or "an object"), and says it is drawn without it: "filter 'f' (feColorMatrix) on an object isn't supported; drawn without it". Fixes #1598 --- crates/svg/src/import.rs | 52 +++++++++++++++++++++++++++++- crates/svg/tests/import_details.rs | 13 ++++++++ 2 files changed, 64 insertions(+), 1 deletion(-) diff --git a/crates/svg/src/import.rs b/crates/svg/src/import.rs index 5a591bad..eb907d70 100644 --- a/crates/svg/src/import.rs +++ b/crates/svg/src/import.rs @@ -245,6 +245,44 @@ impl RootUnits { } } +/// `'id' (feA, feB)` for each filter: its id and the primitives it uses, for warnings. +fn filter_names(filters: &[Arc]) -> String { + use usvg::filter::Kind as K; + let names: Vec = filters + .iter() + .map(|f| { + let mut kinds: Vec<&str> = Vec::new(); + for p in f.primitives() { + let k = match p.kind() { + K::Blend(_) => "feBlend", + K::ColorMatrix(_) => "feColorMatrix", + K::ComponentTransfer(_) => "feComponentTransfer", + K::Composite(_) => "feComposite", + K::ConvolveMatrix(_) => "feConvolveMatrix", + K::DiffuseLighting(_) => "feDiffuseLighting", + K::DisplacementMap(_) => "feDisplacementMap", + K::DropShadow(_) => "feDropShadow", + K::Flood(_) => "feFlood", + K::GaussianBlur(_) => "feGaussianBlur", + K::Image(_) => "feImage", + K::Merge(_) => "feMerge", + K::Morphology(_) => "feMorphology", + K::Offset(_) => "feOffset", + K::SpecularLighting(_) => "feSpecularLighting", + K::Tile(_) => "feTile", + K::Turbulence(_) => "feTurbulence", + }; + if !kinds.contains(&k) { + kinds.push(k); + } + } + let id = if f.id().is_empty() || made_up(f.id()) { String::new() } else { format!("'{}' ", f.id()) }; + format!("{id}({})", kinds.join(", ")) + }) + .collect(); + names.join(", ") +} + /// How many clip paths deep (``) an object's clip is read. const MAX_CLIP_NEST: usize = 16; @@ -902,7 +940,19 @@ impl Importer { let effects = match g.filters() { [] => vec![], filters => fx::effects(filters, ts, &self.blends).unwrap_or_else(|| { - self.warn(format!("filter on {label} ignored")); + // usvg wraps a filtered element in a group of its own: name the element then. + let on = match g.children() { + [usvg::Node::Group(_)] | [] => label.clone(), + [one] if made_up(g.id()) || g.id().is_empty() => { + if one.id().is_empty() || made_up(one.id()) { + "an object".to_string() + } else { + format!("'{}'", one.id()) + } + } + _ => label.clone(), + }; + self.warn(format!("filter {} on {on} isn't supported; drawn without it", filter_names(filters))); vec![] }), }; diff --git a/crates/svg/tests/import_details.rs b/crates/svg/tests/import_details.rs index 4634dec7..467e1a93 100644 --- a/crates/svg/tests/import_details.rs +++ b/crates/svg/tests/import_details.rs @@ -216,3 +216,16 @@ fn percentages_in_a_symbol_are_of_its_view_box() { }; assert_similar(&drawing("100%", "100%", "25%", "50%"), &drawing("100", "50", "25", "25"), 24.0, 0.001); } + +/// #1598: an unsupported filter's warning names the filter, its primitives and the object, and +/// says it is drawn without it (not "a group": usvg wraps the element in one). +#[test] +fn unsupported_filters_are_named_in_the_warning() { + let body = r#" + + "#; + let (d, w) = import_with_report(&svg(body)).unwrap(); + assert!(w.iter().any(|w| w == "filter 'f' (feColorMatrix) on an object isn't supported; drawn without it"), "{w:?}"); + assert!(w.iter().any(|w| w == "filter 't' (feTurbulence) on 'r' isn't supported; drawn without it"), "{w:?}"); + assert_eq!(art(&d).iter().filter(|n| n.path_data().is_some()).count(), 2, "both drawn"); +}