From 66dbe40ea67b0d30ab02615e4c60ed2d68f42ef0 Mon Sep 17 00:00:00 2001 From: dotCooCoo Date: Thu, 15 Oct 2026 02:25:38 -0700 Subject: [PATCH] Export for Screens: the params text and the format error list every format a row writes The document.exportForScreens params text listed png, png8, jpg, webp, gif, svg, svgz and pdf for a row's format, and its settings keys left out svgz. A row of another format VectorCraft writes, such as eps, was refused with "Export for Screens writes png, png8, jpg, webp, gif, svg, svgz or pdf". Rows of tiff, bmp, tga and psd are written too, and the Export for Screens dialog offers TIFF rows. The params text, its settings keys and the error now list the 12 formats a row writes: png, png8, jpg, webp, gif, tiff, bmp, tga, psd, svg, svgz and pdf. Which formats a row accepts does not change. --- crates/engine/src/cmd/fileio/mod.rs | 2 +- crates/engine/src/cmd/fileio/screens.rs | 2 +- crates/engine/src/cmd/fileio/tests_screens.rs | 37 +++++++++++++++++++ 3 files changed, 39 insertions(+), 2 deletions(-) diff --git a/crates/engine/src/cmd/fileio/mod.rs b/crates/engine/src/cmd/fileio/mod.rs index 5294d7000..a9733e219 100644 --- a/crates/engine/src/cmd/fileio/mod.rs +++ b/crates/engine/src/cmd/fileio/mod.rs @@ -133,7 +133,7 @@ pub fn specs() -> Vec { "Export for Screens", ["File", "Export"], None, - "{folder?, zip?: false (one store-only .zip of every file: no folder → {name, dataBase64, bytes, files: [name…]}; with a folder it is written there → {path, bytes, files}), artboards?: [index…] | range?: \"1-3\" (default all), assets?: [asset id…] (instead of artboards: each asset's art alone, cropped to its bounds and named after the asset; see assets.list), fullDocument?: false (one file per format instead: a PDF of every artboard, other formats the bounds of all visible art, named after the document), includeBleed?: false (artboards grown by the document's bleed), subfolders?: false (each row's files in a sub-folder: its folder, else its size for raster (1x, 2x, 100w…) or its format (SVG, PDF)), preset?: mobile (PNG 1x, 2x, 3x) | density (PNG 0.75x–4x in ldpi…xxxhdpi sub-folders) instead of formats (turns subfolders on), formats?: [{format: png|png8|jpg|webp|gif|svg|svgz|pdf, scale?: 1 | \"2x\" | \"100w\" | \"100h\" | \"72ppi\", width?: px | height?: px | ppi? (raster only; width, then height, then ppi win over scale), suffix?: (raster default: @2x, @100w, @100h, none at 1x; vector formats drop size suffixes), folder?: (its sub-folder), quality?: (jpg 0–100), …the format's options (antiAlias, background, preset (pdf)…)}], settings?: {png|png8|jpg|webp|gif|svg|pdf: {…options for every row of that format}} (rows' own options win), prefix?, antiAlias?: none|art|type (raster rows without their own), openLocation?: (remembered; the app shows the files after exporting)} one file per artboard and format (a PDF holds its artboard alone; artboards with the same name, in any case, get -2, -3…; an unnamed one is Artboard-N) → {files: [path…]}; no folder → {files: [{name, dataBase64}]} (names include sub-folders: 2x/Icon@2x.png). The params (but zip and assets) are remembered in the document (document.exportSettings; not an undo step)", + "{folder?, zip?: false (one store-only .zip of every file: no folder → {name, dataBase64, bytes, files: [name…]}; with a folder it is written there → {path, bytes, files}), artboards?: [index…] | range?: \"1-3\" (default all), assets?: [asset id…] (instead of artboards: each asset's art alone, cropped to its bounds and named after the asset; see assets.list), fullDocument?: false (one file per format instead: a PDF of every artboard, other formats the bounds of all visible art, named after the document), includeBleed?: false (artboards grown by the document's bleed), subfolders?: false (each row's files in a sub-folder: its folder, else its size for raster (1x, 2x, 100w…) or its format (SVG, PDF)), preset?: mobile (PNG 1x, 2x, 3x) | density (PNG 0.75x–4x in ldpi…xxxhdpi sub-folders) instead of formats (turns subfolders on), formats?: [{format: png|png8|jpg|webp|gif|tiff|bmp|tga|psd|svg|svgz|pdf, scale?: 1 | \"2x\" | \"100w\" | \"100h\" | \"72ppi\", width?: px | height?: px | ppi? (raster only; width, then height, then ppi win over scale), suffix?: (raster default: @2x, @100w, @100h, none at 1x; vector formats drop size suffixes), folder?: (its sub-folder), quality?: (jpg 0–100), …the format's options (antiAlias, background, preset (pdf)…)}], settings?: {png|png8|jpg|webp|gif|tiff|bmp|tga|psd|svg|svgz|pdf: {…options for every row of that format}} (rows' own options win), prefix?, antiAlias?: none|art|type (raster rows without their own), openLocation?: (remembered; the app shows the files after exporting)} one file per artboard and format (a PDF holds its artboard alone; artboards with the same name, in any case, get -2, -3…; an unnamed one is Artboard-N) → {files: [path…]}; no folder → {files: [{name, dataBase64}]} (names include sub-folders: 2x/Icon@2x.png). The params (but zip and assets) are remembered in the document (document.exportSettings; not an undo step)", has_doc, screens::export_for_screens ), diff --git a/crates/engine/src/cmd/fileio/screens.rs b/crates/engine/src/cmd/fileio/screens.rs index b76da5d1f..29a100d65 100644 --- a/crates/engine/src/cmd/fileio/screens.rs +++ b/crates/engine/src/cmd/fileio/screens.rs @@ -170,7 +170,7 @@ impl Row { } let format = writable(C, Some(str_param(row, "format").unwrap_or("png")), None)?; if matches!(format.id, "vectorcraft" | "template" | "ai" | "txt" | "dxf" | "eps" | "emf" | "wmf") { - return Err(bad(C, "Export for Screens writes png, png8, jpg, webp, gif, svg, svgz or pdf")); + return Err(bad(C, "Export for Screens writes png, png8, jpg, webp, gif, tiff, bmp, tga, psd, svg, svgz or pdf")); } // Vector formats have no pixel size: it doesn't apply and adds no @2x suffix (not even one // left over from a raster row switched to SVG or PDF). diff --git a/crates/engine/src/cmd/fileio/tests_screens.rs b/crates/engine/src/cmd/fileio/tests_screens.rs index 7dc2e7de7..b60af73de 100644 --- a/crates/engine/src/cmd/fileio/tests_screens.rs +++ b/crates/engine/src/cmd/fileio/tests_screens.rs @@ -225,6 +225,43 @@ fn format_settings_apply_to_their_rows_and_rows_win() { assert!(f[2].1.len() < f[3].1.len(), "quality 5 is smaller than quality 100"); } +/// The format ids `text` lists as `a|b|c` after `start`, up to `end`, sorted. +fn listed(text: &str, start: &str, end: char) -> Vec { + let (_, rest) = text.split_once(start).unwrap_or_else(|| panic!("no `{start}` in {text}")); + let mut ids: Vec = rest.split(end).next().unwrap_or_default().split('|').map(|f| f.trim().to_string()).collect(); + ids.sort(); + ids +} + +/// The params text (a row's `format` and the keys of `settings`) and the refusal of a format a row +/// can't have list exactly the formats a row writes. +#[test] +fn the_params_and_the_refusal_list_every_format_a_row_writes() { + let mut s = session(1); + let (mut written, mut refusals) = (vec![], vec![]); + for f in FORMATS.iter().filter(|f| f.write) { + match s.execute("document.exportForScreens", &json!({"formats": [{"format": f.id}]})) { + Ok(r) => { + assert_eq!(files(&r).len(), 1, "{}", f.id); + written.push(f.id.to_string()); + } + Err(crate::EngineError::BadParams { msg, .. }) => refusals.push(msg), + Err(e) => panic!("{}: {e}", f.id), + } + } + written.sort(); + let params = crate::cmd::find_command("document.exportForScreens").unwrap().params; + assert_eq!(listed(params, "[{format: ", ','), written, "a row's format"); + assert_eq!(listed(params, "settings?: {", ':'), written, "the keys of settings"); + assert!(!refusals.is_empty()); + for msg in refusals { + let list = msg.strip_prefix("Export for Screens writes ").unwrap_or_else(|| panic!("{msg}")); + let mut ids: Vec = list.replace(" or ", ", ").split(", ").map(str::to_string).collect(); + ids.sort(); + assert_eq!(ids, written, "{msg}"); + } +} + #[test] fn zip_downloads_round_trip() { let mut s = session(2);