diff --git a/apps/vectorcraft-cli/src/main.rs b/apps/vectorcraft-cli/src/main.rs index e3c3f0748..e8e2fb358 100644 --- a/apps/vectorcraft-cli/src/main.rs +++ b/apps/vectorcraft-cli/src/main.rs @@ -78,8 +78,8 @@ USAGE: vectorcraft-cli run [--in FILE] [--cmd ID [--params JSON]]... [--export FILE]... [--scale N] Headless batch: open FILE (any readable format) or start a new document, run commands in - order, export each FILE in the format its extension picks (see Writable formats). Prints one - JSON result per step. + order, export each FILE in the format its extension picks (see Writable formats; a .ait FILE + is a template, written as file.saveAsTemplate writes it). Prints one JSON result per step. vectorcraft-cli commands Print the command catalogue as JSON. @@ -88,6 +88,8 @@ USAGE: Open IN (any readable format) and export OUT in the format its extension picks (see Writable formats). --artboard is 0-based, --range 1-based (\"1-3,5\"); a PDF gets every artboard unless one of them is given, EPS the bounds of the art, the other formats the first artboard. + A .ait OUT is a template of every artboard, written as file.saveAsTemplate writes it (not with + --artboard or --range). Live effects are kept, and hidden layers and objects (written hidden in SVG and PSD), with SVG's data-* attributes; --outline-text writes SVG text as paths, --decimals sets SVG's coordinate precision (1–7, default 3). OUT may not be IN when reading @@ -259,6 +261,10 @@ fn convert(args: &[String]) -> Result<(), String> { } } let [input, output] = <[String; 2]>::try_from(files).map_err(|_| "convert needs IN and OUT")?; + let template = is_template(&output); + if template && (artboard.is_some() || range.is_some()) { + return Err("a .ait template holds every artboard: --artboard and --range don't apply".into()); + } let mut h = Headless::new(); let opened = if editing_data { let params = json!({"path": input, "editingData": "always"}); @@ -266,16 +272,22 @@ fn convert(args: &[String]) -> Result<(), String> { } else { h.call("app.open", json!({"path": input})).map_err(|e| format!("open {input}: {e}"))? }; - // A conversion keeps hidden layers and objects, written hidden, where the format can (SVG, - // PSD). - let mut params = json!({"path": output, "scale": scale, "artboard": artboard, "range": range, "outlineText": outline_text, "hiddenLayers": true, "acknowledgeLoss": replace_lossy}); - if let Some(decimals) = decimals { - params["decimals"] = json!(decimals); - } + let (command, mut params) = if template { + // Save as Template writes every artboard, with the .ai options. + ("file.saveAsTemplate", json!({"path": output, "format": "ait", "outlineText": outline_text, "acknowledgeLoss": replace_lossy})) + } else { + // A conversion keeps hidden layers and objects, written hidden, where the format can (SVG, + // PSD). + let mut params = json!({"path": output, "scale": scale, "artboard": artboard, "range": range, "outlineText": outline_text, "hiddenLayers": true, "acknowledgeLoss": replace_lossy}); + if let Some(decimals) = decimals { + params["decimals"] = json!(decimals); + } + ("document.export", params) + }; for (key, value) in legacy { params[key] = json!(value); } - let mut r = h.call("engine.execute", json!({"command": "document.export", "params": params})).map_err(|e| format!("export {output}: {e}"))?; + let mut r = h.call("engine.execute", json!({"command": command, "params": params})).map_err(|e| format!("export {output}: {e}"))?; // What reading the input left out (a missing linked file, an unsupported filter…) matters as // much as what writing the output did. if let (Some(w), Some(out)) = (opened.get("warnings").filter(|w| w.as_array().is_some_and(|a| !a.is_empty())), r.as_object_mut()) { @@ -285,6 +297,11 @@ fn convert(args: &[String]) -> Result<(), String> { Ok(()) } +/// Is `path` a `.ait` template? `file.saveAsTemplate` writes it; exports don't. +fn is_template(path: &str) -> bool { + vectorcraft_engine::cmd::fileio::format_for_name(path).is_some_and(|f| f.id == "ait") +} + fn info(args: &[String]) -> Result<(), String> { let file = args.first().ok_or("info needs a FILE")?; let mut h = Headless::new(); @@ -371,7 +388,12 @@ fn run(args: &[String]) -> Result<(), String> { r } Step::Export(path) => { - let r = h.call("app.export", json!({"path": path, "scale": scale})).map_err(|e| format!("export {path}: {e}"))?; + let r = if is_template(&path) { + h.call("engine.execute", json!({"command": "file.saveAsTemplate", "params": {"path": path, "format": "ait"}})) + } else { + h.call("app.export", json!({"path": path, "scale": scale})) + }; + let r = r.map_err(|e| format!("export {path}: {e}"))?; emit(json!({"step": "export", "result": r}))?; r } diff --git a/apps/vectorcraft-cli/tests/fileio.rs b/apps/vectorcraft-cli/tests/fileio.rs index 19e2b7b72..4de10d932 100644 --- a/apps/vectorcraft-cli/tests/fileio.rs +++ b/apps/vectorcraft-cli/tests/fileio.rs @@ -179,3 +179,28 @@ fn convert_writes_legacy_ai_with_its_options() { assert!(!bad.status.success()); assert!(String::from_utf8_lossy(&bad.stderr).contains("hiddenObjects"), "{}", String::from_utf8_lossy(&bad.stderr)); } + +/// convert and `run --export` write a `.ait` as Save as Template does: a PDF-compatible template of +/// every artboard that opens as a new untitled document. +#[test] +fn convert_and_run_write_ait_templates() { + let pdf = tmp("template-in.pdf"); + make(&pdf, 120, 80); + let (converted, exported) = (tmp("converted.ait"), tmp("exported.ait")); + let r: Value = serde_json::from_str(&ok(&["convert", &pdf, &converted])).unwrap(); + assert_eq!((&r["format"], &r["path"]), (&Value::from("ait"), &Value::from(converted.as_str())), "{r}"); + let r: Value = serde_json::from_str(ok(&["run", "--in", &pdf, "--export", &exported]).lines().nth(1).unwrap()).unwrap(); + assert_eq!(r["result"]["format"], "ait", "{r}"); + for ait in [&converted, &exported] { + assert!(std::fs::read(ait).unwrap().starts_with(b"%PDF"), "{ait}"); + let opened: Value = serde_json::from_str(ok(&["run", "--in", ait]).lines().next().unwrap()).unwrap(); + let opened = &opened["result"]; + assert_eq!((&opened["format"], &opened["restored"]), (&Value::from("ait"), &Value::from(true)), "{opened}"); + assert!(opened["title"].as_str().is_some_and(|t| t.starts_with("Untitled-")), "{opened}"); + assert_eq!(info(ait)["artboards"].as_array().unwrap().len(), 2, "{ait}"); + } + // A template holds every artboard: one can't be picked. + let one = Command::new(BIN).args(["convert", &pdf, &tmp("one.ait"), "--artboard", "0"]).output().unwrap(); + assert!(!one.status.success()); + assert!(String::from_utf8_lossy(&one.stderr).contains("every artboard"), "{}", String::from_utf8_lossy(&one.stderr)); +} diff --git a/crates/engine/src/cmd/fileio/mod.rs b/crates/engine/src/cmd/fileio/mod.rs index 68b77a201..3ee4bb086 100644 --- a/crates/engine/src/cmd/fileio/mod.rs +++ b/crates/engine/src/cmd/fileio/mod.rs @@ -115,7 +115,7 @@ pub fn specs() -> Vec { "Serialize Document", [], None, - "{format?: vectorcraft (default)|template|svg|svgz|pdf|ai|png|jpg|webp|gif|png8|txt|dxf|eps|emf|wmf|tiff|bmp|tga|psd, …the format's options (see document.formats; SVG ones also as svg: {…}), selectedOnly?: false (the selected objects alone, in their layers)} → {text, warnings} for svg (plus dataBase64, the file, when its encoding isn't UTF-8), else {dataBase64, warnings}; an SVG of several artboards also gives files: [{name, text}], linked images linked: [{name, dataBase64}]", + "{format?: vectorcraft (default)|template|svg|svgz|pdf|ai|png|jpg|webp|gif|png8|txt|dxf|eps|emf|wmf|tiff|bmp|tga|psd (ait, a .ait template, is written by file.saveAsTemplate), …the format's options (see document.formats; SVG ones also as svg: {…}), selectedOnly?: false (the selected objects alone, in their layers)} → {text, warnings} for svg (plus dataBase64, the file, when its encoding isn't UTF-8), else {dataBase64, warnings}; an SVG of several artboards also gives files: [{name, text}], linked images linked: [{name, dataBase64}]", has_doc, export::serialize ), @@ -124,7 +124,7 @@ pub fn specs() -> Vec { "Export Document", [], None, - "{path?, format?: svg|svgz|pdf|ai|png|jpg|webp|gif|png8|txt|dxf|eps|emf|wmf|tiff|bmp|tga|psd|vectorcraft|template (default: from the path's extension, else png; png8 writes an indexed .png), selectedOnly?: false (the selected objects alone, in their layers), artboard?: 0, artboards?: [i…], rect?: [x, y, width, height] (raster formats: that area of the page in points, y down, instead of an artboard; not with artboard, artboards, range or useArtboards), range?: \"1-3, 5\" | \"all\" (1-based; PDF and .ai write one page per artboard, default all; SVG writes one file per artboard, {stem}-{artboard}.svg; raster formats write one artboard), useArtboards?: true (raster: one file per chosen artboard, default all, {stem}-{artboard}.{ext}; pdf/ai: every page) | false (pdf/ai/raster: the bounds of the visible art; SVG has it as an SVG option), ppi?: 72 (raster formats: pixels per inch, stored in the file; wins over scale), scale?: 1 (pixels per point), background?: transparent|white|black|\"#rrggbb\" (jpg: white when transparent), antiAlias?: none|art (default)|type (text snapped to pixels), interlaced?: false (png, Adam7), embedIcc?: true (png: an sRGB chunk marks the colours as sRGB), quality?: 90 (jpg: 0–100), colorModel?: rgb|cmyk|gray (jpg), method?: baseline|optimized|progressive, scans?: 3 (3–5, progressive), embedIcc?: true, imageMap?: none|client|server (an HTML or NCSA map of the objects with a URL, written as .html / .map), colors?: 256 (gif/png8: 2–256), reduction?: perceptual|selective (default)|adaptive|web|blackWhite|gray, dither?: none|diffusion (default)|pattern|noise, ditherAmount?: 100, transparency?: true, matte?: white|\"#rrggbb\"|none, interlaced?, lossless?: true (webp: lossy WebP isn't available yet: written lossless, with a warning), encoding?: utf8|utf16 (txt: the stories in stacking order, back to front, a thread once; utf16 with a byte order mark), lineEndings?: lf|crlf, selectionOnly?: false, svg?: {styling, outlineText, images, objectIds, decimals, minify, responsive, useArtboards, preserveEditing, metadata, fewerTspans, hiddenLayers, encoding, profile, embedFonts} (see document.formats), …the SVG options given flat instead, …the PDF options of document.exportPdf, …the DXF options of document.exportDxf (useArtboards: one drawing per artboard), …the EPS options of document.exportEps (useArtboards: one file per artboard, {stem}_{artboard}.eps; else the visible art), …emf and wmf write one picture of the artboard (useArtboards: true one file per chosen artboard, false the bounds of the visible art; EMF keeps curves, clipping, transparent images and gradients as images clipped to their shape; WMF flattens curves into polygons behind a placeable header; what a format leaves out comes back in warnings), colorModel?: rgb|cmyk|gray (tiff: rgb keeps transparency as an alpha channel), lzw?: true, byteOrder?: little|big, embedIcc?: true, colorModel?: rgb|gray (bmp), depth?: 24 (bmp: 1 black and white, 4|8 a palette by reduction and dither, 16, 24, 32 with alpha), fileFormat?: windows|os2 (1, 4, 8 or 24 bits), rle?: false (4 and 8 bits, windows), flipRows?: false (top-down rows), depth?: 24 (tga: 16 one-bit alpha, 24, 32 with alpha), colorModel?: rgb|cmyk|gray (psd), layers?: true (each top-level layer a pixel layer with its opacity and blend mode; a background colour is a Background layer; false: one flat image, on white), maxEditability?: false (layers and sublayers become groups, each object a layer named as in the Layers panel, text by its text; clipping, masked or knockout layers stay one layer), hiddenLayers?: false (hidden layers and objects written hidden instead of left out), embedIcc?: true (at most 30000 pixels a side; formats without alpha are flattened on white)} → {path, format, bytes, warnings, files?: [path…] (several), linked?: [path…] (linked images, image maps)}; no path → {dataBase64, format, bytes, warnings, files?: [{name, dataBase64}], linked?: [{name, dataBase64}]}. Never changes the document's path", + "{path?, format?: svg|svgz|pdf|ai|png|jpg|webp|gif|png8|txt|dxf|eps|emf|wmf|tiff|bmp|tga|psd|vectorcraft|template (default: from the path's extension, else png; png8 writes an indexed .png; ait, a .ait template, is written by file.saveAsTemplate), selectedOnly?: false (the selected objects alone, in their layers), artboard?: 0, artboards?: [i…], rect?: [x, y, width, height] (raster formats: that area of the page in points, y down, instead of an artboard; not with artboard, artboards, range or useArtboards), range?: \"1-3, 5\" | \"all\" (1-based; PDF and .ai write one page per artboard, default all; SVG writes one file per artboard, {stem}-{artboard}.svg; raster formats write one artboard), useArtboards?: true (raster: one file per chosen artboard, default all, {stem}-{artboard}.{ext}; pdf/ai: every page) | false (pdf/ai/raster: the bounds of the visible art; SVG has it as an SVG option), ppi?: 72 (raster formats: pixels per inch, stored in the file; wins over scale), scale?: 1 (pixels per point), background?: transparent|white|black|\"#rrggbb\" (jpg: white when transparent), antiAlias?: none|art (default)|type (text snapped to pixels), interlaced?: false (png, Adam7), embedIcc?: true (png: an sRGB chunk marks the colours as sRGB), quality?: 90 (jpg: 0–100), colorModel?: rgb|cmyk|gray (jpg), method?: baseline|optimized|progressive, scans?: 3 (3–5, progressive), embedIcc?: true, imageMap?: none|client|server (an HTML or NCSA map of the objects with a URL, written as .html / .map), colors?: 256 (gif/png8: 2–256), reduction?: perceptual|selective (default)|adaptive|web|blackWhite|gray, dither?: none|diffusion (default)|pattern|noise, ditherAmount?: 100, transparency?: true, matte?: white|\"#rrggbb\"|none, interlaced?, lossless?: true (webp: lossy WebP isn't available yet: written lossless, with a warning), encoding?: utf8|utf16 (txt: the stories in stacking order, back to front, a thread once; utf16 with a byte order mark), lineEndings?: lf|crlf, selectionOnly?: false, svg?: {styling, outlineText, images, objectIds, decimals, minify, responsive, useArtboards, preserveEditing, metadata, fewerTspans, hiddenLayers, encoding, profile, embedFonts} (see document.formats), …the SVG options given flat instead, …the PDF options of document.exportPdf, …the DXF options of document.exportDxf (useArtboards: one drawing per artboard), …the EPS options of document.exportEps (useArtboards: one file per artboard, {stem}_{artboard}.eps; else the visible art), …emf and wmf write one picture of the artboard (useArtboards: true one file per chosen artboard, false the bounds of the visible art; EMF keeps curves, clipping, transparent images and gradients as images clipped to their shape; WMF flattens curves into polygons behind a placeable header; what a format leaves out comes back in warnings), colorModel?: rgb|cmyk|gray (tiff: rgb keeps transparency as an alpha channel), lzw?: true, byteOrder?: little|big, embedIcc?: true, colorModel?: rgb|gray (bmp), depth?: 24 (bmp: 1 black and white, 4|8 a palette by reduction and dither, 16, 24, 32 with alpha), fileFormat?: windows|os2 (1, 4, 8 or 24 bits), rle?: false (4 and 8 bits, windows), flipRows?: false (top-down rows), depth?: 24 (tga: 16 one-bit alpha, 24, 32 with alpha), colorModel?: rgb|cmyk|gray (psd), layers?: true (each top-level layer a pixel layer with its opacity and blend mode; a background colour is a Background layer; false: one flat image, on white), maxEditability?: false (layers and sublayers become groups, each object a layer named as in the Layers panel, text by its text; clipping, masked or knockout layers stay one layer), hiddenLayers?: false (hidden layers and objects written hidden instead of left out), embedIcc?: true (at most 30000 pixels a side; formats without alpha are flattened on white)} → {path, format, bytes, warnings, files?: [path…] (several), linked?: [path…] (linked images, image maps)}; no path → {dataBase64, format, bytes, warnings, files?: [{name, dataBase64}], linked?: [{name, dataBase64}]}. Never changes the document's path", has_doc, export::export ), @@ -813,7 +813,8 @@ pub fn format_for_name(name: &str) -> Option<&'static Format> { Some(extension(name)).filter(|e| !e.is_empty()).and_then(|e| format(&e)) } -/// The format to write: `format` (an id or extension), else the path's extension, else PNG. +/// The format to write: `format` (an id or extension), else the path's extension, else PNG. For a +/// `.ait` template it returns an error that names `file.saveAsTemplate`, which writes it. pub fn writable_format(format_param: Option<&str>, path: Option<&str>) -> std::result::Result<&'static Format, String> { // Formats that can't be written say what to use instead. let unknown = |what: String, key: &str| unsupported(key).map_or(what, |u| u.hint.to_string()); @@ -822,7 +823,13 @@ pub fn writable_format(format_param: Option<&str>, path: Option<&str>) -> std::r (None, Some(e)) => format(&e).ok_or_else(|| unknown(format!("unknown extension `.{e}`: pass `format` (see document.formats)"), &e))?, (None, None) => format("png").ok_or("no PNG encoder")?, }; - if f.write { Ok(f) } else { Err(format!("{} files can be opened but not written (see document.formats)", f.label)) } + if f.write { + Ok(f) + } else if TEMPLATE_FORMATS.contains(&f.id) { + Err(format!("{} files are written by file.saveAsTemplate", f.label)) + } else { + Err(format!("{} files can be opened but not written (see document.formats)", f.label)) + } } /// [`writable_format`] for command `cmd`. diff --git a/crates/engine/src/cmd/fileio/tests_pdfedit.rs b/crates/engine/src/cmd/fileio/tests_pdfedit.rs index 431365589..63991cd19 100644 --- a/crates/engine/src/cmd/fileio/tests_pdfedit.rs +++ b/crates/engine/src/cmd/fileio/tests_pdfedit.rs @@ -232,6 +232,28 @@ fn save_as_template_names_its_file_after_the_template_format() { } } +/// Exports don't write a `.ait`: each export asked for one, by its path or `format`, answers an +/// error that names `file.saveAsTemplate`, which writes it, and writes nothing. A format that +/// nothing writes keeps its own error. +#[test] +fn exports_of_an_ait_name_save_as_template() { + let mut s = rich(); + let path = tmp("exported.ait"); + let _ = std::fs::remove_file(&path); + for (cmd, p) in [ + ("document.export", json!({"path": path})), + ("document.export", json!({"format": "ait"})), + ("document.serialize", json!({"format": "ait"})), + ("document.exportSelection", json!({"path": path})), + ("document.exportForScreens", json!({"formats": [{"format": "ait"}]})), + ] { + let e = s.execute(cmd, &p).unwrap_err().to_string(); + assert!(e.contains("PDF-compatible .ait template files are written by file.saveAsTemplate"), "{cmd} {p}: {e}"); + } + assert!(!std::path::Path::new(&path).exists(), "nothing written"); + assert_eq!(writable_format(Some("ps"), None).unwrap_err(), "PostScript files can be opened but not written (see document.formats)"); +} + #[test] fn standards_and_screens_leave_the_editing_data_out() { let mut s = rich(); diff --git a/crates/ui-egui/src/i18n/bg.tsv b/crates/ui-egui/src/i18n/bg.tsv index c2412831a..6819ea0be 100644 --- a/crates/ui-egui/src/i18n/bg.tsv +++ b/crates/ui-egui/src/i18n/bg.tsv @@ -4136,6 +4136,7 @@ axis Both И двете @msg {C}: no effect at index {from} {C}: няма ефект на индекс {from} @msg {_1} failed: {e} {_1} неуспешно: {e} @msg {_1} files can be opened but not written (see document.formats) {_1} файловете могат да се отварят, но не и да се записват (вижте document.formats) +@msg {_1} files are written by file.saveAsTemplate {_1} файловете се записват с file.saveAsTemplate @msg {_1} font families available наличности на шрифтови семейства: {_1} @msg {_1} is a live effect (Effect › Plug-ins) {_1} е динамичен ефект (Ефект › Добавки) @msg {_1} is already used by {_2} {_1} вече се използва от {_2} diff --git a/crates/ui-egui/src/i18n/cs.tsv b/crates/ui-egui/src/i18n/cs.tsv index df1e83794..63cc057f7 100644 --- a/crates/ui-egui/src/i18n/cs.tsv +++ b/crates/ui-egui/src/i18n/cs.tsv @@ -4516,6 +4516,7 @@ imageTrace Outline Obrys @msg {C}: no effect at index {from} {C}: na indexu {from} není žádný efekt @msg {_1} failed: {e} {_1} selhalo: {e} @msg {_1} files can be opened but not written (see document.formats) soubory {_1} lze otevřít, ale ne zapisovat (viz document.formats) +@msg {_1} files are written by file.saveAsTemplate soubory {_1} zapisuje file.saveAsTemplate @msg {_1} font families available Dostupné rodiny písem: {_1} @msg {_1} is a live effect (Effect › Plug-ins) {_1} je živý efekt (Efekt › Zásuvné moduly) @msg {_1} is already used by {_2} {_1} už používá {_2} diff --git a/crates/ui-egui/src/i18n/da.tsv b/crates/ui-egui/src/i18n/da.tsv index fde8d7207..e1e39ad11 100644 --- a/crates/ui-egui/src/i18n/da.tsv +++ b/crates/ui-egui/src/i18n/da.tsv @@ -4246,6 +4246,7 @@ imageTrace Outline Kontur @msg {C}: no effect at index {from} {C}: ingen effekt ved indeks {from} @msg {_1} failed: {e} {_1} mislykkedes: {e} @msg {_1} files can be opened but not written (see document.formats) {_1}-filer kan åbnes, men ikke skrives (se document.formats) +@msg {_1} files are written by file.saveAsTemplate {_1}-filer skrives af file.saveAsTemplate @msg {_1} font families available {_1} skrifttypefamilier tilgængelige @msg {_1} is a live effect (Effect › Plug-ins) {_1} er en live-effekt (Effekt › Plug-ins) @msg {_1} is already used by {_2} {_1} bruges allerede af {_2} diff --git a/crates/ui-egui/src/i18n/de.tsv b/crates/ui-egui/src/i18n/de.tsv index 82909c8b0..0b11768b2 100644 --- a/crates/ui-egui/src/i18n/de.tsv +++ b/crates/ui-egui/src/i18n/de.tsv @@ -4153,6 +4153,7 @@ theme Appearance Erscheinungsbild @msg {C}: no effect at index {from} {C}: kein Effekt bei Index {from} @msg {_1} failed: {e} {_1} ist fehlgeschlagen: {e} @msg {_1} files can be opened but not written (see document.formats) {_1}-Dateien können geöffnet, aber nicht geschrieben werden (siehe document.formats) +@msg {_1} files are written by file.saveAsTemplate {_1}-Dateien werden von file.saveAsTemplate geschrieben @msg {_1} font families available {_1} Schriftfamilien verfügbar @msg {_1} is a live effect (Effect › Plug-ins) {_1} ist ein Live-Effekt (Effekt › Plug-ins) @msg {_1} is already used by {_2} {_1} wird bereits von {_2} verwendet diff --git a/crates/ui-egui/src/i18n/el.tsv b/crates/ui-egui/src/i18n/el.tsv index ae1df0e6a..887240aee 100644 --- a/crates/ui-egui/src/i18n/el.tsv +++ b/crates/ui-egui/src/i18n/el.tsv @@ -4191,6 +4191,7 @@ theme Appearance Εμφάνιση @msg {C}: no effect at index {from} {C}: δεν υπάρχει εφέ στον δείκτη {from} @msg {_1} failed: {e} η ενέργεια {_1} απέτυχε: {e} @msg {_1} files can be opened but not written (see document.formats) τα αρχεία {_1} μπορούν να ανοιχτούν αλλά όχι να αποθηκευτούν (δείτε document.formats) +@msg {_1} files are written by file.saveAsTemplate τα αρχεία {_1} αποθηκεύονται με το file.saveAsTemplate @msg {_1} font families available {_1} διαθέσιμες οικογένειες γραμματοσειρών @msg {_1} is a live effect (Effect › Plug-ins) το {_1} είναι δυναμικό εφέ (Εφέ › Πρόσθετα) @msg {_1} is already used by {_2} το {_1} χρησιμοποιείται ήδη από το {_2} diff --git a/crates/ui-egui/src/i18n/es.tsv b/crates/ui-egui/src/i18n/es.tsv index e4b3aeb3a..65171d6af 100644 --- a/crates/ui-egui/src/i18n/es.tsv +++ b/crates/ui-egui/src/i18n/es.tsv @@ -4215,6 +4215,7 @@ axis Both Ambos @msg {C}: no effect at index {from} {C}: no hay ningún efecto en el índice {from} @msg {_1} failed: {e} {_1} falló: {e} @msg {_1} files can be opened but not written (see document.formats) los archivos {_1} se pueden abrir pero no escribir (consulta document.formats) +@msg {_1} files are written by file.saveAsTemplate los archivos {_1} se escriben con file.saveAsTemplate @msg {_1} font families available {_1} familias de fuentes disponibles @msg {_1} is a live effect (Effect › Plug-ins) {_1} es un efecto interactivo (Efecto › Plugins) @msg {_1} is already used by {_2} {_1} ya lo usa {_2} diff --git a/crates/ui-egui/src/i18n/fi.tsv b/crates/ui-egui/src/i18n/fi.tsv index b9123bf19..b76ede31a 100644 --- a/crates/ui-egui/src/i18n/fi.tsv +++ b/crates/ui-egui/src/i18n/fi.tsv @@ -4438,6 +4438,7 @@ imageTrace Outline Ääriviiva @msg {C}: no effect at index {from} {C}: ei tehostetta indeksissä {from} @msg {_1} failed: {e} {_1} epäonnistui: {e} @msg {_1} files can be opened but not written (see document.formats) {_1}-tiedostoja voi avata mutta ei kirjoittaa (ks. document.formats) +@msg {_1} files are written by file.saveAsTemplate {_1}-tiedostot kirjoitetaan komennolla file.saveAsTemplate @msg {_1} font families available {_1} fonttiperhettä käytettävissä @msg {_1} is a live effect (Effect › Plug-ins) {_1} on live-tehoste (Tehoste › Laajennukset) @msg {_1} is already used by {_2} {_1} on jo käytössä kohteella {_2} diff --git a/crates/ui-egui/src/i18n/fr.tsv b/crates/ui-egui/src/i18n/fr.tsv index 43b5a6b90..0f4855ccd 100644 --- a/crates/ui-egui/src/i18n/fr.tsv +++ b/crates/ui-egui/src/i18n/fr.tsv @@ -4212,6 +4212,7 @@ axis Both Les deux @msg {C}: no effect at index {from} {C} : aucun effet à l’indice {from} @msg {_1} failed: {e} échec de {_1} : {e} @msg {_1} files can be opened but not written (see document.formats) les fichiers {_1} peuvent être ouverts mais pas écrits (voir document.formats) +@msg {_1} files are written by file.saveAsTemplate les fichiers {_1} sont écrits par file.saveAsTemplate @msg {_1} font families available {_1} familles de polices disponibles @msg {_1} is a live effect (Effect › Plug-ins) {_1} est un effet dynamique (Effet › Plug-ins) @msg {_1} is already used by {_2} {_1} est déjà utilisé par {_2} diff --git a/crates/ui-egui/src/i18n/hu.tsv b/crates/ui-egui/src/i18n/hu.tsv index 750b82c11..95b07011b 100644 --- a/crates/ui-egui/src/i18n/hu.tsv +++ b/crates/ui-egui/src/i18n/hu.tsv @@ -4947,6 +4947,7 @@ imageTrace Outline Körvonal @msg {_1} failed: {e} sikertelen: {_1}: {e} @msg {_1} files {_1} fájlok @msg {_1} files can be opened but not written (see document.formats) ezek a fájlok megnyithatók, de nem írhatók: {_1} (lásd: document.formats) +@msg {_1} files are written by file.saveAsTemplate ezeket a fájlokat a file.saveAsTemplate írja: {_1} @msg {_1} font families available Elérhető betűcsaládok: {_1} @msg {_1} has no {_2} style ennek nincs ilyen stílusa: {_1} ({_2}) @msg {_1} is a live effect (Effect › Plug-ins) élő effektus: {_1} (Effektus › Bővítmények) diff --git a/crates/ui-egui/src/i18n/id.tsv b/crates/ui-egui/src/i18n/id.tsv index 5d3670a68..faeb00fb9 100644 --- a/crates/ui-egui/src/i18n/id.tsv +++ b/crates/ui-egui/src/i18n/id.tsv @@ -4246,6 +4246,7 @@ imageTrace Outline Kontur @msg {C}: no effect at index {from} {C}: tidak ada efek pada indeks {from} @msg {_1} failed: {e} {_1} gagal: {e} @msg {_1} files can be opened but not written (see document.formats) berkas {_1} dapat dibuka tetapi tidak dapat ditulis (lihat document.formats) +@msg {_1} files are written by file.saveAsTemplate berkas {_1} ditulis oleh file.saveAsTemplate @msg {_1} font families available {_1} keluarga font tersedia @msg {_1} is a live effect (Effect › Plug-ins) {_1} adalah efek langsung (Efek › Plug-in) @msg {_1} is already used by {_2} {_1} sudah digunakan oleh {_2} diff --git a/crates/ui-egui/src/i18n/it.tsv b/crates/ui-egui/src/i18n/it.tsv index 7deda3fbc..f8dabb85d 100644 --- a/crates/ui-egui/src/i18n/it.tsv +++ b/crates/ui-egui/src/i18n/it.tsv @@ -4254,6 +4254,7 @@ axis Both Entrambi @msg {C}: no effect at index {from} {C}: nessun effetto all'indice {from} @msg {_1} failed: {e} errore in {_1}: {e} @msg {_1} files can be opened but not written (see document.formats) i file {_1} possono essere aperti ma non scritti (vedi document.formats) +@msg {_1} files are written by file.saveAsTemplate i file {_1} vengono scritti da file.saveAsTemplate @msg {_1} font families available {_1} famiglie di font disponibili @msg {_1} is a live effect (Effect › Plug-ins) {_1} è un effetto dinamico (Effetto › Plug-in) @msg {_1} is already used by {_2} {_1} è già usato da {_2} diff --git a/crates/ui-egui/src/i18n/ja.tsv b/crates/ui-egui/src/i18n/ja.tsv index d65925900..7dee420fe 100644 --- a/crates/ui-egui/src/i18n/ja.tsv +++ b/crates/ui-egui/src/i18n/ja.tsv @@ -3902,6 +3902,7 @@ swatch Bright Violet ビビッドバイオレット @msg this file was saved without PDF compatibility, and its editing data can't be read ({why}) このファイルは PDF 互換性なしで保存されており、編集データを読み込めません ({why}) @msg the export wrote no file 書き出しでファイルが作成されませんでした @msg {_1} files can be opened but not written (see document.formats) {_1} ファイルは開けますが、保存や書き出しはできません (document.formats を参照してください) +@msg {_1} files are written by file.saveAsTemplate {_1} ファイルは file.saveAsTemplate で保存します @msg {path}: no file system here (pass dataBase64 to open; omit the path to get dataBase64) {path}: この環境にはファイルシステムがありません (開くには dataBase64 を渡し、dataBase64 を受け取るにはパスを省略してください) @msg Save writes {_1}, not {_2}: use document.export for other formats 保存できる形式は {_1} のみで、{_2} 形式では保存できません: ほかの形式には document.export を使用してください @msg Save writes {_1}, not {_2}: use file.saveAsTemplate to write it 保存できる形式は {_1} のみで、{_2} 形式では保存できません: この形式で保存するには file.saveAsTemplate を使用してください diff --git a/crates/ui-egui/src/i18n/ko.tsv b/crates/ui-egui/src/i18n/ko.tsv index 8dad6d155..8df075c26 100644 --- a/crates/ui-egui/src/i18n/ko.tsv +++ b/crates/ui-egui/src/i18n/ko.tsv @@ -3834,6 +3834,7 @@ theme Appearance 외관 @msg this file was saved without PDF compatibility, and its editing data can't be read ({why}) 이 파일은 PDF 호환성 없이 저장되어 편집 데이터를 읽을 수 없습니다({why}) @msg the export wrote no file 내보내기에서 파일이 만들어지지 않았습니다 @msg {_1} files can be opened but not written (see document.formats) {_1} 파일은 열 수 있지만 쓸 수 없습니다(document.formats 참조) +@msg {_1} files are written by file.saveAsTemplate {_1} 파일은 file.saveAsTemplate로 기록합니다 @msg {path}: no file system here (pass dataBase64 to open; omit the path to get dataBase64) {path}: 여기에는 파일 시스템이 없습니다(열려면 dataBase64를 전달하고, dataBase64를 얻으려면 경로를 생략하세요) @msg Save writes {_1}, not {_2}: use document.export for other formats 저장은 {_2}이(가) 아닌 {_1}(으)로 기록합니다. 다른 형식은 document.export를 사용하세요 @msg Save writes {_1}, not {_2}: use file.saveAsTemplate to write it 저장은 {_2}이(가) 아닌 {_1}(으)로 기록합니다. 이 형식으로 기록하려면 file.saveAsTemplate를 사용하세요 diff --git a/crates/ui-egui/src/i18n/nl.tsv b/crates/ui-egui/src/i18n/nl.tsv index c7b1d571d..7cb434430 100644 --- a/crates/ui-egui/src/i18n/nl.tsv +++ b/crates/ui-egui/src/i18n/nl.tsv @@ -4260,6 +4260,7 @@ imageTrace Outline Contour @msg {C}: no effect at index {from} {C}: geen effect bij index {from} @msg {_1} failed: {e} {_1} mislukt: {e} @msg {_1} files can be opened but not written (see document.formats) {_1}-bestanden kunnen worden geopend maar niet worden geschreven (zie document.formats) +@msg {_1} files are written by file.saveAsTemplate {_1}-bestanden worden geschreven door file.saveAsTemplate @msg {_1} font families available {_1} lettertypefamilies beschikbaar @msg {_1} is a live effect (Effect › Plug-ins) {_1} is een live-effect (Effect › Plug-ins) @msg {_1} is already used by {_2} {_1} wordt al gebruikt door {_2} diff --git a/crates/ui-egui/src/i18n/pl.tsv b/crates/ui-egui/src/i18n/pl.tsv index d0780fa7a..86ede1c9f 100644 --- a/crates/ui-egui/src/i18n/pl.tsv +++ b/crates/ui-egui/src/i18n/pl.tsv @@ -4341,6 +4341,7 @@ imageTrace Outline Kontur @msg {C}: no effect at index {from} {C}: brak efektu o indeksie {from} @msg {_1} failed: {e} {_1} — niepowodzenie: {e} @msg {_1} files can be opened but not written (see document.formats) pliki {_1} można otwierać, ale nie zapisywać (patrz document.formats) +@msg {_1} files are written by file.saveAsTemplate pliki {_1} zapisuje file.saveAsTemplate @msg {_1} font families available dostępne rodziny czcionek: {_1} @msg {_1} is a live effect (Effect › Plug-ins) {_1} to efekt na żywo (Efekt › Wtyczki) @msg {_1} is already used by {_2} {_1} jest już używany przez {_2} diff --git a/crates/ui-egui/src/i18n/pt-br.tsv b/crates/ui-egui/src/i18n/pt-br.tsv index d8c50174e..705e19188 100644 --- a/crates/ui-egui/src/i18n/pt-br.tsv +++ b/crates/ui-egui/src/i18n/pt-br.tsv @@ -3796,6 +3796,7 @@ axis Both Ambos @msg {C}: no effect at index {from} {C}: nenhum efeito no índice {from} @msg {_1} failed: {e} {_1} falhou: {e} @msg {_1} files can be opened but not written (see document.formats) arquivos {_1} podem ser abertos, mas não gravados (veja document.formats) +@msg {_1} files are written by file.saveAsTemplate arquivos {_1} são gravados por file.saveAsTemplate @msg {_1} font families available {_1} famílias de fontes disponíveis @msg {_1} is a live effect (Effect › Plug-ins) {_1} é um efeito dinâmico (Efeito › Plug-ins) @msg {_1} is already used by {_2} {_1} já é usado por {_2} diff --git a/crates/ui-egui/src/i18n/ru.tsv b/crates/ui-egui/src/i18n/ru.tsv index fab284b8e..01ae39531 100644 --- a/crates/ui-egui/src/i18n/ru.tsv +++ b/crates/ui-egui/src/i18n/ru.tsv @@ -4250,6 +4250,7 @@ axis Both Оба @msg {C}: no effect at index {from} {C}: нет эффекта с индексом {from} @msg {_1} failed: {e} {_1} завершился с ошибкой: {e} @msg {_1} files can be opened but not written (see document.formats) файлы {_1} можно открыть, но не записать (см. document.formats) +@msg {_1} files are written by file.saveAsTemplate файлы {_1} записывает file.saveAsTemplate @msg {_1} font families available доступно семейств шрифтов: {_1} @msg {_1} is a live effect (Effect › Plug-ins) {_1} — динамический эффект (Эффект › Модули) @msg {_1} is already used by {_2} {_1} уже используется {_2} diff --git a/crates/ui-egui/src/i18n/sv.tsv b/crates/ui-egui/src/i18n/sv.tsv index f65776215..e716cb49a 100644 --- a/crates/ui-egui/src/i18n/sv.tsv +++ b/crates/ui-egui/src/i18n/sv.tsv @@ -4139,6 +4139,7 @@ axis Both Båda @msg {C}: no effect at index {from} {C}: ingen effekt vid index {from} @msg {_1} failed: {e} {_1} misslyckades: {e} @msg {_1} files can be opened but not written (see document.formats) {_1}-filer kan öppnas men inte skrivas (se document.formats) +@msg {_1} files are written by file.saveAsTemplate {_1}-filer skrivs av file.saveAsTemplate @msg {_1} font families available {_1} teckensnittsfamiljer tillgängliga @msg {_1} is a live effect (Effect › Plug-ins) {_1} är en live-effekt (Effekt › Insticksprogram) @msg {_1} is already used by {_2} {_1} används redan av {_2} diff --git a/crates/ui-egui/src/i18n/uk.tsv b/crates/ui-egui/src/i18n/uk.tsv index 2f04dbd8c..964993780 100644 --- a/crates/ui-egui/src/i18n/uk.tsv +++ b/crates/ui-egui/src/i18n/uk.tsv @@ -4238,6 +4238,7 @@ axis Both Обидві @msg {C}: no effect at index {from} {C}: немає ефекту з індексом {from} @msg {_1} failed: {e} {_1} завершився з помилкою: {e} @msg {_1} files can be opened but not written (see document.formats) файли {_1} можна відкрити, але не записати (див. document.formats) +@msg {_1} files are written by file.saveAsTemplate файли {_1} записує file.saveAsTemplate @msg {_1} font families available доступно сімейства шрифтів: {_1} @msg {_1} is a live effect (Effect › Plug-ins) {_1} - динамічний ефект (Ефект › Модулі) @msg {_1} is already used by {_2} {_1} вже використовується {_2} diff --git a/crates/ui-egui/src/i18n/vi.tsv b/crates/ui-egui/src/i18n/vi.tsv index 4b79f9ed9..0499a3671 100644 --- a/crates/ui-egui/src/i18n/vi.tsv +++ b/crates/ui-egui/src/i18n/vi.tsv @@ -4605,6 +4605,7 @@ theme Appearance Giao diện @msg {_1} failed: {e} {_1} thất bại: {e} @msg {_1} files {_1} tệp @msg {_1} files can be opened but not written (see document.formats) tệp {_1} có thể mở nhưng không thể ghi (xem document.formats) +@msg {_1} files are written by file.saveAsTemplate tệp {_1} được ghi bằng file.saveAsTemplate @msg {_1} font families available {_1} họ phông chữ khả dụng @msg {_1} has no {_2} style {_1} không có kiểu {_2} @msg {_1} is a live effect (Effect › Plug-ins) {_1} là hiệu ứng trực tiếp (Hiệu ứng › Plug-ins) diff --git a/crates/ui-egui/src/i18n/zh-hans.tsv b/crates/ui-egui/src/i18n/zh-hans.tsv index 542f80ba1..df0ff8f08 100644 --- a/crates/ui-egui/src/i18n/zh-hans.tsv +++ b/crates/ui-egui/src/i18n/zh-hans.tsv @@ -3655,6 +3655,7 @@ imageTrace Outline 轮廓 @msg this file was saved without PDF compatibility, and its editing data can't be read ({why}) 此文件保存时未启用 PDF 兼容性,且无法读取其编辑数据 ({why}) @msg the export wrote no file 导出未写入任何文件 @msg {_1} files can be opened but not written (see document.formats) {_1} 文件可以打开,但不能写入 (请参阅 document.formats) +@msg {_1} files are written by file.saveAsTemplate {_1} 文件由 file.saveAsTemplate 写入 @msg {path}: no file system here (pass dataBase64 to open; omit the path to get dataBase64) {path}:此处没有文件系统 (要打开文件,请传入 dataBase64;省略路径可获取 dataBase64) @msg Save writes {_1}, not {_2}: use document.export for other formats 保存会写入 {_1},而不是 {_2}:其他格式请使用 document.export @msg no changes since the last save 自上次保存以来没有更改 diff --git a/docs/mcp.md b/docs/mcp.md index 414806246..93f951ad1 100644 --- a/docs/mcp.md +++ b/docs/mcp.md @@ -703,7 +703,10 @@ of `range` (default all; `{name}-{artboard}.ai` when several, listed in the resu raster effects are expanded can't be decoded, the result does not show it, and `warnings` carries "an image couldn't be decoded and is missing from the picture" once. From the CLI: `vectorcraft-cli convert in.svg out.ai --ai-version 7 [--hidden-objects layer|omit|visible] [--transparent-images flatten|omit] [--outline-text] -[--range R]`. +[--range R]`. `document.export`, `serialize`, `exportSelection` and `exportForScreens` refuse a `.ait` path or +`format: "ait"` with an error that names `file.saveAsTemplate`, which writes it. The CLI's `run --export x.ait` and +`convert in.svg out.ait` write it as `file.saveAsTemplate` does, with every artboard; `convert` refuses `--artboard` +and `--range` for a `.ait`. Drive a tool like a mouse: diff --git a/docs/target-app-parity.md b/docs/target-app-parity.md index f7ad11f02..89d698104 100644 --- a/docs/target-app-parity.md +++ b/docs/target-app-parity.md @@ -691,6 +691,7 @@ Moved from the ROADMAP's "Shipped so far" on 2026-10-10. Update it when a featur - macOS: the save sheet of Save As, Save a Copy, Save as Template and the first Save of a new document has a Format menu that lists each format the save can write, with the suggested file's format selected. Choosing a format changes the extension in the name field, and a name typed without an extension gets the chosen format's. A name typed with another listed extension is saved in that format. The menu is set up before the sheet opens; a save panel with one file type is shown by rfd as before (#866, `apps/vectorcraft/src/mac_save_format.rs`). - Save as Template writes a PDF-compatible `.ait` as well as a `.vctemplate`, which stays the default: the save panel and the web build's Save as Template dialog list both, and New from Template opens the `.ait` as an untitled document with its layers and art (`file.saveAsTemplate {format: "ait"}`, or a path ending in `.ait`). Save as Template always writes a file named with its template type's extension: `file.saveAsTemplate` saves a path ending in another format's extension (`card.png`) as `card.vctemplate` (or `card.ait`), the desktop save panel adds `.vctemplate` to a name typed with a Save As format's extension (`card.pdf.vctemplate`), and `file.saveAs` and `document.save` refuse `ait` with an error that names `file.saveAsTemplate`. + Exports refuse a `.ait` with an error that names `file.saveAsTemplate`, and `vectorcraft-cli convert in.pdf out.ait` and `run --export out.ait` write the template as `file.saveAsTemplate` does. - SVG/SVGZ in and out with SVG Options, Preserve Editing, symbols, filters, rich text and physical units (SVG type under a transform or `viewBox` scale opens at the size it draws at, the scale moved into its size, leading, baseline shift and strokes, a stretch, skew or rotation kept as its transform: #396; a top-level group with a clip path, such as a clipped Inkscape layer, opens as a layer with a clipping mask, its name kept: #420; hidden objects written hidden on Save and `convert`, and `data-*` attributes kept as the objects' own data: #522; top-level groups open as layers named from `data-name`, else the id, also when one more group (a transform on all the art, a ``) wraps them, hidden ones hidden, and a file whose art sits loose under a root `` with an id (or `data-name`), as one-layer exports write it, opens with its one layer named from it; nested groups stay groups, except the sublayers VectorCraft writes, marked `data-vc-layer`, and Inkscape sublayers, which open as sublayers in their layer's colour: #372); PDF and PDF-compatible `.ai` in and out (layers, with hidden ones kept hidden with their art and nested ones as sublayers, on pages of up to 64 million operators, art whose layer can't be told going to a hidden, non-printing layer rather than showing or printing: #419; strokes kept live, a fill and a stroke of one outline as one object; a type object's stroke written as each glyph's outline stroked back on the type, so it stays one editable object; lines wrapped in a frame (each wrapped line ending in a space, one left edge, leading and font) back as one area type object, left aligned or justified with its paragraph spacing, where laying it out breaks the lines where the file does; glyphs set apart by gaps wider than a space (a barcode's digit groups) keeping their places; layers stacked as the pages mark them even when one first paints on a later page, art ending a layer's marked content or a clip closing in a later layer kept in its own layer, and layers with no art kept empty: #508; art reaching past a plain PDF's page clipped to its crop box, as PDF viewers show it, each such object in a clip group of its own (a PDF-compatible `.ai` opens whole: #675); a `.ai` on its trim boxes with the bleed boxes round them as the document's bleed, as Crop To › Trim opens a PDF (#1266); masks, editable text with type set along a curve coming back as type on a path, each artboard's art on its own page only, an `.ai`'s white page under its layers left out rather than coming in as art, spot colours, presets, security, marks and bleed, ICC output intents, raster effects, subset fonts, PDF/X, PDF layers, overprint, page thumbnails, fast web view, PDF 1.3). CMYK images keep their ink amounts from PDF import (and from placed CMYK TIFFs) to PDF export: a CMYK JPEG passes through unchanged, others stay CMYK (DeviceCMYK, or ICC-based CMYK in PDF/X-3/X-4) unless the Output settings convert them; the canvas shows them through the working CMYK profile (#320). Overprint is kept from PDF import (opened, the PDF part of an `.ai`, placed) to PDF export: the graphics state's `/OP` and `/op` become the overprint of fills, strokes and type, so a spot colour over a process colour still overprints; a page whose overprint can't be read for certain knocks out with a note, as does `/OPM 0` being read as nonzero overprint (#1388; hand-written fixtures, `pdf/src/tests_import_overprint.rs`). A masked CMYK image (`/SMask`, `/Mask` or a colour key) keeps its inks too, its alpha coming in as a greyscale opacity mask. - SVG/SVGZ in and out with SVG Options, Preserve Editing, symbols, filters, rich text and physical units (SVG type under a transform or `viewBox` scale opens at the size it draws at, the scale moved into its size, leading, baseline shift and strokes, a stretch, skew or rotation kept as its transform: #396; a top-level group with a clip path, such as a clipped Inkscape layer, opens as a layer with a clipping mask, its name kept: #420; hidden objects written hidden on Save and `convert`, and `data-*` attributes kept as the objects' own data: #522; top-level groups open as layers named from `data-name`, else the id, also when one more group (a transform on all the art, a ``) wraps them, hidden ones hidden, and a file whose art sits loose under a root `` with an id (or `data-name`), as one-layer exports write it, opens with its one layer named from it; nested groups stay groups, except the sublayers VectorCraft writes, marked `data-vc-layer`, and Inkscape sublayers, which open as sublayers in their layer's colour: #372); PDF and PDF-compatible `.ai` in and out (layers, with hidden ones kept hidden with their art and nested ones as sublayers, on pages of up to 64 million operators, art whose layer can't be told going to a hidden, non-printing layer rather than showing or printing: #419; strokes kept live, a fill and a stroke of one outline as one object; a type object's stroke written as each glyph's outline stroked back on the type, so it stays one editable object; lines wrapped in a frame (each wrapped line ending in a space, one left edge, leading and font) back as one area type object, left aligned or justified with its paragraph spacing, where laying it out breaks the lines where the file does; glyphs set apart by gaps wider than a space (a barcode's digit groups) keeping their places; layers stacked as the pages mark them even when one first paints on a later page, art ending a layer's marked content or a clip closing in a later layer kept in its own layer, and layers with no art kept empty: #508; art reaching past a plain PDF's page clipped to its crop box, as PDF viewers show it, each such object in a clip group of its own (a PDF-compatible `.ai` opens whole: #675); a `.ai` on its trim boxes with the bleed boxes round them as the document's bleed, as Crop To › Trim opens a PDF (#1266); masks, editable text with type set along a curve coming back as type on a path, each artboard's art on its own page only, an `.ai`'s white page under its layers left out rather than coming in as art, spot colours, presets, security, marks and bleed, ICC output intents, raster effects, subset fonts, PDF/X, PDF layers, overprint, page thumbnails, fast web view, PDF 1.3). CMYK images keep their ink amounts from PDF import (and from placed CMYK TIFFs and the images in an `.ai` or EPS file's editing data, an alpha channel coming in as a grayscale opacity mask) to PDF export: a CMYK JPEG passes through unchanged, others stay CMYK (DeviceCMYK, or ICC-based CMYK in PDF/X-3/X-4) unless the Output settings convert them; the canvas shows them through the working CMYK profile (#320). Overprint is kept from PDF import (opened, the PDF part of an `.ai`, placed) to PDF export: the graphics state's `/OP` and `/op` become the overprint of fills, strokes and type, so a spot colour over a process colour still overprints; a page whose overprint can't be read for certain knocks out with a note, as does `/OPM 0` being read as nonzero overprint (#1388; hand-written fixtures, `pdf/src/tests_import_overprint.rs`). A masked CMYK image (`/SMask`, `/Mask` or a colour key) keeps its inks too, its alpha coming in as a greyscale opacity mask. SVG import's style cascade, used for hidden objects, non-scaling strokes and type, tests each element only against the `