Merge pull request #7891 from dotCooCoo/fix/plain-paste-merged-swatch-label-contrast

Paste: Paste without Formatting sets pie legend label contrast from merged swatch colors
This commit is contained in:
BFlat
2026-11-16 01:34:28 +07:00
committed by GitHub
4 changed files with 101 additions and 2 deletions
+29 -1
View File
@@ -595,6 +595,8 @@ struct Relink {
styles: BTreeMap<u32, u32>,
/// Swatch (its name in the document) → the colour linked colours show at 100 %.
colors: BTreeMap<String, Color>,
/// Leave graphic style links as they are ([`Clipboard::show_merged_colors`]).
keep_styles: bool,
}
impl Relink {
@@ -674,7 +676,9 @@ impl Relink {
/// Relink a node tree (the mirror of [`node_refs`]).
fn node(&self, n: &mut Node) {
self.appearance(&mut n.appearance);
n.extras_mut().graphic_style = n.extras().graphic_style.and_then(|id| self.styles.get(&id).copied());
if !self.keep_styles {
n.extras_mut().graphic_style = n.extras().graphic_style.and_then(|id| self.styles.get(&id).copied());
}
if let Some(m) = n.extras_if_any().and_then(|e| e.mask.as_mut()) {
self.node(Arc::make_mut(&mut m.art));
}
@@ -1109,6 +1113,30 @@ impl Clipboard {
self.import_used(d, &self.used(), choices, same_doc)
}
/// Give the objects' colors linked to a global or spot swatch whose conflict with `d`
/// `choices` merge the color that swatch has in `d`, as pasting into `d` gives them (Paste
/// without Formatting sets its pie legend labels' contrast from the wedge colors before it
/// pastes). Nothing changes for the document the objects came from (`same_doc`).
pub(crate) fn show_merged_colors(&mut self, d: &Document, choices: &SwatchChoices, same_doc: bool) {
if same_doc {
return;
}
let used = self.used();
let colors: BTreeMap<String, Color> = self
.conflicts(d, &used)
.into_iter()
.filter(|(w, _)| choices.of(&w.name) == SwatchChoice::Merge)
.filter_map(|(w, _)| d.global_color(&w.name).map(|c| (w.name.clone(), c)))
.collect();
if colors.is_empty() {
return;
}
let relink = Relink { colors, keep_styles: true, ..Default::default() };
for n in &mut self.nodes {
relink.node(n);
}
}
/// [`Self::import_into`] for the resources `used` names. Each style, symbol, pattern and brush
/// comes after the styles, symbols, patterns and brushes it uses ([`use_order`]), so that it is
/// compared and relinked with their names here, and each loop of definitions that use each
+7
View File
@@ -1783,6 +1783,13 @@ fn strip_formatting(n: &mut vectorcraft_doc::Node) {
fn paste_plain(s: &mut Session, p: &Value) -> Result<Value> {
let saved = s.clipboard.nodes.clone();
// Pasted colors linked to a swatch merged with the document's show its color, so a pasted pie's legend labels take
// the contrast of the wedges as pasted. A `swatchConflict` the paste refuses is left to its error.
if let (Ok(choices), Ok(st)) = (super::clipboard::SwatchChoices::parse("edit.pasteWithoutFormatting", p), s.doc()) {
let same_doc = s.clipboard.source_doc == Some(st.uid);
let d = std::sync::Arc::clone(&st.doc);
s.clipboard.show_merged_colors(&d, &choices, same_doc);
}
for n in &mut s.clipboard.nodes {
strip_formatting(n);
}
+62
View File
@@ -1034,6 +1034,68 @@ fn a_repainted_wedge_sets_its_label_color_when_pasted_without_formatting() {
assert_eq!(pasted, labels(&s, p), "copied as {before:?}");
}
/// Wedges linked to a global swatch that the destination document gives a dark color, merged on Paste without
/// Formatting, give their legend labels white characters, as a redraw does; added under a new name, they keep the
/// copied light color and black labels.
#[test]
fn wedges_merged_into_a_dark_swatch_give_white_labels_when_pasted_without_formatting() {
let csv = ",North,South\nP,1,1";
fn labels(s: &Session, id: NodeId) -> Vec<(String, Paint)> {
let mut out = vec![];
node(s, id).walk(&mut |n| {
if let NodeKind::Text(t) = &n.kind
&& ["North", "South"].contains(&t.plain_text().as_str())
{
out.push((t.plain_text(), t.first_style().fill.clone()));
}
});
out
}
let both = |c: Color| vec![("North".to_string(), Paint::solid(c)), ("South".to_string(), Paint::solid(c))];
for (conflict, expected) in [("merge", Color::WHITE), ("add", Color::BLACK)] {
let mut s = session();
let g = id_of(&s.execute("graph.create", &json!({"type": "pie", "x": 50, "y": 50, "width": 200, "height": 200, "csv": csv})).unwrap());
s.execute("graph.setType", &json!({"id": g.0, "pieLegend": "wedges"})).unwrap();
s.execute("swatch.new", &json!({"name": "Ink", "color": "#ffffff", "global": true})).unwrap();
let mut wedges = vec![];
node(&s, g).walk(&mut |m| {
if let NodeKind::Path { .. } = m.kind {
wedges.push(m.id.0);
}
});
s.execute("paint.setFill", &json!({"ids": wedges, "swatch": "Ink"})).unwrap();
s.execute("graph.setData", &json!({"id": g.0, "csv": csv})).unwrap();
assert_eq!(labels(&s, g), both(Color::BLACK), "light wedges have black labels");
copy(&mut s, &[g]);
new_doc(&mut s);
s.execute("swatch.new", &json!({"name": "Ink", "color": "#000000", "global": true})).unwrap();
let p = ids_of(&s.execute("edit.pasteWithoutFormatting", &json!({"swatchConflict": conflict})).unwrap())[0];
let pasted = labels(&s, p);
assert_eq!(pasted, both(expected), "{conflict}");
s.execute("graph.setData", &json!({"id": p.0, "csv": csv})).unwrap();
assert_eq!(pasted, labels(&s, p), "{conflict}: as a redraw sets them");
}
}
/// An object linked to a graphic style and to a swatch merged with the document's keeps its graphic style link when
/// pasted without formatting, and its color shows the document's swatch.
#[test]
fn a_merged_swatch_keeps_graphic_style_links_when_pasted_without_formatting() {
let mut s = session();
let r = swatched_rect(&mut s, "Ink", "#ffffff");
sel(&mut s, &[r]);
s.execute("graphicStyle.new", &json!({"name": "Sun"})).unwrap();
copy(&mut s, &[r]);
new_doc(&mut s);
s.execute("swatch.new", &json!({"name": "Ink", "color": "#000000", "global": true})).unwrap();
let v = s.execute("edit.pasteWithoutFormatting", &json!({})).unwrap();
assert_eq!(v["merged"], 1);
let p = ids_of(&v)[0];
let style = node(&s, p).extras().graphic_style.and_then(|id| doc(&s).graphic_style_by_id(id)).map(|g| g.name.clone());
assert_eq!(style.as_deref(), Some("Sun"));
assert_eq!(fill(&s, p), linked(hex("#000000"), "Ink", 1.0));
}
/// When one pie's wedge of a series is repainted and another pie's wedge of that series is not, each pasted legend label
/// takes the contrast of the wedge it is in.
#[test]
+3 -1
View File
@@ -7247,7 +7247,9 @@ to}]}`.
`edit.pasteWithoutFormatting` sets the labels of a pasted graph in a new graph's label character style and
paragraph attributes, keeping each label's alignment and where it starts. The pasted graph no longer keeps its labels' character and
paragraph formats, so a later `graph.setData` or `graph.setType` sets the labels the same way. The rest of the
graph's art is pasted as it was copied.
graph's art is pasted as it was copied. A pie's legend labels in its wedges are white on a dark wedge and black on a
light one, by the wedge colors as pasted: a wedge linked to a swatch merged with the document's takes that swatch's
color first.
A global or spot swatch whose name the document gives another colour is a conflict: `clipboard.conflicts` lists
them, and `swatchConflict` answers: `"merge"` (the default: the objects take the document's swatch), `"add"` (the