mirror of
https://github.com/storytold/vectorcraft.git
synced 2026-11-08 17:40:54 +00:00
Text styles: loading adds every style and pattern of a loop the document lacks
A style that paints with a pattern whose type is set in that style could be matched to the document's style of the same name and attributes while the pattern came in under a free name, so the loaded style painted with the document's other pattern. When the document does not have such a loop under one set of names, each of its styles, symbols and patterns is now added.
This commit is contained in:
@@ -858,7 +858,8 @@ fn names_of_loop<'d>(d: &'d Document, looped: &[Def<'_>], r: &Relink) -> Option<
|
||||
let names: Vec<Vec<&str>> = looped.iter().map(|def| def.names_in(d)).collect();
|
||||
if names.is_empty() || names.iter().any(Vec::is_empty) {
|
||||
return None;
|
||||
} // The definitions compared once the name at each position is picked.
|
||||
}
|
||||
// The definitions compared once the name at each position is picked.
|
||||
let mut compare_at: Vec<Vec<usize>> = vec![vec![]; looped.len()];
|
||||
for (i, uses) in uses_of(looped).iter().enumerate() {
|
||||
compare_at[uses.iter().copied().fold(i, usize::max)].push(i);
|
||||
@@ -1025,21 +1026,30 @@ impl Clipboard {
|
||||
// when every new name and graphic style id is known; with `in_use_order`, so are the
|
||||
// styles added, which a loop of uses may place before a pattern they paint with.
|
||||
let (mut added_symbols, mut added_patterns, mut added_styles) = (HashSet::new(), HashSet::new(), vec![]);
|
||||
// Each definition, with the loop it starts.
|
||||
let steps =
|
||||
groups.iter().flat_map(|(group, looped)| group.iter().enumerate().map(move |(i, def)| (*def, (*looped && i == 0).then_some(group))));
|
||||
for (def, starts) in steps {
|
||||
// Each definition, whether it is in a loop, and the loop it starts.
|
||||
let steps = groups
|
||||
.iter()
|
||||
.flat_map(|(group, looped)| group.iter().enumerate().map(move |(i, def)| (*def, *looped, (*looped && i == 0).then_some(group))));
|
||||
// In a loop the document does not have all at once, every definition uses one that is
|
||||
// added under a free name, so each one is added too.
|
||||
let mut fresh = false;
|
||||
for (def, looped, starts) in steps {
|
||||
fresh &= looped;
|
||||
// A loop of uses that the document has all at once is known under those names before
|
||||
// its definitions are compared.
|
||||
if let Some(group) = starts
|
||||
&& let Some(names) = names_of_loop(d, group, &out.relink)
|
||||
{
|
||||
let names: Vec<String> = names.into_iter().map(str::to_string).collect();
|
||||
for (def, name) in group.iter().zip(names) {
|
||||
let (res, own) = def.key();
|
||||
if own != name {
|
||||
out.relink.renames.entry(res).or_default().insert(own.to_string(), name);
|
||||
if let Some(group) = starts {
|
||||
match names_of_loop(d, group, &out.relink) {
|
||||
Some(names) => {
|
||||
let names: Vec<String> = names.into_iter().map(str::to_string).collect();
|
||||
for (def, name) in group.iter().zip(names) {
|
||||
let (res, own) = def.key();
|
||||
if own != name {
|
||||
out.relink.renames.entry(res).or_default().insert(own.to_string(), name);
|
||||
}
|
||||
}
|
||||
fresh = false;
|
||||
}
|
||||
None => fresh = true,
|
||||
}
|
||||
}
|
||||
match def {
|
||||
@@ -1047,7 +1057,7 @@ impl Clipboard {
|
||||
let mut s = def.clone();
|
||||
out.relink.attrs(&mut s.attrs);
|
||||
let theirs = if r == Res::CharStyle { &mut d.char_styles } else { &mut d.para_styles };
|
||||
let p = place(theirs, &s, same_doc, PartialEq::eq);
|
||||
let p = place(theirs, &s, same_doc, |a, b| !fresh && a == b);
|
||||
if in_use_order && let Some(name) = p.new_name(&def.name) {
|
||||
added_styles.push((r, name, def));
|
||||
}
|
||||
@@ -1065,7 +1075,8 @@ impl Clipboard {
|
||||
};
|
||||
let r = &out.relink;
|
||||
let p = place(&mut d.symbols, def, same_doc, |a, b| {
|
||||
options.is_none_or(|o| theirs.get(&a.name) == Some(o))
|
||||
!fresh
|
||||
&& options.is_none_or(|o| theirs.get(&a.name) == Some(o))
|
||||
&& same_art(r, std::slice::from_ref(&a.art), std::slice::from_ref(&b.art))
|
||||
});
|
||||
if let Some(name) = p.new_name(&def.name) {
|
||||
@@ -1079,7 +1090,7 @@ impl Clipboard {
|
||||
Def::Pattern(def) => {
|
||||
// Tile options alike, and the art once relinked.
|
||||
let bare = |p: &PatternDef| PatternDef { name: String::new(), art: vec![], ..p.clone() };
|
||||
let same = |n: &str| d.pattern(n).is_some_and(|x| bare(x) == bare(def) && same_art(&out.relink, &x.art, &def.art));
|
||||
let same = |n: &str| !fresh && d.pattern(n).is_some_and(|x| bare(x) == bare(def) && same_art(&out.relink, &x.art, &def.art));
|
||||
let p = existing(&def.name, d.patterns.iter().map(|p| p.name.as_str()), same_doc, same).unwrap_or_else(|| {
|
||||
// Its swatch comes along under the same name.
|
||||
let name = unique_name(&def.name, |n| d.pattern(n).is_some() || d.swatch_name_taken(n));
|
||||
|
||||
@@ -943,6 +943,42 @@ fn a_style_and_the_pattern_that_uses_it_load_together() {
|
||||
assert_eq!((s.doc().unwrap().doc.clone(), s.doc().unwrap().history.undo.len()), (doc, undo));
|
||||
}
|
||||
|
||||
/// A style and the pattern it paints with, whose type is set in that style, are both added when
|
||||
/// the document has a style of that name and attributes but another pattern of that name: the
|
||||
/// copy of the style paints with the copy of the pattern, and a second load adds nothing.
|
||||
#[test]
|
||||
fn a_style_in_a_loop_is_not_matched_to_a_style_that_paints_with_another_pattern() {
|
||||
let (mut src, id) = session_with_text("Tile");
|
||||
src.execute("charStyle.new", &json!({"name": "Accent", "attrs": {"kerning": 40}})).unwrap();
|
||||
src.execute("charStyle.apply", &json!({"name": "Accent", "id": id})).unwrap();
|
||||
src.execute("object.pattern.make", &json!({"name": "Dots", "edit": false, "ids": [id]})).unwrap();
|
||||
let other = src.execute("text.create", &json!({"x": 10, "y": 90, "text": "B"})).unwrap()["id"].as_u64().unwrap();
|
||||
src.execute("charStyle.apply", &json!({"name": "Accent", "id": other})).unwrap();
|
||||
src.execute("paint.setFill", &json!({"swatch": "Dots", "ids": [other]})).unwrap();
|
||||
src.execute("charStyle.new", &json!({"name": "Painted", "id": other})).unwrap();
|
||||
let mut source = (*src.doc().unwrap().doc).clone();
|
||||
let painted = source.char_styles.iter().find(|s| s.name == "Painted").unwrap().attrs.clone();
|
||||
source.char_styles.retain(|s| s.name != "Painted");
|
||||
source.char_styles.iter_mut().find(|s| s.name == "Accent").unwrap().attrs = painted.clone();
|
||||
let data = vectorcraft_format::base64_encode(&vectorcraft_format::save(&source, false));
|
||||
let (mut s, _) = session_with_text("X");
|
||||
let square = s.execute("shape.rectangle", &json!({"x": 200, "y": 200, "width": 30, "height": 30})).unwrap()["id"].clone();
|
||||
s.execute("object.pattern.make", &json!({"name": "Dots", "edit": false, "ids": [square]})).unwrap();
|
||||
let doc = std::sync::Arc::make_mut(&mut s.doc_mut().unwrap().doc);
|
||||
doc.char_styles.push(TextStyleDef { name: "Accent".into(), attrs: painted });
|
||||
let load = json!({"dataBase64": data, "name": "Other.vectorcraft"});
|
||||
let r = s.execute("charStyle.load", &load).unwrap();
|
||||
assert_eq!(r["added"], json!(["Accent 2"]), "{r}");
|
||||
let d = s.doc().unwrap().doc.clone();
|
||||
let fill = |name: &str| super::style_attrs(&d, false, name).unwrap()["fill"].to_string();
|
||||
assert!(fill("Accent").contains("\"Dots\"") && fill("Accent 2").contains("\"Dots 2\""), "{} {}", fill("Accent"), fill("Accent 2"));
|
||||
let NodeKind::Text(text) = &d.pattern("Dots 2").unwrap().art[0].kind else { panic!("pattern art should be type") };
|
||||
assert_eq!(text.runs[0].style.style_name.as_deref(), Some("Accent 2"));
|
||||
let r = s.execute("charStyle.load", &load).unwrap();
|
||||
assert_eq!(r["added"], json!([]), "{r}");
|
||||
assert_eq!(s.doc().unwrap().doc, d);
|
||||
}
|
||||
|
||||
/// A style name from a saved document stays the name used by type in imported pattern art.
|
||||
#[test]
|
||||
fn loaded_style_keeps_the_name_used_by_pattern_type() {
|
||||
|
||||
Reference in New Issue
Block a user