Symbols: an instance fill with no opacity keeps the art's swatch links

A symbol instance whose fill has an opacity of 0 no longer counts as
stained. Expand Appearance and the other places that build an
instance's art used to rebuild every solid paint as an unlinked color
even though the tint changed nothing, so the art lost its global and
spot swatch links.
This commit is contained in:
dotCooCoo
2026-11-05 07:44:08 +07:00
committed by bflatastic
parent 373e013670
commit ee9cce43dc
3 changed files with 51 additions and 2 deletions
+3 -2
View File
@@ -132,9 +132,10 @@ pub fn tint_node(n: &mut Node, color: &Color, amount: f32) {
}
/// The stain instance `inst` puts on its symbol's art: its visible fill colour and that fill's
/// opacity (how far the art mixes towards it). `None` for an unstained instance.
/// opacity (how far the art mixes towards it). `None` for an unstained instance, including one
/// whose fill has an opacity of 0, so its art keeps its paints and their swatch links.
pub fn stain(inst: &Node) -> Option<(Color, f32)> {
let f = inst.appearance.fill().filter(|f| f.visible)?;
let f = inst.appearance.fill().filter(|f| f.visible && f.opacity > 0.0)?;
Some((f.paint.color()?, f.opacity))
}
+20
View File
@@ -764,6 +764,26 @@ fn stain_mixes_type_strokes_too() {
assert_eq!(run_paints(&[art]), [(Paint::solid(blue), Paint::solid(blue))]);
}
/// An instance whose fill has an opacity of 0 does not stain its symbol's art, so the art keeps its
/// swatch links.
#[test]
fn a_fill_with_no_opacity_leaves_the_symbol_art_linked() {
let spot = Paint::Solid { color: Color::cmyk(1.0, 0.7, 0.0, 0.4), swatch: Some("Spot".into()), tint: 1.0 };
let art = Node::path(NodeId(1), shapes::rectangle(Rect::new(0.0, 0.0, 10.0, 10.0)), Appearance::basic(spot.clone(), Paint::None, 0.0));
let mut inst = Node::new(NodeId(2), NodeKind::SymbolInstance { symbol: "Dot".into(), xf: Affine::IDENTITY });
inst.appearance = Appearance::basic(Paint::solid(Color::rgb(1.0, 0.0, 0.0)), Paint::None, 0.0).into();
if let Some(f) = inst.appearance.fill_mut() {
f.opacity = 0.0;
}
assert_eq!(stain(&inst), None);
assert_eq!(instance_art(&art, &inst).appearance.fill_paint(), spot);
// Any opacity above 0 still stains.
if let Some(f) = inst.appearance.fill_mut() {
f.opacity = 0.01;
}
assert_ne!(instance_art(&art, &inst).appearance.fill_paint(), spot);
}
#[test]
fn library_defaults_store_and_find() {
let mut d = Document::new(100.0, 100.0);
+28
View File
@@ -75,6 +75,34 @@ fn symbol_instance_tint_survives_expanding_an_object_effect() {
assert!(differing(&before, &after, 8) < 200, "expanding changed {} pixels", differing(&before, &after, 8));
}
/// An instance fill with an opacity of 0 does not tint the symbol's art, so expanding keeps the
/// art's global swatch link.
#[test]
fn a_symbol_instance_fill_with_no_opacity_keeps_swatch_links_when_expanded() {
let mut s = session();
let sq = square(&mut s);
run(&mut s, "swatch.new", json!({"name": "Brand", "color": "#2a6fb0", "global": true}));
run(&mut s, "paint.setFill", json!({"ids": [sq.0], "swatch": "Brand"}));
let id = NodeId(run(&mut s, "symbol.new", json!({"name": "Linked Square"}))["id"].as_u64().unwrap());
run(&mut s, "appearance.addFill", json!({"ids": [id.0]}));
let fill = node(&s, id).appearance.items.len() - 1;
run(&mut s, "appearance.setItem", json!({"ids": [id.0], "index": fill, "color": "#00ff00", "opacity": 0}));
run(&mut s, "effect.apply", json!({"ids": [id.0], "item": null, "effect": "path.offsetPath", "params": {"offset": 6}}));
assert!(expandable(&s));
run(&mut s, "effect.expandAppearance", json!({}));
let n = node(&s, id);
assert!(!matches!(n.kind, NodeKind::SymbolInstance { .. }));
let mut linked = 0;
n.walk(&mut |part| {
if let vectorcraft_color::Paint::Solid { swatch: Some(name), .. } = part.appearance.fill_paint() {
assert_eq!(name, "Brand");
linked += 1;
}
});
assert!(linked > 0, "the expanded art keeps the Brand swatch link");
}
#[test]
fn two_fills_and_an_offset_stroke_become_three_paths_that_look_the_same() {
let mut s = session();