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);