Fix #1499: PDF and EPS another app copied paste as editable vector art

Paste read PDF only from the Windows clipboard and EPS nowhere, so on macOS a copied
equation or drawing pasted nothing new (the last thing copied in the app came back).
- macOS: the pasteboard's PDF, EPS and SVG are read, ahead of text and bitmaps.
- Windows: EPS is read beside PDF.
- Everywhere: an .eps file copied in a file manager pastes.
- clipboard.importEps loads an EPS into the clipboard as document.open reads one.
This commit is contained in:
bflatastic
2026-10-13 06:34:12 +07:00
parent a046fd30fe
commit b3f6edddfe
23 changed files with 191 additions and 58 deletions
+1 -1
View File
@@ -21,7 +21,7 @@ use vectorcraft_color::Swatch;
use vectorcraft_doc::{Document, GraphicStyle, ImageBlob, Node, NodeId, PatternDef, Symbol, TextStyleDef};
use vectorcraft_geom::Rect;
pub use flavours::{BITMAP, EMF, FILE_HEAD, Flavour, PASTE_ORDER, PDF, PNG, SVG, TEXT, file_flavour, is_address, names_a_picture};
pub use flavours::{BITMAP, EMF, EPS, FILE_HEAD, Flavour, PASTE_ORDER, PDF, PNG, SVG, TEXT, file_flavour, is_address, names_a_picture};
pub(crate) use resources::SwatchChoices;
use super::*;
+34 -5
View File
@@ -1,5 +1,5 @@
//! Clipboard flavours: what Copy offers other apps besides SVG (PNG, PDF and plain text) and what
//! Paste takes from them (bitmaps, plain text, PDF and Windows metafiles).
//! Paste takes from them (bitmaps, plain text, PDF, EPS and Windows metafiles).
//!
//! The UI owns the platform clipboard (`Services::system_clipboard` in the egui frontend): on Copy
//! it publishes [`Session::clipboard_flavours`]; before a Paste it turns what another app put on
@@ -27,9 +27,12 @@ pub const BITMAP: &str = "image/*";
/// An EMF (or WMF) picture: what Windows apps copy as vector art (the system clipboard reads it
/// where the platform offers it).
pub const EMF: &str = "image/emf";
/// An EPS (or other PostScript) picture: what drawing and typesetting apps copy as vector art
/// beside, or instead of, a PDF (#1499).
pub const EPS: &str = "application/postscript";
/// What Paste reads from other apps, best first: vector art before text, text before bitmaps
/// (word processors offer a picture of copied text too).
pub const PASTE_ORDER: [&str; 5] = [SVG, PDF, EMF, TEXT, BITMAP];
pub const PASTE_ORDER: [&str; 6] = [SVG, PDF, EPS, EMF, TEXT, BITMAP];
/// Is `text` only an address (a web page's, an image's, a file's)? A browser's Copy Image puts the
/// image's address as text next to the picture: then the picture is what Paste takes (#597). An
/// address alone pastes as text, and nothing is fetched.
@@ -67,13 +70,14 @@ fn only_images(html: &str) -> bool {
pub const FILE_HEAD: usize = 4096;
/// What a file copied in a file manager pastes as, if one of `mimes` (`image/*`: any bitmap):
/// SVG, PDF, a Windows metafile or a bitmap (its own `image/…` type), told from its first
/// SVG, PDF, EPS, a Windows metafile or a bitmap (its own `image/…` type), told from its first
/// [`FILE_HEAD`] bytes and its name. The file's bytes are that flavour's data.
pub fn file_flavour(name: &str, head: &[u8], mimes: &[&str]) -> Option<&'static str> {
let f = fileio::detect(name, head)?;
let (asked, mime) = match f.id {
"svg" => (SVG, SVG),
"pdf" => (PDF, PDF),
"eps" => (EPS, EPS),
"emf" | "wmf" => (EMF, EMF),
_ if f.raster => (BITMAP, f.mime),
_ => return None,
@@ -167,6 +171,15 @@ pub fn specs() -> Vec<CommandSpec> {
has_doc,
import_emf
),
cmd!(
query "clipboard.importEps",
"Load EPS into Clipboard",
[],
None,
"{dataBase64 (EPS or other PostScript bytes), center?: [x, y]} replace the clipboard with the picture's objects as document.open reads an EPS (editable paths, type and images, with the images, patterns and swatches they use), centred on `center` (default: the first artboard) → {count, warnings}; then run edit.paste",
has_doc,
import_eps
),
]
}
@@ -345,10 +358,26 @@ fn import_emf(s: &mut Session, p: &Value) -> Result<Value> {
if vectorcraft_metafile::sniff(&bytes).is_none() {
return Err(bad(C, "the data isn't an EMF or WMF picture"));
}
let loaded = fileio::load("Clipboard.emf", &bytes).map_err(|e| bad(C, e.to_string()))?;
import_picture(s, p, C, "Clipboard.emf", &bytes)
}
fn import_eps(s: &mut Session, p: &Value) -> Result<Value> {
const C: &str = "clipboard.importEps";
let bytes = data_param(p, C)?;
if !vectorcraft_pdf::is_postscript(&bytes) {
return Err(bad(C, "the data isn't an EPS or PostScript picture"));
}
import_picture(s, p, C, "Clipboard.eps", &bytes)
}
/// Replace the clipboard with the objects of the picture `bytes`, read as the file `name` is
/// opened (command `cmd`).
fn import_picture(s: &mut Session, p: &Value, cmd: &str, name: &str, bytes: &[u8]) -> Result<Value> {
let mut loaded = fileio::load(name, bytes).map_err(|e| bad(cmd, e.to_string()))?;
loaded.doc.drop_edit_modes();
let clip = Clipboard::from_document(&loaded.doc);
if clip.is_empty() {
return Err(bad(C, "the picture has no objects"));
return Err(bad(cmd, "the picture has no objects"));
}
let count = s.load_clipboard(clip, p)?;
Ok(json!({ "count": count, "warnings": loaded.warnings }))