Fixes Pasting Images having broken Coordinates

This commit is contained in:
Combiler01
2026-10-23 15:44:04 +05:30
parent 3641801f13
commit fa11f650cd
3 changed files with 67 additions and 53 deletions
+19 -51
View File
@@ -718,7 +718,7 @@ impl crate::PdfCraftApp {
}
/// Copy Image: image `index` on `page`, at its own resolution, to the clipboard.
pub(crate) fn copy_page_image(&mut self, page: usize, index: usize) -> bool {
pub fn copy_page_image(&mut self, page: usize, index: usize) -> bool {
let Some((_, id)) = self.active_ids() else { return false };
if !self.may_copy(id) {
return false;
@@ -735,7 +735,7 @@ impl crate::PdfCraftApp {
return Err("the web version can't put images on the clipboard yet; use Save Image As".to_string());
}
self.last_copied_image = Some((w, h, rgba));
self.last_copied_image_rect = self.page_image_box(page, index);
self.last_copied_image_rect = None;
Ok((w, h))
});
match copied {
@@ -952,30 +952,11 @@ impl crate::PdfCraftApp {
return false;
};
let target_rect = match rect {
Some(r) => Some(r),
None => {
if let Some(src_rect) = self.last_copied_image_rect {
let w = src_rect[2] - src_rect[0];
let h = src_rect[3] - src_rect[1];
let offset_x = 20.0;
let offset_y = -20.0;
let new_r = [src_rect[0] + offset_x, src_rect[1] + offset_y, src_rect[0] + offset_x + w, src_rect[1] + offset_y + h];
self.last_copied_image_rect = Some(new_r);
Some(new_r)
} else {
None
}
}
};
let target_rect = rect;
let before = self.session.get(id).map_or(0, |d| d.page_images(page).len());
if self.apply_edit(pdfcraft_engine::Edit::AddImage { page, rect: target_rect, name, bytes: std::sync::Arc::new(bytes) }) {
let placed_rect =
target_rect.or_else(|| self.session.get(id).and_then(|d| d.page_images(page).into_iter().nth(before).map(|im| im.rect)));
if let Some(r) = placed_rect {
self.last_copied_image_rect = Some(r);
}
self.last_copied_image_rect = None;
let added_count = self.session.get(id).map_or(0, |d| d.added().iter().filter(|a| a.page == page).count());
let added_idx = added_count.saturating_sub(1);
if let Some(v) = self.views.get_mut(i) {
@@ -1003,44 +984,31 @@ impl crate::PdfCraftApp {
/// Paste an image centered at context menu point `at: [f64; 2]` on `page`.
pub fn paste_image_at(&mut self, page: usize, at: [f64; 2]) -> bool {
let (w, h) = if let Some(crate::create_ui::Clip::Image { width, height, rgba }) = self.clipboard_override.as_ref() {
let is_same = self
.last_copied_image
.as_ref()
.is_some_and(|(w, h, bytes)| *w as usize == *width && *h as usize == *height && bytes.as_slice() == rgba.as_slice());
if is_same && let Some(src_r) = self.last_copied_image_rect {
(src_r[2] - src_r[0], src_r[3] - src_r[1])
} else {
(*width as f64, *height as f64)
}
let (raw_w, raw_h) = if let Some(crate::create_ui::Clip::Image { width, height, .. }) = self.clipboard_override.as_ref() {
(*width as f64, *height as f64)
} else {
#[cfg(not(target_arch = "wasm32"))]
let sys_img = if self.system_clipboard {
crate::os_clipboard::with(|cb| cb.get_image()).ok().map(|img| (img.width, img.height, img.bytes.into_owned()))
} else {
None
};
let sys_img =
if self.system_clipboard { crate::os_clipboard::with(|cb| cb.get_image()).ok().map(|img| (img.width, img.height)) } else { None };
#[cfg(target_arch = "wasm32")]
let sys_img: Option<(usize, usize, Vec<u8>)> = None;
let sys_img: Option<(usize, usize)> = None;
if let Some((img_width, img_height, img_bytes)) = sys_img {
let is_same = self
.last_copied_image
.as_ref()
.is_some_and(|(w, h, bytes)| *w as usize == img_width && *h as usize == img_height && bytes.as_slice() == img_bytes.as_slice());
if is_same && let Some(src_r) = self.last_copied_image_rect {
(src_r[2] - src_r[0], src_r[3] - src_r[1])
} else {
(img_width as f64, img_height as f64)
}
} else if let Some(src_r) = self.last_copied_image_rect {
(src_r[2] - src_r[0], src_r[3] - src_r[1])
if let Some((img_width, img_height)) = sys_img {
(img_width as f64, img_height as f64)
} else if let Some((w, h, _)) = self.last_copied_image {
(w as f64, h as f64)
} else {
(100.0, 100.0)
}
};
let (_, id) = match self.active_ids() {
Some(ids) => ids,
None => return false,
};
let (pw, ph) = self.session.get(id).and_then(|d| d.info.pages.get(page)).map(|p| (p.width as f64, p.height as f64)).unwrap_or((612.0, 792.0));
let k = ((pw * 0.8) / raw_w.max(1.0)).min((ph * 0.8) / raw_h.max(1.0)).min(1.0);
let (w, h) = (raw_w * k, raw_h * k);
let rect = Some([at[0] - w * 0.5, at[1] - h * 0.5, at[0] + w * 0.5, at[1] + h * 0.5]);
self.paste_image(page, rect)
}
+2 -2
View File
@@ -191,7 +191,7 @@ impl PdfCraftApp {
/// Copy Image in Original Format: image `index` on `page` as a file on the clipboard (JPEG
/// as stored in the PDF, otherwise PNG).
pub(crate) fn copy_page_image_file(&mut self, page: usize, index: usize) {
pub fn copy_page_image_file(&mut self, page: usize, index: usize) {
let Some((_, id)) = self.active_ids() else { return };
if !self.may_copy(id) {
return;
@@ -203,7 +203,7 @@ impl PdfCraftApp {
let name = format!("{stem} p{} img{}.{ext}", page.saturating_add(1), index.saturating_add(1));
self.put_file_on_clipboard(&name, &bytes)?;
self.last_copied_file = Some((name, bytes));
self.last_copied_image_rect = self.page_image_box(page, index);
self.last_copied_image_rect = None;
Ok(ext)
});
match copied {
+46
View File
@@ -7093,3 +7093,49 @@ fn mode_tabs_keep_the_hand_over_a_paragraph_scrolled_under_them() {
click_at(&mut h, convert);
assert_eq!(h.state().mode, pdfcraft_ui_egui::Mode::Convert, "the tab takes the click, not the paragraph's editor");
}
#[test]
fn copy_pasted_image_discards_coordinates_and_moves_without_offset() {
let mut png = Vec::new();
image::RgbImage::from_pixel(60, 60, image::Rgb([100, 150, 200])).write_to(&mut std::io::Cursor::new(&mut png), image::ImageFormat::Png).unwrap();
let mut h = harness(1, |_| {});
h.run_steps(4);
// 1. Add image to page 0
h.state_mut().add_image("test_img.png".into(), png);
h.run_steps(3);
let id = h.state().views[0].id;
let images_before = h.state().session.get(id).unwrap().page_images(0);
assert_eq!(images_before.len(), 1);
// 2. Copy the image from page 0
assert!(h.state_mut().copy_page_image(0, 0));
// Verify coordinates data was discarded during copy!
assert!(h.state().last_copied_image_rect.is_none(), "coordinates data must be discarded during copy");
assert!(h.state().last_copied_image.is_some(), "image pixel data is kept");
// 3. Paste the image via Ctrl+V (paste_image with rect: None)
assert!(h.state_mut().paste_image(0, None));
h.run_steps(3);
// Verify after paste that coordinates data remains None
assert!(h.state().last_copied_image_rect.is_none());
let images_after_paste = h.state().session.get(id).unwrap().page_images(0);
assert_eq!(images_after_paste.len(), 2, "second image pasted");
// The pasted image should be centered on the page, not offset by (+20, -20) from the first image
let img1 = &images_after_paste[0];
let img2 = &images_after_paste[1];
assert_eq!(img1.rect, img2.rect, "pasted image placed using natural page centering");
// 4. Move the pasted image by an explicit Move change
let target = [50.0, 50.0, 110.0, 110.0];
h.state_mut().apply_edit(pdfcraft_engine::Edit::EditPageImage { page: 0, index: 1, change: pdfcraft_engine::ImageEdit::Move(target) });
h.run_steps(3);
let images_after_move = h.state().session.get(id).unwrap().page_images(0);
assert_eq!(images_after_move[1].rect, target, "moved image rect matches target exactly without offset");
}