mirror of
https://github.com/storytold/pdfcraft.git
synced 2026-11-03 15:05:02 -06:00
Fixes Pasting Images having broken Coordinates
This commit is contained in:
@@ -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)
|
||||
}
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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");
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user