diff --git a/apps/figcraft/src/services.rs b/apps/figcraft/src/services.rs index 44bb20c0..ca881067 100644 --- a/apps/figcraft/src/services.rs +++ b/apps/figcraft/src/services.rs @@ -445,7 +445,7 @@ impl Services for Desktop { fn clipboard_get_png(&mut self) -> Option> { let board = objc2_app_kit::NSPasteboard::generalPasteboard(); let png = board.dataForType(&objc2_foundation::NSString::from_str(MACOS_PASTEBOARD_PNG))?; - Some(png.to_vec()) + (png.len() <= figcraft_ui_egui::MAX_CLIPBOARD_PNG_BYTES).then(|| png.to_vec()) } fn open_url(&mut self, url: &str) { let _ = webbrowser::open(url); diff --git a/crates/ui-egui/src/lib.rs b/crates/ui-egui/src/lib.rs index 1eb714e9..8eba96f9 100644 --- a/crates/ui-egui/src/lib.rs +++ b/crates/ui-egui/src/lib.rs @@ -284,7 +284,10 @@ mod state; pub(crate) use clock::repaint_in; pub use clock::{AUTO_SAVE_SECS, AUTOSAVE_SECS, FrameClock, ToastClock}; pub(crate) use open::{drop_overlay, dropped_bytes, is_document, opens}; -pub use services::{ContentsFuture, DialogFuture, NoServices, ReadFuture, Services, browser_can_read, egui_copy_png, egui_copy_text, unreadable}; +pub use services::{ + ContentsFuture, DialogFuture, MAX_CLIPBOARD_PNG_BYTES, NoServices, ReadFuture, Services, browser_can_read, egui_copy_png, egui_copy_text, + unreadable, +}; use services::{DialogDone, PickFuture, Picked, RepaintWaker}; pub use state::{ AutoSaveEncoding, CANVAS_MIN, LEFT_PANEL, LeftTab, MobilePanel, NARROW_WIDTH, PANEL_DEFAULT, RIGHT_PANEL, RightTab, TitleBar, fit_panels, diff --git a/crates/ui-egui/src/menus/clipboard.rs b/crates/ui-egui/src/menus/clipboard.rs index 5185e4a8..0af14176 100644 --- a/crates/ui-egui/src/menus/clipboard.rs +++ b/crates/ui-egui/src/menus/clipboard.rs @@ -101,7 +101,8 @@ fn paste_text(app: &mut FigApp, text: &str, (cx, cy): (f64, f64), center: bool) /// The system clipboard's image as PNG bytes, with its size. fn clipboard_png(app: &mut FigApp) -> Option<(Vec, f64, f64)> { - let (png, (w, h)) = match app.services.clipboard_get_png().and_then(with_pixel_size) { + let clipboard_png = app.services.clipboard_get_png().filter(|png| png.len() <= crate::MAX_CLIPBOARD_PNG_BYTES); + let (png, (w, h)) = match clipboard_png.and_then(with_pixel_size) { Some(readable) => readable, None => with_pixel_size(encoded_clipboard_image(app)?)?, }; diff --git a/crates/ui-egui/src/paste_image_tests.rs b/crates/ui-egui/src/paste_image_tests.rs index 821e1ec8..8608b2c2 100644 --- a/crates/ui-egui/src/paste_image_tests.rs +++ b/crates/ui-egui/src/paste_image_tests.rs @@ -534,3 +534,17 @@ fn an_unreadable_png_on_the_clipboard_falls_back_to_the_decoded_image() { let pasted = doc.nodes().find(|n| n.name == "Pasted image").expect("pasted from the decoded image"); assert_eq!((pasted.width, pasted.height), (2.0, 2.0)); } + +#[test] +fn a_clipboard_png_past_the_byte_limit_falls_back_to_the_decoded_image() { + let mut w = Win::new(); + let mut png = std::io::Cursor::new(Vec::new()); + image::RgbaImage::new(4, 4).write_to(&mut png, image::ImageFormat::Png).unwrap(); + let mut png = png.into_inner(); + png.resize(crate::MAX_CLIPBOARD_PNG_BYTES + 1, 0); + *w.clip.borrow_mut() = Clip { png: Some(png), image: Some((2, 2, vec![255; 16])), ..Clip::default() }; + w.cmd_v(CMD); + let doc = w.app.session.doc().unwrap(); + let pasted = doc.nodes().find(|n| n.name == "Pasted image").expect("pasted from the decoded image"); + assert_eq!((pasted.width, pasted.height), (2.0, 2.0)); +} diff --git a/crates/ui-egui/src/services.rs b/crates/ui-egui/src/services.rs index 80a25e48..75a9b020 100644 --- a/crates/ui-egui/src/services.rs +++ b/crates/ui-egui/src/services.rs @@ -1,5 +1,7 @@ use super::*; +pub const MAX_CLIPBOARD_PNG_BYTES: usize = 256 * 1024 * 1024; + /// Platform services the app crate provides (file dialogs, clipboard, URLs). pub trait Services { fn pick_open(&mut self, _title: &str, _exts: &[&str]) -> DialogFuture {