Merge pull request #5717 from dotCooCoo/fix/zero-opacity-stain-keeps-swatches

Symbols: an instance fill with no opacity keeps the art's swatch links
This commit is contained in:
BFlat
2026-11-05 07:56:13 +07:00
committed by GitHub
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();