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 <rect>: 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
This commit is contained in:
Gert Pellin 2026-10-13 05:23:25 +02:00 committed by bflatastic
parent 13bd24ba79
commit 546c81624a
2 changed files with 64 additions and 1 deletions

View File

@ -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<usvg::filter::Filter>]) -> String {
use usvg::filter::Kind as K;
let names: Vec<String> = 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 (`<clipPath clip-path>`) 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![]
}),
};

View File

@ -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#"<filter id="f"><feColorMatrix type="saturate" values="0"/></filter><filter id="t"><feTurbulence baseFrequency="0.05"/></filter>
<rect x="20" y="20" width="60" height="60" fill="red" filter="url(#f)"/>
<rect id="r" x="100" y="20" width="60" height="60" fill="blue" filter="url(#t)"/>"#;
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");
}