Brushes: tests expect the first brush of a repeated name

With repeated names numbered when a brush library is read, a stroke that
names a repeated brush draws the first brush of that name, and a later
brush of the name is read as "Name 2". Three tests built a library with a
repeated name and expected the later brush:

- Document Info lists a name two stored brushes share with the first
  brush's type (art), not the last one's.
- The two render memory tests add their own `Dots` scatter brush beside
  the default library's `Dots`. They now remove the default `Dots` first,
  so the stroke draws their brush.
- The .ai writer test that leaves out a Pattern brush "X" gets the
  Calligraphic brush after it back as "X 2", the name it had in the
  document.
This commit is contained in:
dotCooCoo
2026-11-13 11:23:40 -08:00
parent 711d7163f8
commit b6e3e96db3
3 changed files with 11 additions and 6 deletions
+3 -3
View File
@@ -633,7 +633,7 @@ fn info(s: &mut Session, p: &Value) -> Result<Value> {
fonts.extend(super::fonts::art_texts_of(d).into_iter().flat_map(|(_, t, _)| t.runs.iter().map(|r| super::fonts::font_of(&r.style))));
}
let font_names: Vec<String> = fonts.iter().map(super::fonts::font_label).collect();
// Each brush the art uses with its type, found by name in the Brushes panel as the renderer finds it (the last of two
// Each brush the art uses with its type, found by name in the Brushes panel as the renderer finds it (the first of two
// brushes with one name); a brush the panel does not hold draws as a plain stroke and has no type. A row names the type
// as the New Brush dialog does (`Art Brush`), and the panel translates these values when it shows them.
let kinds: HashMap<&str, &vectorcraft_brush::BrushKind> = library.iter().map(|b| (b.name.as_str(), &b.kind)).collect();
@@ -910,7 +910,7 @@ mod tests {
let d = std::sync::Arc::make_mut(&mut s.doc_mut().unwrap().doc);
d.node_mut(NodeId(missing)).unwrap().appearance.stroke_mut().unwrap().paint = Paint::None;
assert_eq!(s.execute("document.info", &json!({"category": "brushes"})).unwrap()["brushes"], json!([]));
// Two panel brushes with one name: the type is the one the renderer draws with, the last one's.
// Two panel brushes with one name: the type is the one the renderer draws with, the first one's.
let twin = square(&mut s, 140.0);
s.execute("brush.apply", &json!({"ids": [twin], "name": "Charcoal"})).unwrap();
let d = std::sync::Arc::make_mut(&mut s.doc_mut().unwrap().doc);
@@ -920,7 +920,7 @@ mod tests {
lib.extend(kinds.into_iter().map(|kind| vectorcraft_brush::Brush { name: "Twin".into(), kind }));
vectorcraft_brush::store(d, &lib);
d.node_mut(NodeId(twin)).unwrap().appearance.stroke_mut().unwrap().brush = Some("Twin".into());
assert_eq!(s.execute("document.info", &json!({"category": "brushes"})).unwrap()["brushes"], json!([{"name": "Twin", "type": "scatter"}]));
assert_eq!(s.execute("document.info", &json!({"category": "brushes"})).unwrap()["brushes"], json!([{"name": "Twin", "type": "art"}]));
// Type: a brushed stroke on drawn characters is listed, one on spaces alone is not.
let blank = s.execute("text.create", &json!({"x": 10, "y": 60, "text": " "})).unwrap()["id"].as_u64().unwrap();
let word = s.execute("text.create", &json!({"x": 10, "y": 90, "text": "Hi"})).unwrap()["id"].as_u64().unwrap();
+4 -3
View File
@@ -8378,8 +8378,9 @@ fn brushes_the_reader_cant_read_are_left_out() {
}
left_out(vec![Brush { name: "A/B".into(), ..keep.clone() }], "slash");
left_out(vec![Brush { kind: BrushKind::Bristle(Bristle::default()), ..keep.clone() }], "a second brush of the name");
// A Pattern brush named "X" is left out, so a Calligraphic brush named "X" after it comes back. A
// path with "X" is drawn with the Pattern brush, so it comes back as its drawn look.
// A Pattern brush named "X" is left out, so a Calligraphic brush named "X" after it comes back,
// as "X 2", the name the library gives it when it is read. A path with "X" is drawn with the
// Pattern brush, so it comes back as its drawn look.
let mut doc = Document::new(300.0, 200.0);
let x = Brush { name: "X".into(), ..keep.clone() };
vectorcraft_brush::store(&mut doc, &[Brush { name: "X".into(), kind: BrushKind::Pattern(PatternBrush::default()) }, x.clone()]);
@@ -8387,7 +8388,7 @@ fn brushes_the_reader_cant_read_are_left_out() {
let out = export_ai_editing(&doc, &opts()).unwrap();
assert!(out.warnings.iter().any(|w| w == writer::BRUSHES), "{:?}", out.warnings);
let back = read_back(&doc);
assert_eq!(vectorcraft_brush::library(&back), [x]);
assert_eq!(vectorcraft_brush::library(&back), [Brush { name: "X 2".into(), ..x }]);
let n = &back.layers[0].children().unwrap()[0];
assert!(n.appearance.stroke().is_none_or(|s| s.brush.is_none()), "not with the other brush of the name: {n:#?}");
}
+4
View File
@@ -931,6 +931,8 @@ fn brush_art_draws_edits_of_the_symbols_and_patterns_it_shows() {
painted.transform(Affine::translate((0.0, 8.0)), true);
let scatter = Node::group(d.alloc_id(), vec![Arc::new(instance), Arc::new(painted)]);
let mut lib = vectorcraft_brush::library(&d);
// The default library has its own `Dots`, which a stroke would draw with first.
lib.retain(|b| b.name != "Dots");
lib.push(Brush { name: "Dots".into(), kind: BrushKind::Scatter(Scatter { art: scatter, spacing: (150.0, 150.0), ..Default::default() }) });
vectorcraft_brush::store(&mut d, &lib);
let mut ap = vectorcraft_doc::Appearance::basic(Paint::None, Paint::solid(Color::BLACK), 1.0);
@@ -997,6 +999,8 @@ fn brush_art_draws_renamed_symbols_and_patterns_it_shows() {
painted.transform(Affine::translate((0.0, 8.0)), true);
let scatter = Node::group(d.alloc_id(), vec![Arc::new(instance), Arc::new(painted)]);
let mut lib = vectorcraft_brush::library(&d);
// The default library has its own `Dots`, which a stroke would draw with first.
lib.retain(|b| b.name != "Dots");
lib.push(Brush { name: "Dots".into(), kind: BrushKind::Scatter(Scatter { art: scatter, spacing: (150.0, 150.0), ..Default::default() }) });
vectorcraft_brush::store(&mut d, &lib);
let mut ap = vectorcraft_doc::Appearance::basic(Paint::None, Paint::solid(Color::BLACK), 1.0);