mirror of
https://github.com/storytold/vectorcraft.git
synced 2026-11-14 14:41:52 +00:00
Merge pull request #7487 from dotCooCoo/fix/paste-graph-label-styles
Paste: graphs bring and follow the styles and swatches their labels keep
This commit is contained in:
@@ -127,6 +127,17 @@ fn node_refs(n: &Node, f: Visit) {
|
||||
if let Some(cm) = Document::color_mask_of(m) {
|
||||
f(Ref::Name(Res::Image, &cm.key));
|
||||
}
|
||||
// A graph keeps paints and style names for its series and labels, which a redraw gives them.
|
||||
if let Some(g) = m.extras().graph.as_deref() {
|
||||
vectorcraft_doc::swatches::graph_paints(m).for_each(|p| paint_refs(p, f));
|
||||
let l = &g.label_styles;
|
||||
for s in [&l.values, &l.categories, &l.legend].into_iter().flatten().filter_map(|st| st.style_name.as_ref()) {
|
||||
f(Ref::Name(Res::CharStyle, s));
|
||||
}
|
||||
for s in [&l.values_para, &l.categories_para, &l.legend_para].into_iter().flatten().filter_map(|pa| pa.style_name.as_ref()) {
|
||||
f(Ref::Name(Res::ParaStyle, s));
|
||||
}
|
||||
}
|
||||
match &m.kind {
|
||||
NodeKind::Text(t) => {
|
||||
for r in &t.runs {
|
||||
@@ -671,6 +682,27 @@ impl Relink {
|
||||
self.name(Res::Image, &mut cm.key);
|
||||
self.name(Res::Image, &mut cm.from);
|
||||
}
|
||||
if let Some(g) = n.extras_if_any().and_then(|e| e.graph.as_deref_mut()) {
|
||||
for s in &mut g.series_paints {
|
||||
s.fill.iter_mut().chain(s.stroke.iter_mut()).for_each(|p| self.paint(p));
|
||||
}
|
||||
let l = &mut g.label_styles;
|
||||
for st in [&mut l.values, &mut l.categories, &mut l.legend].into_iter().flatten() {
|
||||
self.paint(&mut st.fill);
|
||||
self.paint(&mut st.stroke);
|
||||
if let Some(s) = &mut st.style_name {
|
||||
self.name(Res::CharStyle, s);
|
||||
}
|
||||
}
|
||||
for pa in [&mut l.values_para, &mut l.categories_para, &mut l.legend_para].into_iter().flatten() {
|
||||
if let Some(s) = &mut pa.style_name {
|
||||
self.name(Res::ParaStyle, s);
|
||||
}
|
||||
}
|
||||
for r in [&mut l.values_rows, &mut l.categories_rows, &mut l.legend_rows].into_iter().flatten() {
|
||||
r.paint.fill.iter_mut().chain(r.paint.stroke.iter_mut()).for_each(|p| self.paint(p));
|
||||
}
|
||||
}
|
||||
match &mut n.kind {
|
||||
NodeKind::Text(t) => {
|
||||
for r in &mut t.runs {
|
||||
|
||||
@@ -138,7 +138,7 @@ pub fn specs() -> Vec<CommandSpec> {
|
||||
"Paste without Formatting",
|
||||
["Edit"],
|
||||
Some("Cmd+Alt+V"),
|
||||
"{center?, dx?, dy?, swatchConflict?} paste as edit.paste; pasted text takes the default character style (one run) and leaves its character and paragraph styles behind → {ids, added, merged, renamed}",
|
||||
"{center?, dx?, dy?, swatchConflict?} paste as edit.paste; pasted text takes the default character style (one run) and leaves its character and paragraph styles behind, as do the labels of pasted graphs → {ids, added, merged, renamed}",
|
||||
has_clipboard,
|
||||
paste_plain
|
||||
),
|
||||
@@ -1460,6 +1460,16 @@ fn strip_formatting(n: &mut vectorcraft_doc::Node) {
|
||||
t.set_all_paras(Default::default());
|
||||
refresh_bounds(t);
|
||||
}
|
||||
// A graph keeps the names of its labels' character and paragraph styles for each redraw.
|
||||
if let Some(g) = n.extras_if_any().and_then(|e| e.graph.as_deref_mut()) {
|
||||
let l = &mut g.label_styles;
|
||||
for st in [&mut l.values, &mut l.categories, &mut l.legend].into_iter().flatten() {
|
||||
st.style_name = None;
|
||||
}
|
||||
for pa in [&mut l.values_para, &mut l.categories_para, &mut l.legend_para].into_iter().flatten() {
|
||||
pa.style_name = None;
|
||||
}
|
||||
}
|
||||
if let Some(ch) = n.children_mut() {
|
||||
for c in ch.iter_mut() {
|
||||
strip_formatting(std::sync::Arc::make_mut(c));
|
||||
|
||||
@@ -468,6 +468,72 @@ fn styles_symbols_and_images_of_the_same_name_are_renamed() {
|
||||
assert_eq!(key(&s, img2), src_key);
|
||||
}
|
||||
|
||||
/// A graph whose series fill, value labels and legend rows paint with global swatch "Brand" and
|
||||
/// whose value labels are set in character style "Emphasis" and paragraph style "Body", as a
|
||||
/// later redraw would give them to its marks and labels.
|
||||
fn styled_graph(s: &mut Session) -> NodeId {
|
||||
s.execute("swatch.new", &json!({"name": "Brand", "color": "#ff0000", "global": true})).unwrap();
|
||||
s.execute("charStyle.new", &json!({"name": "Emphasis", "attrs": {"size": 24.0}})).unwrap();
|
||||
s.execute("paraStyle.new", &json!({"name": "Body", "attrs": {"space_after": 8.0}})).unwrap();
|
||||
let v = s.execute("graph.create", &json!({"type": "column", "x": 200, "y": 200, "width": 60, "height": 40, "csv": ",2024\nQ1,3\nQ2,5"}));
|
||||
let id = id_of(&v.unwrap());
|
||||
let brand = linked(hex("#ff0000"), "Brand", 1.0);
|
||||
let mut extras = doc_mut(s).node_mut(id).unwrap().extras_mut();
|
||||
let g = extras.graph.as_mut().unwrap();
|
||||
g.series_paints = vec![vectorcraft_doc::SeriesPaint { fill: Some(brand.clone()), ..Default::default() }];
|
||||
let l = &mut g.label_styles;
|
||||
l.values = Some(vectorcraft_doc::CharStyle { fill: brand.clone(), style_name: Some("Emphasis".into()), ..Default::default() });
|
||||
l.values_para = Some(vectorcraft_doc::ParaStyle { style_name: Some("Body".into()), ..Default::default() });
|
||||
let rows = vectorcraft_doc::SeriesPaint { stroke: Some(brand), ..Default::default() };
|
||||
l.legend_rows = Some(vectorcraft_doc::graph::LabelRows { paint: rows, ..Default::default() });
|
||||
id
|
||||
}
|
||||
|
||||
/// The swatch names a graph's stored paints link to, and its value labels' character and
|
||||
/// paragraph style names.
|
||||
fn graph_links(s: &Session, id: NodeId) -> (Vec<String>, Option<String>, Option<String>) {
|
||||
let n = node(s, id);
|
||||
let swatches = vectorcraft_doc::swatches::graph_paints(&n).filter_map(|p| match p {
|
||||
Paint::Solid { swatch: Some(w), .. } => Some(w.clone()),
|
||||
_ => None,
|
||||
});
|
||||
let l = &n.extras().graph.as_deref().unwrap().label_styles;
|
||||
let values = l.values.as_ref().and_then(|st| st.style_name.clone());
|
||||
(swatches.collect(), values, l.values_para.as_ref().and_then(|pa| pa.style_name.clone()))
|
||||
}
|
||||
|
||||
/// A pasted graph brings the swatches and text styles its series and labels keep, and follows
|
||||
/// them when they come in under new names.
|
||||
#[test]
|
||||
fn a_pasted_graph_brings_and_follows_its_label_styles_and_paints() {
|
||||
let mut s = session();
|
||||
let g = styled_graph(&mut s);
|
||||
copy(&mut s, &[g]);
|
||||
|
||||
new_doc(&mut s);
|
||||
let v = s.execute("edit.pasteInPlace", &json!({})).unwrap();
|
||||
let d = doc(&s);
|
||||
assert!(d.swatch("Brand").is_some(), "{v}");
|
||||
assert!(d.char_styles.iter().any(|x| x.name == "Emphasis") && d.para_styles.iter().any(|x| x.name == "Body"), "{v}");
|
||||
let brand = vec!["Brand".to_string(); 3];
|
||||
assert_eq!(graph_links(&s, ids_of(&v)[0]), (brand.clone(), Some("Emphasis".into()), Some("Body".into())));
|
||||
|
||||
// Into a document that gives each name something else: they come in renamed.
|
||||
new_doc(&mut s);
|
||||
s.execute("swatch.new", &json!({"name": "Brand", "color": "#0000ff", "global": true})).unwrap();
|
||||
s.execute("charStyle.new", &json!({"name": "Emphasis", "attrs": {"size": 8.0}})).unwrap();
|
||||
s.execute("paraStyle.new", &json!({"name": "Body", "attrs": {"space_after": 2.0}})).unwrap();
|
||||
let v = s.execute("edit.pasteInPlace", &json!({"swatchConflict": "add"})).unwrap();
|
||||
let brand2 = vec!["Brand 2".to_string(); 3];
|
||||
assert_eq!(graph_links(&s, ids_of(&v)[0]), (brand2, Some("Emphasis 2".into()), Some("Body 2".into())), "{v}");
|
||||
|
||||
// Without formatting: the labels' text styles stay behind.
|
||||
new_doc(&mut s);
|
||||
let v = s.execute("edit.pasteWithoutFormatting", &json!({})).unwrap();
|
||||
assert!(doc(&s).char_styles.is_empty() && doc(&s).para_styles.is_empty(), "{v}");
|
||||
assert_eq!(graph_links(&s, ids_of(&v)[0]), (brand, None, None));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn pasting_again_reuses_what_an_earlier_paste_added() {
|
||||
let mut s = session();
|
||||
|
||||
+2
-2
@@ -6598,8 +6598,8 @@ number, naming the key, and place nothing; null is the same as leaving the key o
|
||||
`edit.copy` keeps the copied objects with the document resources they use: image blobs, symbols, patterns, global
|
||||
and spot swatches (with the tint swatches of the tints used; never the built-in [Registration] swatch), the gradient
|
||||
swatches their gradients came from, graphic styles the objects are linked to, character and paragraph styles and
|
||||
brushes. Width profiles live in the preferences, so they need no copying. Every `edit.paste*` command (also `edit.pasteWithoutFormatting`, which leaves
|
||||
text styles behind) brings them into the active document in the same undo step: an identical resource is reused,
|
||||
brushes, with the swatches, patterns and text styles a graph keeps for its series and labels. Width profiles live in the preferences, so they need no copying. Every `edit.paste*` command (also `edit.pasteWithoutFormatting`, which leaves
|
||||
text styles behind, those of graph labels included) brings them into the active document in the same undo step: an identical resource is reused,
|
||||
a missing one added, and one of the same name that differs is added renamed ("Mark 2"; a copy an earlier paste
|
||||
added is reused), which the pasted objects follow. Linked colours show the document's swatch at their tint (a Lab
|
||||
spot colour as its Spot Colors option says). The result reports `{ids, added, merged, renamed: [{kind, from,
|
||||
|
||||
Reference in New Issue
Block a user