mirror of
https://github.com/storytold/designcraft.git
synced 2026-10-26 19:26:46 -06:00
M6: retain layer order when native parents reopen closed subtrees
This commit is contained in:
committed by
Andee Collard
parent
3d362d0bdf
commit
b7e36bf616
@@ -12,6 +12,8 @@
|
||||
|
||||
DesignCraft aims at full Adobe InDesign parity — and to be better: faster, open (documented JSON format + IDML), scriptable by agents (MCP), and available on the web.
|
||||
|
||||
- **2026-10-23** · IDML graphic-layer export rejects parents in already closed subtrees,
|
||||
preserving native-edited layer order through round trips. Synthetic regression added.
|
||||
- **2026-10-23** · PDF layer choices now validate before editing all targets; rejected rewrites
|
||||
leave document/undo state intact. Null trailer entries remain unencrypted, and unsupported
|
||||
all-content crops retain the whole page rather than silently clipping hidden layers.
|
||||
|
||||
@@ -2077,14 +2077,20 @@ impl<'a> Ex<'a> {
|
||||
option.set(key, value);
|
||||
}
|
||||
}
|
||||
// Parent indices must precede their children. Ignore invalid parents and bound DOM depth
|
||||
// even when a native file has been edited independently of the IDML importer.
|
||||
let mut depths = Vec::with_capacity(options.layers.len());
|
||||
// Only an ancestor whose subtree is still open can parent the next layer.
|
||||
// Reopening a closed subtree would reorder native-edited lists on roundtrip.
|
||||
let mut open = Vec::new();
|
||||
let mut parents = Vec::with_capacity(options.layers.len());
|
||||
for i in 0..options.layers.len() {
|
||||
let parent = options.parents.get(i).copied().flatten().filter(|p| *p < i && depths.get(*p).is_some_and(|d| *d < 240));
|
||||
depths.push(parent.and_then(|p| depths.get(p)).copied().unwrap_or(0) + 1);
|
||||
parents.push(parent);
|
||||
let parent = options.parents.get(i).copied().flatten();
|
||||
if let Some(at) = parent.and_then(|p| open.iter().position(|id| *id == p)).filter(|at| *at < 239) {
|
||||
open.truncate(at + 1);
|
||||
parents.push(parent);
|
||||
} else {
|
||||
open.clear();
|
||||
parents.push(None);
|
||||
}
|
||||
open.push(i);
|
||||
}
|
||||
let mut children: Vec<Vec<El>> = vec![Vec::new(); options.layers.len()];
|
||||
let mut roots = Vec::new();
|
||||
|
||||
@@ -155,6 +155,23 @@ fn malformed_native_graphic_layer_parents_cannot_cycle_or_overflow_xml_depth() {
|
||||
assert_eq!(reopened.spreads[0].items[0].graphic().unwrap().layer_options.as_ref().unwrap().layers.len(), 300);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn closed_graphic_layer_subtrees_cannot_reorder_native_layers() {
|
||||
let mut d = import_idml(&package(&[frame(1, 0.0, 0.0, &image(1, PHOTO, None))])).unwrap();
|
||||
let Content::Graphic(g) = &mut std::sync::Arc::make_mut(&mut std::sync::Arc::make_mut(&mut d.spreads[0]).items[0]).content else {
|
||||
panic!("graphic")
|
||||
};
|
||||
g.layer_options = Some(designcraft_doc::GraphicLayerOptions {
|
||||
layers: ["A", "B", "C", "D"].into_iter().map(|name| std::collections::BTreeMap::from([("Name".into(), name.into())])).collect(),
|
||||
parents: vec![None, None, Some(0), Some(2)],
|
||||
..Default::default()
|
||||
});
|
||||
let reopened = import_idml(&export_idml(&d)).unwrap();
|
||||
let options = reopened.spreads[0].items[0].graphic().unwrap().layer_options.as_ref().unwrap();
|
||||
assert_eq!(options.layers.iter().map(|a| a["Name"].as_str()).collect::<Vec<_>>(), ["A", "B", "C", "D"]);
|
||||
assert_eq!(options.parents, [None, None, None, Some(2)]);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn graphic_layer_attributes_from_native_data_cannot_inject_xml_or_self_ids() {
|
||||
let mut d = import_idml(&package(&[frame(1, 0.0, 0.0, &image(1, PHOTO, None))])).unwrap();
|
||||
|
||||
@@ -8,7 +8,7 @@ visibility, link-update policy and view/export/print flags survive import,
|
||||
native save/reopen and IDML export. XML Self IDs are regenerated. Native-edited
|
||||
attribute maps cannot inject XML syntax, declare namespaces or override those Self IDs.
|
||||
Parent indices are optional in native files; missing entries retain the older
|
||||
flat representation. Invalid/cyclic parent indices and over-budget nesting
|
||||
flat representation. Invalid/cyclic parent indices, parents in closed subtrees and over-budget nesting
|
||||
are emitted as top-level layers, retaining their scalar metadata without
|
||||
unbounded XML recursion.
|
||||
|
||||
|
||||
Reference in New Issue
Block a user