Brushes: a brush name the library lacks doesn't make art draw a brushed stroke

Follow-up to #5293. draws_brushed rejected masks, symbol instances and pattern paints whose art names a brush the library doesn't have, though such a stroke draws plain. It now counts only brush names that resolve.
This commit is contained in:
Dylan M. Taylor
2026-11-03 22:52:25 +07:00
committed by bflatastic
parent 4612ffb91d
commit f0f3320d39
2 changed files with 15 additions and 4 deletions
+7 -1
View File
@@ -848,9 +848,15 @@ fn draws_brushed(doc: &Document, def: Named<'_>, known: &mut Known) -> bool {
};
// Without recursion, each one looked at once: they may use one another without end.
let (mut seen, mut stack) = (std::collections::HashSet::from([key.clone()]), art(def));
// A brush name the library lacks draws a plain stroke ([`node_pieces`]), which runs no brush.
let mut names: Option<std::collections::HashSet<String>> = None;
let mut resolves = |n: &Node| {
let names = names.get_or_insert_with(|| library(doc).into_iter().map(|b| b.name).collect());
n.appearance.items.iter().any(|i| matches!(i, AppearanceItem::Stroke(s) if s.visible && s.brush.as_ref().is_some_and(|b| names.contains(b))))
};
let mut answer = false;
while let Some(n) = stack.pop() {
if has_brush(n) {
if has_brush(n) && resolves(n) {
answer = true;
break;
}
+8 -3
View File
@@ -1301,6 +1301,7 @@ fn pieces_leave_out_what_draws_a_brushed_stroke_by_name() {
};
let art = Node::group(NodeId(0), [instance("plain"), instance("brushed"), painted("plain"), painted("brushed")].map(std::sync::Arc::new).into());
let b = Brush { name: "s".into(), kind: BrushKind::Scatter(Scatter { art, colorization: Colorization::None, ..Default::default() }) };
store(&mut doc, std::slice::from_ref(&b));
let out = stroke_pieces(&doc, &b, &line(0.0, 0.0, 100.0, 0.0), &stroke(1.0));
assert!(!out.is_empty());
for piece in &out {
@@ -1325,7 +1326,10 @@ fn pieces_leave_out_a_mask_whose_root_draws_a_brushed_stroke() {
let mut doc = Document::new(100.0, 100.0);
let mut brushed = rect_art(10.0, 4.0);
brushed.appearance.items.push(vectorcraft_doc::AppearanceItem::Stroke(StrokeLayer { brush: Some("s".into()), ..stroke(1.0) }));
for (name, art) in [("plain", rect_art(10.0, 4.0)), ("brushed", brushed)] {
// A brush the library lacks draws a plain stroke: its mask stays.
let mut unresolved = rect_art(10.0, 4.0);
unresolved.appearance.items.push(vectorcraft_doc::AppearanceItem::Stroke(StrokeLayer { brush: Some("missing".into()), ..stroke(1.0) }));
for (name, art) in [("plain", rect_art(10.0, 4.0)), ("brushed", brushed), ("unresolved", unresolved)] {
doc.symbols.push(Symbol { dynamic: false, name: name.into(), art: std::sync::Arc::new(art) });
}
let instance = |symbol: &str| Node::new(NodeId(0), NodeKind::SymbolInstance { symbol: symbol.into(), xf: vectorcraft_geom::Affine::IDENTITY });
@@ -1334,8 +1338,9 @@ fn pieces_leave_out_a_mask_whose_root_draws_a_brushed_stroke() {
n.extras_mut().mask = Some(Box::new(OpacityMask::new(instance(symbol), true)));
n
};
let art = Node::group(NodeId(0), [masked("plain"), masked("brushed")].map(std::sync::Arc::new).into());
let art = Node::group(NodeId(0), [masked("plain"), masked("brushed"), masked("unresolved")].map(std::sync::Arc::new).into());
let b = Brush { name: "s".into(), kind: BrushKind::Scatter(Scatter { art, colorization: Colorization::None, ..Default::default() }) };
store(&mut doc, std::slice::from_ref(&b));
let out = stroke_pieces(&doc, &b, &line(0.0, 0.0, 100.0, 0.0), &stroke(1.0));
assert!(!out.is_empty());
for piece in &out {
@@ -1348,7 +1353,7 @@ fn pieces_leave_out_a_mask_whose_root_draws_a_brushed_stroke() {
_ => None,
})
.collect();
assert_eq!(roots, [Some("plain"), None], "the plain mask stays and the brushed one goes");
assert_eq!(roots, [Some("plain"), None, Some("unresolved")], "the plain and unresolved masks stay and the brushed one goes");
}
}