Symbols: the edited symbol takes the text wrap its area type gets after a wrap object moves

Symbol editing copies the editing layer's art into the symbol before Text Wrap, list numbering and threaded re-flow update that layer, so moving a wrap object in the edit left the symbol, its other instances and the saved file with the old wrap. The edit now syncs the symbol again after those passes, and when that changes the symbol, inline graphics of it are refreshed and the threads holding them re-flow.
This commit is contained in:
dotCooCoo
2026-11-02 12:04:20 +07:00
committed by bflatastic
parent 294d13d857
commit 2b7b12df33
2 changed files with 68 additions and 1 deletions
+26 -1
View File
@@ -39,10 +39,13 @@ impl Session {
}
// Symbol editing: the symbol follows its art on the editing layer, before
// inline graphics follow their symbols.
let symbol_synced =
let mut symbol_synced =
if st.doc.symbol_edit.is_some() { cmd::symboledit::sync(&before, Arc::make_mut(&mut st.doc)) } else { Ok(()) };
// Inline graphics in text follow their symbols.
cmd::inline::refresh(&before, Arc::make_mut(&mut st.doc));
// The document the passes below start from, for the symbol to follow what they
// change on its editing layer.
let synced = st.doc.symbol_edit.is_some().then(|| st.doc.clone());
// Text Wrap: area type follows its wrap objects; then threads re-flow.
cmd::textwrap::refresh(&before, Arc::make_mut(&mut st.doc));
// Type in no thread numbers its list paragraphs from 1 (before the mask copies
@@ -56,6 +59,28 @@ impl Session {
if !st.doc.text_threads.is_empty() {
cmd::threads::reflow(&before, Arc::make_mut(&mut st.doc));
}
// Symbol editing: the symbol also takes the text wrap, list numbering and
// re-flow those passes gave its editing layer; when that changes the symbol, its
// inline graphics follow it and the threads they are in re-flow.
let editing = st.doc.symbol_edit.as_ref().map(|e| e.layer);
if let Some(synced) = &synced
&& symbol_synced.is_ok()
&& editing.is_some()
&& before.symbol_edit.as_ref().map(|e| e.layer) == editing
{
let art =
|d: &Document| d.symbol_edit.as_ref().and_then(|e| d.symbols.iter().find(|x| x.name == e.symbol)).map(|x| x.art.clone());
let old = art(&st.doc);
symbol_synced = cmd::symboledit::sync(synced, Arc::make_mut(&mut st.doc));
let new = art(&st.doc);
if symbol_synced.is_ok() && !matches!((&old, &new), (Some(a), Some(b)) if Arc::ptr_eq(a, b)) {
let resynced = st.doc.clone();
cmd::inline::refresh(synced, Arc::make_mut(&mut st.doc));
if !st.doc.text_threads.is_empty() {
cmd::threads::reflow(&resynced, Arc::make_mut(&mut st.doc));
}
}
}
// Asset Export: assets let go of deleted art.
if !st.doc.assets.is_empty() {
Arc::make_mut(&mut st.doc).prune_assets();
+42
View File
@@ -676,3 +676,45 @@ fn undoing_entry_with_a_group_isolated_inside_the_art_goes_back_to_before() {
assert_eq!(st.isolation, None);
assert_eq!(st.active_layer, before);
}
/// The text wrap of the area type in `n`.
fn wrap_in(n: &Node) -> Vec<vectorcraft_doc::WrapShape> {
let mut wrap = None;
n.walk(&mut |n| {
if let NodeKind::Text(t) = &n.kind {
wrap = Some(t.wrap.clone());
}
});
wrap.expect("area type")
}
#[test]
fn moving_a_wrap_object_in_the_edit_gives_the_symbol_the_new_text_wrap() {
let mut s = Session::new();
run(&mut s, "file.new", json!({"width": 600, "height": 600}));
let text = "word ".repeat(200);
let t = id(&run(&mut s, "text.create", json!({"x": 0, "y": 0, "text": text, "size": 12, "area": {"width": 400, "height": 400}})));
let r = id(&run(&mut s, "shape.rectangle", json!({"x": 0, "y": 50, "width": 200, "height": 100})));
run(&mut s, "select.set", json!({"ids": [r.0]}));
run(&mut s, "object.textWrap.make", json!({}));
run(&mut s, "select.set", json!({"ids": [t.0, r.0]}));
let instance = id(&run(&mut s, "symbol.new", json!({"name": "Wrapped"})));
let symbol_wrap = |s: &Session| wrap_in(&s.doc().unwrap().doc.symbols.iter().find(|x| x.name == "Wrapped").expect("the symbol").art);
let entered = symbol_wrap(&s);
assert!(!entered.is_empty());
run(&mut s, "symbol.edit", json!({"id": instance.0}));
let (layer, rect) = {
let st = s.doc().unwrap();
let layer = st.doc.symbol_edit.as_ref().unwrap().layer;
let rect = layer_art(&s).into_iter().find(|i| st.doc.node(*i).is_some_and(|n| n.extras().wrap.is_some())).expect("the wrap object");
(layer, rect)
};
run(&mut s, "select.set", json!({"ids": [rect.0]}));
run(&mut s, "object.move", json!({"dx": 150, "dy": 0}));
let on_layer = wrap_in(s.doc().unwrap().doc.node(layer).unwrap());
assert_ne!(on_layer, entered, "the editing layer's text wraps around the moved object");
assert_eq!(symbol_wrap(&s), on_layer);
// Leaving keeps it.
run(&mut s, "symbol.exitEditing", json!({}));
assert_eq!(symbol_wrap(&s), on_layer);
}