fix(io): a layer comp entry without enab inherits the previous visibility, as Photoshop reads cmls (#8093)

This commit is contained in:
Korg1ch
2026-10-21 05:05:55 +03:00
parent 872748fb5b
commit 385d2a7a01
2 changed files with 33 additions and 21 deletions
+24 -20
View File
@@ -215,33 +215,37 @@ pub(crate) fn decode_comps(resource: Option<&[u8]>, doc: &Document) -> (Vec<Laye
for (_, _, l) in doc.walk() {
let Some(settings) = layer_cmls(l).and_then(|c| parse_cmls(&c)) else { continue };
let here = layer_position(l);
// Photoshop writes `enab` only where the layer's visibility changes from one comp to the
// next: a comp's entry without it has the last `enab` before it, or visible when none
// came before (#8093). Only comps that capture visibility take that inherited value; the
// Last Document State entry (comp 0) and the others keep what the entry says.
let mut inherited = true;
for s in settings {
if s.visible.is_none() && s.offset.is_none() {
continue;
let resolved = s.visible.unwrap_or(inherited);
if s.comps.iter().any(|id| *id != 0) {
inherited = resolved;
}
let state = CompLayerState {
layer: l.id,
visible: s.visible,
position: match (here, s.offset) {
(Some(h), Some(o)) => {
let p = offset_position(h, o);
if p.is_none() {
warnings.push(format!(
"layer \"{}\": layer comp position offset ({}, {}) is out of range; that position was not imported",
l.name, o.0, o.1
));
}
p
let position = match (here, s.offset) {
(Some(h), Some(o)) => {
let p = offset_position(h, o);
if p.is_none() {
warnings.push(format!(
"layer \"{}\": layer comp position offset ({}, {}) is out of range; that position was not imported",
l.name, o.0, o.1
));
}
_ => None,
},
appearance: None,
p
}
_ => None,
};
for id in &s.comps {
let target = if *id == 0 { Some(&mut last) } else { comps.iter_mut().find(|c| c.id == *id) };
if let Some(c) = target {
c.states.push(state.clone());
let Some(c) = target else { continue };
let visible = if *id != 0 && c.apply_visibility { Some(resolved) } else { s.visible };
if visible.is_none() && s.offset.is_none() {
continue;
}
c.states.push(CompLayerState { layer: l.id, visible, position, appearance: None });
}
}
}
+9 -1
View File
@@ -182,7 +182,15 @@ fn synthetic_psd_roundtrip_all_depths() {
v.sort_by(|x, y| x.0.cmp(&y.0));
v
};
assert_eq!(named(&d, a), named(&back, b), "{depth:?} comp {}", a.name);
let mut want = named(&d, a);
if a.name == "One" {
// "One" records nothing for Board 1. A PSD can't say "leaves it alone": its entry
// has no `enab`, which Photoshop reads as the previous comp's visibility (Both:
// shown), and so does the import now (#8093).
want.push(("Board 1".to_string(), Some(true), None));
want.sort_by(|x, y| x.0.cmp(&y.0));
}
assert_eq!(want, named(&back, b), "{depth:?} comp {}", a.name);
}
// Re-exporting the imported document is byte-stable for comps and artboards.
let f1 = PsdFile::from_bytes(&to_psd(&back)).unwrap();