Preserve SVG styles with encoded attribute selectors

This commit is contained in:
dotCooCoo
2026-11-03 16:03:40 +07:00
committed by bflatastic
parent 012ac29d15
commit 449dd5f5ed
2 changed files with 21 additions and 3 deletions
+4 -3
View File
@@ -149,7 +149,7 @@ fn named_made_up_ids(xml: &roxmltree::Document) -> HashSet<usize> {
/// * so do objects with attributes in other applications' namespaces ([`foreign`]);
/// * so do `<circle>` and `<ellipse>` elements, recorded: usvg makes their arcs into a varying
/// number of curves, and they come back as the ellipse VectorCraft draws (not in a file whose
/// style sheet has an `[id` selector);
/// style sheet has an attribute selector);
/// * linked files are read ([`files`]);
/// * percentage lengths in a `<symbol>` with a `viewBox` become numbers ([`symbol_units`]);
/// * what style sheet rules with a `:not()` set goes in `style` attributes ([`not_rules`]).
@@ -164,8 +164,9 @@ pub(super) fn prepass<'s>(svg: &'s str, markers: &str, xml: &roxmltree::Document
let not = markers.contains(":not(");
let events = SVG_EVENTS.iter().any(|e| markers.contains(e));
let foreign_attrs = markers.contains("xmlns:") && has_foreign_attributes(xml);
// An id would change what a style sheet's `[id]` selector matches: such files keep usvg's curves.
let ellipses = !markers.contains("[id") && ["<circle", ":circle", "<ellipse", ":ellipse"].iter().any(|t| markers.contains(t));
let attribute_selectors =
xml.descendants().filter(|n| n.is_element() && n.tag_name().name() == "style").filter_map(|n| n.text()).any(|text| text.contains('['));
let ellipses = !attribute_selectors && ["<circle", ":circle", "<ellipse", ":ellipse"].iter().any(|t| markers.contains(t));
if !css_transform
&& !not
&& !events
+17
View File
@@ -20,6 +20,23 @@ fn open(body: &str) -> (Document, Vec<String>) {
import_with_report(&svg(body)).unwrap()
}
#[test]
fn decoded_id_selectors_do_not_recolor_bare_ellipses() {
for selector in ["circle&#91;id]", "ellipse&#x5b;id]", "circle[id]", "ellipse[id]", "circle[ id ]", r"ellipse[\69 d]"] {
let source = svg(&format!(
"<style>{selector} {{ fill: red }}</style><circle cx=\"40\" cy=\"50\" r=\"20\"/><ellipse cx=\"100\" cy=\"50\" rx=\"20\" ry=\"10\"/><circle id=\"c\" cx=\"40\" cy=\"100\" r=\"8\"/><ellipse id=\"e\" cx=\"100\" cy=\"100\" rx=\"12\" ry=\"8\"/>"
));
let doc = import(&source).unwrap();
let actual = render_artboard(&doc);
let expected = resvg_render(&source, 200, 120);
assert_eq!((actual.width, actual.height), (expected.width, expected.height));
for (x, y) in [(40, 50), (100, 50), (40, 100), (100, 100)] {
let at = 4 * (y * actual.width + x) as usize;
assert_eq!(&actual.rgba[at..at + 4], &expected.rgba[at..at + 4], "{selector}: ({x}, {y})");
}
}
}
/// All non-layer nodes in paint order.
fn art(d: &Document) -> Vec<&Node> {
let mut v = Vec::new();