From 8d39765e936f388bc7fa8675ca4fe598acab5088 Mon Sep 17 00:00:00 2001 From: dotCooCoo Date: Thu, 12 Nov 2026 09:15:31 -0800 Subject: [PATCH] Layers: use the anchored copies object copying now makes Copying a frame now gives the objects anchored in its story copy ids of their own and copies of the stories of anchored text frames. Duplicate Layer renumbered them a second time, so live captions kept pointing at the originals and shared stories were copied again. It now records the ids of those copies instead. Copying a frame also renumbers objects anchored in endnotes inside table cells and footnotes, which kept their original ids before. --- crates/engine/src/cmd/layout/layers.rs | 113 ++++++++++++------------- crates/engine/src/cmd/object.rs | 19 +++-- 2 files changed, 63 insertions(+), 69 deletions(-) diff --git a/crates/engine/src/cmd/layout/layers.rs b/crates/engine/src/cmd/layout/layers.rs index b2a2ec8ac..f93a3c09c 100644 --- a/crates/engine/src/cmd/layout/layers.rs +++ b/crates/engine/src/cmd/layout/layers.rs @@ -1,7 +1,7 @@ use std::collections::{HashMap, HashSet}; use std::sync::Arc; -use designcraft_doc::{Content, Guide, Hyperlink, HyperlinkSource, Item, ItemId, SpreadRef, Story, StoryId}; +use designcraft_doc::{Guide, Hyperlink, HyperlinkSource, Item, ItemId, SpreadRef, Story, StoryId}; use super::{LAYER_COLORS, Layer, LayerId, Result, Session, Value, bad, json, ok}; @@ -91,15 +91,12 @@ pub(super) fn duplicate_layer(s: &mut Session, p: &Value) -> Result { copies.push((r, copy)); } } - // A story copy holds the same anchored objects as the original: they get ids of their own, and the stories of - // anchored text frames get copies in turn. The story copies are taken in story order. - let mut pending: Vec = stories.values().copied().collect(); - pending.sort_unstable(); - pending.reverse(); - while let Some(new) = pending.pop() { - let Some(mut st) = d.story(new).cloned() else { continue }; - renumber_anchored(d, &mut st, new_id, &mut stories, &mut ids, &mut pending); - d.stories.insert(new, Arc::new(st)); + // The story copies hold copies of the original's anchored objects with ids of their own (and copies of the + // stories of anchored text frames): record each copy's id against the original's. + for (old, new) in &stories { + if let (Some(orig), Some(copy)) = (src.story(*old), d.story(*new)) { + map_anchored(orig, copy, &mut ids); + } } // Every copy, anchored objects included, has its id before overrides are pointed at the copied parent items and // live captions at the copied objects. @@ -242,27 +239,21 @@ fn too_deep(it: &Item, d: &designcraft_doc::Document, seen: &mut std::collection false } -/// Give the items anchored in `st`, and in the stories of its table cells and notes, new ids from `d` (recorded in `ids`) -/// and layer `to`. An anchored text frame's story gets one copy, recorded in `stories`, that every copied frame of the -/// story shares; a new copy goes on `pending`, and its frame list is filled in afterwards. [`too_deep`] bounds the nesting. -fn renumber_anchored( - d: &mut designcraft_doc::Document, - st: &mut Story, - to: LayerId, - stories: &mut HashMap, - ids: &mut HashMap, - pending: &mut Vec, -) { - for o in &mut st.objects { - renumber_anchored_item(d, &mut Arc::make_mut(o).item, to, stories, ids, pending); +/// Run [`map_ids`] on the items anchored in story `orig` and in its copy `copy`, and in the stories of their table cells +/// and notes. [`too_deep`] bounds the nesting. +fn map_anchored(orig: &Story, copy: &Story, ids: &mut HashMap) { + for (o, c) in orig.objects.iter().zip(©.objects) { + map_ids(&o.item, &c.item, ids); } - for t in st.tables.values_mut() { - for c in &mut Arc::make_mut(t).cells { - renumber_anchored(d, &mut c.text, to, stories, ids, pending); + for (k, t) in &orig.tables { + if let Some(ct) = copy.tables.get(k) { + for (o, c) in t.cells.iter().zip(&ct.cells) { + map_anchored(&o.text, &c.text, ids); + } } } - for n in st.notes.iter_mut().chain(st.endnotes.iter_mut()) { - renumber_anchored(d, &mut Arc::make_mut(n).text, to, stories, ids, pending); + for (o, c) in orig.notes.iter().chain(&orig.endnotes).zip(copy.notes.iter().chain(©.endnotes)) { + map_anchored(&o.text, &c.text, ids); } } @@ -343,39 +334,6 @@ fn remap_xrefs(st: &mut Story, map: &HashMap) { } } -/// [`renumber_anchored`] for one anchored item and its descendants. -fn renumber_anchored_item( - d: &mut designcraft_doc::Document, - it: &mut Item, - to: LayerId, - stories: &mut HashMap, - ids: &mut HashMap, - pending: &mut Vec, -) { - let id = ItemId(d.alloc()); - ids.insert(it.id, id); - it.id = id; - it.layer = to; - if let Content::Text(tf) = &mut it.content { - tf.story = match stories.get(&tf.story) { - Some(n) => *n, - None => { - let n = StoryId(d.alloc()); - let mut copy = d.story(tf.story).cloned().unwrap_or_else(|| Story::new(n)); - copy.id = n; - copy.frames.clear(); - d.stories.insert(n, Arc::new(copy)); - stories.insert(tf.story, n); - pending.push(n); - n - } - }; - } - for c in it.children_mut().into_iter().flatten() { - renumber_anchored_item(d, Arc::make_mut(c), to, stories, ids, pending); - } -} - /// Put `it` and its descendants on layer `to`, and point overrides at the copied parent items and live captions at the /// copied objects. A copy whose parent item was not copied is detached from it, so the original override stays the only /// one of that item. @@ -908,6 +866,39 @@ mod layer_tests { assert_eq!(redone, after); } + #[test] + fn duplicate_layer_gives_objects_anchored_in_endnotes_of_cells_their_own_ids() { + use designcraft_doc::AnchoredObject; + use std::sync::Arc; + + let mut s = crate::Session::new(); + s.execute("file.new", &json!({})).unwrap(); + let base = s.doc().unwrap().doc.layers[0].id.0; + let a = s.execute("frame.create", &json!({"rect": [450, 450, 474, 490]})).unwrap()["id"].as_u64().unwrap(); + s.execute("object.caption", &json!({"ids": [a], "text": "{name}", "live": true})).unwrap(); + let f = s.execute("frame.create", &json!({"rect": [72, 72, 400, 400], "content": "text", "text": "Rows"})).unwrap()["id"].as_u64().unwrap(); + let sid = s.doc().unwrap().doc.item(designcraft_doc::ItemId(f)).unwrap().text_frame().unwrap().story; + let d = Arc::make_mut(&mut s.doc_mut().unwrap().doc); + // The caption's source moves into an endnote of a table cell and keeps its id. + let it = d.item(designcraft_doc::ItemId(a)).cloned().unwrap(); + Arc::make_mut(&mut d.spreads[0]).items.retain(|i| i.id.0 != a); + let mut table = designcraft_doc::Table::new(d.alloc(), 1, 1, 0, 0, 40.0); + let cell = &mut table.cells[0].text; + cell.insert_endnote(cell.len(), "Endnote", Default::default()).unwrap(); + Arc::make_mut(&mut cell.endnotes[0]).text.insert_object(0, AnchoredObject::new(it, Default::default())); + d.story_mut(sid).unwrap().insert_table(0, table); + d.check().unwrap(); + let copy = s.execute("layer.duplicate", &json!({"id": base})).unwrap()["id"].as_u64().unwrap(); + let d = &s.doc().unwrap().doc; + d.check().unwrap(); + let anchored: Vec<_> = d.anchored_items().into_iter().map(|(_, i)| (i.id, i.layer.0)).collect(); + assert_eq!(anchored.len(), 2); + let copied = anchored.iter().find(|i| i.1 == copy).unwrap().0; + assert_ne!(copied.0, a, "the copy has an id of its own"); + let caption = on_layer(d, copy).iter().find_map(|i| i.live_caption.clone()).unwrap(); + assert_eq!(caption.source, copied, "the caption's copy follows the anchored object's copy"); + } + #[test] fn duplicate_layer_gives_copied_cell_anchors_ids_no_other_anchor_has() { use std::sync::Arc; diff --git a/crates/engine/src/cmd/object.rs b/crates/engine/src/cmd/object.rs index 634c90a61..a6526e93b 100644 --- a/crates/engine/src/cmd/object.rs +++ b/crates/engine/src/cmd/object.rs @@ -1161,16 +1161,19 @@ fn renumber_in(dst: &mut Document, src: &Document, it: &mut Item, stories: &mut } } -/// Fresh ids for the objects anchored in story copy `st`, its table cells, footnotes and endnotes (#1522). +/// Fresh ids for the objects anchored in story copy `st`, its table cells, footnotes and endnotes, at every depth +/// (#1522). fn renumber_anchored(dst: &mut Document, src: &Document, st: &mut Story, stories: &mut HashMap) { - let mut fresh = |t: &mut Story| { - for o in &mut t.objects { - renumber_in(dst, src, &mut Arc::make_mut(o).item, stories, false); + for o in &mut st.objects { + renumber_in(dst, src, &mut Arc::make_mut(o).item, stories, false); + } + for t in st.tables.values_mut() { + for c in &mut Arc::make_mut(t).cells { + renumber_anchored(dst, src, &mut c.text, stories); } - }; - st.for_each_text_mut(&mut fresh); - for n in &mut st.endnotes { - Arc::make_mut(n).text.for_each_text_mut(&mut fresh); + } + for n in st.notes.iter_mut().chain(st.endnotes.iter_mut()) { + renumber_anchored(dst, src, &mut Arc::make_mut(n).text, stories); } }