Clipboard: a .svgz 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 gzip data SVGZ unless it is a compressed
native file, and file_flavour gave no flavor for the format id svgz.
So a copied .svgz file did not paste as its art, although
clipboard.importSvg, the SVG flavor's command, reads SVGZ data.

A copied file whose gzip data unpacks to SVG markup now pastes through
the SVG flavor. file_flavour unpacks the head it is given with the
gzip decoder the SVGZ reader uses, up to where the head ends, and
checks the first 4,096 unpacked bytes with looks_like_svg, as detect
checks an uncompressed file, reading them as UTF-16 after a UTF-16
byte order mark. Other gzip files get no flavor, as before.

Docs: docs/target-app-parity.md.
This commit is contained in:
dotCooCoo
2026-10-16 03:36:20 +07:00
committed by bflatastic
parent c5a1504e99
commit c246fc2f0e
3 changed files with 83 additions and 0 deletions
@@ -77,6 +77,9 @@ pub fn file_flavour(name: &str, head: &[u8], mimes: &[&str]) -> Option<&'static
let f = fileio::detect(name, head)?;
let (asked, mime) = match f.id {
"svg" => (SVG, SVG),
// `detect` names gzip data SVGZ (a compressed native file aside); `clipboard.importSvg`
// reads SVGZ data.
"svgz" if unpacks_to_svg(head) => (SVG, SVG),
"pdf" => (PDF, PDF),
"eps" => (EPS, EPS),
"ai" | "ait" if head.starts_with(b"%PDF") => (PDF, PDF),
@@ -88,6 +91,25 @@ pub fn file_flavour(name: &str, head: &[u8], mimes: &[&str]) -> Option<&'static
mimes.contains(&asked).then_some(mime)
}
/// Does gzip data `head` (a file's first bytes, often a stream cut short) unpack to SVG markup?
/// The first [`FILE_HEAD`] bytes it unpacks to are read as UTF-16 after a UTF-16 byte order mark,
/// else as UTF-8, and checked with [`looks_like_svg`].
fn unpacks_to_svg(head: &[u8]) -> bool {
use std::io::Read as _;
let mut raw = Vec::with_capacity(FILE_HEAD);
// A stream cut short ends in an error after the bytes that unpacked: judge those.
let _ = flate2::read::GzDecoder::new(head).take(FILE_HEAD as u64).read_to_end(&mut raw);
let utf16 = |rest: &[u8], unit: fn([u8; 2]) -> u16| -> String {
char::decode_utf16(rest.as_chunks::<2>().0.iter().map(|u| unit(*u))).map(|c| c.unwrap_or(char::REPLACEMENT_CHARACTER)).collect()
};
let text = match raw.as_slice() {
[0xfe, 0xff, rest @ ..] => utf16(rest, u16::from_be_bytes),
[0xff, 0xfe, rest @ ..] => utf16(rest, u16::from_le_bytes),
_ => String::from_utf8_lossy(&raw).into_owned(),
};
looks_like_svg(&text)
}
/// One representation of the clipboard's contents.
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct Flavour {