From ea3fe168961006dc530131354983b70b4b0c201f Mon Sep 17 00:00:00 2001 From: dotCooCoo Date: Sat, 7 Nov 2026 08:47:51 -0800 Subject: [PATCH] Text styles: loading uses a loop the document has under the names found for it When the document has a loop of styles and patterns under one set of names, its styles were still matched one by one, so a style could be matched to another copy with the same attributes and the pattern then compared with the wrong style name. Each definition of such a loop is now the document's under the name found for it. --- crates/engine/src/cmd/clipboard/resources.rs | 16 +++++++++++++++- crates/engine/src/cmd/textstyles/tests.rs | 17 ++++++++++++++++- 2 files changed, 31 insertions(+), 2 deletions(-) diff --git a/crates/engine/src/cmd/clipboard/resources.rs b/crates/engine/src/cmd/clipboard/resources.rs index 3a87fec20..91e9bbb3e 100644 --- a/crates/engine/src/cmd/clipboard/resources.rs +++ b/crates/engine/src/cmd/clipboard/resources.rs @@ -1044,24 +1044,38 @@ impl Clipboard { // 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; + // The names in the document of the loop being placed, when it has them all at once. + let mut matched: BTreeMap<(Res, String), String> = BTreeMap::new(); for (def, looped, starts) in steps { fresh &= looped; + if !looped { + matched.clear(); + } // 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 { + matched.clear(); match names_of_loop(d, group, &out.relink) { Some(names) => { 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); + out.relink.renames.entry(res).or_default().insert(own.to_string(), name.clone()); } + matched.insert((res, own.to_string()), name); } fresh = false; } None => fresh = true, } } + // A definition of such a loop is the document's under the name found for it. + let (res, own) = def.key(); + if let Some(name) = matched.get(&(res, own.to_string())) { + let p = if name == own { Placed::Existing } else { Placed::Reused(name.clone()) }; + out.placed(res, own, &p); + continue; + } match def { Def::Style(r, def) => { let mut s = def.clone(); diff --git a/crates/engine/src/cmd/textstyles/tests.rs b/crates/engine/src/cmd/textstyles/tests.rs index 180e1c480..73c938dc1 100644 --- a/crates/engine/src/cmd/textstyles/tests.rs +++ b/crates/engine/src/cmd/textstyles/tests.rs @@ -945,7 +945,8 @@ fn a_style_and_the_pattern_that_uses_it_load_together() { /// 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. +/// copy of the style paints with the copy of the pattern, and a second load adds nothing. A loop the +/// document has is used under the names it has together. #[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"); @@ -977,6 +978,20 @@ fn a_style_in_a_loop_is_not_matched_to_a_style_that_paints_with_another_pattern( let r = s.execute("charStyle.load", &load).unwrap(); assert_eq!(r["added"], json!([]), "{r}"); assert_eq!(s.doc().unwrap().doc, d); + // With the loop's style renamed "Accent 3" and an "Accent 2" listed before it that also paints + // with "Dots 2", the loop is still found as "Accent 3" and "Dots 2", the names it has together. + let doc = std::sync::Arc::make_mut(&mut s.doc_mut().unwrap().doc); + let at = doc.char_styles.iter().position(|c| c.name == "Accent 2").unwrap(); + doc.char_styles[at].name = "Accent 3".into(); + let copy = TextStyleDef { name: "Accent 2".into(), attrs: doc.char_styles[at].attrs.clone() }; + doc.char_styles.insert(at, copy); + let tile = doc.patterns.iter_mut().find(|p| p.name == "Dots 2").unwrap(); + let NodeKind::Text(text) = &mut std::sync::Arc::make_mut(&mut tile.art[0]).kind else { panic!("pattern art should be type") }; + text.runs[0].style.style_name = Some("Accent 3".into()); + let d = s.doc().unwrap().doc.clone(); + let r = s.execute("charStyle.load", &load).unwrap(); + assert_eq!(r, json!({"added": [], "existing": ["Accent 3"], "skipped": []})); + assert_eq!(s.doc().unwrap().doc, d); } /// A brush in a loaded style's pattern whose art sets type in a style renamed on the way in