fix: 粘贴图片的内容哈希键被占用时改用空闲键

This commit is contained in:
Nicek
2026-10-29 09:33:55 +08:00
committed by trae-solo-agent
parent 4c0eb71a4c
commit fae356fd50
2 changed files with 37 additions and 2 deletions
+13 -2
View File
@@ -391,8 +391,19 @@ pub(super) fn paint_paste(s: &mut Session, p: &Value, fills: bool) -> Result<Val
if let Some(d) = s.clipboard.paint_images.get(&copied).cloned() {
let doc = s.doc_mut()?;
// The destination may already use that key for different data (other layers keep
// that image): the pasted paint takes the copied data's content hash instead.
let key = if doc.images.get(&copied).is_some_and(|existing| existing != &d) { figcraft_format::image_hash(&d.bytes) } else { copied };
// that image): the pasted paint takes the copied data's content hash instead. That key
// can be taken too — images are usually keyed by their hash, so the copied key often
// is that hash — and then a numbered variant of it.
let base = if doc.images.get(&copied).is_some_and(|existing| existing != &d) { figcraft_format::image_hash(&d.bytes) } else { copied };
let mut key = base.clone();
let mut n = 0u32;
while doc.images.get(&key).is_some_and(|existing| existing != &d) {
n = n.saturating_add(1);
if n > 999 {
return Err(bad(cmd, "no free image key for the copied image"));
}
key = format!("{base}-{n}");
}
doc.images.entry(key.clone()).or_insert(d);
img.image = key;
}
+24
View File
@@ -143,6 +143,30 @@ fn a_copied_image_key_taken_in_another_file_gets_the_copied_data() {
assert_eq!(doc.images.get("img-key"), Some(&taken), "the destination's image stays");
}
#[test]
fn a_copied_image_whose_hash_key_is_also_taken_gets_a_free_key() {
let mut s = Session::new();
let a = rect(&mut s);
let bytes = vec![1, 2, 3];
let copied = figcraft_doc::ImageData { bytes: std::sync::Arc::new(bytes.clone()), width: 2, height: 2 };
let taken = figcraft_doc::ImageData { bytes: std::sync::Arc::new(vec![9, 9, 9]), width: 3, height: 3 };
// Images are usually keyed by their content hash: here the copied key is that hash, and the
// other file already uses it for other data, so the replacement key is taken as well.
let hash = figcraft_format::image_hash(&bytes);
ex(&mut s, "fill.add", json!({"ids": [a], "paint": {"type": "image", "image": hash}}));
s.doc_mut().unwrap().images.insert(hash.clone(), copied.clone());
ex(&mut s, "fill.copy", json!({"id": a, "index": 1}));
ex(&mut s, "file.new", json!({}));
let b = rect(&mut s);
s.doc_mut().unwrap().images.insert(hash.clone(), taken.clone());
ex(&mut s, "fill.paste", json!({"ids": [b], "index": 0}));
let doc = s.doc().unwrap();
let figcraft_color::PaintKind::Image(img) = &doc.node(b).unwrap().fills[0].kind else { panic!("the pasted fill is an image") };
assert_ne!(img.image, hash, "the taken hash is not reused");
assert_eq!(doc.images.get(&img.image), Some(&copied), "the pasted paint points at the copied data");
assert_eq!(doc.images.get(&hash), Some(&taken), "the destination's image stays");
}
#[test]
fn a_copied_pattern_does_not_paste_into_another_file() {
let mut s = Session::new();