//! Layer tree reconstruction. use photocraft_psd::testgen; use photocraft_psd::*; fn rec(name: &str, kind: Option) -> LayerRecord { let mut r = LayerRecord { name: name.as_bytes().to_vec(), ..Default::default() }; if let Some(k) = kind { r.blocks.push(TaggedBlock::section_divider(k, Some(BlendMode::PassThrough), None)); } r } fn file_with(records: Vec) -> PsdFile { let mut f = testgen::merged_only(Version::Psd, ColorMode::Rgb, 8, Compression::Raw, 1, 1); f.layer_info = Some(LayerInfo { layers: records, ..Default::default() }); f } fn names(f: &PsdFile, nodes: &[LayerNode]) -> String { nodes .iter() .map(|n| match n { LayerNode::Layer { index } => f.layers()[*index].name(), LayerNode::Group { index, children, .. } => { format!("{}[{}]", f.layers()[*index].name(), names(f, children)) } }) .collect::>() .join(",") } const END: Option = Some(SectionType::BoundingDivider); const OPEN: Option = Some(SectionType::OpenFolder); const CLOSED: Option = Some(SectionType::ClosedFolder); #[test] fn flat_layers() { let f = file_with(vec![rec("a", None), rec("b", None), rec("c", None)]); let t = f.layer_tree(); assert_eq!(names(&f, &t), "a,b,c"); assert!(t.iter().all(|n| !n.is_group())); } #[test] fn empty_file_has_empty_tree() { let f = testgen::merged_only(Version::Psd, ColorMode::Rgb, 8, Compression::Raw, 1, 1); assert!(f.layer_tree().is_empty()); } #[test] fn single_group() { let f = file_with(vec![rec("bg", None), rec("", END), rec("x", None), rec("y", None), rec("G", OPEN)]); let t = f.layer_tree(); assert_eq!(names(&f, &t), "bg,G[x,y]"); match &t[1] { LayerNode::Group { index, divider, children } => { assert_eq!(*index, 4); assert_eq!(*divider, Some(1)); assert_eq!(children.len(), 2); } _ => panic!(), } } #[test] fn closed_group() { let f = file_with(vec![rec("", END), rec("x", None), rec("G", CLOSED)]); assert_eq!(names(&f, &f.layer_tree()), "G[x]"); } #[test] fn nested_groups() { let f = file_with(vec![ rec("", END), rec("a", None), rec("", END), rec("b", None), rec("Inner", CLOSED), rec("c", None), rec("Outer", OPEN), rec("top", None), ]); let t = f.layer_tree(); assert_eq!(names(&f, &t), "Outer[a,Inner[b],c],top"); assert_eq!(t[0].count(), 5); } #[test] fn empty_group() { let f = file_with(vec![rec("", END), rec("G", OPEN)]); let t = f.layer_tree(); assert_eq!(names(&f, &t), "G[]"); assert!(t[0].children().is_empty()); } #[test] fn sibling_groups() { let f = file_with(vec![rec("", END), rec("a", None), rec("G1", OPEN), rec("", END), rec("b", None), rec("G2", OPEN)]); assert_eq!(names(&f, &f.layer_tree()), "G1[a],G2[b]"); } #[test] fn deeply_nested() { let mut v = Vec::new(); for _ in 0..50 { v.push(rec("", END)); } v.push(rec("leaf", None)); for i in 0..50 { v.push(rec(&format!("g{i}"), OPEN)); } let f = file_with(v); let t = f.layer_tree(); assert_eq!(t.len(), 1); assert_eq!(t[0].count(), 51); let mut n = &t[0]; let mut depth = 1; while let Some(c) = n.children().first() { n = c; depth += 1; } assert_eq!(depth, 51); } /// #2940: the capped tree keeps a group at the cap but not its children, so its depth is bounded /// whatever the file nests. #[test] fn layer_tree_max_depth_drops_deeper_children() { let mut v = vec![rec("", END), rec("", END), rec("", END), rec("x", None)]; v.extend([rec("g2", OPEN), rec("g1", OPEN), rec("g0", OPEN), rec("top", None)]); let f = file_with(v); assert_eq!(names(&f, &f.layer_tree()), "g0[g1[g2[x]]],top"); assert_eq!(names(&f, &f.layer_tree_max_depth(usize::MAX)), "g0[g1[g2[x]]],top"); assert_eq!(names(&f, &f.layer_tree_max_depth(2)), "g0[g1[g2[]]],top"); assert_eq!(names(&f, &f.layer_tree_max_depth(1)), "g0[g1[]],top"); assert_eq!(names(&f, &f.layer_tree_max_depth(0)), "g0[],top"); } fn depth(nodes: &[LayerNode]) -> usize { // Iterative so a deep tree cannot overflow the test's stack. let mut max = 0; let mut todo: Vec<(&LayerNode, usize)> = nodes.iter().map(|n| (n, 1)).collect(); while let Some((n, d)) = todo.pop() { max = max.max(d); todo.extend(n.children().iter().map(|c| (c, d + 1))); } max } /// #5758: layers under a divider that is never closed are hoisted to the enclosing level, as /// `layer_tree` does, also when the cap is shallower than the unclosed divider. #[test] fn layer_tree_max_depth_hoists_unclosed_divider_children() { let f = file_with(vec![rec("a", None), rec("", END), rec("b", None), rec("c", None)]); assert_eq!(names(&f, &f.layer_tree()), "a,b,c"); assert_eq!(names(&f, &f.layer_tree_max_depth(1)), "a,b,c"); assert_eq!(names(&f, &f.layer_tree_max_depth(0)), "a,b,c"); } /// #5758: a closed group inside unclosed dividers sits at the depth it ends up at after hoisting, /// so the cap keeps its node and drops only its deeper children. #[test] fn layer_tree_max_depth_keeps_closed_group_inside_unclosed_dividers() { let mut v = vec![rec("", END), rec("", END)]; v.extend([rec("", END), rec("", END), rec("x", None), rec("g1", OPEN), rec("g0", OPEN)]); let f = file_with(v); assert_eq!(names(&f, &f.layer_tree()), "g0[g1[x]]"); assert_eq!(names(&f, &f.layer_tree_max_depth(1)), "g0[g1[]]"); assert_eq!(names(&f, &f.layer_tree_max_depth(0)), "g0[]"); } /// #5758: thousands of unclosed dividers around a closed chain stay bounded by the cap and keep /// every layer the full tree has. #[test] fn layer_tree_max_depth_bounds_deep_unclosed_chain() { let mut v = Vec::new(); for i in 0..16_000 { v.push(rec("", END)); v.push(rec(&format!("l{i}"), None)); } v.extend([rec("", END), rec("", END), rec("", END), rec("x", None)]); v.extend([rec("g2", OPEN), rec("g1", OPEN), rec("g0", OPEN)]); let f = file_with(v); let full = f.layer_tree(); assert_eq!(full.len(), 16_001); assert_eq!(depth(&full), 4); let capped = f.layer_tree_max_depth(1); assert!(depth(&capped) <= 2); assert_eq!(capped.len(), 16_001); assert_eq!(names(&f, &capped[16_000..]), "g0[g1[]]"); assert_eq!(names(&f, &capped[..2]), "l0,l1"); } #[test] fn folder_without_divider_is_empty_group() { let f = file_with(vec![rec("a", None), rec("G", OPEN), rec("b", None)]); assert_eq!(names(&f, &f.layer_tree()), "a,G[],b"); } #[test] fn unclosed_divider_hoists_children() { let f = file_with(vec![rec("a", None), rec("", END), rec("b", None), rec("c", None)]); assert_eq!(names(&f, &f.layer_tree()), "a,b,c"); } #[test] fn lsdk_is_honored() { let mut end = rec("", None); let mut b = TaggedBlock::section_divider(SectionType::BoundingDivider, None, None); b.key = *b"lsdk"; end.blocks.push(b); let f = file_with(vec![end, rec("x", None), rec("G", OPEN)]); assert_eq!(names(&f, &f.layer_tree()), "G[x]"); } #[test] fn other_section_type_is_layer() { let f = file_with(vec![rec("a", Some(SectionType::Other)), rec("b", Some(SectionType::Unknown(9)))]); assert_eq!(names(&f, &f.layer_tree()), "a,b"); } #[test] fn generated_layered_tree() { let f = testgen::layered(Version::Psd, ColorMode::Rgb, 8, Compression::Rle); let t = f.layer_tree(); let outer = t.iter().find(|n| n.is_group()).expect("group"); assert_eq!(f.layers()[outer.index()].name(), "Outer"); assert_eq!(outer.children().len(), 2); let inner = &outer.children()[1]; assert!(inner.is_group()); assert_eq!(f.layers()[inner.index()].name(), "Inner"); assert_eq!(f.layers()[inner.index()].section_type(), SectionType::ClosedFolder); assert_eq!(f.layers()[inner.children()[0].index()].name(), "Grüße"); } #[test] fn builder_groups_tree() { let px = |n: usize| PixelData::Rgba8(vec![255; n * 4]); let mut b = PsdBuilder::new(2, 2); b.push_layer(LayerSpec::new("bg", 0, 0, 2, 2, px(4))); b.begin_group(GroupSpec::new("A")); b.push_layer(LayerSpec::new("a1", 0, 0, 1, 1, px(1))); b.begin_group(GroupSpec { open: false, ..GroupSpec::new("B") }); b.push_layer(LayerSpec::new("b1", 0, 0, 1, 1, px(1))); b.end_group().unwrap(); b.end_group().unwrap(); let f = PsdFile::from_bytes(&b.to_bytes().unwrap()).unwrap(); assert_eq!(names(&f, &f.layer_tree()), "bg,A[a1,B[b1]]"); }