Clipboard: a .ai or .ait file copied in a file manager pastes as its art

Paste takes a file copied in a file manager by its flavor, which
file_flavour tells from the file's first 4,096 bytes through
fileio::detect. detect names PDF or PostScript bytes in a file called
.ai or .ait by that extension, and file_flavour gave no flavor for
the format ids ai and ait. So a copied .ai or .ait file did not paste
as its art, while the same bytes in a .pdf or .eps file did.

A copied .ai or .ait file whose bytes are a PDF now pastes through the
PDF flavor (clipboard.importPdf), and one whose bytes are PostScript
pastes through the EPS flavor (clipboard.importEps). Other files
with those extensions paste as before. A .ai saved without its PDF
part also gets the PDF flavor, and Paste answers it with
clipboard.importPdf's error, as it does for the same file named .pdf.

Docs: docs/control-protocol.md, docs/development.md,
docs/target-app-parity.md.
This commit is contained in:
dotCooCoo
2026-10-16 03:36:20 +07:00
committed by bflatastic
parent b9341d3b3b
commit c5a1504e99
5 changed files with 29 additions and 4 deletions
+4 -1
View File
@@ -71,13 +71,16 @@ 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, 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.
/// [`FILE_HEAD`] bytes and its name. A `.ai` or `.ait` file pastes as PDF when its bytes are a
/// PDF and as EPS when they are PostScript. 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),
"ai" | "ait" if head.starts_with(b"%PDF") => (PDF, PDF),
"ai" | "ait" if vectorcraft_pdf::is_postscript(head) => (EPS, EPS),
"emf" | "wmf" => (EMF, EMF),
_ if f.raster => (BITMAP, f.mime),
_ => return None,
+22 -1
View File
@@ -7,7 +7,7 @@ use vectorcraft_geom::Point;
use vectorcraft_tools::{Mods, PointerEvent, PointerKind, ToolKey};
use super::*;
use crate::cmd::clipboard::{BITMAP, FILE_HEAD, PASTE_ORDER, PDF, PNG, SVG, TEXT, file_flavour};
use crate::cmd::clipboard::{BITMAP, EPS, FILE_HEAD, PASTE_ORDER, PDF, PNG, SVG, TEXT, file_flavour};
fn session() -> Session {
let mut s = Session::new();
@@ -141,6 +141,27 @@ fn copied_files_paste_by_their_content() {
assert_eq!(file_flavour("art.svg", svg, &[BITMAP]), None);
}
#[test]
fn copied_ai_files_paste_as_the_pdf_or_postscript_inside() {
let mut s = session();
copy_red_square(&mut s);
let pdf = decode(&run(&mut s, "clipboard.exportPdf", json!({})));
let ps = b"%!PS-Adobe-3.0\n%%BoundingBox: 0 0 40 40\n%%EndComments\n1 0 0 setrgbcolor\nnewpath 0 0 moveto 40 0 lineto 40 40 lineto closepath fill\nshowpage\n";
let of = |name: &str, bytes: &[u8]| file_flavour(name, &bytes[..bytes.len().min(FILE_HEAD)], &PASTE_ORDER);
for name in ["art.ai", "art.ait", "ART.AI"] {
assert_eq!(of(name, &pdf), Some(PDF), "{name}");
assert_eq!(of(name, ps), Some(EPS), "{name}");
}
// Neither PDF nor PostScript: nothing to paste, whatever the name says.
assert_eq!(of("art.ai", b"hello"), None);
// Only what was asked for.
assert_eq!(file_flavour("art.ai", &pdf, &[EPS]), None);
assert_eq!(file_flavour("art.ait", ps, &[PDF]), None);
// clipboard.importEps, the EPS flavor's command, reads the PostScript.
let r = run(&mut s, "clipboard.importEps", json!({"dataBase64": vectorcraft_format::base64_encode(ps)}));
assert_eq!(r["count"], 1, "{r}");
}
#[test]
fn import_text_then_paste_gives_point_text() {
let mut s = session();