mirror of
https://github.com/storytold/photocraft.git
synced 2026-10-14 19:26:51 -04:00
This commit is contained in:
1 parent
8a31f2431b
commit
4f33c81524
3 files changed
+51
-3
No files matched your search
@@ -229,8 +229,14 @@ pub fn notes_from_blocks(doc: &Document) -> Vec<Note> {
|
||||
doc.metadata.psd_global_blocks.iter().find(|(_, k, _)| *k == ANNO).and_then(|(_, _, d)| parse_anno(d)).unwrap_or_default()
|
||||
}
|
||||
|
||||
/// Global blocks to write: the raw `Anno` block while it still describes `doc.notes`, else a
|
||||
/// regenerated one in its place (appended when new, dropped when the last note is deleted).
|
||||
/// Whether the document holds an `Anno` block that doesn't parse (its notes can't be shown).
|
||||
pub fn anno_unreadable(doc: &Document) -> bool {
|
||||
doc.metadata.psd_global_blocks.iter().find(|(_, k, _)| *k == ANNO).is_some_and(|(_, _, d)| parse_entries(d).is_none())
|
||||
}
|
||||
|
||||
/// Global blocks to write: the raw `Anno` block while it still describes `doc.notes` (or doesn't
|
||||
/// parse and no note was added), else a regenerated one in its place (appended when new, dropped
|
||||
/// when the last note is deleted).
|
||||
pub fn export_blocks(doc: &Document, mut blocks: Vec<photocraft_doc::PsdGlobalBlock>) -> Vec<photocraft_doc::PsdGlobalBlock> {
|
||||
let at = blocks.iter().position(|(_, k, _)| *k == ANNO);
|
||||
let raw = at.map(|i| blocks[i].2.clone());
|
||||
@@ -246,7 +252,9 @@ pub fn export_blocks(doc: &Document, mut blocks: Vec<photocraft_doc::PsdGlobalBl
|
||||
Note { color: y.color, ..x.clone() } == *y && cx.iter().zip(cy).all(|(p, q)| (p - q).abs() < 1.0 / 512.0)
|
||||
})
|
||||
};
|
||||
if parsed.as_deref().zip(want.as_deref()).is_some_and(|(p, w)| same(p, w)) || (raw.is_none() && doc.notes.is_empty()) {
|
||||
// With no notes, a block we can't read is kept as is: it isn't "the last note deleted", and
|
||||
// dropping it would delete the annotations it holds (issue #944).
|
||||
if parsed.as_deref().zip(want.as_deref()).is_some_and(|(p, w)| same(p, w)) || (entries.is_none() && doc.notes.is_empty()) {
|
||||
return blocks;
|
||||
}
|
||||
let extra: Vec<Vec<u8>> = entries.unwrap_or_default().into_iter().filter_map(|e| if let Entry::Raw(r) = e { Some(r) } else { None }).collect();
|
||||
@@ -395,6 +403,25 @@ mod tests {
|
||||
assert!(parse_anno(&[0, 2, 0, 1, 0, 0, 0, 9]).is_none());
|
||||
}
|
||||
|
||||
/// Issue #944: an `Anno` block that doesn't parse was taken for "no notes" and dropped on save.
|
||||
#[test]
|
||||
fn export_blocks_keeps_an_unreadable_block() {
|
||||
let mut d = Document::new("n", photocraft_doc::Size::new(10, 10), photocraft_doc::ColorMode::Rgb, photocraft_doc::SampleType::U8);
|
||||
let bad: Vec<photocraft_doc::PsdGlobalBlock> = vec![(*b"8BIM", ANNO, Arc::new(vec![0, 2, 0, 1, 0, 0, 0, 9]))];
|
||||
d.metadata.psd_global_blocks = bad.clone();
|
||||
assert!(anno_unreadable(&d) && notes_from_blocks(&d).is_empty());
|
||||
let out = export_blocks(&d, bad.clone());
|
||||
assert_eq!(out.len(), 1);
|
||||
assert!(Arc::ptr_eq(&out[0].2, &bad[0].2));
|
||||
// A note added by the user still writes a fresh block in its place.
|
||||
d.notes.push(Note { text: "new".into(), ..Default::default() });
|
||||
let out = export_blocks(&d, bad);
|
||||
assert_eq!(parse_anno(&out[0].2).unwrap()[0].text, "new");
|
||||
// A readable block is not reported.
|
||||
d.metadata.psd_global_blocks = out;
|
||||
assert!(!anno_unreadable(&d));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn scale_resource_roundtrip_and_keep_rules() {
|
||||
let s = MeasurementScale { pixel_length: 150.0, logical_length: 2.0, units: "mm".into() };
|
||||
|
||||
@@ -588,6 +588,9 @@ pub fn psd_to_document_with(file: &PsdFile, ctl: &photocraft_raster::Interrupt)
|
||||
doc.patterns = crate::pattern_map::from_global_blocks(&doc);
|
||||
// Notes (`Anno`) and the measurement scale (resource 1074); raw data stays for verbatim export.
|
||||
doc.notes = crate::annotations_map::notes_from_blocks(&doc);
|
||||
if crate::annotations_map::anno_unreadable(&doc) {
|
||||
warnings.push("the notes (Anno block) could not be read; they are kept unchanged on PSD save unless a note is added".into());
|
||||
}
|
||||
if let Some(scale) = crate::annotations_map::raw_scale(&doc) {
|
||||
doc.measurement.scale = scale;
|
||||
}
|
||||
|
||||
@@ -147,3 +147,21 @@ fn a_note_icon_near_i32_max_saves_with_representable_geometry() {
|
||||
let back = import("x.psd", &to_psd(&doc)).unwrap().document;
|
||||
assert_eq!(back.notes[0].position, [42.0, 28.0]);
|
||||
}
|
||||
|
||||
/// Issue #944: an `Anno` block the reader rejects opened as "no notes" without a word, and the
|
||||
/// next save deleted it. It now opens with a warning and is written back byte for byte.
|
||||
#[test]
|
||||
fn an_unreadable_anno_block_warns_and_survives_a_save() {
|
||||
// The block declares two annotations but holds one.
|
||||
let mut broken = anno_with_icon(28, 42);
|
||||
broken[4..8].copy_from_slice(&2u32.to_be_bytes());
|
||||
let imp = import("notes.psd", &psd_with_anno(broken.clone())).expect("import");
|
||||
assert!(imp.document.notes.is_empty());
|
||||
assert!(imp.warnings.iter().any(|w| w.contains("Anno")), "{:?}", imp.warnings);
|
||||
let f = PsdFile::from_bytes(&to_psd(&imp.document)).unwrap();
|
||||
assert_eq!(f.global_block(b"Anno").expect("Anno block kept").data, broken);
|
||||
// Control: the intact block reads with no warning.
|
||||
let ok = import("notes.psd", &psd_with_anno(anno_with_icon(28, 42))).expect("import");
|
||||
assert_eq!(ok.document.notes.len(), 1);
|
||||
assert!(!ok.warnings.iter().any(|w| w.contains("Anno")));
|
||||
}
|
||||
Reference in new issue
Block a user