From 17b986d11ccec611271e5cd22f3ca4fad81800c5 Mon Sep 17 00:00:00 2001 From: dotCooCoo Date: Tue, 10 Nov 2026 16:59:15 -0800 Subject: [PATCH 1/6] Docs: pattern.options keeps current values for omitted params The pattern and Repeat paragraph in docs/mcp.md said that every omitted or null param keeps its default. pattern.options and object.repeat.options keep the pattern's or the repeat's current value instead (except the brick offset, which a brick tileType or gridType without brickOffset sets to 0.5), object.pattern.edit and pattern.options fall back to the selected object's pattern and the pattern being edited when name is omitted, and pattern.delete requires name. --- docs/mcp.md | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/docs/mcp.md b/docs/mcp.md index c8841adf6..b220a1011 100644 --- a/docs/mcp.md +++ b/docs/mcp.md @@ -3103,8 +3103,13 @@ The pattern commands (`object.pattern.make`, `object.pattern.edit`, `object.patt `object.repeat.options`) reject a param of the wrong type, such as `"instances": "8"`, `"edit": "false"` or `"tileType": 2`, and change nothing. `pattern.options` also rejects an `overlap` that is not an object, and an `overlap.h` other than `left` or `right` or an `overlap.v` other than `top` or `bottom`. `object.repeat.options` rejects -a `center` that is not `[x, y]`, and `object.repeat.radial` rejects a negative `radius`. Omitting a param or giving null -keeps its default. +a `center` that is not `[x, y]`, and `object.repeat.radial` rejects a negative `radius`. In `object.pattern.make`, +`object.pattern.saveCopy`, `object.repeat.radial`, `object.repeat.grid` and `object.repeat.mirror`, omitting a param or +giving null keeps its default. In `pattern.options` and `object.repeat.options`, omitting a param or giving null keeps +the pattern's or the repeat's current value, with one exception: a brick `tileType` in `pattern.options` or a brick +`gridType` in `object.repeat.options` given without `brickOffset` sets the offset to 0.5. Without a `name`, +`object.pattern.edit` edits the pattern that fills or strokes a selected object, and `pattern.options` changes the +pattern being edited; each returns an error when there is no such pattern. `pattern.delete` requires `name`. ```json {"name":"run_command","arguments":{"command":"pattern.options","params":{"showSwatchBounds":true}}} From 8430715ec7736de9a26817956ca5346ad6a00acf Mon Sep 17 00:00:00 2001 From: dotCooCoo Date: Tue, 10 Nov 2026 17:27:50 -0800 Subject: [PATCH 2/6] Docs: pattern.options sizes the tile to the art while sizeTileToArt is on A brick tileType or gridType with brickOffset omitted or null sets the offset to 0.5. While sizeTileToArt is on, every pattern.options call sizes the tile to the art plus hSpacing and vSpacing again and ignores width and height, so a new hSpacing changes the tile's width. --- docs/mcp.md | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/docs/mcp.md b/docs/mcp.md index b220a1011..7b9abf63f 100644 --- a/docs/mcp.md +++ b/docs/mcp.md @@ -3106,8 +3106,10 @@ The pattern commands (`object.pattern.make`, `object.pattern.edit`, `object.patt a `center` that is not `[x, y]`, and `object.repeat.radial` rejects a negative `radius`. In `object.pattern.make`, `object.pattern.saveCopy`, `object.repeat.radial`, `object.repeat.grid` and `object.repeat.mirror`, omitting a param or giving null keeps its default. In `pattern.options` and `object.repeat.options`, omitting a param or giving null keeps -the pattern's or the repeat's current value, with one exception: a brick `tileType` in `pattern.options` or a brick -`gridType` in `object.repeat.options` given without `brickOffset` sets the offset to 0.5. Without a `name`, +the pattern's or the repeat's current value, with two exceptions. A brick `tileType` in `pattern.options` or a brick +`gridType` in `object.repeat.options` with `brickOffset` omitted or null sets the offset to 0.5. While `sizeTileToArt` +is on, each `pattern.options` call sizes the tile to the art plus `hSpacing` and `vSpacing` again and ignores `width` +and `height`, so a new `hSpacing` changes the tile's width. Without a `name`, `object.pattern.edit` edits the pattern that fills or strokes a selected object, and `pattern.options` changes the pattern being edited; each returns an error when there is no such pattern. `pattern.delete` requires `name`. From e60b0c3aa6b5f98b819e71cc40479cf73c2b8483 Mon Sep 17 00:00:00 2001 From: dotCooCoo Date: Tue, 10 Nov 2026 17:57:41 -0800 Subject: [PATCH 3/6] Docs: pattern.options rejects a bad width or height while sizeTileToArt is on With sizeTileToArt on, pattern.options ignores width and height for sizing but still rejects a value that is not a positive number, such as width 0, and changes nothing. --- docs/mcp.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/docs/mcp.md b/docs/mcp.md index 7b9abf63f..f9c61590f 100644 --- a/docs/mcp.md +++ b/docs/mcp.md @@ -3109,7 +3109,8 @@ giving null keeps its default. In `pattern.options` and `object.repeat.options`, the pattern's or the repeat's current value, with two exceptions. A brick `tileType` in `pattern.options` or a brick `gridType` in `object.repeat.options` with `brickOffset` omitted or null sets the offset to 0.5. While `sizeTileToArt` is on, each `pattern.options` call sizes the tile to the art plus `hSpacing` and `vSpacing` again and ignores `width` -and `height`, so a new `hSpacing` changes the tile's width. Without a `name`, +and `height`, so a new `hSpacing` changes the tile's width. `pattern.options` still rejects a `width` or `height` that +is not a positive number, such as `"width": 0`, and changes nothing. Without a `name`, `object.pattern.edit` edits the pattern that fills or strokes a selected object, and `pattern.options` changes the pattern being edited; each returns an error when there is no such pattern. `pattern.delete` requires `name`. From d426c70a929375d9984a8f80a4fa147d70863907 Mon Sep 17 00:00:00 2001 From: dotCooCoo Date: Tue, 10 Nov 2026 18:11:01 -0800 Subject: [PATCH 4/6] Preferences: export and import preferences as a JSON file Part of #6435. An optional VectorCraft feature: the Preferences dialog's Export Preferences... saves the applied preferences to a JSON file (the web build downloads it), and Import Preferences... applies such a file and shows the imported values. They run the new commands prefs.export {path?} and prefs.import {path | data}. The file holds every preference prefs.list lists and the eyedropper, perspectiveWidget, pathfinder and glyphSnap groups, without the four folder preferences and saved presets. prefs.import reads at most 1 MB and checks every value first: another format, a newer version, an unknown preference, a bad value or a folder preference is an error, and nothing changes. It keeps the open document's units, records no undo step, and is refused as a command.batch step. prefs.set now rejects a group value that is not an object. --- crates/engine/src/cmd/fileio/batch.rs | 3 + crates/engine/src/cmd/prefscmds.rs | 112 ++++++++++++++- crates/engine/src/command_sets.rs | 1 + crates/engine/src/tests.rs | 1 + crates/engine/src/tests/prefs_file.rs | 169 +++++++++++++++++++++++ crates/ui-egui/src/app/commands.rs | 4 + crates/ui-egui/src/i18n/bg.tsv | 21 +++ crates/ui-egui/src/i18n/bs.tsv | 21 +++ crates/ui-egui/src/i18n/ca.tsv | 21 +++ crates/ui-egui/src/i18n/cs.tsv | 21 +++ crates/ui-egui/src/i18n/da.tsv | 21 +++ crates/ui-egui/src/i18n/de.tsv | 21 +++ crates/ui-egui/src/i18n/el.tsv | 21 +++ crates/ui-egui/src/i18n/es.tsv | 21 +++ crates/ui-egui/src/i18n/fi.tsv | 21 +++ crates/ui-egui/src/i18n/fr.tsv | 21 +++ crates/ui-egui/src/i18n/hi.tsv | 21 +++ crates/ui-egui/src/i18n/hu.tsv | 21 +++ crates/ui-egui/src/i18n/id.tsv | 21 +++ crates/ui-egui/src/i18n/it.tsv | 21 +++ crates/ui-egui/src/i18n/ja.tsv | 21 +++ crates/ui-egui/src/i18n/ka.tsv | 21 +++ crates/ui-egui/src/i18n/ko.tsv | 21 +++ crates/ui-egui/src/i18n/nl.tsv | 21 +++ crates/ui-egui/src/i18n/pl.tsv | 21 +++ crates/ui-egui/src/i18n/pt-br.tsv | 21 +++ crates/ui-egui/src/i18n/ru.tsv | 21 +++ crates/ui-egui/src/i18n/sv.tsv | 21 +++ crates/ui-egui/src/i18n/tr.tsv | 21 +++ crates/ui-egui/src/i18n/uk.tsv | 21 +++ crates/ui-egui/src/i18n/vi.tsv | 21 +++ crates/ui-egui/src/i18n/zh-hans.tsv | 21 +++ crates/ui-egui/src/i18n/zh-hant.tsv | 21 +++ crates/ui-egui/src/io/save.rs | 1 + crates/ui-egui/src/prefs_dialog.rs | 76 ++++++++++ crates/ui-egui/src/prefs_dialog/tests.rs | 99 +++++++++++++ docs/mcp.md | 18 +++ docs/target-app-parity.md | 1 + 38 files changed, 1049 insertions(+), 3 deletions(-) create mode 100644 crates/engine/src/tests/prefs_file.rs diff --git a/crates/engine/src/cmd/fileio/batch.rs b/crates/engine/src/cmd/fileio/batch.rs index 60f8be329..dec69c9ee 100644 --- a/crates/engine/src/cmd/fileio/batch.rs +++ b/crates/engine/src/cmd/fileio/batch.rs @@ -92,6 +92,9 @@ fn run_steps( if id == "command.batch" { return Err(bad("command.batch", "batches cannot nest")); } + if id == "prefs.import" { + return Err(bad("command.batch", "`prefs.import` runs on its own, not as a batch step")); + } if s.active().is_some() { s.begin_interaction(label)?; } diff --git a/crates/engine/src/cmd/prefscmds.rs b/crates/engine/src/cmd/prefscmds.rs index 45b723e98..ffca12454 100644 --- a/crates/engine/src/cmd/prefscmds.rs +++ b/crates/engine/src/cmd/prefscmds.rs @@ -1,11 +1,12 @@ -//! Preferences (Edit → Preferences): `prefs.get`, `prefs.set`, `prefs.reset`, `prefs.list`. +//! Preferences (Edit → Preferences): `prefs.get`, `prefs.set`, `prefs.reset`, `prefs.list`, +//! `prefs.export`, `prefs.import`. //! //! [`PREF_SPECS`] is the single source of truth for every preference: its category, section, //! label and value kind. The Preferences dialog renders from it and `prefs.set` validates with it. use serde_json::{Map, Value, json}; -use super::{CommandSpec, EngineError, Result, Session, always, bad, cmd, str_param, switch_or}; +use super::{CommandSpec, EngineError, Result, Session, always, bad, cmd, str_param, switch_or, text_param}; use crate::Prefs; use crate::units::Measure; use vectorcraft_doc::{GridPrefs, Unit}; @@ -457,6 +458,9 @@ pub const PREF_GROUPS: &[&str] = &["eyedropper", "perspectiveWidget", "pathfinde /// Validate a value for preference group `key` against `current`. fn validate_group(key: &str, current: &Value, v: &Value) -> std::result::Result { + if !v.is_object() { + return Err(format!("`{key}` must be an object")); + } match key { "eyedropper" => { let cur: super::EyedropperOptions = serde_json::from_value(current.clone()).unwrap_or_default(); @@ -763,15 +767,117 @@ pub fn specs() -> Vec { "Set Preference", [], None, - "{key, value} or {values: {key: value, …}, documentUnits?: bool=true} set preferences (validated; see prefs.list; lengths in pt or strings with a unit; unitsGeneral also sets the active document's units, one undo step, unless documentUnits is false; the eyedropper group takes a partial object, as eyedropper.setOptions; a new recoveryFolder must be a folder that can be listed and written in, and a new pluginsFolder a folder that can be listed: otherwise an error, and nothing changes; an empty value is not checked)", + "{key, value} or {values: {key: value, …}, documentUnits?: bool=true} set preferences (validated; see prefs.list; lengths in pt or strings with a unit; unitsGeneral also sets the active document's units, one undo step, unless documentUnits is false; the eyedropper group takes a partial object, as eyedropper.setOptions, and a group given anything but an object is an error; a new recoveryFolder must be a folder that can be listed and written in, and a new pluginsFolder a folder that can be listed: otherwise an error, and nothing changes; an empty value is not checked)", always, set ), cmd!(query "prefs.reset", "Reset Preferences", [], None, "{category?} reset one category (or everything) to defaults", always, reset), cmd!(query "prefs.list", "List Preferences", [], None, "{} → [{key, category, section, label, kind, value, …}] every preference", always, list), + cmd!( + query "prefs.export", + "Export Preferences", + [], + None, + "{path?} write the preferences as a JSON file {format: \"vectorcraft-preferences\", version: 1, prefs: {key: value, …}}: every preference prefs.list lists and the eyedropper, perspectiveWidget, pathfinder and glyphSnap groups, without the folder preferences fontsFolder, pluginsFolder, recoveryFolder and templatesFolder and without saved presets; a file over 1 MB, which prefs.import would refuse, is an error and is not written → {path, count}; without path → {data: the file's text, count}", + always, + export + ), + cmd!( + query "prefs.import", + "Import Preferences", + [], + None, + "{path? | data?: the file's text} apply the preferences of a file prefs.export wrote (at most 1 MB). Every value is checked first, as prefs.set checks it: another format, a newer version, an unknown preference, a value of the wrong type or out of range, or a folder preference is an error, and nothing changes. Preferences the file leaves out keep their values; the open document's units stay as they are; no undo step; not a command.batch step → {imported: count}", + always, + import + ), ] } +/// The `format` of a preferences file (`prefs.export`, `prefs.import`). +pub const PREFS_FORMAT: &str = "vectorcraft-preferences"; + +/// The `version` `prefs.export` writes; `prefs.import` refuses a newer one. +pub const PREFS_VERSION: u64 = 1; + +/// The largest preferences file `prefs.import` reads. +const MAX_PREFS_FILE_BYTES: usize = 1 << 20; + +/// Folder preferences, which name folders on one computer: a preferences file leaves them out. +const FOLDER_PREFS: &[&str] = &["fontsFolder", "pluginsFolder", "recoveryFolder", "templatesFolder"]; + +/// The preferences `prefs.export` writes: every [`PREF_SPECS`] key but the [`FOLDER_PREFS`], and +/// the [`PREF_GROUPS`]. +fn exported(prefs: &crate::Prefs) -> Map { + let all = prefs.to_json(); + let keys = PREF_SPECS.iter().map(|s| s.key).filter(|k| !FOLDER_PREFS.contains(k)).chain(PREF_GROUPS.iter().copied()); + keys.map(|k| (k.to_string(), all[k].clone())).collect() +} + +fn export(s: &mut Session, p: &Value) -> Result { + const C: &str = "prefs.export"; + let path = text_param(p, "path", C)?; + let prefs = exported(&s.prefs); + let count = prefs.len(); + let file = json!({ "format": PREFS_FORMAT, "version": PREFS_VERSION, "prefs": prefs }); + let text = serde_json::to_string_pretty(&file).map_err(|e| bad(C, e.to_string()))?; + // A file `prefs.import` would refuse is not written. + if text.len() > MAX_PREFS_FILE_BYTES { + return Err(bad(C, "the file is larger than 1 MB")); + } + match path { + Some(path) => { + super::fileio::write_file(path, text.as_bytes())?; + Ok(json!({"path": path, "count": count})) + } + None => Ok(json!({"data": text, "count": count})), + } +} + +/// The preferences in the bytes of a preferences file, unchecked: `Err` says why the file is not +/// one this version reads. +fn read_prefs_file(bytes: &[u8]) -> std::result::Result, String> { + let v: Value = serde_json::from_slice(bytes).map_err(|e| format!("not a preferences file: {e}"))?; + let Some(file) = v.as_object() else { return Err("not a preferences file: no JSON object".into()) }; + match file.get("format").and_then(Value::as_str) { + Some(PREFS_FORMAT) => {} + Some(format) => return Err(format!("not a preferences file (format `{format}`)")), + None => return Err("not a preferences file: no `format`".into()), + } + match file.get("version").and_then(Value::as_u64) { + Some(v) if v > PREFS_VERSION => return Err(format!("the file was saved by a newer version of VectorCraft (version {v})")), + Some(1..) => {} + _ => return Err("not a preferences file: no `version`".into()), + } + let Some(prefs) = file.get("prefs").and_then(Value::as_object) else { return Err("not a preferences file: no `prefs`".into()) }; + if prefs.is_empty() { + return Err("the file holds no preferences".into()); + } + if let Some(key) = prefs.keys().find(|k| FOLDER_PREFS.contains(&k.as_str())) { + return Err(format!("`{key}` names a folder, which preferences files leave out")); + } + Ok(prefs.clone()) +} + +fn import(s: &mut Session, p: &Value) -> Result { + const C: &str = "prefs.import"; + let too_large = || bad(C, "the file is larger than 1 MB"); + let bytes = match (text_param(p, "path", C)?, text_param(p, "data", C)?) { + (Some(path), None) => super::fileio::read_file_head(path, MAX_PREFS_FILE_BYTES + 1)?, + (None, Some(text)) if text.len() > MAX_PREFS_FILE_BYTES => return Err(too_large()), + (None, Some(text)) => text.as_bytes().to_vec(), + _ => return Err(bad(C, "give `path` or `data`")), + }; + if bytes.len() > MAX_PREFS_FILE_BYTES { + return Err(too_large()); + } + let values = read_prefs_file(&bytes).map_err(|e| bad(C, e))?; + let mut next = s.prefs.clone(); + next.set_values(&values).map_err(|e| bad(C, e))?; + s.apply_prefs(next); + Ok(json!({ "imported": values.len() })) +} + fn get(s: &mut Session, p: &Value) -> Result { let all = s.prefs.to_json(); match str_param(p, "key") { diff --git a/crates/engine/src/command_sets.rs b/crates/engine/src/command_sets.rs index 77068d125..8d0d2b8ee 100644 --- a/crates/engine/src/command_sets.rs +++ b/crates/engine/src/command_sets.rs @@ -94,6 +94,7 @@ const WAIT_FOR_TOOL_EDIT: &[&str] = &[ "view.separationsPreview", "prefs.set", "prefs.reset", + "prefs.import", "text.addFontFiles", ]; diff --git a/crates/engine/src/tests.rs b/crates/engine/src/tests.rs index f6e32d719..ac0c8444a 100644 --- a/crates/engine/src/tests.rs +++ b/crates/engine/src/tests.rs @@ -130,6 +130,7 @@ mod place; mod placed_document; mod plugins; mod prefs; +mod prefs_file; mod previewbounds; mod print; mod printadvanced; diff --git a/crates/engine/src/tests/prefs_file.rs b/crates/engine/src/tests/prefs_file.rs new file mode 100644 index 000000000..7693ad0d5 --- /dev/null +++ b/crates/engine/src/tests/prefs_file.rs @@ -0,0 +1,169 @@ +//! Preferences files: `prefs.export` writes the preferences as JSON and `prefs.import` checks and +//! applies such a file. + +use serde_json::{Value, json}; +use vectorcraft_doc::Unit; + +use crate::cmd::prefscmds::{PREF_GROUPS, PREF_SPECS, PREFS_FORMAT}; +use crate::*; + +fn is_bad_params(r: Result) -> bool { + matches!(r, Err(EngineError::BadParams { .. })) +} + +/// Folder preferences, which a preferences file leaves out. +const FOLDERS: [&str; 4] = ["fontsFolder", "pluginsFolder", "recoveryFolder", "templatesFolder"]; + +/// A preferences file holding `prefs`, as `prefs.export` writes one. +fn file(prefs: Value) -> String { + json!({"format": PREFS_FORMAT, "version": 1, "prefs": prefs}).to_string() +} + +/// A session whose preferences differ from the defaults in every kind of value. +fn changed() -> Session { + let mut s = Session::new(); + let values = json!({ + "showToolTips": false, + "keyboardIncrement": 2.5, + "historyStates": 42, + "uiAccent": "green", + "guideColor": "#12ab34", + "unitsGeneral": "inches", + "eyedropper": {"sampleSize": 5}, + }); + s.execute("prefs.set", &json!({"values": values})).unwrap(); + s +} + +/// The preferences a preferences file holds, read from a session. +fn in_file(s: &Session) -> Value { + let all = s.prefs.to_json(); + let keys = PREF_SPECS.iter().map(|sp| sp.key).filter(|k| !FOLDERS.contains(k)).chain(PREF_GROUPS.iter().copied()); + Value::Object(keys.map(|k| (k.to_string(), all[k].clone())).collect()) +} + +#[test] +fn exported_preferences_import_unchanged() { + let mut s = changed(); + s.prefs.fonts_folder = "/fonts/here".into(); + let r = s.execute("prefs.export", &json!({})).unwrap(); + let text = r["data"].as_str().unwrap().to_string(); + let written: Value = serde_json::from_str(&text).unwrap(); + assert_eq!((written["format"].as_str(), written["version"].as_u64()), (Some(PREFS_FORMAT), Some(1))); + let prefs = written["prefs"].as_object().unwrap(); + assert_eq!(r["count"], json!(prefs.len())); + assert_eq!(prefs.len(), PREF_SPECS.len() - FOLDERS.len() + PREF_GROUPS.len()); + for k in FOLDERS { + assert!(!prefs.contains_key(k), "{k} is left out"); + } + assert!(!prefs.contains_key("printPresets"), "saved presets are left out"); + assert_eq!(prefs["uiAccent"], "green"); + + // Into a session with defaults and a document in millimeters. + let mut t = Session::new(); + t.execute("file.new", &json!({"width": 400, "height": 400})).unwrap(); + t.execute("document.setUnits", &json!({"units": "Millimeters"})).unwrap(); + let undo = t.doc().unwrap().history.undo.len(); + let r = t.execute("prefs.import", &json!({"data": text})).unwrap(); + assert_eq!(r["imported"], json!(prefs.len())); + assert_eq!(in_file(&t), in_file(&s), "export then import gives the same preferences"); + assert_eq!(t.prefs.units_general, "inches"); + assert_eq!(t.doc().unwrap().doc.units, Unit::Millimeters, "the document's units stay as they are"); + assert_eq!(t.doc().unwrap().history.undo.len(), undo, "no undo step"); + assert_eq!(t.prefs.fonts_folder, "", "folders are not carried over"); + + // Through a file, too. + let path = std::env::temp_dir().join(format!("vc-prefs-{}.json", std::process::id())).to_string_lossy().to_string(); + assert_eq!(s.execute("prefs.export", &json!({"path": path})).unwrap()["path"], json!(path)); + let mut u = Session::new(); + u.execute("prefs.import", &json!({"path": path})).unwrap(); + let _ = std::fs::remove_file(&path); + assert_eq!(in_file(&u), in_file(&s)); +} + +/// A file written by hand, not by `prefs.export`: so the two sides cannot agree on a wrong layout. +#[test] +fn a_hand_written_file_sets_what_it_names_and_leaves_the_rest() { + let mut s = Session::new(); + s.execute("prefs.set", &json!({"key": "historyStates", "value": 77})).unwrap(); + let text = r##"{ + "format": "vectorcraft-preferences", + "version": 1, + "prefs": {"uiAccent": "Violet", "keyboardIncrement": "3 pt", "guideColor": "#ABCDEF", "pathfinder": {}} + }"##; + assert_eq!(s.execute("prefs.import", &json!({"data": text})).unwrap(), json!({"imported": 4})); + assert_eq!(s.prefs.ui_accent, "violet"); + assert_eq!(s.prefs.keyboard_increment, 3.0); + assert_eq!(s.prefs.guide_color, "#abcdef"); + assert_eq!(s.prefs.history_states, 77, "a preference the file leaves out keeps its value"); +} + +#[test] +fn a_bad_preferences_file_is_refused_and_nothing_changes() { + let mut s = changed(); + let before = s.prefs.to_json().to_string(); + let too_large = " ".repeat((1 << 20) + 1); + let cases: Vec<(Value, &str)> = vec![ + (json!({"data": ""}), "not a preferences file"), + (json!({"data": "{\"format\": "}), "not a preferences file"), + (json!({"data": "[]"}), "not a preferences file: no JSON object"), + (json!({"data": "{\"version\": 1, \"prefs\": {}}"}), "no `format`"), + (json!({"data": "{\"format\": \"vcprintpresets\", \"version\": 1, \"prefs\": {}}"}), "(format `vcprintpresets`)"), + (json!({"data": "{\"format\": \"vectorcraft-preferences\", \"prefs\": {\"uiAccent\": \"blue\"}}"}), "no `version`"), + (json!({"data": "{\"format\": \"vectorcraft-preferences\", \"version\": 0, \"prefs\": {\"uiAccent\": \"blue\"}}"}), "no `version`"), + (json!({"data": file(json!({"uiAccent": "blue"})).replace("\"version\":1", "\"version\":2")}), "newer version of VectorCraft (version 2)"), + (json!({"data": "{\"format\": \"vectorcraft-preferences\", \"version\": 1}"}), "no `prefs`"), + (json!({"data": file(json!({}))}), "holds no preferences"), + (json!({"data": file(json!({"uiAccent": "blue", "noSuchPreference": 1}))}), "unknown preference `noSuchPreference`"), + (json!({"data": file(json!({"uiAccent": "blue", "showToolTips": 3}))}), "`showToolTips` must be true or false"), + (json!({"data": file(json!({"uiAccent": "blue", "historyStates": 5000}))}), "`historyStates` must be a whole number between 5 and 1000"), + (json!({"data": file(json!({"uiAccent": "blue", "eyedropper": 5}))}), "`eyedropper` must be an object"), + (json!({"data": file(json!({"uiAccent": "blue", "fontsFolder": "/fonts"}))}), "`fontsFolder` names a folder"), + (json!({"data": too_large}), "larger than 1 MB"), + (json!({"data": 5}), "`data` must be a string"), + (json!({"path": "/a.json", "data": file(json!({"uiAccent": "blue"}))}), "give `path` or `data`"), + (json!({}), "give `path` or `data`"), + ]; + for (params, says) in cases { + let r = s.execute("prefs.import", ¶ms); + let e = r.as_ref().map_err(ToString::to_string).unwrap_err(); + assert!(e.contains(says), "{params}: {e}"); + assert!(is_bad_params(r), "{params}"); + assert_eq!(s.prefs.to_json().to_string(), before, "{params}: nothing changes"); + } +} + +/// `prefs.import` as a step of a batch is an error, and the preferences stay as they are. +#[test] +fn prefs_import_is_refused_as_a_batch_step() { + let mut s = Session::new(); + s.execute("file.new", &json!({"width": 400, "height": 400})).unwrap(); + let before = s.prefs.to_json(); + let steps = json!({"commands": [{"command": "prefs.import", "params": {"data": file(json!({"uiAccent": "green"}))}}]}); + let e = s.execute("command.batch", &steps).unwrap_err().to_string(); + assert!(e.contains("`prefs.import` runs on its own"), "{e}"); + assert_eq!(s.prefs.to_json(), before); +} + +#[test] +fn a_file_over_the_limit_is_refused_before_it_is_parsed() { + let mut s = Session::new(); + let before = s.prefs.to_json().to_string(); + let path = std::env::temp_dir().join(format!("vc-prefs-large-{}.json", std::process::id())); + // A valid file padded past 1 MB: refused for its size, not read for its preferences. + let mut text = file(json!({"uiAccent": "pink"})); + text.push_str(&" ".repeat(1 << 20)); + std::fs::write(&path, text).unwrap(); + let e = s.execute("prefs.import", &json!({"path": path.to_string_lossy()})).unwrap_err().to_string(); + let _ = std::fs::remove_file(&path); + assert!(e.contains("larger than 1 MB"), "{e}"); + assert_eq!(s.prefs.to_json().to_string(), before); + // Nor is such a file written: a preferences file over 1 MB could not be imported again. + s.prefs.hyphenation_exceptions = "x".repeat(1 << 20); + let e = s.execute("prefs.export", &json!({})).unwrap_err().to_string(); + assert!(e.contains("larger than 1 MB"), "{e}"); + s.prefs.hyphenation_exceptions.clear(); + let missing = std::env::temp_dir().join("vc-prefs-no-such-file.json"); + assert!(s.execute("prefs.import", &json!({"path": missing.to_string_lossy()})).is_err()); + assert_eq!(s.prefs.to_json().to_string(), before); +} diff --git a/crates/ui-egui/src/app/commands.rs b/crates/ui-egui/src/app/commands.rs index 9e698b1bf..d97d11181 100644 --- a/crates/ui-egui/src/app/commands.rs +++ b/crates/ui-egui/src/app/commands.rs @@ -73,6 +73,7 @@ fn substitute_warnings_follow_current_font_inventory() { impl VectorcraftApp { /// Run a UI or engine command by id. The single entry point for every frontend path. pub fn run(&mut self, id: &str, params: Value) -> Result { + let touch_workspace = self.session.prefs.touch_workspace; // A file dialog it shows off the UI thread runs it again with the path picked. let result = if self.picks.is_entry_free() { let entry = picks::Entry::Command(id.to_string(), params.clone()); @@ -80,6 +81,9 @@ impl VectorcraftApp { } else { self.run_now(id, params) }; + if id == "prefs.import" && result.is_ok() { + crate::prefs_dialog::imported(self, touch_workspace); + } placing::settle_swatch_paste(self); result } diff --git a/crates/ui-egui/src/i18n/bg.tsv b/crates/ui-egui/src/i18n/bg.tsv index 354d811a3..6c865408b 100644 --- a/crates/ui-egui/src/i18n/bg.tsv +++ b/crates/ui-egui/src/i18n/bg.tsv @@ -2744,6 +2744,12 @@ digits Malayalam Малаялам Reset OpenType Features Нулиране на OpenType характеристиките Reset Panel Нулиране на панела Reset Preferences Нулиране на настройките + Export Preferences… Експортиране на настройките… + Import Preferences… Импортиране на настройките… + Save the applied preferences to a JSON file Запазване на приложените настройки във файл JSON + Apply the preferences in a file Export Preferences saved Прилагане на настройките от файл, запазен с „Експортиране на настройките“ + Export Preferences Експортиране на настройките + Import Preferences Импортиране на настройките Search Preferences Търсене в настройките No settings found Няма намерени настройки Reset Profiles Нулиране на профилите @@ -4506,6 +4512,20 @@ axis Both И двете @msg unknown overprints `{o}` (preserve, simulate or discard) непознати надписи `{o}` (запазване, симулиране или отхвърляне) @msg unknown panel `{raw}` непознат панел `{raw}` @msg unknown preference `{key}` непозната настройка `{key}` +@msg Preferences imported. Настройките са импортирани. +@msg not a preferences file: {e} не е файл с настройки: {e} +@msg not a preferences file: no JSON object не е файл с настройки: няма обект JSON +@msg not a preferences file (format `{format}`) не е файл с настройки (формат `{format}`) +@msg not a preferences file: no `format` не е файл с настройки: няма `format` +@msg not a preferences file: no `version` не е файл с настройки: няма `version` +@msg not a preferences file: no `prefs` не е файл с настройки: няма `prefs` +@msg the file was saved by a newer version of VectorCraft (version {v}) файлът е запазен от по-нова версия на VectorCraft (версия {v}) +@msg the file holds no preferences файлът не съдържа настройки +@msg `{key}` names a folder, which preferences files leave out `{key}` указва папка, а файловете с настройки не съдържат папки +@msg the file is larger than 1 MB файлът е по-голям от 1 MB +@msg give `path` or `data` посочете `path` или `data` +@msg `{key}` must be an object `{key}` трябва да бъде обект +@msg `prefs.import` runs on its own, not as a batch step `prefs.import` се изпълнява самостоятелно, а не като стъпка от пакет @msg unknown preset (one of {_1}) непозната предварителна настройка (една от {_1}) @msg unknown preset `{name}` (high, medium, low or a saved one: see flattener.presets.list) непозната предварителна настройка `{name}` (висока, средна, ниска или запазена: вижте flattener.presets.list) @msg unknown preset `{name}` (see flattener.presets.list) непозната предварителна настройка `{name}` (вижте flattener.presets.list) @@ -5421,6 +5441,7 @@ file-filter Flattener presets Предварителни настройки за file-filter PDF presets Предварителни настройки за PDF file-filter PDF-compatible .ai Съвместими с PDF .ai файлове file-filter Print presets Предварителни настройки за печат +file-filter Preferences Настройки file-filter Export presets Набори за експортиране file-filter SVG Compressed Компресиран SVG file-filter Swatch libraries Библиотеки с образци diff --git a/crates/ui-egui/src/i18n/bs.tsv b/crates/ui-egui/src/i18n/bs.tsv index d1fc0ea54..cdf727828 100644 --- a/crates/ui-egui/src/i18n/bs.tsv +++ b/crates/ui-egui/src/i18n/bs.tsv @@ -3712,6 +3712,12 @@ theme Appearance Izgled Real-time Drawing and Editing Crtanje i uređivanje u stvarnom vremenu Render Threads (-1 = Automatic) Niti iscrtavanja (-1 = automatski) Reset Preferences Vrati postavke na početne + Export Preferences… Izvezi postavke… + Import Preferences… Uvezi postavke… + Save the applied preferences to a JSON file Sačuvaj primijenjene postavke u JSON datoteku + Apply the preferences in a file Export Preferences saved Primijeni postavke iz datoteke sačuvane naredbom Izvezi postavke + Export Preferences Izvezi postavke + Import Preferences Uvezi postavke Search Preferences Pretraga postavki No settings found Nema pronađenih postavki Scale Corners Skaliraj uglove @@ -4905,6 +4911,20 @@ imageTrace Outline Obris @msg unknown overprints `{o}` (preserve, simulate or discard) nepoznati pretisci `{o}` (preserve, simulate ili discard) @msg unknown panel `{raw}` nepoznat panel `{raw}` @msg unknown preference `{key}` nepoznata postavka `{key}` +@msg Preferences imported. Postavke su uvezene. +@msg not a preferences file: {e} nije datoteka postavki: {e} +@msg not a preferences file: no JSON object nije datoteka postavki: nema JSON objekta +@msg not a preferences file (format `{format}`) nije datoteka postavki (format `{format}`) +@msg not a preferences file: no `format` nije datoteka postavki: nema `format` +@msg not a preferences file: no `version` nije datoteka postavki: nema `version` +@msg not a preferences file: no `prefs` nije datoteka postavki: nema `prefs` +@msg the file was saved by a newer version of VectorCraft (version {v}) datoteku je sačuvala novija verzija programa VectorCraft (verzija {v}) +@msg the file holds no preferences datoteka ne sadrži postavke +@msg `{key}` names a folder, which preferences files leave out `{key}` navodi folder, a datoteke postavki ne sadrže foldere +@msg the file is larger than 1 MB datoteka je veća od 1 MB +@msg give `path` or `data` navedite `path` ili `data` +@msg `{key}` must be an object `{key}` mora biti objekat +@msg `prefs.import` runs on its own, not as a batch step `prefs.import` se pokreće samostalno, ne kao korak paketa @msg unknown preset (one of {_1}) nepoznat predložak (jedan od {_1}) @msg unknown preset `{name}` (high, medium, low or a saved one: see flattener.presets.list) nepoznat predložak `{name}` (high, medium, low ili sačuvani: vidi flattener.presets.list) @msg unknown preset `{name}` (see flattener.presets.list) nepoznat predložak `{name}` (vidi flattener.presets.list) @@ -5445,6 +5465,7 @@ file-filter Swatch libraries Biblioteke uzoraka boja file-filter Flattener presets Predlošci spljoštavanja file-filter PDF presets PDF predlošci file-filter Print presets Predlošci štampanja +file-filter Preferences Postavke file-filter Export presets Predlošci izvoza file-filter SVG Compressed Komprimirani SVG file-filter PDF-compatible .ai .ai kompatibilan s PDF-om diff --git a/crates/ui-egui/src/i18n/ca.tsv b/crates/ui-egui/src/i18n/ca.tsv index 9e77ecb32..d2e2b53ad 100644 --- a/crates/ui-egui/src/i18n/ca.tsv +++ b/crates/ui-egui/src/i18n/ca.tsv @@ -3474,6 +3474,12 @@ digits Malayalam Malaiàlam Reset Painting Restableix Pintura Reset Panel Restableix el panell Reset Preferences Restableix les preferències + Export Preferences… Exporta les preferències… + Import Preferences… Importa les preferències… + Save the applied preferences to a JSON file Desa les preferències aplicades en un fitxer JSON + Apply the preferences in a file Export Preferences saved Aplica les preferències d’un fitxer desat amb Exporta les preferències + Export Preferences Exporta les preferències + Import Preferences Importa les preferències Search Preferences Cerca a les preferències No settings found No s'ha trobat cap paràmetre Reset Printing and Proofing Restableix impressió i prova @@ -5904,6 +5910,20 @@ digits Malayalam Malaiàlam @msg missing `{path}` falta `{path}` @msg `{path}` must be {expected}, not {got} `{path}` ha de ser {expected}, no {got} @msg unknown preference `{key}` preferència desconeguda `{key}` +@msg Preferences imported. S’han importat les preferències. +@msg not a preferences file: {e} no és un fitxer de preferències: {e} +@msg not a preferences file: no JSON object no és un fitxer de preferències: no hi ha cap objecte JSON +@msg not a preferences file (format `{format}`) no és un fitxer de preferències (format `{format}`) +@msg not a preferences file: no `format` no és un fitxer de preferències: no hi ha `format` +@msg not a preferences file: no `version` no és un fitxer de preferències: no hi ha `version` +@msg not a preferences file: no `prefs` no és un fitxer de preferències: no hi ha `prefs` +@msg the file was saved by a newer version of VectorCraft (version {v}) el fitxer s’ha desat amb una versió més nova del VectorCraft (versió {v}) +@msg the file holds no preferences el fitxer no conté cap preferència +@msg `{key}` names a folder, which preferences files leave out `{key}` indica una carpeta, i els fitxers de preferències no inclouen carpetes +@msg the file is larger than 1 MB el fitxer ocupa més d’1 MB +@msg give `path` or `data` indiqueu `path` o `data` +@msg `{key}` must be an object `{key}` ha de ser un objecte +@msg `prefs.import` runs on its own, not as a batch step `prefs.import` s’executa sol, no com a pas d’un lot @msg unknown preset (one of {_1}) preajust desconegut (un de {_1}) @msg unknown preset `{name}` (high, medium, low or a saved one: see flattener.presets.list) preajust desconegut `{name}` (high, medium, low o un de desat: consulta flattener.presets.list) @msg unknown preset `{name}` (see flattener.presets.list) preajust desconegut `{name}` (consulta flattener.presets.list) @@ -6066,6 +6086,7 @@ file-filter Flattener presets Preajustos de l’aplanador file-filter PDF presets Preajustos de PDF file-filter PDF-compatible .ai .ai compatible amb PDF file-filter Print presets Preajustos d’impressió +file-filter Preferences Preferències file-filter Export presets Preajustos d’exportació file-filter SVG Compressed SVG comprimit file-filter Swatch libraries Biblioteques de mostres diff --git a/crates/ui-egui/src/i18n/cs.tsv b/crates/ui-egui/src/i18n/cs.tsv index de1723b90..b9b2b5b72 100644 --- a/crates/ui-egui/src/i18n/cs.tsv +++ b/crates/ui-egui/src/i18n/cs.tsv @@ -3764,6 +3764,12 @@ theme Appearance Motiv Real-time Drawing and Editing Kreslení a úpravy v reálném čase Render Threads (-1 = Automatic) Vlákna vykreslování (-1 = automaticky) Reset Preferences Obnovit předvolby + Export Preferences… Exportovat předvolby… + Import Preferences… Importovat předvolby… + Save the applied preferences to a JSON file Uložit použité předvolby do souboru JSON + Apply the preferences in a file Export Preferences saved Použít předvolby ze souboru uloženého příkazem Exportovat předvolby + Export Preferences Exportovat předvolby + Import Preferences Importovat předvolby Search Preferences Hledat v předvolbách No settings found Nenalezena žádná nastavení Scale Corners Měnit velikost rohů @@ -4989,6 +4995,20 @@ imageTrace Outline Obrys @msg unknown overprints `{o}` (preserve, simulate or discard) neznámé přetisky `{o}` (preserve, simulate nebo discard) @msg unknown panel `{raw}` neznámý panel `{raw}` @msg unknown preference `{key}` neznámá předvolba `{key}` +@msg Preferences imported. Předvolby byly importovány. +@msg not a preferences file: {e} nejde o soubor předvoleb: {e} +@msg not a preferences file: no JSON object nejde o soubor předvoleb: chybí objekt JSON +@msg not a preferences file (format `{format}`) nejde o soubor předvoleb (formát `{format}`) +@msg not a preferences file: no `format` nejde o soubor předvoleb: chybí `format` +@msg not a preferences file: no `version` nejde o soubor předvoleb: chybí `version` +@msg not a preferences file: no `prefs` nejde o soubor předvoleb: chybí `prefs` +@msg the file was saved by a newer version of VectorCraft (version {v}) soubor byl uložen novější verzí aplikace VectorCraft (verze {v}) +@msg the file holds no preferences soubor neobsahuje žádné předvolby +@msg `{key}` names a folder, which preferences files leave out `{key}` určuje složku a soubory předvoleb složky neobsahují +@msg the file is larger than 1 MB soubor je větší než 1 MB +@msg give `path` or `data` zadejte `path` nebo `data` +@msg `{key}` must be an object `{key}` musí být objekt +@msg `prefs.import` runs on its own, not as a batch step `prefs.import` se spouští samostatně, ne jako krok dávky @msg unknown preset (one of {_1}) neznámé přednastavení (jedno z {_1}) @msg unknown preset `{name}` (high, medium, low or a saved one: see flattener.presets.list) neznámé přednastavení `{name}` (high, medium, low nebo uložené: viz flattener.presets.list) @msg unknown preset `{name}` (see flattener.presets.list) neznámé přednastavení `{name}` (viz flattener.presets.list) @@ -5560,6 +5580,7 @@ file-filter Swatch libraries Knihovny vzorníků file-filter Flattener presets Přednastavení sloučení průhlednosti file-filter PDF presets Přednastavení PDF file-filter Print presets Přednastavení tisku +file-filter Preferences Předvolby file-filter Export presets Přednastavení exportu file-filter SVG Compressed Komprimované SVG file-filter PDF-compatible .ai .ai kompatibilní s PDF diff --git a/crates/ui-egui/src/i18n/da.tsv b/crates/ui-egui/src/i18n/da.tsv index 77687493e..e840280d7 100644 --- a/crates/ui-egui/src/i18n/da.tsv +++ b/crates/ui-egui/src/i18n/da.tsv @@ -3487,6 +3487,12 @@ theme Appearance Udseende Real-time Drawing and Editing Tegning og redigering i realtid Render Threads (-1 = Automatic) Renderingstråde (-1 = automatisk) Reset Preferences Nulstil indstillinger + Export Preferences… Eksportér præferencer… + Import Preferences… Importér præferencer… + Save the applied preferences to a JSON file Gem de anvendte præferencer i en JSON-fil + Apply the preferences in a file Export Preferences saved Anvend præferencerne i en fil, som Eksportér præferencer har gemt + Export Preferences Eksportér præferencer + Import Preferences Importér præferencer Search Preferences Søg i præferencer No settings found Ingen indstillinger fundet Scale Corners Skalér hjørner @@ -4602,6 +4608,20 @@ imageTrace Outline Kontur @msg unknown overprints `{o}` (preserve, simulate or discard) ukendt overprint `{o}` (bevar, simulér eller forkast) @msg unknown panel `{raw}` ukendt panel `{raw}` @msg unknown preference `{key}` ukendt præference `{key}` +@msg Preferences imported. Præferencerne er importeret. +@msg not a preferences file: {e} ikke en præferencefil: {e} +@msg not a preferences file: no JSON object ikke en præferencefil: intet JSON-objekt +@msg not a preferences file (format `{format}`) ikke en præferencefil (format `{format}`) +@msg not a preferences file: no `format` ikke en præferencefil: intet `format` +@msg not a preferences file: no `version` ikke en præferencefil: ingen `version` +@msg not a preferences file: no `prefs` ikke en præferencefil: ingen `prefs` +@msg the file was saved by a newer version of VectorCraft (version {v}) filen er gemt af en nyere version af VectorCraft (version {v}) +@msg the file holds no preferences filen indeholder ingen præferencer +@msg `{key}` names a folder, which preferences files leave out `{key}` angiver en mappe, og præferencefiler indeholder ikke mapper +@msg the file is larger than 1 MB filen er større end 1 MB +@msg give `path` or `data` angiv `path` eller `data` +@msg `{key}` must be an object `{key}` skal være et objekt +@msg `prefs.import` runs on its own, not as a batch step `prefs.import` køres alene, ikke som et trin i en batch @msg unknown preset (one of {_1}) ukendt forudindstilling (én af {_1}) @msg unknown preset `{name}` (high, medium, low or a saved one: see flattener.presets.list) ukendt forudindstilling `{name}` (høj, mellem, lav eller en gemt: se flattener.presets.list) @msg unknown preset `{name}` (see flattener.presets.list) ukendt forudindstilling `{name}` (se flattener.presets.list) @@ -5098,6 +5118,7 @@ file-filter Swatch libraries Farveprøvebiblioteker file-filter Flattener presets Flattener-forudindstillinger file-filter PDF presets PDF-forudindstillinger file-filter Print presets Printforudindstillinger +file-filter Preferences Præferencer file-filter Export presets Eksportforudindstillinger file-filter SVG Compressed Komprimeret SVG file-filter PDF-compatible .ai PDF-kompatibel .ai diff --git a/crates/ui-egui/src/i18n/de.tsv b/crates/ui-egui/src/i18n/de.tsv index 7e2619386..436739f85 100644 --- a/crates/ui-egui/src/i18n/de.tsv +++ b/crates/ui-egui/src/i18n/de.tsv @@ -1248,6 +1248,7 @@ file-filter Swatch libraries Farbfeldbibliotheken file-filter Flattener presets Reduzierungsvorgaben file-filter PDF presets PDF-Vorgaben file-filter Print presets Druckvorgaben +file-filter Preferences Voreinstellungen file-filter Export presets Exportvorgaben file-filter SVG Compressed SVG komprimiert file-filter PDF-compatible .ai PDF-kompatible .ai @@ -2776,6 +2777,12 @@ digits Malayalam Malayalam Reset OpenType Features OpenType-Funktionen zurücksetzen Reset Panel Bedienfeld zurücksetzen Reset Preferences Voreinstellungen zurücksetzen + Export Preferences… Voreinstellungen exportieren… + Import Preferences… Voreinstellungen importieren… + Save the applied preferences to a JSON file Die angewendeten Voreinstellungen in einer JSON-Datei speichern + Apply the preferences in a file Export Preferences saved Die Voreinstellungen aus einer mit „Voreinstellungen exportieren“ gespeicherten Datei anwenden + Export Preferences Voreinstellungen exportieren + Import Preferences Voreinstellungen importieren Search Preferences Voreinstellungen durchsuchen No settings found Keine Einstellungen gefunden Reset Profiles Profile zurücksetzen @@ -4597,6 +4604,20 @@ theme Appearance Erscheinungsbild @msg unknown overprints `{o}` (preserve, simulate or discard) unbekannter Wert für overprints `{o}` (preserve, simulate oder discard) @msg unknown panel `{raw}` unbekanntes Bedienfeld `{raw}` @msg unknown preference `{key}` unbekannte Voreinstellung `{key}` +@msg Preferences imported. Voreinstellungen importiert. +@msg not a preferences file: {e} keine Voreinstellungsdatei: {e} +@msg not a preferences file: no JSON object keine Voreinstellungsdatei: kein JSON-Objekt +@msg not a preferences file (format `{format}`) keine Voreinstellungsdatei (Format `{format}`) +@msg not a preferences file: no `format` keine Voreinstellungsdatei: kein `format` +@msg not a preferences file: no `version` keine Voreinstellungsdatei: keine `version` +@msg not a preferences file: no `prefs` keine Voreinstellungsdatei: keine `prefs` +@msg the file was saved by a newer version of VectorCraft (version {v}) die Datei wurde mit einer neueren Version von VectorCraft gespeichert (Version {v}) +@msg the file holds no preferences die Datei enthält keine Voreinstellungen +@msg `{key}` names a folder, which preferences files leave out `{key}` nennt einen Ordner, und Voreinstellungsdateien enthalten keine Ordner +@msg the file is larger than 1 MB die Datei ist größer als 1 MB +@msg give `path` or `data` geben Sie `path` oder `data` an +@msg `{key}` must be an object `{key}` muss ein Objekt sein +@msg `prefs.import` runs on its own, not as a batch step `prefs.import` läuft für sich allein, nicht als Stapelschritt @msg unknown preset (one of {_1}) unbekannte Vorgabe (eine von {_1}) @msg unknown preset `{name}` (high, medium, low or a saved one: see flattener.presets.list) unbekannte Vorgabe `{name}` (high, medium, low oder eine gespeicherte: siehe flattener.presets.list) @msg unknown preset `{name}` (see flattener.presets.list) unbekannte Vorgabe `{name}` (siehe flattener.presets.list) diff --git a/crates/ui-egui/src/i18n/el.tsv b/crates/ui-egui/src/i18n/el.tsv index a95d84be3..53593f722 100644 --- a/crates/ui-egui/src/i18n/el.tsv +++ b/crates/ui-egui/src/i18n/el.tsv @@ -1919,6 +1919,12 @@ font-technology Variable Μεταβλητές Reselect Επαναφορά προηγούμενης επιλογής Reset Επαναφορά Reset Preferences Επαναφορά προτιμήσεων + Export Preferences… Εξαγωγή προτιμήσεων… + Import Preferences… Εισαγωγή προτιμήσεων… + Save the applied preferences to a JSON file Αποθήκευση των εφαρμοσμένων προτιμήσεων σε αρχείο JSON + Apply the preferences in a file Export Preferences saved Εφαρμογή των προτιμήσεων από αρχείο που αποθήκευσε η Εξαγωγή προτιμήσεων + Export Preferences Εξαγωγή προτιμήσεων + Import Preferences Εισαγωγή προτιμήσεων Search Preferences Αναζήτηση στις προτιμήσεις No settings found Δεν βρέθηκαν ρυθμίσεις Reset to Default Επαναφορά προεπιλογής @@ -2616,6 +2622,7 @@ file-filter Flattener presets Προεπιλογές ισοπέδωσης file-filter PDF presets Προεπιλογές PDF file-filter PDF-compatible .ai .ai συμβατό με PDF file-filter Print presets Προεπιλογές εκτύπωσης +file-filter Preferences Προτιμήσεις file-filter Export presets Προκαθορισμένα εξαγωγής file-filter SVG Compressed Συμπιεσμένο SVG file-filter Swatch libraries Βιβλιοθήκες δειγμάτων χρωμάτων @@ -4629,6 +4636,20 @@ digits Malayalam Μαλαγιάλαμ @msg unknown overprints `{o}` (preserve, simulate or discard) άγνωστη επιλογή υπερεκτύπωσης `{o}` (preserve, simulate ή discard) @msg unknown panel `{raw}` άγνωστος πίνακας `{raw}` @msg unknown preference `{key}` άγνωστη προτίμηση `{key}` +@msg Preferences imported. Οι προτιμήσεις εισήχθησαν. +@msg not a preferences file: {e} δεν είναι αρχείο προτιμήσεων: {e} +@msg not a preferences file: no JSON object δεν είναι αρχείο προτιμήσεων: δεν υπάρχει αντικείμενο JSON +@msg not a preferences file (format `{format}`) δεν είναι αρχείο προτιμήσεων (μορφή `{format}`) +@msg not a preferences file: no `format` δεν είναι αρχείο προτιμήσεων: δεν υπάρχει `format` +@msg not a preferences file: no `version` δεν είναι αρχείο προτιμήσεων: δεν υπάρχει `version` +@msg not a preferences file: no `prefs` δεν είναι αρχείο προτιμήσεων: δεν υπάρχει `prefs` +@msg the file was saved by a newer version of VectorCraft (version {v}) το αρχείο αποθηκεύτηκε από νεότερη έκδοση του VectorCraft (έκδοση {v}) +@msg the file holds no preferences το αρχείο δεν περιέχει προτιμήσεις +@msg `{key}` names a folder, which preferences files leave out το `{key}` ορίζει φάκελο, και τα αρχεία προτιμήσεων δεν περιέχουν φακέλους +@msg the file is larger than 1 MB το αρχείο είναι μεγαλύτερο από 1 MB +@msg give `path` or `data` δώστε `path` ή `data` +@msg `{key}` must be an object το `{key}` πρέπει να είναι αντικείμενο +@msg `prefs.import` runs on its own, not as a batch step το `prefs.import` εκτελείται μόνο του, όχι ως βήμα δέσμης @msg unknown preset (one of {_1}) άγνωστη προεπιλογή (μία από τις {_1}) @msg unknown preset `{name}` (high, medium, low or a saved one: see flattener.presets.list) άγνωστη προεπιλογή `{name}` (high, medium, low ή αποθηκευμένη: δείτε flattener.presets.list) @msg unknown preset `{name}` (see flattener.presets.list) άγνωστη προεπιλογή `{name}` (δείτε flattener.presets.list) diff --git a/crates/ui-egui/src/i18n/es.tsv b/crates/ui-egui/src/i18n/es.tsv index e0903d19d..0d1ecfcff 100644 --- a/crates/ui-egui/src/i18n/es.tsv +++ b/crates/ui-egui/src/i18n/es.tsv @@ -2819,6 +2819,12 @@ digits Malayalam Malayálam Reset OpenType Features Restablecer funciones OpenType Reset Panel Restablecer panel Reset Preferences Restablecer preferencias + Export Preferences… Exportar preferencias… + Import Preferences… Importar preferencias… + Save the applied preferences to a JSON file Guardar las preferencias aplicadas en un archivo JSON + Apply the preferences in a file Export Preferences saved Aplicar las preferencias de un archivo guardado con Exportar preferencias + Export Preferences Exportar preferencias + Import Preferences Importar preferencias Search Preferences Buscar en preferencias No settings found No se encontraron ajustes Reset Profiles Restablecer perfiles @@ -4681,6 +4687,20 @@ axis Both Ambos @msg unknown overprints `{o}` (preserve, simulate or discard) sobreimpresiones desconocidas `{o}` (preserve, simulate o discard) @msg unknown panel `{raw}` panel desconocido `{raw}` @msg unknown preference `{key}` preferencia desconocida `{key}` +@msg Preferences imported. Preferencias importadas. +@msg not a preferences file: {e} no es un archivo de preferencias: {e} +@msg not a preferences file: no JSON object no es un archivo de preferencias: no hay ningún objeto JSON +@msg not a preferences file (format `{format}`) no es un archivo de preferencias (formato `{format}`) +@msg not a preferences file: no `format` no es un archivo de preferencias: falta `format` +@msg not a preferences file: no `version` no es un archivo de preferencias: falta `version` +@msg not a preferences file: no `prefs` no es un archivo de preferencias: falta `prefs` +@msg the file was saved by a newer version of VectorCraft (version {v}) el archivo se guardó con una versión más reciente de VectorCraft (versión {v}) +@msg the file holds no preferences el archivo no contiene preferencias +@msg `{key}` names a folder, which preferences files leave out `{key}` indica una carpeta, y los archivos de preferencias no incluyen carpetas +@msg the file is larger than 1 MB el archivo ocupa más de 1 MB +@msg give `path` or `data` indique `path` o `data` +@msg `{key}` must be an object `{key}` debe ser un objeto +@msg `prefs.import` runs on its own, not as a batch step `prefs.import` se ejecuta por sí solo, no como paso de un lote @msg unknown preset (one of {_1}) preajuste desconocido (uno de estos: {_1}) @msg unknown preset `{name}` (high, medium, low or a saved one: see flattener.presets.list) preajuste desconocido `{name}` (high, medium, low o uno guardado: consulta flattener.presets.list) @msg unknown preset `{name}` (see flattener.presets.list) preajuste desconocido `{name}` (consulta flattener.presets.list) @@ -5429,6 +5449,7 @@ file-filter Swatch libraries Bibliotecas de muestras file-filter Flattener presets Preajustes del acoplador file-filter PDF presets Preajustes de PDF file-filter Print presets Preajustes de impresión +file-filter Preferences Preferencias file-filter Export presets Ajustes de exportación file-filter SVG Compressed SVG comprimido file-filter PDF-compatible .ai .ai compatible con PDF diff --git a/crates/ui-egui/src/i18n/fi.tsv b/crates/ui-egui/src/i18n/fi.tsv index b593200aa..e78c2991a 100644 --- a/crates/ui-egui/src/i18n/fi.tsv +++ b/crates/ui-egui/src/i18n/fi.tsv @@ -3688,6 +3688,12 @@ theme Appearance Ulkoasu Real-time Drawing and Editing Reaaliaikainen piirto ja muokkaus Render Threads (-1 = Automatic) Renderöintisäikeet (-1 = automaattinen) Reset Preferences Palauta asetukset + Export Preferences… Vie asetukset… + Import Preferences… Tuo asetukset… + Save the applied preferences to a JSON file Tallenna käytössä olevat asetukset JSON-tiedostoon + Apply the preferences in a file Export Preferences saved Ota käyttöön asetukset tiedostosta, jonka Vie asetukset tallensi + Export Preferences Vie asetukset + Import Preferences Tuo asetukset Search Preferences Hae asetuksista No settings found Asetuksia ei löytynyt Scale Corners Skaalaa kulmat @@ -4891,6 +4897,20 @@ imageTrace Outline Ääriviiva @msg unknown overprints `{o}` (preserve, simulate or discard) tuntematon päällepainatusten käsittely `{o}` (preserve, simulate tai discard) @msg unknown panel `{raw}` tuntematon paneeli `{raw}` @msg unknown preference `{key}` tuntematon asetus `{key}` +@msg Preferences imported. Asetukset tuotiin. +@msg not a preferences file: {e} ei ole asetustiedosto: {e} +@msg not a preferences file: no JSON object ei ole asetustiedosto: ei JSON-objektia +@msg not a preferences file (format `{format}`) ei ole asetustiedosto (muoto `{format}`) +@msg not a preferences file: no `format` ei ole asetustiedosto: `format` puuttuu +@msg not a preferences file: no `version` ei ole asetustiedosto: `version` puuttuu +@msg not a preferences file: no `prefs` ei ole asetustiedosto: `prefs` puuttuu +@msg the file was saved by a newer version of VectorCraft (version {v}) tiedosto on tallennettu VectorCraftin uudemmalla versiolla (versio {v}) +@msg the file holds no preferences tiedostossa ei ole asetuksia +@msg `{key}` names a folder, which preferences files leave out `{key}` nimeää kansion, eikä asetustiedostoissa ole kansioita +@msg the file is larger than 1 MB tiedosto on suurempi kuin 1 Mt +@msg give `path` or `data` anna `path` tai `data` +@msg `{key}` must be an object `{key}` on oltava objekti +@msg `prefs.import` runs on its own, not as a batch step `prefs.import` ajetaan yksinään, ei erän vaiheena @msg unknown preset (one of {_1}) tuntematon esiasetus (jokin seuraavista: {_1}) @msg unknown preset `{name}` (high, medium, low or a saved one: see flattener.presets.list) tuntematon esiasetus `{name}` (high, medium, low tai tallennettu: ks. flattener.presets.list) @msg unknown preset `{name}` (see flattener.presets.list) tuntematon esiasetus `{name}` (ks. flattener.presets.list) @@ -5446,6 +5466,7 @@ file-filter Swatch libraries Värimallikirjastot file-filter Flattener presets Litistyksen esiasetukset file-filter PDF presets PDF-esiasetukset file-filter Print presets Tulostuksen esiasetukset +file-filter Preferences Asetukset file-filter Export presets Vientiesiasetukset file-filter SVG Compressed Pakattu SVG file-filter PDF-compatible .ai PDF-yhteensopiva .ai diff --git a/crates/ui-egui/src/i18n/fr.tsv b/crates/ui-egui/src/i18n/fr.tsv index 6dd4e37a4..3998a3d4f 100644 --- a/crates/ui-egui/src/i18n/fr.tsv +++ b/crates/ui-egui/src/i18n/fr.tsv @@ -2811,6 +2811,12 @@ digits Malayalam Malayalam Reset OpenType Features Réinitialiser les fonctions OpenType Reset Panel Réinitialiser le panneau Reset Preferences Réinitialiser les préférences + Export Preferences… Exporter les préférences… + Import Preferences… Importer des préférences… + Save the applied preferences to a JSON file Enregistrer les préférences appliquées dans un fichier JSON + Apply the preferences in a file Export Preferences saved Appliquer les préférences d’un fichier enregistré par Exporter les préférences + Export Preferences Exporter les préférences + Import Preferences Importer des préférences Search Preferences Rechercher dans les préférences No settings found Aucun réglage trouvé Reset Profiles Réinitialiser les profils @@ -4670,6 +4676,20 @@ axis Both Les deux @msg unknown overprints `{o}` (preserve, simulate or discard) surimpressions inconnues `{o}` (preserve, simulate ou discard) @msg unknown panel `{raw}` panneau inconnu `{raw}` @msg unknown preference `{key}` préférence inconnue `{key}` +@msg Preferences imported. Préférences importées. +@msg not a preferences file: {e} ce n’est pas un fichier de préférences : {e} +@msg not a preferences file: no JSON object ce n’est pas un fichier de préférences : aucun objet JSON +@msg not a preferences file (format `{format}`) ce n’est pas un fichier de préférences (format `{format}`) +@msg not a preferences file: no `format` ce n’est pas un fichier de préférences : `format` manquant +@msg not a preferences file: no `version` ce n’est pas un fichier de préférences : `version` manquant +@msg not a preferences file: no `prefs` ce n’est pas un fichier de préférences : `prefs` manquant +@msg the file was saved by a newer version of VectorCraft (version {v}) le fichier a été enregistré par une version plus récente de VectorCraft (version {v}) +@msg the file holds no preferences le fichier ne contient aucune préférence +@msg `{key}` names a folder, which preferences files leave out `{key}` désigne un dossier, et les fichiers de préférences n’incluent pas les dossiers +@msg the file is larger than 1 MB le fichier dépasse 1 Mo +@msg give `path` or `data` indiquez `path` ou `data` +@msg `{key}` must be an object `{key}` doit être un objet +@msg `prefs.import` runs on its own, not as a batch step `prefs.import` s’exécute seul, pas comme étape d’un lot @msg unknown preset (one of {_1}) paramètre prédéfini inconnu (valeurs possibles : {_1}) @msg unknown preset `{name}` (high, medium, low or a saved one: see flattener.presets.list) paramètre prédéfini inconnu `{name}` (high, medium, low ou un paramètre enregistré : voir flattener.presets.list) @msg unknown preset `{name}` (see flattener.presets.list) paramètre prédéfini inconnu `{name}` (voir flattener.presets.list) @@ -5623,6 +5643,7 @@ file-filter Flattener presets Paramètres prédéfinis d’aplatissement file-filter PDF presets Paramètres prédéfinis PDF file-filter PDF-compatible .ai .ai compatible PDF file-filter Print presets Paramètres prédéfinis d’impression +file-filter Preferences Préférences file-filter Export presets Paramètres prédéfinis d’export file-filter SVG Compressed SVG compressé file-filter Swatch libraries Bibliothèques de nuances diff --git a/crates/ui-egui/src/i18n/hi.tsv b/crates/ui-egui/src/i18n/hi.tsv index 3397e7f34..77facb4c1 100644 --- a/crates/ui-egui/src/i18n/hi.tsv +++ b/crates/ui-egui/src/i18n/hi.tsv @@ -3778,6 +3778,12 @@ antiAlias Sharp शार्प Reset Painting पेंटिंग रीसेट करें Reset Panel पैनल रीसेट करें Reset Preferences प्रेफ़रेंसेज़ रीसेट करें + Export Preferences… प्रेफ़रेंसेज़ निर्यात करें… + Import Preferences… प्रेफ़रेंसेज़ आयात करें… + Save the applied preferences to a JSON file लागू प्रेफ़रेंसेज़ को JSON फ़ाइल में सहेजें + Apply the preferences in a file Export Preferences saved प्रेफ़रेंसेज़ निर्यात करें से सहेजी गई फ़ाइल की प्रेफ़रेंसेज़ लागू करें + Export Preferences प्रेफ़रेंसेज़ निर्यात करें + Import Preferences प्रेफ़रेंसेज़ आयात करें Search Preferences प्रेफ़रेंसेज़ में खोजें No settings found कोई सेटिंग नहीं मिली Reset Printing and Proofing मुद्रण और प्रूफिंग रीसेट करें @@ -7866,6 +7872,20 @@ antiAlias Sharp शार्प @msg unknown plate `{n}` अज्ञात प्लेट `{n}` @msg unknown preference `{k}` अज्ञात वरीयता `{k}` @msg unknown preference `{key}` अज्ञात वरीयता `{key}` +@msg Preferences imported. प्रेफ़रेंसेज़ आयात की गईं। +@msg not a preferences file: {e} प्रेफ़रेंसेज़ फ़ाइल नहीं: {e} +@msg not a preferences file: no JSON object प्रेफ़रेंसेज़ फ़ाइल नहीं: कोई JSON ऑब्जेक्ट नहीं +@msg not a preferences file (format `{format}`) प्रेफ़रेंसेज़ फ़ाइल नहीं (फ़ॉर्मैट `{format}`) +@msg not a preferences file: no `format` प्रेफ़रेंसेज़ फ़ाइल नहीं: `format` नहीं है +@msg not a preferences file: no `version` प्रेफ़रेंसेज़ फ़ाइल नहीं: `version` नहीं है +@msg not a preferences file: no `prefs` प्रेफ़रेंसेज़ फ़ाइल नहीं: `prefs` नहीं है +@msg the file was saved by a newer version of VectorCraft (version {v}) फ़ाइल VectorCraft के नए संस्करण से सहेजी गई थी (संस्करण {v}) +@msg the file holds no preferences फ़ाइल में कोई प्रेफ़रेंस नहीं है +@msg `{key}` names a folder, which preferences files leave out `{key}` एक फ़ोल्डर बताता है, और प्रेफ़रेंसेज़ फ़ाइलों में फ़ोल्डर नहीं होते +@msg the file is larger than 1 MB फ़ाइल 1 MB से बड़ी है +@msg give `path` or `data` `path` या `data` दें +@msg `{key}` must be an object `{key}` एक ऑब्जेक्ट होना चाहिए +@msg `prefs.import` runs on its own, not as a batch step `prefs.import` अकेले चलता है, बैच के चरण के रूप में नहीं @msg unknown preset (one of {_1}) अज्ञात प्रीसेट ({_1} में से एक) @msg unknown preset `{name}` (high, medium, low or a saved one: see flattener.presets.list) अज्ञात प्रीसेट `{name}` (उच्च, मध्यम, निम्न या एक बचाया: flattener.presets.list देखें) @msg unknown preset `{name}` (see flattener.presets.list) अज्ञात प्रीसेट `{name}` (देखें flattener.presets.list) @@ -8065,6 +8085,7 @@ file-filter Flattener presets फ़्लैटनर प्रीसेट् file-filter PDF presets PDF प्रीसेट्स file-filter PDF-compatible .ai PDF-संगत .ai file-filter Print presets प्रिंट प्रीसेट्स +file-filter Preferences प्रेफ़रेंसेज़ file-filter SVG Compressed SVG संपीड़ित file-filter Swatch libraries स्वॉच लाइब्रेरीज़ file-filter Templates टेम्पलेट्स diff --git a/crates/ui-egui/src/i18n/hu.tsv b/crates/ui-egui/src/i18n/hu.tsv index 4b813215e..b98ea6da3 100644 --- a/crates/ui-egui/src/i18n/hu.tsv +++ b/crates/ui-egui/src/i18n/hu.tsv @@ -1572,6 +1572,7 @@ file-filter Swatch libraries Színminta-könyvtárak file-filter Flattener presets Lapítási előbeállítások file-filter PDF presets PDF-előbeállítások file-filter Print presets Nyomtatási előbeállítások +file-filter Preferences Beállítások file-filter Export presets Exportálási előbeállítások file-filter SVG Compressed Tömörített SVG file-filter PDF-compatible .ai PDF-kompatibilis .ai @@ -3361,6 +3362,12 @@ mark-style Japanese Japán Reset Painting Painting visszaállítása Reset Panel Panel visszaállítása Reset Preferences Beállítások visszaállítása + Export Preferences… Beállítások exportálása… + Import Preferences… Beállítások importálása… + Save the applied preferences to a JSON file Az alkalmazott beállítások mentése JSON-fájlba + Apply the preferences in a file Export Preferences saved A Beállítások exportálása paranccsal mentett fájl beállításainak alkalmazása + Export Preferences Beállítások exportálása + Import Preferences Beállítások importálása Search Preferences Keresés a beállításokban No settings found Nincs ilyen beállítás Reset Printing and Proofing Printing and Proofing visszaállítása @@ -5491,6 +5498,20 @@ imageTrace Outline Körvonal @msg unknown overprints `{o}` (preserve, simulate or discard) ismeretlen felülnyomás-kezelés: `{o}` (preserve, simulate vagy discard) @msg unknown panel `{raw}` ismeretlen panel: `{raw}` @msg unknown preference `{key}` ismeretlen beállítás: `{key}` +@msg Preferences imported. A beállítások importálva. +@msg not a preferences file: {e} nem beállításfájl: {e} +@msg not a preferences file: no JSON object nem beállításfájl: nincs JSON-objektum +@msg not a preferences file (format `{format}`) nem beállításfájl (formátum: `{format}`) +@msg not a preferences file: no `format` nem beállításfájl: nincs `format` +@msg not a preferences file: no `version` nem beállításfájl: nincs `version` +@msg not a preferences file: no `prefs` nem beállításfájl: nincs `prefs` +@msg the file was saved by a newer version of VectorCraft (version {v}) a fájlt a VectorCraft újabb verziója mentette (verzió: {v}) +@msg the file holds no preferences a fájl nem tartalmaz beállításokat +@msg `{key}` names a folder, which preferences files leave out a(z) `{key}` mappát ad meg, a beállításfájlok pedig nem tartalmaznak mappákat +@msg the file is larger than 1 MB a fájl nagyobb 1 MB-nál +@msg give `path` or `data` adja meg a `path` vagy a `data` értékét +@msg `{key}` must be an object a(z) `{key}` értékének objektumnak kell lennie +@msg `prefs.import` runs on its own, not as a batch step a `prefs.import` önállóan fut, nem kötegelt lépésként @msg unknown preset (one of {_1}) ismeretlen előbeállítás (ezek egyike lehet: {_1}) @msg unknown preset `{name}` (high, medium, low or a saved one: see flattener.presets.list) ismeretlen előbeállítás: `{name}` (high, medium, low vagy egy mentett; lásd: flattener.presets.list) @msg unknown preset `{name}` (see flattener.presets.list) ismeretlen előbeállítás: `{name}` (lásd: flattener.presets.list) diff --git a/crates/ui-egui/src/i18n/id.tsv b/crates/ui-egui/src/i18n/id.tsv index 33d229c85..3974543bb 100644 --- a/crates/ui-egui/src/i18n/id.tsv +++ b/crates/ui-egui/src/i18n/id.tsv @@ -3487,6 +3487,12 @@ theme Appearance Tampilan Real-time Drawing and Editing Penggambaran dan pengeditan waktu nyata Render Threads (-1 = Automatic) Utas render (-1 = Otomatis) Reset Preferences Atur ulang preferensi + Export Preferences… Ekspor preferensi… + Import Preferences… Impor preferensi… + Save the applied preferences to a JSON file Simpan preferensi yang diterapkan ke file JSON + Apply the preferences in a file Export Preferences saved Terapkan preferensi dari file yang disimpan oleh Ekspor preferensi + Export Preferences Ekspor preferensi + Import Preferences Impor preferensi Search Preferences Cari preferensi No settings found Tidak ada pengaturan yang ditemukan Scale Corners Skalakan sudut @@ -4602,6 +4608,20 @@ imageTrace Outline Kontur @msg unknown overprints `{o}` (preserve, simulate or discard) overprint `{o}` tidak dikenal (pertahankan, simulasikan, atau buang) @msg unknown panel `{raw}` panel `{raw}` tidak dikenal @msg unknown preference `{key}` preferensi `{key}` tidak dikenal +@msg Preferences imported. Preferensi diimpor. +@msg not a preferences file: {e} bukan file preferensi: {e} +@msg not a preferences file: no JSON object bukan file preferensi: tidak ada objek JSON +@msg not a preferences file (format `{format}`) bukan file preferensi (format `{format}`) +@msg not a preferences file: no `format` bukan file preferensi: tidak ada `format` +@msg not a preferences file: no `version` bukan file preferensi: tidak ada `version` +@msg not a preferences file: no `prefs` bukan file preferensi: tidak ada `prefs` +@msg the file was saved by a newer version of VectorCraft (version {v}) file disimpan oleh versi VectorCraft yang lebih baru (versi {v}) +@msg the file holds no preferences file tidak berisi preferensi +@msg `{key}` names a folder, which preferences files leave out `{key}` menunjuk folder, dan file preferensi tidak menyertakan folder +@msg the file is larger than 1 MB file lebih besar dari 1 MB +@msg give `path` or `data` berikan `path` atau `data` +@msg `{key}` must be an object `{key}` harus berupa objek +@msg `prefs.import` runs on its own, not as a batch step `prefs.import` dijalankan sendiri, bukan sebagai langkah batch @msg unknown preset (one of {_1}) preset tidak dikenal (salah satu dari {_1}) @msg unknown preset `{name}` (high, medium, low or a saved one: see flattener.presets.list) preset `{name}` tidak dikenal (tinggi, sedang, rendah, atau yang tersimpan: lihat flattener.presets.list) @msg unknown preset `{name}` (see flattener.presets.list) preset `{name}` tidak dikenal (lihat flattener.presets.list) @@ -5098,6 +5118,7 @@ file-filter Swatch libraries Pustaka swatch file-filter Flattener presets Preset perata file-filter PDF presets Preset PDF file-filter Print presets Preset cetak +file-filter Preferences Preferensi file-filter Export presets Preset ekspor file-filter SVG Compressed SVG terkompresi file-filter PDF-compatible .ai .ai kompatibel PDF diff --git a/crates/ui-egui/src/i18n/it.tsv b/crates/ui-egui/src/i18n/it.tsv index b5fd752e9..db239c9a3 100644 --- a/crates/ui-egui/src/i18n/it.tsv +++ b/crates/ui-egui/src/i18n/it.tsv @@ -1278,6 +1278,7 @@ file-filter Swatch libraries Librerie campioni file-filter Flattener presets Preset di appiattimento file-filter PDF presets Preset PDF file-filter Print presets Preset di stampa +file-filter Preferences Preferenze file-filter Export presets Predefiniti di esportazione file-filter SVG Compressed SVG compresso file-filter PDF-compatible .ai .ai compatibile con PDF @@ -2852,6 +2853,12 @@ digits Malayalam Malayalam Reset OpenType Features Reimposta funzioni OpenType Reset Panel Reimposta pannello Reset Preferences Reimposta preferenze + Export Preferences… Esporta preferenze… + Import Preferences… Importa preferenze… + Save the applied preferences to a JSON file Salva le preferenze applicate in un file JSON + Apply the preferences in a file Export Preferences saved Applica le preferenze di un file salvato con Esporta preferenze + Export Preferences Esporta preferenze + Import Preferences Importa preferenze Search Preferences Cerca nelle preferenze No settings found Nessuna impostazione trovata Reset Profiles Reimposta profili @@ -4723,6 +4730,20 @@ axis Both Entrambi @msg unknown overprints `{o}` (preserve, simulate or discard) valore di sovrastampa sconosciuto `{o}` (preserve, simulate o discard) @msg unknown panel `{raw}` pannello sconosciuto `{raw}` @msg unknown preference `{key}` preferenza sconosciuta `{key}` +@msg Preferences imported. Preferenze importate. +@msg not a preferences file: {e} non è un file di preferenze: {e} +@msg not a preferences file: no JSON object non è un file di preferenze: nessun oggetto JSON +@msg not a preferences file (format `{format}`) non è un file di preferenze (formato `{format}`) +@msg not a preferences file: no `format` non è un file di preferenze: manca `format` +@msg not a preferences file: no `version` non è un file di preferenze: manca `version` +@msg not a preferences file: no `prefs` non è un file di preferenze: manca `prefs` +@msg the file was saved by a newer version of VectorCraft (version {v}) il file è stato salvato da una versione più recente di VectorCraft (versione {v}) +@msg the file holds no preferences il file non contiene preferenze +@msg `{key}` names a folder, which preferences files leave out `{key}` indica una cartella, e i file di preferenze non includono cartelle +@msg the file is larger than 1 MB il file supera 1 MB +@msg give `path` or `data` specifica `path` o `data` +@msg `{key}` must be an object `{key}` deve essere un oggetto +@msg `prefs.import` runs on its own, not as a batch step `prefs.import` viene eseguito da solo, non come passo di un batch @msg unknown preset (one of {_1}) preset sconosciuto (uno dei seguenti: {_1}) @msg unknown preset `{name}` (high, medium, low or a saved one: see flattener.presets.list) preset sconosciuto `{name}` (high, medium, low o uno salvato: vedi flattener.presets.list) @msg unknown preset `{name}` (see flattener.presets.list) preset sconosciuto `{name}` (vedi flattener.presets.list) diff --git a/crates/ui-egui/src/i18n/ja.tsv b/crates/ui-egui/src/i18n/ja.tsv index 56fc8a590..b58c1ebf4 100644 --- a/crates/ui-egui/src/i18n/ja.tsv +++ b/crates/ui-egui/src/i18n/ja.tsv @@ -2843,6 +2843,12 @@ mark-style Japanese 日本式 Reset OpenType Features OpenType 機能をリセット Reset Panel パネルをリセット Reset Preferences 環境設定をリセット + Export Preferences… 環境設定を書き出し… + Import Preferences… 環境設定を読み込み… + Save the applied preferences to a JSON file 適用中の環境設定を JSON ファイルに保存 + Apply the preferences in a file Export Preferences saved 「環境設定を書き出し」で保存したファイルの環境設定を適用 + Export Preferences 環境設定を書き出し + Import Preferences 環境設定を読み込み Search Preferences 環境設定を検索 No settings found 設定が見つかりません Reset Profiles プロファイルをリセット @@ -3639,6 +3645,7 @@ file-filter Swatch libraries スウォッチライブラリ file-filter Flattener presets 分割・統合プリセット file-filter PDF presets PDF プリセット file-filter Print presets プリントプリセット +file-filter Preferences 環境設定 file-filter Export presets 書き出しプリセット file-filter SVG Compressed SVG 圧縮 file-filter PDF-compatible .ai PDF 互換の .ai @@ -4427,6 +4434,20 @@ swatch Bright Violet ビビッドバイオレット @msg `{key}` must be between {min} and {max} (got {x}) `{key}` は {min} から {max} までの値にしてください (指定値: {x}) @msg `{key}` must be one of {_1} `{key}` は {_1} のいずれかにしてください @msg unknown preference `{key}` `{key}` は不明な環境設定の項目です +@msg Preferences imported. 環境設定を読み込みました。 +@msg not a preferences file: {e} 環境設定ファイルではありません: {e} +@msg not a preferences file: no JSON object 環境設定ファイルではありません: JSON オブジェクトがありません +@msg not a preferences file (format `{format}`) 環境設定ファイルではありません (形式 `{format}`) +@msg not a preferences file: no `format` 環境設定ファイルではありません: `format` がありません +@msg not a preferences file: no `version` 環境設定ファイルではありません: `version` がありません +@msg not a preferences file: no `prefs` 環境設定ファイルではありません: `prefs` がありません +@msg the file was saved by a newer version of VectorCraft (version {v}) ファイルは新しいバージョンの VectorCraft で保存されています (バージョン {v}) +@msg the file holds no preferences ファイルに環境設定がありません +@msg `{key}` names a folder, which preferences files leave out `{key}` はフォルダーを指定しますが、環境設定ファイルにはフォルダーを含めません +@msg the file is larger than 1 MB ファイルが 1 MB を超えています +@msg give `path` or `data` `path` または `data` を指定してください +@msg `{key}` must be an object `{key}` はオブジェクトである必要があります +@msg `prefs.import` runs on its own, not as a batch step `prefs.import` は単独で実行します。バッチのステップにはできません @msg Data Recovery has nowhere to keep copies: set Preferences → File Handling → Data Recovery → Folder (recoveryFolder) データ復帰のコピーを保存する場所がありません: 環境設定 → ファイル管理 → データ復帰 → フォルダー (recoveryFolder) を設定してください @msg Data Recovery can't keep copies in {path}: {e} データ復帰は {path} にコピーを保存できません: {e} @msg Plug-ins can't be loaded from {path}: {e} {path} からプラグインを読み込めません: {e} diff --git a/crates/ui-egui/src/i18n/ka.tsv b/crates/ui-egui/src/i18n/ka.tsv index 388dd4a01..301100080 100644 --- a/crates/ui-egui/src/i18n/ka.tsv +++ b/crates/ui-egui/src/i18n/ka.tsv @@ -3307,6 +3307,12 @@ theme Appearance იერსახე Real-time Drawing and Editing რეალურ დროში ხატვა და რედაქტირება Render Threads (-1 = Automatic) ნაკადები (-1 = ავტომატური) Reset Preferences პარამეტრების ჩამოყრა + Export Preferences… პარამეტრების ექსპორტირება… + Import Preferences… პარამეტრების იმპორტირება… + Save the applied preferences to a JSON file გამოყენებული პარამეტრების შენახვა JSON ფაილში + Apply the preferences in a file Export Preferences saved პარამეტრების გამოყენება ფაილიდან, რომელიც „პარამეტრების ექსპორტირებამ“ შეინახა + Export Preferences პარამეტრების ექსპორტირება + Import Preferences პარამეტრების იმპორტირება Search Preferences პარამეტრებში ძიება No settings found პარამეტრები ვერ მოიძებნა Scale Corners კუთხეების მასშტაბირება @@ -4405,6 +4411,20 @@ font-technology Variable ცვლადი @msg unknown overprints `{o}` (preserve, simulate or discard) უცნობი ზედდაბეჭდვა `{o}` (preserve, simulate ან discard) @msg unknown panel `{raw}` უცნობი პანელი `{raw}` @msg unknown preference `{key}` უცნობი პარამეტრი `{key}` +@msg Preferences imported. პარამეტრები იმპორტირებულია. +@msg not a preferences file: {e} ეს არ არის პარამეტრების ფაილი: {e} +@msg not a preferences file: no JSON object ეს არ არის პარამეტრების ფაილი: JSON ობიექტი არ არის +@msg not a preferences file (format `{format}`) ეს არ არის პარამეტრების ფაილი (ფორმატი `{format}`) +@msg not a preferences file: no `format` ეს არ არის პარამეტრების ფაილი: `format` არ არის +@msg not a preferences file: no `version` ეს არ არის პარამეტრების ფაილი: `version` არ არის +@msg not a preferences file: no `prefs` ეს არ არის პარამეტრების ფაილი: `prefs` არ არის +@msg the file was saved by a newer version of VectorCraft (version {v}) ფაილი შენახულია VectorCraft-ის უფრო ახალი ვერსიით (ვერსია {v}) +@msg the file holds no preferences ფაილში პარამეტრები არ არის +@msg `{key}` names a folder, which preferences files leave out `{key}` საქაღალდეს მიუთითებს, პარამეტრების ფაილები კი საქაღალდეებს არ შეიცავს +@msg the file is larger than 1 MB ფაილი 1 MB-ზე დიდია +@msg give `path` or `data` მიუთითეთ `path` ან `data` +@msg `{key}` must be an object `{key}` უნდა იყოს ობიექტი +@msg `prefs.import` runs on its own, not as a batch step `prefs.import` ცალკე სრულდება და არა პაკეტის ნაბიჯად @msg unknown preset (one of {_1}) უცნობი პრესეტი (ერთ-ერთი: {_1}) @msg unknown preset `{name}` (high, medium, low or a saved one: see flattener.presets.list) უცნობი პრესეტი `{name}` (high, medium, low ან შენახული: იხ. flattener.presets.list) @msg unknown preset `{name}` (see flattener.presets.list) უცნობი პრესეტი `{name}` (იხ. flattener.presets.list) @@ -4991,6 +5011,7 @@ file-filter Swatch libraries ფერის ნიმუშების ბი file-filter Flattener presets გაბრტყელების პრესეტები file-filter PDF presets PDF-ის პრესეტები file-filter Print presets ბეჭდვის პრესეტები +file-filter Preferences პარამეტრები file-filter Export presets ექსპორტის პრესეტები file-filter SVG Compressed შეკუმშული SVG file-filter PDF-compatible .ai PDF-თან თავსებადი .ai diff --git a/crates/ui-egui/src/i18n/ko.tsv b/crates/ui-egui/src/i18n/ko.tsv index 03930932a..fe3ea7bbf 100644 --- a/crates/ui-egui/src/i18n/ko.tsv +++ b/crates/ui-egui/src/i18n/ko.tsv @@ -2832,6 +2832,12 @@ mark-style Japanese 일본식 Reset OpenType Features OpenType 기능 재설정 Reset Panel 패널 재설정 Reset Preferences 환경 설정 재설정 + Export Preferences… 환경 설정 내보내기… + Import Preferences… 환경 설정 가져오기… + Save the applied preferences to a JSON file 적용된 환경 설정을 JSON 파일로 저장 + Apply the preferences in a file Export Preferences saved 환경 설정 내보내기로 저장한 파일의 환경 설정 적용 + Export Preferences 환경 설정 내보내기 + Import Preferences 환경 설정 가져오기 Search Preferences 환경 설정 검색 No settings found 설정을 찾을 수 없음 Reset Profiles 프로필 재설정 @@ -3627,6 +3633,7 @@ file-filter Swatch libraries 견본 라이브러리 file-filter Flattener presets 병합 프리셋 file-filter PDF presets PDF 프리셋 file-filter Print presets 인쇄 프리셋 +file-filter Preferences 환경 설정 file-filter Export presets 내보내기 사전 설정 file-filter SVG Compressed 압축된 SVG file-filter PDF-compatible .ai PDF 호환 .ai @@ -4354,6 +4361,20 @@ theme Appearance 외관 @msg `{key}` must be between {min} and {max} (got {x}) `{key}`은(는) {min}에서 {max} 사이여야 합니다({x} 입력됨) @msg `{key}` must be one of {_1} `{key}`은(는) {_1} 중 하나여야 합니다 @msg unknown preference `{key}` 알 수 없는 환경 설정 `{key}` +@msg Preferences imported. 환경 설정을 가져왔습니다. +@msg not a preferences file: {e} 환경 설정 파일이 아닙니다: {e} +@msg not a preferences file: no JSON object 환경 설정 파일이 아닙니다: JSON 개체가 없습니다 +@msg not a preferences file (format `{format}`) 환경 설정 파일이 아닙니다(형식 `{format}`) +@msg not a preferences file: no `format` 환경 설정 파일이 아닙니다: `format`이 없습니다 +@msg not a preferences file: no `version` 환경 설정 파일이 아닙니다: `version`이 없습니다 +@msg not a preferences file: no `prefs` 환경 설정 파일이 아닙니다: `prefs`가 없습니다 +@msg the file was saved by a newer version of VectorCraft (version {v}) 더 새로운 버전의 VectorCraft에서 저장한 파일입니다(버전 {v}) +@msg the file holds no preferences 파일에 환경 설정이 없습니다 +@msg `{key}` names a folder, which preferences files leave out `{key}`은(는) 폴더를 지정하며, 환경 설정 파일에는 폴더가 포함되지 않습니다 +@msg the file is larger than 1 MB 파일이 1MB보다 큽니다 +@msg give `path` or `data` `path` 또는 `data`를 지정하세요 +@msg `{key}` must be an object `{key}`은(는) 개체여야 합니다 +@msg `prefs.import` runs on its own, not as a batch step `prefs.import`은(는) 일괄 처리 단계가 아니라 단독으로 실행됩니다 @msg Data Recovery has nowhere to keep copies: set Preferences → File Handling → Data Recovery → Folder (recoveryFolder) 데이터 복구에 복사본을 보관할 곳이 없습니다. 환경 설정 → 파일 처리 → 데이터 복구 → 폴더(recoveryFolder)를 지정하세요 @msg Data Recovery can't keep copies in {path}: {e} 데이터 복구가 {path}에 복사본을 보관할 수 없습니다: {e} @msg Plug-ins can't be loaded from {path}: {e} {path}에서 플러그인을 불러올 수 없습니다: {e} diff --git a/crates/ui-egui/src/i18n/nl.tsv b/crates/ui-egui/src/i18n/nl.tsv index c5151622d..28ff053da 100644 --- a/crates/ui-egui/src/i18n/nl.tsv +++ b/crates/ui-egui/src/i18n/nl.tsv @@ -3501,6 +3501,12 @@ theme Appearance Vormgeving Real-time Drawing and Editing Tekenen en bewerken in realtime Render Threads (-1 = Automatic) Renderthreads (-1 = automatisch) Reset Preferences Voorkeuren herstellen + Export Preferences… Voorkeuren exporteren… + Import Preferences… Voorkeuren importeren… + Save the applied preferences to a JSON file De toegepaste voorkeuren in een JSON-bestand opslaan + Apply the preferences in a file Export Preferences saved De voorkeuren toepassen uit een bestand dat Voorkeuren exporteren heeft opgeslagen + Export Preferences Voorkeuren exporteren + Import Preferences Voorkeuren importeren Search Preferences Voorkeuren doorzoeken No settings found Geen instellingen gevonden Scale Corners Hoeken schalen @@ -4614,6 +4620,20 @@ imageTrace Outline Contour @msg unknown overprints `{o}` (preserve, simulate or discard) onbekende overdruk `{o}` (behouden, simuleren of verwerpen) @msg unknown panel `{raw}` onbekend paneel `{raw}` @msg unknown preference `{key}` onbekende voorkeur `{key}` +@msg Preferences imported. Voorkeuren geïmporteerd. +@msg not a preferences file: {e} geen voorkeurenbestand: {e} +@msg not a preferences file: no JSON object geen voorkeurenbestand: geen JSON-object +@msg not a preferences file (format `{format}`) geen voorkeurenbestand (indeling `{format}`) +@msg not a preferences file: no `format` geen voorkeurenbestand: geen `format` +@msg not a preferences file: no `version` geen voorkeurenbestand: geen `version` +@msg not a preferences file: no `prefs` geen voorkeurenbestand: geen `prefs` +@msg the file was saved by a newer version of VectorCraft (version {v}) het bestand is opgeslagen met een nieuwere versie van VectorCraft (versie {v}) +@msg the file holds no preferences het bestand bevat geen voorkeuren +@msg `{key}` names a folder, which preferences files leave out `{key}` geeft een map aan, en voorkeurenbestanden bevatten geen mappen +@msg the file is larger than 1 MB het bestand is groter dan 1 MB +@msg give `path` or `data` geef `path` of `data` op +@msg `{key}` must be an object `{key}` moet een object zijn +@msg `prefs.import` runs on its own, not as a batch step `prefs.import` wordt los uitgevoerd, niet als stap van een batch @msg unknown preset (one of {_1}) onbekende voorinstelling (één van {_1}) @msg unknown preset `{name}` (high, medium, low or a saved one: see flattener.presets.list) onbekende voorinstelling `{name}` (hoog, middel, laag of een opgeslagen voorinstelling: zie flattener.presets.list) @msg unknown preset `{name}` (see flattener.presets.list) onbekende voorinstelling `{name}` (zie flattener.presets.list) @@ -5110,6 +5130,7 @@ file-filter Swatch libraries Stalenbibliotheken file-filter Flattener presets Afvlakvoorinstellingen file-filter PDF presets PDF-voorinstellingen file-filter Print presets Afdrukvoorinstellingen +file-filter Preferences Voorkeuren file-filter Export presets Exportvoorinstellingen file-filter SVG Compressed Gecomprimeerde SVG file-filter PDF-compatible .ai PDF-compatibel .ai diff --git a/crates/ui-egui/src/i18n/pl.tsv b/crates/ui-egui/src/i18n/pl.tsv index 58016a4bd..948020801 100644 --- a/crates/ui-egui/src/i18n/pl.tsv +++ b/crates/ui-egui/src/i18n/pl.tsv @@ -3627,6 +3627,12 @@ theme Appearance Wygląd Real-time Drawing and Editing Rysowanie i edycja w czasie rzeczywistym Render Threads (-1 = Automatic) Wątki renderowania (-1 = automatycznie) Reset Preferences Resetuj preferencje + Export Preferences… Eksportuj preferencje… + Import Preferences… Importuj preferencje… + Save the applied preferences to a JSON file Zapisz zastosowane preferencje w pliku JSON + Apply the preferences in a file Export Preferences saved Zastosuj preferencje z pliku zapisanego poleceniem Eksportuj preferencje + Export Preferences Eksportuj preferencje + Import Preferences Importuj preferencje Search Preferences Szukaj w preferencjach No settings found Nie znaleziono ustawień Scale Corners Skaluj narożniki @@ -4815,6 +4821,20 @@ imageTrace Outline Kontur @msg unknown overprints `{o}` (preserve, simulate or discard) nieznana wartość nadruków `{o}` (preserve, simulate lub discard) @msg unknown panel `{raw}` nieznany panel `{raw}` @msg unknown preference `{key}` nieznana preferencja `{key}` +@msg Preferences imported. Zaimportowano preferencje. +@msg not a preferences file: {e} to nie jest plik preferencji: {e} +@msg not a preferences file: no JSON object to nie jest plik preferencji: brak obiektu JSON +@msg not a preferences file (format `{format}`) to nie jest plik preferencji (format `{format}`) +@msg not a preferences file: no `format` to nie jest plik preferencji: brak `format` +@msg not a preferences file: no `version` to nie jest plik preferencji: brak `version` +@msg not a preferences file: no `prefs` to nie jest plik preferencji: brak `prefs` +@msg the file was saved by a newer version of VectorCraft (version {v}) plik zapisano w nowszej wersji programu VectorCraft (wersja {v}) +@msg the file holds no preferences plik nie zawiera preferencji +@msg `{key}` names a folder, which preferences files leave out `{key}` wskazuje folder, a pliki preferencji nie zawierają folderów +@msg the file is larger than 1 MB plik jest większy niż 1 MB +@msg give `path` or `data` podaj `path` lub `data` +@msg `{key}` must be an object `{key}` musi być obiektem +@msg `prefs.import` runs on its own, not as a batch step `prefs.import` działa samodzielnie, nie jako krok wsadu @msg unknown preset (one of {_1}) nieznane ustawienie (jedno z: {_1}) @msg unknown preset `{name}` (high, medium, low or a saved one: see flattener.presets.list) nieznane ustawienie `{name}` (high, medium, low lub zapisane: patrz flattener.presets.list) @msg unknown preset `{name}` (see flattener.presets.list) nieznane ustawienie `{name}` (patrz flattener.presets.list) @@ -5393,6 +5413,7 @@ file-filter Swatch libraries Biblioteki próbek file-filter Flattener presets Ustawienia spłaszczania file-filter PDF presets Ustawienia PDF file-filter Print presets Ustawienia drukowania +file-filter Preferences Preferencje file-filter Export presets Ustawienia eksportu file-filter SVG Compressed SVG skompresowany file-filter PDF-compatible .ai .ai zgodny z PDF diff --git a/crates/ui-egui/src/i18n/pt-br.tsv b/crates/ui-egui/src/i18n/pt-br.tsv index b0d940693..5ff12fea6 100644 --- a/crates/ui-egui/src/i18n/pt-br.tsv +++ b/crates/ui-egui/src/i18n/pt-br.tsv @@ -2356,6 +2356,12 @@ digits Malayalam Malaiala Reset OpenType Features Redefinir Recursos OpenType Reset Panel Redefinir Painel Reset Preferences Redefinir Preferências + Export Preferences… Exportar Preferências… + Import Preferences… Importar Preferências… + Save the applied preferences to a JSON file Salvar as preferências aplicadas em um arquivo JSON + Apply the preferences in a file Export Preferences saved Aplicar as preferências de um arquivo salvo com Exportar Preferências + Export Preferences Exportar Preferências + Import Preferences Importar Preferências Search Preferences Pesquisar nas Preferências No settings found Nenhuma configuração encontrada Reset Profiles Redefinir Perfis @@ -4229,6 +4235,20 @@ axis Both Ambos @msg unknown overprints `{o}` (preserve, simulate or discard) sobreimpressões desconhecidas `{o}` (preserve, simulate ou discard) @msg unknown panel `{raw}` painel desconhecido `{raw}` @msg unknown preference `{key}` preferência desconhecida `{key}` +@msg Preferences imported. Preferências importadas. +@msg not a preferences file: {e} não é um arquivo de preferências: {e} +@msg not a preferences file: no JSON object não é um arquivo de preferências: nenhum objeto JSON +@msg not a preferences file (format `{format}`) não é um arquivo de preferências (formato `{format}`) +@msg not a preferences file: no `format` não é um arquivo de preferências: falta `format` +@msg not a preferences file: no `version` não é um arquivo de preferências: falta `version` +@msg not a preferences file: no `prefs` não é um arquivo de preferências: falta `prefs` +@msg the file was saved by a newer version of VectorCraft (version {v}) o arquivo foi salvo por uma versão mais recente do VectorCraft (versão {v}) +@msg the file holds no preferences o arquivo não contém preferências +@msg `{key}` names a folder, which preferences files leave out `{key}` indica uma pasta, e os arquivos de preferências não incluem pastas +@msg the file is larger than 1 MB o arquivo tem mais de 1 MB +@msg give `path` or `data` informe `path` ou `data` +@msg `{key}` must be an object `{key}` deve ser um objeto +@msg `prefs.import` runs on its own, not as a batch step `prefs.import` é executado sozinho, não como etapa de um lote @msg unknown preset (one of {_1}) predefinição desconhecida (uma de {_1}) @msg unknown preset `{name}` (high, medium, low or a saved one: see flattener.presets.list) predefinição desconhecida `{name}` (high, medium, low ou uma salva: veja flattener.presets.list) @msg unknown preset `{name}` (see flattener.presets.list) predefinição desconhecida `{name}` (veja flattener.presets.list) @@ -5436,6 +5456,7 @@ file-filter Swatch libraries Bibliotecas de amostras file-filter Flattener presets Predefinidos de achatamento file-filter PDF presets Predefinidos de PDF file-filter Print presets Predefinidos de impressão +file-filter Preferences Preferências file-filter Export presets Predefinições de exportação file-filter SVG Compressed SVG Compactado file-filter PDF-compatible .ai .ai compatível com PDF diff --git a/crates/ui-egui/src/i18n/ru.tsv b/crates/ui-egui/src/i18n/ru.tsv index a00325e8b..bef300ea9 100644 --- a/crates/ui-egui/src/i18n/ru.tsv +++ b/crates/ui-egui/src/i18n/ru.tsv @@ -1275,6 +1275,7 @@ file-filter Swatch libraries Библиотеки образцов file-filter Flattener presets Наборы объединения file-filter PDF presets Наборы PDF file-filter Print presets Наборы печати +file-filter Preferences Настройки file-filter Export presets Наборы настроек экспорта file-filter SVG Compressed Сжатый SVG file-filter PDF-compatible .ai .ai, совместимый с PDF @@ -2847,6 +2848,12 @@ digits Malayalam Малаялам Reset OpenType Features Сбросить функции OpenType Reset Panel Сбросить панель Reset Preferences Сбросить настройки + Export Preferences… Экспортировать настройки… + Import Preferences… Импортировать настройки… + Save the applied preferences to a JSON file Сохранить применённые настройки в файл JSON + Apply the preferences in a file Export Preferences saved Применить настройки из файла, сохранённого командой «Экспортировать настройки» + Export Preferences Экспортировать настройки + Import Preferences Импортировать настройки Search Preferences Поиск в настройках No settings found Настройки не найдены Reset Profiles Сбросить профили @@ -4717,6 +4724,20 @@ axis Both Оба @msg unknown overprints `{o}` (preserve, simulate or discard) неизвестные наложения `{o}` (preserve, simulate или discard) @msg unknown panel `{raw}` неизвестная панель `{raw}` @msg unknown preference `{key}` неизвестная настройка `{key}` +@msg Preferences imported. Настройки импортированы. +@msg not a preferences file: {e} не является файлом настроек: {e} +@msg not a preferences file: no JSON object не является файлом настроек: нет объекта JSON +@msg not a preferences file (format `{format}`) не является файлом настроек (формат `{format}`) +@msg not a preferences file: no `format` не является файлом настроек: нет `format` +@msg not a preferences file: no `version` не является файлом настроек: нет `version` +@msg not a preferences file: no `prefs` не является файлом настроек: нет `prefs` +@msg the file was saved by a newer version of VectorCraft (version {v}) файл сохранён более новой версией VectorCraft (версия {v}) +@msg the file holds no preferences файл не содержит настроек +@msg `{key}` names a folder, which preferences files leave out `{key}` указывает папку, а файлы настроек не содержат папок +@msg the file is larger than 1 MB файл больше 1 МБ +@msg give `path` or `data` укажите `path` или `data` +@msg `{key}` must be an object `{key}` должен быть объектом +@msg `prefs.import` runs on its own, not as a batch step `prefs.import` выполняется отдельно, а не как шаг пакета @msg unknown preset (one of {_1}) неизвестный набор (один из: {_1}) @msg unknown preset `{name}` (high, medium, low or a saved one: see flattener.presets.list) неизвестный набор `{name}` (high, medium, low или сохранённый: см. flattener.presets.list) @msg unknown preset `{name}` (see flattener.presets.list) неизвестный набор `{name}` (см. flattener.presets.list) diff --git a/crates/ui-egui/src/i18n/sv.tsv b/crates/ui-egui/src/i18n/sv.tsv index f2b24e7d0..3614a8cb2 100644 --- a/crates/ui-egui/src/i18n/sv.tsv +++ b/crates/ui-egui/src/i18n/sv.tsv @@ -2755,6 +2755,12 @@ digits Malayalam Malayalam Reset OpenType Features Återställ OpenType-funktioner Reset Panel Återställ panel Reset Preferences Återställ inställningar + Export Preferences… Exportera inställningar… + Import Preferences… Importera inställningar… + Save the applied preferences to a JSON file Spara de tillämpade inställningarna i en JSON-fil + Apply the preferences in a file Export Preferences saved Tillämpa inställningarna i en fil som Exportera inställningar har sparat + Export Preferences Exportera inställningar + Import Preferences Importera inställningar Search Preferences Sök i inställningar No settings found Inga inställningar hittades Reset Profiles Återställ profiler @@ -4579,6 +4585,20 @@ axis Both Båda @msg unknown overprints `{o}` (preserve, simulate or discard) okänt värde för övertryck `{o}` (preserve, simulate eller discard) @msg unknown panel `{raw}` okänd panel `{raw}` @msg unknown preference `{key}` okänd inställning `{key}` +@msg Preferences imported. Inställningarna har importerats. +@msg not a preferences file: {e} inte en inställningsfil: {e} +@msg not a preferences file: no JSON object inte en inställningsfil: inget JSON-objekt +@msg not a preferences file (format `{format}`) inte en inställningsfil (format `{format}`) +@msg not a preferences file: no `format` inte en inställningsfil: inget `format` +@msg not a preferences file: no `version` inte en inställningsfil: ingen `version` +@msg not a preferences file: no `prefs` inte en inställningsfil: ingen `prefs` +@msg the file was saved by a newer version of VectorCraft (version {v}) filen sparades av en nyare version av VectorCraft (version {v}) +@msg the file holds no preferences filen innehåller inga inställningar +@msg `{key}` names a folder, which preferences files leave out `{key}` anger en mapp, och inställningsfiler innehåller inga mappar +@msg the file is larger than 1 MB filen är större än 1 MB +@msg give `path` or `data` ange `path` eller `data` +@msg `{key}` must be an object `{key}` måste vara ett objekt +@msg `prefs.import` runs on its own, not as a batch step `prefs.import` körs för sig, inte som ett steg i en batch @msg unknown preset (one of {_1}) okänd förinställning (en av {_1}) @msg unknown preset `{name}` (high, medium, low or a saved one: see flattener.presets.list) okänd förinställning `{name}` (high, medium, low eller en sparad: se flattener.presets.list) @msg unknown preset `{name}` (see flattener.presets.list) okänd förinställning `{name}` (se flattener.presets.list) @@ -5527,6 +5547,7 @@ file-filter Flattener presets Förinställningar för plattning file-filter PDF presets PDF-förinställningar file-filter PDF-compatible .ai PDF-kompatibel .ai file-filter Print presets Utskriftsförinställningar +file-filter Preferences Inställningar file-filter Export presets Exportförinställningar file-filter SVG Compressed SVG komprimerad file-filter Swatch libraries Färgrutebibliotek diff --git a/crates/ui-egui/src/i18n/tr.tsv b/crates/ui-egui/src/i18n/tr.tsv index 76d73994b..12ed56823 100644 --- a/crates/ui-egui/src/i18n/tr.tsv +++ b/crates/ui-egui/src/i18n/tr.tsv @@ -2624,6 +2624,12 @@ digits Malayalam Malayalam Reset OpenType Features OpenType Özelliklerini Sıfırla Reset Panel Paneli Sıfırla Reset Preferences Tercihleri Sıfırla + Export Preferences… Tercihleri Dışa Aktar… + Import Preferences… Tercihleri İçe Aktar… + Save the applied preferences to a JSON file Uygulanan tercihleri bir JSON dosyasına kaydet + Apply the preferences in a file Export Preferences saved Tercihleri Dışa Aktar ile kaydedilen bir dosyadaki tercihleri uygula + Export Preferences Tercihleri Dışa Aktar + Import Preferences Tercihleri İçe Aktar Search Preferences Tercihlerde Ara No settings found Ayar bulunamadı Reset Profiles Profilleri Sıfırla @@ -4428,6 +4434,20 @@ axis Both Her İkisi @msg unknown overprints `{o}` (preserve, simulate or discard) bilinmeyen üst baskılar `{o}` (koru, benzet veya at) @msg unknown panel `{raw}` bilinmeyen panel `{raw}` @msg unknown preference `{key}` bilinmeyen tercih `{key}` +@msg Preferences imported. Tercihler içe aktarıldı. +@msg not a preferences file: {e} tercih dosyası değil: {e} +@msg not a preferences file: no JSON object tercih dosyası değil: JSON nesnesi yok +@msg not a preferences file (format `{format}`) tercih dosyası değil (biçim `{format}`) +@msg not a preferences file: no `format` tercih dosyası değil: `format` yok +@msg not a preferences file: no `version` tercih dosyası değil: `version` yok +@msg not a preferences file: no `prefs` tercih dosyası değil: `prefs` yok +@msg the file was saved by a newer version of VectorCraft (version {v}) dosya VectorCraft'ın daha yeni bir sürümüyle kaydedilmiş (sürüm {v}) +@msg the file holds no preferences dosyada tercih yok +@msg `{key}` names a folder, which preferences files leave out `{key}` bir klasör belirtir ve tercih dosyaları klasör içermez +@msg the file is larger than 1 MB dosya 1 MB'tan büyük +@msg give `path` or `data` `path` veya `data` verin +@msg `{key}` must be an object `{key}` bir nesne olmalıdır +@msg `prefs.import` runs on its own, not as a batch step `prefs.import` tek başına çalışır, toplu iş adımı olarak değil @msg unknown preset (one of {_1}) bilinmeyen ön ayar ({_1}'den biri) @msg unknown preset `{name}` (high, medium, low or a saved one: see flattener.presets.list) bilinmeyen ön ayar `{name}` (yüksek, orta, düşük veya kaydedilmiş bir ayar: bkz. flattener.presets.list) @msg unknown preset `{name}` (see flattener.presets.list) bilinmeyen ön ayar `{name}` (bkz. düzleştirici.presets.list) @@ -5324,6 +5344,7 @@ file-filter Flattener presets Düzleştirici önayarları file-filter PDF presets PDF önayarları file-filter PDF-compatible .ai PDF ile uyumlu .ai file-filter Print presets Baskı önayarları +file-filter Preferences Tercihler file-filter SVG Compressed Sıkıştırılmış SVG file-filter Swatch libraries Renk örneği kitaplıkları file-filter Templates Şablonlar diff --git a/crates/ui-egui/src/i18n/uk.tsv b/crates/ui-egui/src/i18n/uk.tsv index 4652ce4ca..80dd6e4db 100644 --- a/crates/ui-egui/src/i18n/uk.tsv +++ b/crates/ui-egui/src/i18n/uk.tsv @@ -1272,6 +1272,7 @@ file-filter Swatch libraries Бібліотеки зразків file-filter Flattener presets Набори зведення file-filter PDF presets Набори PDF file-filter Print presets Набори друку +file-filter Preferences Налаштування file-filter Export presets Набори параметрів експорту file-filter SVG Compressed Стиснутий SVG file-filter PDF-compatible .ai .ai, сумісний з PDF @@ -2844,6 +2845,12 @@ digits Malayalam Малаялам Reset OpenType Features Скинути функції OpenType Reset Panel Скинути панель Reset Preferences Скинути налаштування + Export Preferences… Експортувати налаштування… + Import Preferences… Імпортувати налаштування… + Save the applied preferences to a JSON file Зберегти застосовані налаштування у файл JSON + Apply the preferences in a file Export Preferences saved Застосувати налаштування з файлу, збереженого командою «Експортувати налаштування» + Export Preferences Експортувати налаштування + Import Preferences Імпортувати налаштування Search Preferences Пошук у налаштуваннях No settings found Налаштування не знайдено Reset Profiles Скинути профілі @@ -4707,6 +4714,20 @@ axis Both Обидві @msg unknown overprints `{o}` (preserve, simulate or discard) невідомі накладення `{o}` (preserve, simulate або discard) @msg unknown panel `{raw}` невідома панель `{raw}` @msg unknown preference `{key}` невідоме налаштування `{key}` +@msg Preferences imported. Налаштування імпортовано. +@msg not a preferences file: {e} не є файлом налаштувань: {e} +@msg not a preferences file: no JSON object не є файлом налаштувань: немає об’єкта JSON +@msg not a preferences file (format `{format}`) не є файлом налаштувань (формат `{format}`) +@msg not a preferences file: no `format` не є файлом налаштувань: немає `format` +@msg not a preferences file: no `version` не є файлом налаштувань: немає `version` +@msg not a preferences file: no `prefs` не є файлом налаштувань: немає `prefs` +@msg the file was saved by a newer version of VectorCraft (version {v}) файл збережено новішою версією VectorCraft (версія {v}) +@msg the file holds no preferences файл не містить налаштувань +@msg `{key}` names a folder, which preferences files leave out `{key}` вказує теку, а файли налаштувань не містять тек +@msg the file is larger than 1 MB файл більший за 1 МБ +@msg give `path` or `data` вкажіть `path` або `data` +@msg `{key}` must be an object `{key}` має бути об’єктом +@msg `prefs.import` runs on its own, not as a batch step `prefs.import` виконується окремо, а не як крок пакета @msg unknown preset (one of {_1}) невідомий набір (один із: {_1}) @msg unknown preset `{name}` (high, medium, low or a saved one: see flattener.presets.list) невідомий набір `{name}` (high, medium, low або збережений: див. flattener.presets.list) @msg unknown preset `{name}` (see flattener.presets.list) невідомий набір `{name}` (див. flattener.presets.list) diff --git a/crates/ui-egui/src/i18n/vi.tsv b/crates/ui-egui/src/i18n/vi.tsv index 4c9dac833..49424ebcb 100644 --- a/crates/ui-egui/src/i18n/vi.tsv +++ b/crates/ui-egui/src/i18n/vi.tsv @@ -1475,6 +1475,7 @@ file-filter Swatch libraries Thư viện mẫu màu file-filter Flattener presets Thiết lập sẵn làm phẳng file-filter PDF presets Thiết lập sẵn PDF file-filter Print presets Thiết lập sẵn in +file-filter Preferences Tùy chọn file-filter Export presets Cài đặt sẵn xuất file-filter SVG Compressed SVG nén file-filter PDF-compatible .ai .ai tương thích PDF @@ -3125,6 +3126,12 @@ mark-style Japanese Kiểu Nhật Reset OpenType Features Đặt lại tính năng OpenType Reset Panel Đặt lại bảng Reset Preferences Đặt lại tùy chọn + Export Preferences… Xuất tùy chọn… + Import Preferences… Nhập tùy chọn… + Save the applied preferences to a JSON file Lưu các tùy chọn đang áp dụng vào tệp JSON + Apply the preferences in a file Export Preferences saved Áp dụng các tùy chọn trong tệp do Xuất tùy chọn lưu + Export Preferences Xuất tùy chọn + Import Preferences Nhập tùy chọn Search Preferences Tìm trong tùy chọn No settings found Không tìm thấy cài đặt Reset Profiles Đặt lại hồ sơ màu @@ -5159,6 +5166,20 @@ theme Appearance Giao diện @msg missing `{path}` thiếu `{path}` @msg `{path}` must be {expected}, not {got} `{path}` phải là {expected}, không phải {got} @msg unknown preference `{key}` tùy chọn không xác định `{key}` +@msg Preferences imported. Đã nhập tùy chọn. +@msg not a preferences file: {e} không phải tệp tùy chọn: {e} +@msg not a preferences file: no JSON object không phải tệp tùy chọn: không có đối tượng JSON +@msg not a preferences file (format `{format}`) không phải tệp tùy chọn (định dạng `{format}`) +@msg not a preferences file: no `format` không phải tệp tùy chọn: thiếu `format` +@msg not a preferences file: no `version` không phải tệp tùy chọn: thiếu `version` +@msg not a preferences file: no `prefs` không phải tệp tùy chọn: thiếu `prefs` +@msg the file was saved by a newer version of VectorCraft (version {v}) tệp được lưu bằng phiên bản VectorCraft mới hơn (phiên bản {v}) +@msg the file holds no preferences tệp không chứa tùy chọn nào +@msg `{key}` names a folder, which preferences files leave out `{key}` chỉ định một thư mục, và tệp tùy chọn không chứa thư mục +@msg the file is larger than 1 MB tệp lớn hơn 1 MB +@msg give `path` or `data` hãy cung cấp `path` hoặc `data` +@msg `{key}` must be an object `{key}` phải là một đối tượng +@msg `prefs.import` runs on its own, not as a batch step `prefs.import` chạy riêng, không phải là một bước trong lô @msg unknown preset (one of {_1}) thiết lập sẵn không xác định (một trong {_1}) @msg unknown preset `{name}` (high, medium, low or a saved one: see flattener.presets.list) thiết lập sẵn không xác định `{name}` (high, medium, low hoặc thiết lập đã lưu: xem flattener.presets.list) @msg unknown preset `{name}` (see flattener.presets.list) thiết lập sẵn không xác định `{name}` (xem flattener.presets.list) diff --git a/crates/ui-egui/src/i18n/zh-hans.tsv b/crates/ui-egui/src/i18n/zh-hans.tsv index c62b17668..60d075ca8 100644 --- a/crates/ui-egui/src/i18n/zh-hans.tsv +++ b/crates/ui-egui/src/i18n/zh-hans.tsv @@ -1216,6 +1216,7 @@ file-filter Swatch libraries 色板库 file-filter Flattener presets 拼合预设 file-filter PDF presets PDF 预设 file-filter Print presets 打印预设 +file-filter Preferences 首选项 file-filter Export presets 导出预设 file-filter SVG Compressed 压缩 SVG file-filter PDF-compatible .ai PDF 兼容 .ai @@ -2718,6 +2719,12 @@ digits Malayalam 马拉雅拉姆数字 Reset OpenType Features 重置 OpenType 特性 Reset Panel 重置面板 Reset Preferences 重置首选项 + Export Preferences… 导出首选项… + Import Preferences… 导入首选项… + Save the applied preferences to a JSON file 将已应用的首选项保存到 JSON 文件 + Apply the preferences in a file Export Preferences saved 应用“导出首选项”所保存文件中的首选项 + Export Preferences 导出首选项 + Import Preferences 导入首选项 Search Preferences 搜索首选项 No settings found 未找到设置 Reset Profiles 重置配置文件 @@ -4148,6 +4155,20 @@ imageTrace Outline 轮廓 @msg `{key}` must be between {min} and {max} (got {x}) `{key}` 必须介于 {min} 和 {max} 之间 (实际为 {x}) @msg `{key}` must be one of {_1} `{key}` 必须是 {_1} 之一 @msg unknown preference `{key}` 未知首选项 `{key}` +@msg Preferences imported. 已导入首选项。 +@msg not a preferences file: {e} 不是首选项文件:{e} +@msg not a preferences file: no JSON object 不是首选项文件:没有 JSON 对象 +@msg not a preferences file (format `{format}`) 不是首选项文件(格式 `{format}`) +@msg not a preferences file: no `format` 不是首选项文件:没有 `format` +@msg not a preferences file: no `version` 不是首选项文件:没有 `version` +@msg not a preferences file: no `prefs` 不是首选项文件:没有 `prefs` +@msg the file was saved by a newer version of VectorCraft (version {v}) 该文件由更新版本的 VectorCraft 保存(版本 {v}) +@msg the file holds no preferences 该文件不包含首选项 +@msg `{key}` names a folder, which preferences files leave out `{key}` 指定的是文件夹,首选项文件不包含文件夹 +@msg the file is larger than 1 MB 文件大于 1 MB +@msg give `path` or `data` 请提供 `path` 或 `data` +@msg `{key}` must be an object `{key}` 必须是对象 +@msg `prefs.import` runs on its own, not as a batch step `prefs.import` 需单独运行,不能作为批处理步骤 @msg Data Recovery has nowhere to keep copies: set Preferences → File Handling → Data Recovery → Folder (recoveryFolder) 数据恢复没有可保存副本的位置:请设置“首选项 → 文件处理 → 数据恢复 → 文件夹” (recoveryFolder) @msg Data Recovery can't keep copies in {path}: {e} 数据恢复无法将副本保存到 {path}:{e} @msg Plug-ins can't be loaded from {path}: {e} 无法从 {path} 载入增效工具:{e} diff --git a/crates/ui-egui/src/i18n/zh-hant.tsv b/crates/ui-egui/src/i18n/zh-hant.tsv index 0b3a21250..c95abd3f0 100644 --- a/crates/ui-egui/src/i18n/zh-hant.tsv +++ b/crates/ui-egui/src/i18n/zh-hant.tsv @@ -1242,6 +1242,7 @@ file-filter Swatch libraries 色票資料庫 file-filter Flattener presets 平面化預設集 file-filter PDF presets PDF 預設集 file-filter Print presets 列印預設集 +file-filter Preferences 偏好設定 file-filter Export presets 轉存預設集 file-filter SVG Compressed 壓縮 SVG file-filter PDF-compatible .ai PDF 相容 .ai @@ -1982,6 +1983,20 @@ file-filter Templates 範本 @msg `{key}` must be between {min} and {max} (got {x}) `{key}` 必須介於 {min} 和 {max} 之間 (收到 {x}) @msg `{key}` must be one of {_1} `{key}` 必須是 {_1} 其中之一 @msg unknown preference `{key}` 未知的偏好設定 `{key}` +@msg Preferences imported. 已匯入偏好設定。 +@msg not a preferences file: {e} 不是偏好設定檔案:{e} +@msg not a preferences file: no JSON object 不是偏好設定檔案:沒有 JSON 物件 +@msg not a preferences file (format `{format}`) 不是偏好設定檔案(格式 `{format}`) +@msg not a preferences file: no `format` 不是偏好設定檔案:沒有 `format` +@msg not a preferences file: no `version` 不是偏好設定檔案:沒有 `version` +@msg not a preferences file: no `prefs` 不是偏好設定檔案:沒有 `prefs` +@msg the file was saved by a newer version of VectorCraft (version {v}) 此檔案由較新版本的 VectorCraft 儲存(版本 {v}) +@msg the file holds no preferences 此檔案不含偏好設定 +@msg `{key}` names a folder, which preferences files leave out `{key}` 指定的是資料夾,偏好設定檔案不含資料夾 +@msg the file is larger than 1 MB 檔案大於 1 MB +@msg give `path` or `data` 請提供 `path` 或 `data` +@msg `{key}` must be an object `{key}` 必須是物件 +@msg `prefs.import` runs on its own, not as a batch step `prefs.import` 須單獨執行,不能作為批次步驟 @msg Data Recovery has nowhere to keep copies: set Preferences → File Handling → Data Recovery → Folder (recoveryFolder) 「資料復原」沒有可存放拷貝的位置:請設定「偏好設定 → 檔案處理 → 資料復原 → 資料夾」 (recoveryFolder) @msg Data Recovery can't keep copies in {path}: {e} 「資料復原」無法將拷貝存放到 {path}:{e} @msg Plug-ins can't be loaded from {path}: {e} 無法從 {path} 載入增效模組:{e} @@ -3774,6 +3789,12 @@ digits Malayalam 馬來亞拉姆數字 Reset OpenType Features 重設 OpenType 功能 Reset Panel 重設面板 Reset Preferences 重設偏好設定 + Export Preferences… 匯出偏好設定… + Import Preferences… 匯入偏好設定… + Save the applied preferences to a JSON file 將已套用的偏好設定儲存為 JSON 檔案 + Apply the preferences in a file Export Preferences saved 套用「匯出偏好設定」所儲存檔案中的偏好設定 + Export Preferences 匯出偏好設定 + Import Preferences 匯入偏好設定 Search Preferences 搜尋偏好設定 No settings found 找不到設定 Reset Profiles 重設描述檔 diff --git a/crates/ui-egui/src/io/save.rs b/crates/ui-egui/src/io/save.rs index e9860e99a..cfbdaaadb 100644 --- a/crates/ui-egui/src/io/save.rs +++ b/crates/ui-egui/src/io/save.rs @@ -132,6 +132,7 @@ const FILE_TYPES: &[(&str, &[&str])] = &[ ("Composite fonts", vectorcraft_engine::cmd::compositefonts::EXTS), ("CSS", &["css"]), ("PostScript", &["ps"]), + ("Preferences", &["json"]), ]; /// The file type a save panel shows for a file with extension `ext`: its format's, else its diff --git a/crates/ui-egui/src/prefs_dialog.rs b/crates/ui-egui/src/prefs_dialog.rs index d3ac65c95..9ef6f1a26 100644 --- a/crates/ui-egui/src/prefs_dialog.rs +++ b/crates/ui-egui/src/prefs_dialog.rs @@ -400,6 +400,21 @@ pub fn show(app: &mut VectorcraftApp, ctx: &egui::Context) { }); }); ui.add_space(14.0); + ui.horizontal(|ui| { + if widgets::secondary_button(ui, tl!("Export Preferences…")).on_hover_text(tl!("Save the applied preferences to a JSON file")).clicked() + { + export_preferences(app, &mut d); + } + // Not on the web, which has no open dialog that returns a file here. + if crate::picks::can(app, &crate::picks::PickRequest::Open(crate::FilePick::default())) + && widgets::secondary_button(ui, tl!("Import Preferences…")) + .on_hover_text(tl!("Apply the preferences in a file Export Preferences saved")) + .clicked() + { + import_preferences(app, &mut d); + } + }); + ui.add_space(8.0); ui.horizontal(|ui| { if widgets::secondary_button(ui, tl!("Reset Preferences")) .on_hover_text(tl!("Restore every preference to its default (applied on OK)")) @@ -664,6 +679,67 @@ fn choose_folder(app: &mut VectorcraftApp, d: &mut Dialog, key: &'static str) { } } +/// The file name Export Preferences… suggests. +const PREFS_FILE_NAME: &str = "VectorCraft Preferences.json"; + +/// Export Preferences…: the applied preferences (`prefs.export`) go to a file picked in a save +/// dialog (the web downloads it). Changes in the dialog not yet applied with OK are not in it. +fn export_preferences(app: &mut VectorcraftApp, d: &mut Dialog) { + let r = + crate::picks::in_app_dialog(app, d, |app, _| crate::io::save_command_output_named(app, "prefs.export", PREFS_FILE_NAME, json!({})).map(drop)); + if let Err(e) = r + && e != "cancelled" + { + app.status(e); + } +} + +/// Import Preferences…: the preferences in a file picked in an open dialog are applied +/// (`prefs.import`), and the dialog shows them. On an error nothing changes and the status bar +/// says why. +fn import_preferences(app: &mut VectorcraftApp, d: &mut Dialog) { + let r = crate::picks::in_app_dialog(app, d, |app, d| { + let pick = crate::FilePick { filters: vec![crate::FilePick::filter("Preferences", &["json"])], ..Default::default() }; + let Some(path) = crate::picks::open(app, &pick) else { return Ok(()) }; + app.run("prefs.import", json!({ "path": path }))?; + show_prefs(d, &app.session.prefs.to_json()); + app.status("Preferences imported."); + Ok(()) + }); + if let Err(e) = r { + app.status(e); + } +} + +/// After `prefs.import`: the preferences are saved with the UI state, Enable Touch Workspace takes +/// effect when the import changed it from `touch_workspace`, and an open Preferences dialog shows +/// the imported values. +pub(crate) fn imported(app: &mut VectorcraftApp, touch_workspace: bool) { + snapshot(app); + app.prefs_changed = true; + if app.session.prefs.touch_workspace != touch_workspace { + crate::workspaces::sync_touch_workspace(app, app.touch_screen); + } + let prefs = app.session.prefs.to_json(); + if let Some(d) = app.ui.dialog.as_mut().filter(|d| d.kind == "preferences") { + show_prefs(d, &prefs); + } +} + +/// The Preferences dialog `d` shows the preferences `prefs` in place of its working copy. Units › +/// General shows the preference, and OK then leaves the open document's units alone unless the +/// field is changed. +fn show_prefs(d: &mut Dialog, prefs: &Value) { + for (k, v) in prefs.as_object().into_iter().flatten() { + if d.fields.contains_key(k) { + d.fields.insert(k.clone(), v.clone()); + } + } + if let Some(v) = d.fields.get("unitsGeneral").cloned() { + d.fields.insert(RESET_UNITS.into(), v); + } +} + /// Is one of the dialog's Units (General, Stroke, Type, East Asian Type) Picas? fn picas_in_use(d: &Dialog) -> bool { ["unitsGeneral", "unitsStroke", "unitsType", "unitsAsianType"].iter().any(|k| d.str(k) == vectorcraft_doc::Unit::Picas.key()) diff --git a/crates/ui-egui/src/prefs_dialog/tests.rs b/crates/ui-egui/src/prefs_dialog/tests.rs index ccb7ce320..9cc78bd8c 100644 --- a/crates/ui-egui/src/prefs_dialog/tests.rs +++ b/crates/ui-egui/src/prefs_dialog/tests.rs @@ -930,6 +930,105 @@ fn temp_folder(name: &str) -> std::path::PathBuf { dir } +/// An app whose save and open dialogs answer `path`, and that writes and reads real files. +fn app_with_file_dialogs(path: &str) -> VectorcraftApp { + let mut a = app(); + let (save, open) = (path.to_string(), path.to_string()); + a.services.pick_save = Some(Box::new(move |_: &crate::FilePick| Some(save.clone()))); + a.services.pick_open = Some(Box::new(move |_: &crate::FilePick| Some(open.clone()))); + a.services.write = Some(Box::new(|p: &str, b: &[u8]| std::fs::write(p, b).map_err(|e| e.to_string()))); + a.services.read = Some(Box::new(|p: &str| std::fs::read(p).map_err(|e| e.to_string()))); + a +} + +/// Export Preferences… saves the applied preferences to a file, and Import Preferences… applies +/// them again: the dialog shows them, OK keeps them, and the open document's units stay as they +/// are. +#[test] +fn export_and_import_preferences_buttons_round_trip() { + let dir = temp_folder("prefs-file"); + let path = dir.join("VectorCraft Preferences.json").to_string_lossy().into_owned(); + let mut a = app_with_file_dialogs(&path); + a.run("file.new", json!({"width": 400, "height": 400})).unwrap(); + a.run("document.setUnits", json!({"units": "Millimeters"})).unwrap(); + let values = json!({"keyboardIncrement": 5, "uiAccent": "green", "unitsGeneral": "inches"}); + a.run("prefs.set", json!({"values": values, "documentUnits": false})).unwrap(); + let saved = a.session.prefs.to_json(); + open(&mut a, Some("General")); + click_button(&mut a, "Export Preferences…"); + let file: Value = serde_json::from_slice(&std::fs::read(&path).unwrap()).unwrap(); + assert_eq!(file["format"], "vectorcraft-preferences"); + assert_eq!(file["prefs"]["uiAccent"], "green"); + + a.ui.dialog = None; + a.run("prefs.reset", json!({})).unwrap(); + let undo = a.session.active().unwrap().history.undo.len(); + open(&mut a, Some("General")); + a.ui.dialog.as_mut().unwrap().fields.insert("keyboardIncrement".into(), json!(9)); + click_button(&mut a, "Import Preferences…"); + assert_eq!(a.ui.status, "Preferences imported."); + assert_eq!(a.session.prefs.to_json(), saved, "the exported preferences are applied"); + assert_eq!(a.ui.engine_prefs, saved, "and saved with the UI state"); + let d = a.ui.dialog.as_ref().expect("the dialog stays open"); + assert_eq!((d.fields["keyboardIncrement"].clone(), d.fields["unitsGeneral"].clone()), (json!(5.0), json!("inches")), "the dialog shows them"); + confirm(&mut a).unwrap(); + assert_eq!(a.session.prefs.to_json(), saved); + let st = a.session.active().unwrap(); + assert_eq!(st.doc.units, vectorcraft_doc::Unit::Millimeters, "the document's units stay"); + assert_eq!(st.history.undo.len(), undo, "no undo step"); + let _ = std::fs::remove_dir_all(&dir); +} + +/// A file Import Preferences… can't use changes nothing, the dialog's edits included, and the +/// status bar says why. +#[test] +fn import_preferences_of_a_bad_file_changes_nothing() { + let dir = temp_folder("prefs-bad-file"); + let path = dir.join("newer.json"); + std::fs::write(&path, r#"{"format": "vectorcraft-preferences", "version": 2, "prefs": {"uiAccent": "pink"}}"#).unwrap(); + let mut a = app_with_file_dialogs(&path.to_string_lossy()); + let before = a.session.prefs.to_json(); + open(&mut a, Some("General")); + a.ui.dialog.as_mut().unwrap().fields.insert("keyboardIncrement".into(), json!(9)); + click_button(&mut a, "Import Preferences…"); + assert!(a.ui.status.contains("newer version of VectorCraft"), "{}", a.ui.status); + assert_eq!(a.session.prefs.to_json(), before); + assert_eq!(a.ui.dialog.as_ref().unwrap().fields["keyboardIncrement"], json!(9), "the dialog keeps its edits"); + let _ = std::fs::remove_dir_all(&dir); +} + +/// `prefs.import` from a script or MCP while Preferences is open: the dialog shows the imported +/// values. Import Preferences… shows only where the host has an open dialog (not on the web). +#[test] +fn prefs_import_refreshes_an_open_preferences_dialog() { + let mut a = app(); + open(&mut a, Some("General")); + let data = json!({"format": "vectorcraft-preferences", "version": 1, "prefs": {"keyboardIncrement": 7}}).to_string(); + a.run("prefs.import", json!({ "data": data })).unwrap(); + assert_eq!(a.ui.dialog.as_ref().unwrap().fields["keyboardIncrement"], json!(7.0)); + assert_eq!(a.ui.engine_prefs["keyboardIncrement"], json!(7.0)); + let text = crate::tests_labels::painted_text(&mut a, |a, ui| show(a, ui.ctx())); + assert!(text.contains("Export Preferences…") && !text.contains("Import Preferences…"), "{text}"); + a.services.pick_open = Some(Box::new(|_: &crate::FilePick| None)); + let text = crate::tests_labels::painted_text(&mut a, |a, ui| show(a, ui.ctx())); + assert!(text.contains("Import Preferences…"), "{text}"); +} + +/// An import that turns Enable Touch Workspace on or off switches the workspace on a touch screen, +/// as OK in Preferences does. +#[test] +fn prefs_import_of_touch_workspace_switches_the_workspace() { + let mut a = app(); + a.run("prefs.set", json!({"key": "touchWorkspace", "value": false})).unwrap(); + a.touch_screen = true; + a.ui.workspace = "Typography".into(); + let file = |on: bool| json!({"format": "vectorcraft-preferences", "version": 1, "prefs": {"touchWorkspace": on}}).to_string(); + a.run("prefs.import", json!({ "data": file(true) })).unwrap(); + assert_eq!(a.ui.workspace, crate::workspaces::TOUCH); + a.run("prefs.import", json!({ "data": file(false) })).unwrap(); + assert_eq!(a.ui.workspace, "Typography"); +} + /// File Handling › Data Recovery › Folder has a Choose… button where the host has a folder /// picker, and none where it has not (the web). #[test] diff --git a/docs/mcp.md b/docs/mcp.md index 23d12e76b..69b9da4b1 100644 --- a/docs/mcp.md +++ b/docs/mcp.md @@ -4532,6 +4532,24 @@ field with text clears it, and Escape in the empty field closes the dialog. {"name":"run_command","arguments":{"command":"prefs.set","params":{"key":"unitsGeneral","value":"millimeters"}}} ``` +`prefs.export {path?}` writes the preferences as a JSON file, `{"format": "vectorcraft-preferences", "version": 1, +"prefs": {…}}`, and replies `{path, count}`; without `path` it replies `{data, count}` with the file's text. The file +holds every preference `prefs.list` lists and the `eyedropper`, `perspectiveWidget`, `pathfinder` and `glyphSnap` +groups. It leaves out the folder preferences (`fontsFolder`, `pluginsFolder`, `recoveryFolder`, `templatesFolder`) and +saved presets. An export over 1 MB, the most `prefs.import` reads, is an error and writes nothing. +`prefs.import {path | data}` applies such a file and replies `{imported}`, the number of preferences it +set. It checks every value first, as `prefs.set` does: a file over 1 MB, malformed JSON, another `format`, a newer +`version`, an empty `prefs`, an unknown preference, a value of the wrong type or out of range, or a folder preference +is an error, and nothing changes. Preferences the file leaves out keep their values. An import leaves the open +document's units as they are, records no undo step, and is saved with the other preferences. An open Preferences +dialog shows the imported values. `prefs.import` is refused as a `command.batch` step. The dialog's Export Preferences… and Import Preferences… buttons run the two +commands with a save or open dialog; the web build downloads the exported file and has no Import Preferences… button. +Exporting and importing preferences is an optional VectorCraft feature. + +```json +{"name":"run_command","arguments":{"command":"prefs.import","params":{"data":"{\"format\":\"vectorcraft-preferences\",\"version\":1,\"prefs\":{\"uiAccent\":\"green\"}}"}}} +``` + ## Document Setup `document.setup` with no params reports the document setup: units, the bleed ([top, bottom, left, right] in pt, drawn diff --git a/docs/target-app-parity.md b/docs/target-app-parity.md index 4721d30a0..e4043e876 100644 --- a/docs/target-app-parity.md +++ b/docs/target-app-parity.md @@ -656,6 +656,7 @@ Moved from the ROADMAP's "Shipped so far" on 2026-10-10. Update it when a featur - **Window › Arrange › Float in Window, Float All in Windows and Cascade (G7):** documents float in windows of their own, out of the tab strip ([public source](https://helpx.adobe.com/illustrator/using/customizing-workspace.html)). Float in Window (`window.arrange.floatInWindow`) floats the current document, Float All in Windows (`window.arrange.floatAllInWindows`) every tabbed one, and Cascade (`window.arrange.cascade`) floats every document and stacks the windows from the upper left to the lower right ([public source](https://helpx.adobe.com/illustrator/using/viewing-artwork.html)). On the desktop each window is an OS window; on the web and on Wayland it is a window inside the app's window. The window of the current document holds the canvas; the others draw their documents as Tile's panes do, and a press in one makes its document the current one. Closing a window closes its document, asking to save unsaved changes. Consolidate All Windows brings every document back under the tabs, and Tile brings them back into its grid. The cascade's 30 pt step, the windows' size and when the items are enabled (Cascade with two documents or more, Float in Window for a tabbed current document, Float All in Windows while a document is tabbed, Consolidate All Windows while documents are tiled or floating) are VectorCraft's own. Tests: `crates/ui-egui/src/doc_windows/tests.rs`. - **Dragging a document tab out and a document window back (G7):** a document tab dragged more than 24 pt above or below the tab strip floats its document in a window under the pointer, and a document window dragged onto the tab strip goes back under the tabs ([public source](https://helpx.adobe.com/illustrator/using/customizing-workspace.html)). A window inside the app's window is dragged by its title bar; an OS window by the document's tab at its top, and the window follows the pointer. The 24 pt distance and the tab at the top of an OS window are VectorCraft's own. Tests: `crates/ui-egui/src/doc_windows/tests.rs`. - **Reordering document tabs (G7, #5663):** a document tab dragged along the tab strip moves its document among the others ([public source](https://helpx.adobe.com/illustrator/using/customizing-workspace.html)); a line marks the gap it goes in. The current document stays current, each document keeps its own zoom, centre and artboard, the views Window › New Window opened move with their document, and Window › Next/Previous Document follow the new order. `document.move` (`{from?, to}`) does the same from MCP, the control channel and recordings. `crates/ui-egui/src/tests/tab_reorder.rs`, `crates/engine/src/tests/document_move.rs`. +- **Export and Import Preferences (#6435):** an optional VectorCraft feature. The Preferences dialog's Export Preferences… saves the applied preferences to a JSON file (`prefs.export`; the web build downloads it), and Import Preferences… applies such a file (`prefs.import`). The file leaves out the folder preferences. The import checks every value first and changes nothing on an error; it keeps the open document's units and records no undo step. The file format is VectorCraft's own. Tests: `crates/engine/src/tests/prefs_file.rs`, `crates/ui-egui/src/prefs_dialog/tests.rs`. - **Preferences › User Interface › Open Documents As Tabs (G7, #394):** checked by default, documents open under tabs in one document window ([public source](https://helpx.adobe.com/illustrator/desktop/get-started/learn-the-basics/workspace-overview.html)). With the box cleared, each document opened or created afterwards opens in a floating window of its own, at the next cascade place, as Float in Window floats it; documents already open stay where they are, and checking the box again leaves floating documents in their windows. Tests: `crates/ui-egui/src/doc_windows/tests.rs`. - **Preferences search:** a Search Preferences field above the category list has the keyboard focus when the dialog opens, and Cmd/Ctrl+F gives it back; typing lists the settings it finds, and choosing one shows its category, as the [help page](https://helpx.adobe.com/illustrator/desktop/get-started/preferences-and-settings/set-app-preferences.html) describes. VectorCraft's own rules: the field matches a setting's label, section or category, ignoring case, in the interface language or in English; the list replaces the categories while the field has text and shows every match, or "No settings found"; the chosen setting's row is highlighted and scrolled into view; Escape in the field with text clears it, and Escape in the empty field closes the dialog. The dialog's `__search` field (`ui.dialog.set`) holds the text. Tests: `crates/ui-egui/src/prefs_dialog/tests.rs`. - **Window › New Window (G7, #4116):** New Window (`window.newWindow`) opens another view of the current document ([public source](https://helpx.adobe.com/illustrator/using/viewing-artwork.html) describes showing one document in several windows at once), with its own zoom, center, rotation, artboard and Outline or Preview, as a tab of its own; an edit in one view shows in every view. Tile, Cascade, Float in Window, Float All in Windows and Consolidate All Windows arrange each view as they arrange a document, and Next and Previous Document step through the views. Closing a view leaves the document open; closing its last view closes the document, asking to save it first. The view's number after the name in its tab ("Name:2", while the document has more than one view) is VectorCraft's own. Tests: `crates/ui-egui/src/doc_windows/tests.rs`. From 83245cfe54a659fa1450995994d5747fc7a6de59 Mon Sep 17 00:00:00 2001 From: jmverges Date: Tue, 10 Nov 2026 21:47:45 +0000 Subject: [PATCH 5/6] .ai import: stroked type keeps its cap, join and miter limit (#6472) The text document reader set a type stroke's paint and weight but never its cap, join or miter limit, so every stroked text object imported from a .ai or EPS editing copy drew with a miter join at a limit of 10. Type the file strokes with round joins, or with a miter limit of 4 or less, drew long spikes at its sharp corners. Keys 61, 62 and 64 of a run's character attributes (the run, its character style, then the document defaults) are now read as the stroke's cap (0 butt, 1 round, 2 projecting), join (0 miter, 1 round, 2 bevel) and miter limit, as PostScript numbers caps and joins. In the users' files the page strokes type with exactly these settings. Other values keep the defaults, a miter limit is capped at 500, and type without a stroke keeps the defaults. - ate.rs: Style reads the three keys and char_style passes them on, with a unit test over defaults, character styles, run overrides, hostile values and unstroked type. - tests_native.rs: an imported story with round, bevel, default and unstroked runs. - docs/file-format-parity.md: revision row, status line, one line on the mapping. Co-Authored-By: Claude Opus 5.5 --- crates/eps/src/import/ate.rs | 72 ++++++++++++++++++++++++++- crates/eps/src/import/tests_native.rs | 44 ++++++++++++++++ docs/file-format-parity.md | 5 +- 3 files changed, 117 insertions(+), 4 deletions(-) diff --git a/crates/eps/src/import/ate.rs b/crates/eps/src/import/ate.rs index 2250dea96..194c0ff1e 100644 --- a/crates/eps/src/import/ate.rs +++ b/crates/eps/src/import/ate.rs @@ -26,8 +26,8 @@ use std::collections::BTreeMap; use kurbo::{BezPath, CubicBez, ParamCurve, ParamCurveArclen}; use vectorcraft_color::{Color, Paint}; use vectorcraft_doc::{ - CharStyle, HyphenationOptions, JustificationOptions, Justify, Node, NodeId, NodeKind, ParaDirection, ParaStyle, PathAlign, PathEffect, TabAlign, - TabStop, TextKind, TextObject, TextRun, + CharStyle, HyphenationOptions, JustificationOptions, Justify, LineCap, LineJoin, Node, NodeId, NodeKind, ParaDirection, ParaStyle, PathAlign, + PathEffect, TabAlign, TabStop, TextKind, TextObject, TextRun, }; use vectorcraft_geom::{Affine, PathData, Point, Rect}; @@ -462,6 +462,12 @@ struct Style { fill: Option, stroke: Option, stroke_width: f64, + /// Key 61, the stroke's cap: 0 butt, 1 round, 2 projecting (square). + stroke_cap: LineCap, + /// Key 62, the stroke's join: 0 miter, 1 round, 2 bevel. + stroke_join: LineJoin, + /// Key 64, the stroke's miter limit (a multiple of the stroke weight). + stroke_miter_limit: f64, /// Key 12, the case: 1 Small Caps, 2 All Caps. caps: u8, /// Key 35 at 3: Tate-chu-yoko, the characters set across the column as one block. @@ -752,6 +758,21 @@ impl Texts { fill: if on("56") { Some(pick("53").and_then(paint).unwrap_or(Color::BLACK)) } else { None }, stroke: if matches!(pick("57"), Some(Val::Bool(true))) { pick("54").and_then(paint) } else { None }, stroke_width: num("63").filter(|v| *v >= 0.0 && *v < 1e4).unwrap_or(1.0), + // Numbered as PostScript's `setlinecap` and `setlinejoin` operators number them + // (PostScript Language Reference, 3rd ed.): in users' files the page strokes type with + // the cap and join these give, and with key 64 as its miter limit. No file had a bevel + // join (2). Any other value keeps the default. + stroke_cap: match num("61") { + Some(1.0) => LineCap::Round, + Some(2.0) => LineCap::Square, + _ => LineCap::Butt, + }, + stroke_join: match num("62") { + Some(1.0) => LineJoin::Round, + Some(2.0) => LineJoin::Bevel, + _ => LineJoin::Miter, + }, + stroke_miter_limit: num("64").filter(|v| v.is_finite() && *v >= 1.0).map_or(10.0, |v| v.min(500.0)), caps: num("12").filter(|v| *v == 1.0 || *v == 2.0).map_or(0, |v| v as u8), tate_chu_yoko: num("35") == Some(3.0), standard_vertical_roman: num("35") == Some(1.0), @@ -1278,6 +1299,9 @@ fn para_style(p: &Para, scale: f64) -> ParaStyle { fn char_style(s: &Style, scale: f64) -> CharStyle { let (family, style) = s.font.as_deref().map_or_else(|| (CharStyle::default().font_family, "Regular".to_string()), crate::family_style); let family = vectorcraft_text::FontDb::global().find_family(&family).unwrap_or(family); + // Type without a stroke keeps the stroke's defaults, as its weight is 0. + let (stroke_cap, stroke_join, stroke_miter_limit) = + if s.stroke.is_some() { (s.stroke_cap, s.stroke_join, s.stroke_miter_limit) } else { (LineCap::Butt, LineJoin::Miter, 10.0) }; CharStyle { font_family: family, font_style: style, @@ -1293,6 +1317,9 @@ fn char_style(s: &Style, scale: f64) -> CharStyle { fill: s.fill.map_or(Paint::None, Paint::solid), stroke: s.stroke.map_or(Paint::None, Paint::solid), stroke_width: if s.stroke.is_some() { s.stroke_width * scale } else { 0.0 }, + stroke_cap, + stroke_join, + stroke_miter_limit, all_caps: s.caps == 2, // At the default Small Caps size of Document Setup: the file's own isn't read. small_caps: (s.caps == 1).then(|| vectorcraft_doc::DocSetup::default().small_caps_size), @@ -1573,6 +1600,44 @@ mod tests { assert!(texts(b"/35 3").style(None).tate_chu_yoko && !texts(b"/35 3").style(None).standard_vertical_roman); } + /// Keys 61, 62 and 64 of a character style are its stroke's cap (0 butt, 1 round, 2 + /// projecting), join (0 miter, 1 round, 2 bevel) and miter limit, from the run, its character + /// style or the document's defaults. A value that isn't one of theirs keeps the default, a miter + /// limit past 500 is 500, and type without a stroke keeps the defaults. + #[test] + fn a_run_keeps_its_stroke_cap_join_and_miter_limit() { + let texts = Texts { + stories: vec![], + fonts: vec![], + defaults: read(b"/57 true /54 << /0 << /0 0 /1 [ 1.0 1.0 ] >> >> /64 4.0").unwrap(), + para_defaults: Val::Dict(BTreeMap::new()), + char_sheets: vec![read(b"/0 ([Normal Character Style]) /6 << >>").unwrap(), read(b"/0 (Rounded) /6 << /61 1 /62 1 >>").unwrap()], + para_sheets: vec![], + frames: vec![], + }; + let stroke = |run: &[u8]| { + let s = char_style(&texts.style(Some(&read(run).unwrap())), 1.0); + (s.stroke_cap, s.stroke_join, s.stroke_miter_limit) + }; + assert_eq!(stroke(b"/6 << /61 2 /62 2 /64 2.5 >>"), (LineCap::Square, LineJoin::Bevel, 2.5), "the run's own"); + assert_eq!(stroke(b"/5 1 /6 << >>"), (LineCap::Round, LineJoin::Round, 4.0), "its character style's and the defaults'"); + assert_eq!(stroke(b"/5 1 /6 << /62 0 >>"), (LineCap::Round, LineJoin::Miter, 4.0), "the run's own wins over its style"); + assert_eq!(stroke(b"/6 << >>"), (LineCap::Butt, LineJoin::Miter, 4.0), "the defaults'"); + assert_eq!(stroke(b"/6 << /64 1 >>").2, 1.0); + assert_eq!(stroke(b"/6 << /64 1e9 >>").2, 500.0); + for bad in [ + &b"/6 << /61 3 /62 3 /64 0.5 >>"[..], + b"/6 << /61 -1 /62 -1 /64 -4 >>", + b"/6 << /61 1.5 /62 0.5 /64 NaN >>", + b"/6 << /61 (1) /62 (1) /64 inf >>", + b"/6 << /61 true /62 [ 1 ] /64 (4) >>", + b"/6 << /61 1e308 /62 /Round /64 -inf >>", + ] { + assert_eq!(stroke(bad), (LineCap::Butt, LineJoin::Miter, 10.0), "{}", String::from_utf8_lossy(bad)); + } + assert_eq!(stroke(b"/5 1 /6 << /57 false >>"), (LineCap::Butt, LineJoin::Miter, 10.0), "no stroke"); + } + /// A right-to-left paragraph is laid out right to left even when it has no right-to-left /// letters (a dimension's label " 120.5 mm " reads "mm 120.5"); a left-to-right one, or one /// that doesn't say, goes by its letters. @@ -1840,6 +1905,9 @@ mod tests { fill: Some(Color::BLACK), stroke: None, stroke_width: 1.0, + stroke_cap: LineCap::Butt, + stroke_join: LineJoin::Miter, + stroke_miter_limit: 10.0, caps: 0, tate_chu_yoko: false, standard_vertical_roman: false, diff --git a/crates/eps/src/import/tests_native.rs b/crates/eps/src/import/tests_native.rs index f71c037c9..867d36d1a 100644 --- a/crates/eps/src/import/tests_native.rs +++ b/crates/eps/src/import/tests_native.rs @@ -3291,6 +3291,50 @@ fn a_standard_baseline_run_keeps_standard_vertical_roman_alignment() { } } +#[test] +fn stroked_type_keeps_its_cap_join_and_miter_limit() { + // GUARDS: type strokes came in with a miter join at a limit of 10 whatever the file said, so + // type the file strokes with round or bevel joins, or a lower limit, drew spikes at its corners. + // Four runs over "abcdefgh": "ab" stroked with round caps and joins at a miter limit of 4, "cd" + // with projecting caps and bevel joins at 2.5, "ef" stroked without the keys (the defaults), and + // "gh" with the keys but no stroke. + use vectorcraft_doc::{LineCap, LineJoin}; + let doc = text_document(&[("abcdefgh\r", 0, (8200.0, 8180.0), (0.0, 0.0), 0.0)]); + let run = |n: u32, own: &str| { + format!( + "<< /0 << /0 << /0 () /5 0 /6 << /0 0 /1 14.0 {own}/53 << /99 /CAITextPaint /0 << /0 2 /1 [ 1.0 0.0 1.0 0.0 0.0 ] >> >> >> >> >> /1 {n} >>" + ) + }; + let one = run(9, ""); + assert!(doc.contains(&one)); + let stroke = "/54 << /99 /CAITextPaint /0 << /0 2 /1 [ 1.0 0.0 0.0 0.0 1.0 ] >> >> /57 true /63 3.0 "; + let runs = [ + run(2, &format!("{stroke}/61 1 /62 1 /64 4.0 ")), + run(2, &format!("{stroke}/61 2 /62 2 /64 2.5 ")), + run(2, stroke), + run(3, "/61 1 /62 1 /64 4.0 "), + ]; + let doc = doc.replacen(&one, &runs.join(" "), 1); + let art = native_with_text(&(layer("Back", true, &square(10, 10)) + &layer("Spare", false, &text_object(0))), &doc); + let r = import(&eps(&page_square(10, 10), &art)).unwrap(); + let texts = text_of(&r.document.layers[1]); + assert_eq!(texts.len(), 1, "{:?}", r.warnings); + let strokes: Vec<(String, f64, LineCap, LineJoin, f64)> = texts[0] + .runs + .iter() + .map(|r| (r.text.clone(), r.style.stroke_width, r.style.stroke_cap, r.style.stroke_join, r.style.stroke_miter_limit)) + .collect(); + assert_eq!( + strokes, + [ + ("ab".to_string(), 3.0, LineCap::Round, LineJoin::Round, 4.0), + ("cd".to_string(), 3.0, LineCap::Square, LineJoin::Bevel, 2.5), + ("ef".to_string(), 3.0, LineCap::Butt, LineJoin::Miter, 10.0), + ("gh".to_string(), 0.0, LineCap::Butt, LineJoin::Miter, 10.0), + ] + ); +} + #[test] fn orientation_and_tate_chu_yoko_values_it_cant_use_are_ignored() { for bad in ["(2)", "2.5", "1e300", "-2", "1", "true", "[ 2 ]"] { diff --git a/docs/file-format-parity.md b/docs/file-format-parity.md index 810d1568b..06fac1069 100644 --- a/docs/file-format-parity.md +++ b/docs/file-format-parity.md @@ -1,6 +1,6 @@ # File format parity -> **Last reviewed:** 2026-10-10 · **Last updated:** 2026-11-10 · **Change:** minor (`.ai` save: type in a variable font's named instance names its design-space setting, #6388; `.ai` and EPS import: envelopes of type open live where they draw as their drawn art and the layers with them draw as the page, #3179; `.ai` and EPS import: a note for type in an envelope, #3179; `.ai` and EPS import: the live envelope and warped type check sees white art; PDF import: DeviceN images of one ink or of process plates keep their inks, #6348; `.ai` import: images in a spot ink's Separation space keep their ink, #6350; PDF import: Separation images keep their ink, #6314; PDF editable import preserves authored ActualText once, including empty/nested spans and authored ordering (#6165); cross-line/style layout remains approximate; scores and estimates unchanged; PDF marked-text export (#6309): compatible glyphs stay on one layout line with per-glyph offsets; scores and estimates unchanged; PDF import: image masks open colorized by the color they are painted in, #6313; EPS and `.ai` editing data: a ligature the page names by a private-use glyph matches its text object; EPS import: CIDFonts only named, over a Unicode CMap, set as live type; EPS import: CMap mappings to base-font codes and glyph names; EPS import: CID-keyed fonts in FontSets (CFF) draw their glyphs; EPS import: `kshow` procedures kern live type; EPS import: embedded Type 1 fonts' glyphs and widths; EPS import: CID-keyed fonts draw their glyphs; EPS import: Type 42 fonts' characters and widths from the font they carry, show operators' spacing kept as tracking; EPS import: an image decoder's own buffers stop counting against the memory budget once it is decoded; EPS import: sampled functions interpolate between their samples; `.ai` and EPS type from the text document keeps its tab stops; it keeps auto leading and hyphenation options, and may show more of the file's last lines in an area frame; `.ai` and EPS import: Art brushes in the older form of their objects come into the Brushes panel; EPS import: gradients drawn as abutting strips come in as one gradient, without seams; EPS import: composite fonts draw their Type 42 glyphs; SVG import: filter and mask regions clip, #5876, #5875; SVG import: Gaussian blurs keep `color-interpolation-filters`, #5877; PDF and `.ai` export: objects' URLs as links, #5785; SVG import: `color-interpolation="linearRGB"` gradients, #5878; `.ai` import: paths with a Calligraphic brush open brushed, #372) · **Target:** Adobe Illustrator 2026 (30.x) +> **Last reviewed:** 2026-10-10 · **Last updated:** 2026-11-11 · **Change:** minor (`.ai` and EPS import: stroked type keeps its stroke's cap, join and miter limit from the text document, #6472; `.ai` save: type in a variable font's named instance names its design-space setting, #6388; `.ai` and EPS import: envelopes of type open live where they draw as their drawn art and the layers with them draw as the page, #3179; `.ai` and EPS import: a note for type in an envelope, #3179; `.ai` and EPS import: the live envelope and warped type check sees white art; PDF import: DeviceN images of one ink or of process plates keep their inks, #6348; `.ai` import: images in a spot ink's Separation space keep their ink, #6350; PDF import: Separation images keep their ink, #6314; PDF editable import preserves authored ActualText once, including empty/nested spans and authored ordering (#6165); cross-line/style layout remains approximate; scores and estimates unchanged; PDF marked-text export (#6309): compatible glyphs stay on one layout line with per-glyph offsets; scores and estimates unchanged; PDF import: image masks open colorized by the color they are painted in, #6313; EPS and `.ai` editing data: a ligature the page names by a private-use glyph matches its text object; EPS import: CIDFonts only named, over a Unicode CMap, set as live type; EPS import: CMap mappings to base-font codes and glyph names; EPS import: CID-keyed fonts in FontSets (CFF) draw their glyphs; EPS import: `kshow` procedures kern live type; EPS import: embedded Type 1 fonts' glyphs and widths; EPS import: CID-keyed fonts draw their glyphs; EPS import: Type 42 fonts' characters and widths from the font they carry, show operators' spacing kept as tracking; EPS import: an image decoder's own buffers stop counting against the memory budget once it is decoded; EPS import: sampled functions interpolate between their samples; `.ai` and EPS type from the text document keeps its tab stops; it keeps auto leading and hyphenation options, and may show more of the file's last lines in an area frame; `.ai` and EPS import: Art brushes in the older form of their objects come into the Brushes panel; EPS import: gradients drawn as abutting strips come in as one gradient, without seams; EPS import: composite fonts draw their Type 42 glyphs; SVG import: filter and mask regions clip, #5876, #5875; SVG import: Gaussian blurs keep `color-interpolation-filters`, #5877; PDF and `.ai` export: objects' URLs as links, #5785; SVG import: `color-interpolation="linearRGB"` gradients, #5878; `.ai` import: paths with a Calligraphic brush open brushed, #372) · **Target:** Adobe Illustrator 2026 (30.x) Every format Illustrator opens, places, saves or exports, against VectorCraft's format table (`FORMATS`, `OPEN_EXTS`, `PLACE_EXTS` in `crates/engine/src/cmd/fileio/formats.rs`). Illustrator's list comes from its public @@ -54,7 +54,7 @@ private editing data. VectorCraft: own paints with the paint before the mask's dictionary, as the writer leaves out paint that doesn't change. Type from the text document keeps auto leading as auto, each line taking the paragraph's share (Auto Leading) of the largest type on it ([Line and character spacing](https://helpx.adobe.com/illustrator/using/line-character-spacing.html)), - and the paragraph's hyphenation options and tab stops. Area type of one + and the paragraph's hyphenation options and tab stops. Stroked type keeps its stroke's cap, join and miter limit. Area type of one frame whose last lines the file sets but which don't fit here (the fallback font, or a descent past the frame's bottom) may get a frame taller from its bottom, by its lines' deepest descent plus 0.05 pt and only when that shows more of those lines, with a warning when it does. RGB in the PDF page's `ICCBased` spaces keeps the file's numbers in a `.ai` (the `uncalibrated_rgb` @@ -339,6 +339,7 @@ Stop opacity on translated artboards remains outside this regression coverage. | Date | Change | Summary | |---|---|---| +| 2026-11-11 | minor | `.ai` and EPS import: type stroked in the text document keeps its stroke's cap, join and miter limit (keys 61, 62 and 64 of its character attributes, numbered as PostScript's `setlinecap` and `setlinejoin`: 0 butt or miter, 1 round, 2 projecting or bevel). They were left at a miter join with a limit of 10 whatever the file said, so type the file strokes with round joins, or with the usual limit of 4 or less, drew spikes at its sharp corners, some many points long. In users' files, used locally and never committed, the page strokes the type with the cap, join and miter limit these keys give; no file had a bevel join, which is read by the same numbering. A value that isn't one of theirs keeps the default, a miter limit past 500 is 500, and type without a stroke keeps the defaults. Dashes are not read: no file had dashed type to tell which keys hold them. Tests `ate::tests::a_run_keeps_its_stroke_cap_join_and_miter_limit`, `tests_native::stroked_type_keeps_its_cap_join_and_miter_limit`; scores unchanged | | 2026-11-10 | minor | `.ai` and EPS import: an envelope of type (a warp, a mesh or a top object around type) kept its drawn art, its type hidden, because the type is laid out from the text document only after the layers are read. It now waits for that: its text slots get their type, and where VectorCraft's own envelope of that type draws as the drawn art the file saves (the same check and budget as other envelopes, seen on white and on black), it opens live in the drawn art's place, its type editable in Edit Contents, and the hidden copy goes. As with other live envelopes, the layers with one are then compared with the page, which weighs the envelope as the art it draws (not as type laid out again, which the comparison allows more for), and when they don't draw as it the drawn art and the hidden type are put back. Layers used though they don't draw as the page (the editing data trusted, or the objects that differ coming in as the page's art) leave envelopes of type their drawn art and hidden type too. A `.ai` whose layers would open alone, without its PDF part, imports the PDF part to compare them with, and should they still not draw as it, or should no envelope of type open live, they open alone as before, with the envelopes' drawn art. Type in a font that isn't available, slots without type of their own (threaded, bound to a text or a visibility variable, a wrap object, type a look draws), envelopes in a symbol's or a pattern's art, and envelopes that draw differently keep their drawn art, with the note (#3179). Tests `tests_native::an_envelope_of_type_that_draws_as_its_drawn_art_opens_live`, `an_envelope_of_type_that_draws_apart_from_its_drawn_art_keeps_it`, `an_envelope_of_white_type_that_draws_apart_from_its_drawn_art_keeps_it`, `an_envelope_of_type_waits_on_the_page_of_layers_that_would_open_alone`, `an_envelope_of_type_that_draws_apart_from_the_page_does_not_open_live`, `trusted_layers_that_draw_apart_from_the_page_leave_an_envelope_of_type_its_drawn_art`, `layers_with_objects_that_come_in_as_the_page_art_leave_an_envelope_of_type_its_drawn_art`, `an_envelope_of_type_bound_to_a_visibility_variable_keeps_its_drawn_art` | | 2026-11-10 | minor | `.ai` save: a variable font's record in the text document's font set names the type's place in the font's design space (`/4`, each `fvar` axis as 16.16 fixed: the named instance's setting, or the run's own Variable Font setting) and the font's version (`/5`). Without them, type in a named instance such as Bold opened at the font's default instance in the app that opens the file. Two settings of one font are now two fonts of the set; a static font's record is unchanged. The record's form comes from the public report in #6388. Test `ai_editing::tests::variable_font_instances_name_their_design_space` (#6388) | | 2026-11-10 | minor | `.ai` and EPS import: an envelope of type, which keeps its drawn art with its type hidden, now says so in the import notes ("type in an envelope comes in as the envelope's drawn art, not as editable type"); nothing said it before, while the other envelopes that keep their drawn art have a note. The type is still not editable in the envelope (part of #3179). Test `tests_native::envelope_options_keep_the_source_text_hidden_under_its_outlined_appearance` | From d399b176052567c6ee8f857836b9b9a669b01bdb Mon Sep 17 00:00:00 2001 From: dotCooCoo Date: Tue, 10 Nov 2026 18:27:23 -0800 Subject: [PATCH 6/6] Docs: pattern.options accepts a null width or height as omitted The width and height check in pattern.options applies only to values that are not null; null keeps the current value. --- docs/mcp.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/mcp.md b/docs/mcp.md index f9c61590f..902bfff22 100644 --- a/docs/mcp.md +++ b/docs/mcp.md @@ -3109,8 +3109,8 @@ giving null keeps its default. In `pattern.options` and `object.repeat.options`, the pattern's or the repeat's current value, with two exceptions. A brick `tileType` in `pattern.options` or a brick `gridType` in `object.repeat.options` with `brickOffset` omitted or null sets the offset to 0.5. While `sizeTileToArt` is on, each `pattern.options` call sizes the tile to the art plus `hSpacing` and `vSpacing` again and ignores `width` -and `height`, so a new `hSpacing` changes the tile's width. `pattern.options` still rejects a `width` or `height` that -is not a positive number, such as `"width": 0`, and changes nothing. Without a `name`, +and `height`, so a new `hSpacing` changes the tile's width. `pattern.options` still rejects a non-null `width` or +`height` that is not a positive number, such as `"width": 0`, and changes nothing. Without a `name`, `object.pattern.edit` edits the pattern that fills or strokes a selected object, and `pattern.options` changes the pattern being edited; each returns an error when there is no such pattern. `pattern.delete` requires `name`.