diff --git a/crates/engine/src/cmd/place.rs b/crates/engine/src/cmd/place.rs index dc95f925b6..f6c6f7a9c3 100644 --- a/crates/engine/src/cmd/place.rs +++ b/crates/engine/src/cmd/place.rs @@ -75,7 +75,7 @@ pub fn specs() -> Vec { "Place…", ["File"], Some("Cmd+Shift+P"), - "{path | name | dataBase64 (a file's path, or its name with its bytes), link?: true (a raster image keeps its file's path; a VectorCraft document at path becomes one locked placed document showing artboard `page`, linked to the file and read again when it changes (links.update; saving it in this app updates it at once), drawn as vectors in every output; so does a PDF/.ai page at path, showing its `crop` box, unless password or textAs: \"outlines\" is given; false: an editable copy of its art, as from dataBase64; other files are embedded), text?: {characterSet?: \"unicode\" (UTF-8, or UTF-16 with a byte-order mark, or without one by dataset.import's rule: an even length, at least half of its two-byte units holding a NUL byte, the byte order that reads a line break, valid UTF-16 with no NUL character; other bytes as the platform's 8-bit set) | \"ansi\" (the platform's 8-bit set), platform?: \"windows\" (Windows-1252) | \"mac\" (Mac Roman), removeLineReturns?: false (each block of lines becomes one paragraph; blank lines end paragraphs), removeParagraphReturns?: false (drop blank lines), replaceSpaces?: n (runs of n ≥ 2 spaces become a tab)} (a .txt file, placed as area type filling rect, the replaced object's bounds, or else the artboard less a 36 pt margin), template?: false (onto a new locked template layer below the current layer), replace?: false (swap the one selected object, keeping its stacking place and transform; no at/rect; refused while an object of a symbol instance's art is selected), at?: [x, y] centre (default: the first artboard's centre), rect?: [x, y, width, height] fit inside; aspect kept (wins over at), page?: 1 (PDF/.ai page, or a native document's artboard), crop?: \"crop\" (clipped to that page or artboard, default; an SVG, only when given: framed and clipped by its viewBox, else by its art's bounds) | \"bounding\" (the bounds of the art that shows, with its strokes and effects, as an export measures them: hidden objects and guides left out) | \"art\" | \"trim\" | \"bleed\" | \"media\" (a PDF page's boxes), password? (an encrypted PDF), dxf?: {…the DXF options of document.open} (a .dxf drawing: fit fills the artboard under at, else the first; fitted or uncentred art lands with its drawing's artboard on that artboard, else it is centred on at)} → {ids, name, format, linked, width, height, warnings, image?: the id of the image of a DCS file placed in a clip group, which the Links commands take}. Raster images come in at 100% of their physical size (the file's ppi, else 72); SVG, DXF, HP-GL (.plt, .hpgl, .hgl, .hpg), PDF/.ai, EMF/WMF and PICT (.pct, .pic, .pict; clipped to the picture's frame), EPS (clipped to its bounding box; as document.open reads it), a .ai saved without PDF compatibility (from its editing data, as document.open reads it: the art of the layers that show on artboard `page`, clipped to that artboard unless crop is \"bounding\") and native documents as one group, with the images, symbols, patterns, swatches and brushes they use. A DCS file (an EPS file with one program per printing plate, DCS 1.0 or 2.0) is placed only from its path with link (else an error, as are document.open and links.embed): one linked image of its plates mixed into CMYK, clipped by the clipping path its composite is drawn through, its spot plates added as spot swatches in the process equivalents the file gives (a swatch of the same name is kept, with a warning); print separations and Separations Preview show each plate on its own ink, and plate files of their own are read from its folder. One undo step; selects what it placed (unless on a template layer); never touches the clipboard. A PNG, JPEG, GIF or WebP image whose pixels can't be read (a file cut short) is an error, and nothing is placed", + "{path | name | dataBase64 (a file's path, or its name with its bytes), link?: true (a raster image keeps its file's path; a VectorCraft document at path becomes one locked placed document showing artboard `page`, linked to the file and read again when it changes (links.update; saving it in this app updates it at once), drawn as vectors in every output; so does a PDF/.ai page at path, showing its `crop` box, unless password or textAs: \"outlines\" is given; false: an editable copy of its art, as from dataBase64; other files are embedded), text?: {characterSet?: \"unicode\" (UTF-8, or UTF-16 with a byte-order mark, or without one by dataset.import's rule: an even length, at least half of its two-byte units holding a NUL byte, the byte order that reads a line break, valid UTF-16 with no NUL character; other bytes as the platform's 8-bit set) | \"ansi\" (the platform's 8-bit set), platform?: \"windows\" (Windows-1252) | \"mac\" (Mac Roman), removeLineReturns?: false (each block of lines becomes one paragraph; blank lines end paragraphs), removeParagraphReturns?: false (drop blank lines), replaceSpaces?: n (runs of n ≥ 2 spaces become a tab)} (a .txt file, placed as area type filling rect, the replaced object's bounds, or else the artboard less a 36 pt margin), template?: false (onto a new locked template layer below the current layer), replace?: false (swap the one selected object, keeping its stacking place and transform; no at/rect; refused while an object of a symbol instance's art is selected), at?: [x, y] centre (default: the first artboard's centre), rect?: [x, y, width, height] fit inside; aspect kept (wins over at), page?: 1 (PDF/.ai page, or a native document's artboard), crop?: \"crop\" (clipped to that page or artboard, default; an SVG, only when given: framed and clipped by its viewBox, else by its art's bounds) | \"bounding\" (the bounds of the art that shows, with its strokes and effects, as an export measures them: hidden objects and guides left out) | \"art\" | \"trim\" | \"bleed\" | \"media\" (a PDF page's boxes), password? (an encrypted PDF), dxf?: {…the DXF options of document.open} (a .dxf drawing: fit fills the artboard under at, else the first; fitted or uncentred art lands with its drawing's artboard on that artboard, else it is centred on at), showImportOptions?: true (in the app, a text file without text, a DXF drawing without dxf and a PDF with several pages or a password and no page ask for those options first; false places them with the options last confirmed, a PDF on page 1 (or page), and asks only an encrypted PDF's password)} → {ids, name, format, linked, width, height, warnings, image?: the id of the image of a DCS file placed in a clip group, which the Links commands take}. Raster images come in at 100% of their physical size (the file's ppi, else 72); SVG, DXF, HP-GL (.plt, .hpgl, .hgl, .hpg), PDF/.ai, EMF/WMF and PICT (.pct, .pic, .pict; clipped to the picture's frame), EPS (clipped to its bounding box; as document.open reads it), a .ai saved without PDF compatibility (from its editing data, as document.open reads it: the art of the layers that show on artboard `page`, clipped to that artboard unless crop is \"bounding\") and native documents as one group, with the images, symbols, patterns, swatches and brushes they use. A DCS file (an EPS file with one program per printing plate, DCS 1.0 or 2.0) is placed only from its path with link (else an error, as are document.open and links.embed): one linked image of its plates mixed into CMYK, clipped by the clipping path its composite is drawn through, its spot plates added as spot swatches in the process equivalents the file gives (a swatch of the same name is kept, with a warning); print separations and Separations Preview show each plate on its own ink, and plate files of their own are read from its folder. One undo step; selects what it placed (unless on a template layer); never touches the clipboard. A PNG, JPEG, GIF or WebP image whose pixels can't be read (a file cut short) is an error, and nothing is placed", has_doc, place ), diff --git a/crates/ui-egui/src/dialogs/dxf_import.rs b/crates/ui-egui/src/dialogs/dxf_import.rs index 64a6cac5b4..8567dd90dc 100644 --- a/crates/ui-egui/src/dialogs/dxf_import.rs +++ b/crates/ui-egui/src/dialogs/dxf_import.rs @@ -84,6 +84,24 @@ pub fn offer_place(app: &mut VectorcraftApp, p: &Value) -> bool { offer(app, &name, &bytes, path, Some(p)) } +/// The `dxf` options a drawing `file.place` params `p` name is placed with when Show Import Options +/// is off: the choices confirmed last (fit, lineweights, centring, layers), the drawing at 1:1 in +/// its own unit. `None` when `p` names no `.dxf` drawing or already gives `dxf` options. +pub fn last_options(app: &VectorcraftApp, p: &Value) -> Option { + let named = p.get("path").or_else(|| p.get("name")).and_then(Value::as_str).map(fileio::extension); + if p.get("dxf").is_some() || named.as_deref() != Some("dxf") { + return None; + } + Some(remembered(app)) +} + +/// The `dxf` options of the choices confirmed last (fit, lineweights, centring, layers; `{}` when +/// none was). +pub fn remembered(app: &VectorcraftApp) -> Value { + let last = app.ui.dxf_import.as_object(); + Value::Object(REMEMBERED.iter().filter_map(|k| last?.get(*k).filter(|v| v.is_boolean()).map(|v| (k.to_string(), v.clone()))).collect()) +} + /// The `dxf` options of `document.open` and `file.place` the fields set. fn options(d: &Dialog) -> Value { let fit = d.bool("fit"); diff --git a/crates/ui-egui/src/dialogs/import_pdf.rs b/crates/ui-egui/src/dialogs/import_pdf.rs index 4820680a09..4ef8c925d8 100644 --- a/crates/ui-egui/src/dialogs/import_pdf.rs +++ b/crates/ui-egui/src/dialogs/import_pdf.rs @@ -4,7 +4,8 @@ //! Fields: `mode` (`open` | `place`), `name`, `path?`, `pageCount`, `page` (the page shown, and the //! one placed; 1-based), `allPages`, `range` and `linkPages` (open: Import PDF Pages as Links, for //! a file read from a path and opened without a password), `cropTo` and `password`. `__locked` is set -//! until the password is accepted: OK then unlocks the file. `__place` keeps the other `file.place` +//! until the password is accepted: OK then unlocks the file, and with `__passwordOnly` (Place with +//! Show Import Options off) places its first page at once. `__place` keeps the other `file.place` //! params (Link, Template, `at`…). The file's bytes stay out of the fields, in //! [`crate::state::UiState::dialog_file`]. @@ -168,6 +169,33 @@ pub fn offer_place(app: &mut VectorcraftApp, p: &Value) -> bool { offer(app, &name, &bytes, path, Some(p)) } +/// [`offer_place`] when Show Import Options is off: asks (true) only for the password of an +/// encrypted PDF given no `password`, which nothing else opens, and places its first page (or +/// `page`) and crop box once it is unlocked; any other PDF is placed that way without asking. +pub fn offer_place_password(app: &mut VectorcraftApp, p: &Value) -> bool { + // A `page` that isn't a page number is left for `file.place` to refuse. + if p.get("password").is_some() || p.get("page").is_some_and(|v| v.as_u64().is_none_or(|n| n == 0)) { + return false; + } + let Some((name, bytes, path)) = place_source(app, p, |ext| matches!(ext, "pdf" | "ai" | "ait")) else { return false }; + if !matches!(vectorcraft_pdf::open_pdf(&bytes, None).map(|_| ()), Err(PdfError::NeedsPassword)) { + return false; + } + let offered = offer(app, &name, &bytes, path, Some(p)); + if let (true, Some(d)) = (offered, app.ui.dialog.as_mut()) { + d.fields.insert("__passwordOnly".into(), json!(true)); + // A `page` and `crop` given are placed as given, for `file.place` to check. + if let Some(place) = d.fields.get_mut("__place").and_then(Value::as_object_mut) { + for k in ["page", "crop"] { + if let Some(v) = p.get(k) { + place.insert(k.into(), v.clone()); + } + } + } + } + offered +} + fn page_count(d: &Dialog) -> usize { d.fields.get("pageCount").and_then(Value::as_u64).unwrap_or(0) as usize } @@ -204,7 +232,7 @@ fn confirm(app: &mut VectorcraftApp, d: &Dialog) -> Result { let info = vectorcraft_pdf::info(&file.bytes, password(&d).as_deref()).map_err(|e| e.to_string())?; d.fields.insert("__locked".into(), json!(false)); d.fields.insert("pageCount".into(), json!(info.pages.len())); - if info.pages.len() > 1 { + if info.pages.len() > 1 && !d.bool("__passwordOnly") { // Unlocked: now pick the pages. app.ui.dialog = Some(d); return Ok(Value::Null); @@ -214,8 +242,14 @@ fn confirm(app: &mut VectorcraftApp, d: &Dialog) -> Result { let path = d.fields.get("path").and_then(Value::as_str).map(str::to_string); let r = if d.str("mode") == "place" { let mut p = with_file(d.fields.get("__place"), &d, &file); - p["page"] = json!(page(&d)); - p["crop"] = json!(crop(&d).id()); + // Asked only for the password: a `page` and `crop` given with the file stand. + let [page_given, crop_given] = ["page", "crop"].map(|k| d.bool("__passwordOnly") && p.get(k).is_some()); + if !page_given { + p["page"] = json!(page(&d)); + } + if !crop_given { + p["crop"] = json!(crop(&d).id()); + } if let Some(pw) = password(&d) { p["password"] = json!(pw); } diff --git a/crates/ui-egui/src/dialogs/place.rs b/crates/ui-egui/src/dialogs/place.rs index e53c0f4a4e..f6b5ab5a9c 100644 --- a/crates/ui-egui/src/dialogs/place.rs +++ b/crates/ui-egui/src/dialogs/place.rs @@ -1,8 +1,9 @@ //! File → Place: the files picked, one line each about what they are, and the Place options row -//! (Link, Template, Replace). OK places one file centred in the view (or in place of the selected -//! object) or loads the place cursor with several ([`crate::place::confirm`]). +//! (Link, Template, Replace, Show Import Options). OK places one file centred in the view (or in +//! place of the selected object) or loads the place cursor with several ([`crate::place::confirm`]). //! -//! Fields: `files` (`[{path} | {name}]`), `link`, `template`, `replace`, and `__replace` (one file +//! Fields: `files` (`[{path} | {name}]`), `link`, `template`, `replace`, `showImportOptions` (each +//! PDF, DXF or text file asks for its import options before it is placed), and `__replace` (one file //! and one selected object: Replace applies), `__documents` and `__others` (VectorCraft documents //! from files are picked, other files are: what Link's tooltip explains), `__info` (a line per //! file; on the desktop the lines are read off the UI thread and are empty until they are read, @@ -29,10 +30,15 @@ pub(super) const SPEC: DialogSpec = DialogSpec { }; /// The options row: (field, label, tooltip). -const OPTIONS: [(&str, &str, &str); 3] = [ +const OPTIONS: [(&str, &str, &str); 4] = [ ("link", "Link", "Keep a link to an image file instead of only embedding it"), ("template", "Template", "Place onto a new locked, dimmed template layer below the current layer"), ("replace", "Replace", "Swap the selected object for the file, keeping its place and transform"), + ( + "showImportOptions", + "Show Import Options", + "Ask for each PDF, DXF or text file's import options before placing it; off, use the options chosen last", + ), ]; fn body(app: &mut VectorcraftApp, ui: &mut egui::Ui, d: &mut Dialog) -> bool { @@ -73,7 +79,7 @@ fn body(app: &mut VectorcraftApp, ui: &mut egui::Ui, d: &mut Dialog) -> bool { } ui.add_space(10.0); // The options row. - ui.horizontal(|ui| { + ui.horizontal_wrapped(|ui| { ui.spacing_mut().item_spacing.x = 18.0; for (key, label, tip) in OPTIONS { let on = d.bool(key); diff --git a/crates/ui-egui/src/dialogs/text_import.rs b/crates/ui-egui/src/dialogs/text_import.rs index 295a5d1f9a..a52e9f47fe 100644 --- a/crates/ui-egui/src/dialogs/text_import.rs +++ b/crates/ui-egui/src/dialogs/text_import.rs @@ -1,6 +1,7 @@ //! Text Import Options: placing a text file asks how to read it (platform and character set) and //! what to clean up (line breaks inside paragraphs, blank lines between them, runs of spaces). OK -//! places it as area type (`file.place` with `text`). +//! places it as area type (`file.place` with `text`) and remembers the options: the next text file +//! starts from them, and is placed with them when Show Import Options is off. //! //! Fields: `platform` (`windows` | `mac`), `characterSet` (`unicode` | `ansi`), //! `removeLineReturns`, `removeParagraphReturns`, `replaceSpaces` (on or off), `spaces` (how many @@ -27,9 +28,10 @@ pub fn is_text(p: &Value) -> bool { vectorcraft_engine::cmd::fileio::TEXT_EXTS.contains(&vectorcraft_engine::cmd::fileio::extension(name).as_str()) } -/// Ask how to place the text file `params` (`file.place` params) names. +/// Ask how to place the text file `params` (`file.place` params) names, starting from the options +/// confirmed last. pub fn open(app: &mut VectorcraftApp, params: &Value) -> Value { - let fields = json!({ + let mut fields = json!({ "platform": if cfg!(target_os = "macos") { "mac" } else { "windows" }, "characterSet": "unicode", "removeLineReturns": false, @@ -38,10 +40,31 @@ pub fn open(app: &mut VectorcraftApp, params: &Value) -> Value { "spaces": 3, "__params": params, }); + let last = &app.ui.text_import; + for (k, wanted) in [("platform", ["windows", "mac"]), ("characterSet", ["unicode", "ansi"])] { + if let Some(v) = last.get(k).and_then(Value::as_str).filter(|v| wanted.contains(v)) { + fields[k] = json!(v); + } + } + for k in ["removeLineReturns", "removeParagraphReturns"] { + if let Some(v) = last.get(k).and_then(Value::as_bool) { + fields[k] = json!(v); + } + } + if let Some(n) = last.get("replaceSpaces").and_then(Value::as_u64).filter(|n| (2..=100).contains(n)) { + fields["replaceSpaces"] = json!(true); + fields["spaces"] = json!(n); + } app.ui.dialog = Some(Dialog::new(KIND, fields)); json!({ "dialog": KIND }) } +/// The `text` options a text file is placed with when Show Import Options is off: those confirmed +/// last, else `file.place`'s defaults. +pub fn last_options(app: &VectorcraftApp) -> Value { + if app.ui.text_import.is_object() { app.ui.text_import.clone() } else { json!({}) } +} + fn body(_: &mut VectorcraftApp, ui: &mut egui::Ui, d: &mut Dialog) -> bool { widgets::subheader(ui, tl!("Encoding")); form::choice(ui, d, "platform", tl!("Platform:"), (LABEL, 180.0), &[("windows", "Windows"), ("mac", "Mac")]); @@ -87,10 +110,12 @@ fn options(d: &Dialog) -> Value { o } -/// Places the file with the options; closes first, so an error shows in the status bar. +/// Places the file with the options and remembers them for the next text file; closes first, so an +/// error shows in the status bar. fn confirm(app: &mut VectorcraftApp, d: &Dialog) -> Result { let mut params = d.fields.get("__params").cloned().unwrap_or_else(|| json!({})); params["text"] = options(d); + app.ui.text_import = params["text"].clone(); app.ui.dialog = None; crate::place::run_for_user(app, ¶ms) } diff --git a/crates/ui-egui/src/i18n/bg.tsv b/crates/ui-egui/src/i18n/bg.tsv index fb3080c8c2..902981148e 100644 --- a/crates/ui-egui/src/i18n/bg.tsv +++ b/crates/ui-egui/src/i18n/bg.tsv @@ -3515,6 +3515,8 @@ digits Malayalam Малаялам Swap Fill and Stroke (Shift+X) Размяна на запълването и щриха (Shift+X) Swap start and end arrowheads Размяна на началната и крайната стрелка Swap the selected object for the file, keeping its place and transform Замяна на избрания обект с файла, като се запазват мястото и трансформациите му + Show Import Options Показване на опциите за импортиране + Ask for each PDF, DXF or text file's import options before placing it; off, use the options chosen last Питане за опциите за импортиране на всеки PDF, DXF или текстов файл преди поставянето му; изключено: използват се последно избраните опции Stylistic Alternates Стилистични алтернативи Set {n} Набор {n} Swash Декоративен елемент diff --git a/crates/ui-egui/src/i18n/bs.tsv b/crates/ui-egui/src/i18n/bs.tsv index 118609f801..b02d1e5fb9 100644 --- a/crates/ui-egui/src/i18n/bs.tsv +++ b/crates/ui-egui/src/i18n/bs.tsv @@ -2749,6 +2749,8 @@ digits Malayalam Malajalam Keep a link to an image file instead of only embedding it Zadrži vezu na slikovnu datoteku umjesto samo ugrađivanja Place onto a new locked, dimmed template layer below the current layer Postavi na novi zaključani, zatamnjeni sloj predloška ispod trenutnog sloja Swap the selected object for the file, keeping its place and transform Zamijeni odabrani objekat datotekom, zadržavajući njegovo mjesto i transformaciju + Show Import Options Prikaži opcije uvoza + Ask for each PDF, DXF or text file's import options before placing it; off, use the options chosen last Pitaj za opcije uvoza svake PDF, DXF ili tekstualne datoteke prije postavljanja; isključeno: koriste se posljednje odabrane opcije On: one locked object showing the document, updated when it changes. Uključeno: jedan zaključani objekat koji prikazuje dokument, ažuriran kad se on promijeni. Off: an editable copy of its art, embedded in this document. Isključeno: kopija njegovog crteža koja se može uređivati, ugrađena u ovaj dokument. Images: link to the file instead of only embedding it. Slike: poveži s datotekom umjesto samo ugrađivanja. diff --git a/crates/ui-egui/src/i18n/ca.tsv b/crates/ui-egui/src/i18n/ca.tsv index ab50e14a58..fa6b22309b 100644 --- a/crates/ui-egui/src/i18n/ca.tsv +++ b/crates/ui-egui/src/i18n/ca.tsv @@ -4587,6 +4587,8 @@ digits Malayalam Malaiàlam Swap Fill and Stroke (Shift+X) Intercanvia el farciment i el traç (Maj+X) Swap start and end arrowheads Intercanvia les puntes de fletxa inicial i final Swap the selected object for the file, keeping its place and transform Substitueix l’objecte seleccionat pel fitxer, mantenint-ne la posició i la transformació + Show Import Options Mostra les opcions d’importació + Ask for each PDF, DXF or text file's import options before placing it; off, use the options chosen last Demana les opcions d’importació de cada fitxer PDF, DXF o de text abans de col·locar-lo; desactivat: s’usen les últimes opcions triades Stylistic Alternates Alternatives estilístiques Swash Volada Swatch Conflict Conflicte de mostres diff --git a/crates/ui-egui/src/i18n/cs.tsv b/crates/ui-egui/src/i18n/cs.tsv index 3b9c300980..3e1b248aff 100644 --- a/crates/ui-egui/src/i18n/cs.tsv +++ b/crates/ui-egui/src/i18n/cs.tsv @@ -2784,6 +2784,8 @@ digits Malayalam Malajálamské Keep a link to an image file instead of only embedding it Ponechat propojení na soubor obrázku, ne jen ho vložit Place onto a new locked, dimmed template layer below the current layer Umístit do nové zamčené, ztlumené vrstvy šablony pod aktuální vrstvou Swap the selected object for the file, keeping its place and transform Nahradit vybraný objekt souborem se zachováním jeho polohy a transformace + Show Import Options Zobrazit možnosti importu + Ask for each PDF, DXF or text file's import options before placing it; off, use the options chosen last Před umístěním se zeptat na možnosti importu každého souboru PDF, DXF nebo textového souboru; vypnuto: použijí se naposledy zvolené možnosti On: one locked object showing the document, updated when it changes. Zapnuto: jeden zamčený objekt zobrazující dokument, aktualizovaný při jeho změně. Off: an editable copy of its art, embedded in this document. Vypnuto: upravitelná kopie jeho kresby vložená do tohoto dokumentu. Images: link to the file instead of only embedding it. Obrázky: propojit se souborem místo pouhého vložení. diff --git a/crates/ui-egui/src/i18n/da.tsv b/crates/ui-egui/src/i18n/da.tsv index 59ce36d15f..28dd7c2dc8 100644 --- a/crates/ui-egui/src/i18n/da.tsv +++ b/crates/ui-egui/src/i18n/da.tsv @@ -3175,6 +3175,8 @@ digits Malayalam Malayalam Keep a link to an image file instead of only embedding it Behold et link til en billedfil i stedet for kun at indlejre den Place onto a new locked, dimmed template layer below the current layer Placér på et nyt låst, nedtonet skabelonlag under det aktuelle lag Swap the selected object for the file, keeping its place and transform Udskift det markerede objekt med filen, mens placering og transformering bevares + Show Import Options Vis importindstillinger + Ask for each PDF, DXF or text file's import options before placing it; off, use the options chosen last Spørg om importindstillinger for hver PDF-, DXF- eller tekstfil, før den placeres; fra: brug de senest valgte indstillinger On: one locked object showing the document, updated when it changes. Til: ét låst objekt, der viser dokumentet, og som opdateres, når det ændres. Off: an editable copy of its art, embedded in this document. Fra: en redigerbar kopi af dens grafik, indlejret i dette dokument. Images: link to the file instead of only embedding it. Billeder: link til filen i stedet for kun at indlejre den. diff --git a/crates/ui-egui/src/i18n/de.tsv b/crates/ui-egui/src/i18n/de.tsv index ddc01a8016..8415faa23c 100644 --- a/crates/ui-egui/src/i18n/de.tsv +++ b/crates/ui-egui/src/i18n/de.tsv @@ -3578,6 +3578,8 @@ effect Points Punkte Swap Fill and Stroke (Shift+X) Fläche und Kontur vertauschen (Shift+X) Swap start and end arrowheads Pfeilspitzen an Anfang und Ende vertauschen Swap the selected object for the file, keeping its place and transform Das ausgewählte Objekt durch die Datei ersetzen; Position und Transformation bleiben erhalten + Show Import Options Importoptionen anzeigen + Ask for each PDF, DXF or text file's import options before placing it; off, use the options chosen last Vor dem Platzieren nach den Importoptionen jeder PDF-, DXF- oder Textdatei fragen; aus: die zuletzt gewählten Optionen verwenden Stylistic Alternates Stilistische Varianten Swash Schwungschrift Swatch Conflict Farbfeldkonflikt diff --git a/crates/ui-egui/src/i18n/el.tsv b/crates/ui-egui/src/i18n/el.tsv index a89d3c72e4..65d4a8c6d3 100644 --- a/crates/ui-egui/src/i18n/el.tsv +++ b/crates/ui-egui/src/i18n/el.tsv @@ -4102,6 +4102,8 @@ digits Malayalam Μαλαγιάλαμ Swap Fill and Stroke (Shift+X) Εναλλαγή γεμίσματος και περιγράμματος (Shift+X) Swap start and end arrowheads Εναλλαγή αρχικής και τελικής αιχμής βέλους Swap the selected object for the file, keeping its place and transform Αντικατάσταση επιλεγμένου αντικειμένου με το αρχείο, διατηρώντας θέση και μετασχηματισμό + Show Import Options Εμφάνιση επιλογών εισαγωγής + Ask for each PDF, DXF or text file's import options before placing it; off, use the options chosen last Ερώτηση για τις επιλογές εισαγωγής κάθε αρχείου PDF, DXF ή κειμένου πριν από την τοποθέτηση· απενεργοποιημένο: χρήση των τελευταίων επιλογών Swatch Conflict Διένεξη δείγματος χρώματος Swatch Conflicts Διενέξεις δειγμάτων χρωμάτων Swatch exchange files keep solid colors only. Τα αρχεία ανταλλαγής δειγμάτων διατηρούν μόνο συμπαγή χρώματα. diff --git a/crates/ui-egui/src/i18n/es.tsv b/crates/ui-egui/src/i18n/es.tsv index 4a912f8005..75f4da6d4f 100644 --- a/crates/ui-egui/src/i18n/es.tsv +++ b/crates/ui-egui/src/i18n/es.tsv @@ -3607,6 +3607,8 @@ digits Malayalam Malayálam Swap Fill and Stroke (Shift+X) Intercambiar relleno y trazo (Shift+X) Swap start and end arrowheads Intercambiar puntas de flecha inicial y final Swap the selected object for the file, keeping its place and transform Sustituir el objeto seleccionado por el archivo, conservando su posición y transformación + Show Import Options Mostrar opciones de importación + Ask for each PDF, DXF or text file's import options before placing it; off, use the options chosen last Preguntar las opciones de importación de cada archivo PDF, DXF o de texto antes de colocarlo; desactivado: usar las últimas opciones elegidas Stylistic Alternates Alternativas estilísticas Swash Decorativa Swatch Conflict Conflicto de muestras diff --git a/crates/ui-egui/src/i18n/fi.tsv b/crates/ui-egui/src/i18n/fi.tsv index 06499d13ea..26b8a5cd81 100644 --- a/crates/ui-egui/src/i18n/fi.tsv +++ b/crates/ui-egui/src/i18n/fi.tsv @@ -2728,6 +2728,8 @@ digits Malayalam Malajalam Keep a link to an image file instead of only embedding it Säilytä linkki kuvatiedostoon sen sijaan, että kuva vain upotetaan Place onto a new locked, dimmed template layer below the current layer Sijoita uudelle lukitulle, himmennetylle mallitasolle nykyisen tason alapuolelle Swap the selected object for the file, keeping its place and transform Korvaa valittu objekti tiedostolla säilyttäen sen paikan ja muunnoksen + Show Import Options Näytä tuontiasetukset + Ask for each PDF, DXF or text file's import options before placing it; off, use the options chosen last Kysy kunkin PDF-, DXF- tai tekstitiedoston tuontiasetukset ennen sijoittamista; pois: käytä viimeksi valittuja asetuksia On: one locked object showing the document, updated when it changes. Päällä: yksi lukittu objekti, joka näyttää asiakirjan ja päivittyy sen muuttuessa. Off: an editable copy of its art, embedded in this document. Pois: muokattava kopio sen kuvituksesta upotettuna tähän asiakirjaan. Images: link to the file instead of only embedding it. Kuvat: linkitä tiedostoon sen sijaan, että se vain upotetaan. diff --git a/crates/ui-egui/src/i18n/fr.tsv b/crates/ui-egui/src/i18n/fr.tsv index 90882cf105..cc66a16461 100644 --- a/crates/ui-egui/src/i18n/fr.tsv +++ b/crates/ui-egui/src/i18n/fr.tsv @@ -3599,6 +3599,8 @@ digits Malayalam Malayalam Swap Fill and Stroke (Shift+X) Permuter le fond et le contour (Maj+X) Swap start and end arrowheads Permuter les pointes de flèche de début et de fin Swap the selected object for the file, keeping its place and transform Remplacer l’objet sélectionné par le fichier, en conservant sa position et sa transformation + Show Import Options Afficher les options d’importation + Ask for each PDF, DXF or text file's import options before placing it; off, use the options chosen last Demander les options d’importation de chaque fichier PDF, DXF ou texte avant de le placer, sinon utiliser les dernières options choisies Stylistic Alternates Variantes stylistiques Swash Lettres ornées Swatch Conflict Conflit de nuances diff --git a/crates/ui-egui/src/i18n/hi.tsv b/crates/ui-egui/src/i18n/hi.tsv index 671052e82b..8780ffdc22 100644 --- a/crates/ui-egui/src/i18n/hi.tsv +++ b/crates/ui-egui/src/i18n/hi.tsv @@ -4968,6 +4968,8 @@ antiAlias Sharp शार्प Swap Objects and Sizes स्वैप ऑब्जेक्ट्स और साइज Swap start and end arrowheads स्वैप शुरू और अंत तीरहेड्स Swap the selected object for the file, keeping its place and transform फ़ाइल के लिए सिलेक्टेड ऑब्जेक्ट को स्वैप करें, अपनी जगह को बनाए रखें और परिवर्तित करें + Show Import Options आयात विकल्प दिखाएँ + Ask for each PDF, DXF or text file's import options before placing it; off, use the options chosen last रखने से पहले हर PDF, DXF या टेक्स्ट फ़ाइल के आयात विकल्प पूछें; बंद होने पर पिछली बार चुने गए विकल्प इस्तेमाल करें Swash स्वॉश Swatch Conflict स्वॉच विरोध Swatch Conflicts स्वॉच विरोध diff --git a/crates/ui-egui/src/i18n/hu.tsv b/crates/ui-egui/src/i18n/hu.tsv index a218a2ce15..4f79746d01 100644 --- a/crates/ui-egui/src/i18n/hu.tsv +++ b/crates/ui-egui/src/i18n/hu.tsv @@ -4295,6 +4295,8 @@ mark-style Japanese Japán Swap Fill and Stroke (Shift+X) Kitöltés és körvonal cseréje (Shift+X) Swap start and end arrowheads Kezdő és záró nyílhegy cseréje Swap the selected object for the file, keeping its place and transform A kijelölt objektum cseréje a fájlra, a helye és az átalakítása megtartásával + Show Import Options Importálási beállítások megjelenítése + Ask for each PDF, DXF or text file's import options before placing it; off, use the options chosen last Elhelyezés előtt rákérdez minden PDF-, DXF- vagy szövegfájl importálási beállításaira; kikapcsolva a legutóbb választott beállításokat használja Stylistic Alternates Stilisztikai változatok Swash Cifra változatok Swatch Conflict Színminta-ütközés diff --git a/crates/ui-egui/src/i18n/id.tsv b/crates/ui-egui/src/i18n/id.tsv index fdf06cc9ee..ad914f4391 100644 --- a/crates/ui-egui/src/i18n/id.tsv +++ b/crates/ui-egui/src/i18n/id.tsv @@ -2566,6 +2566,8 @@ digits Malayalam Malayalam Keep a link to an image file instead of only embedding it Pertahankan tautan ke file gambar alih-alih hanya menyematkannya Place onto a new locked, dimmed template layer below the current layer Tempatkan pada lapisan templat baru yang terkunci dan diredupkan di bawah lapisan saat ini Swap the selected object for the file, keeping its place and transform Tukar objek terpilih dengan file, mempertahankan tempat dan transformasinya + Show Import Options Tampilkan Opsi Impor + Ask for each PDF, DXF or text file's import options before placing it; off, use the options chosen last Tanyakan opsi impor setiap file PDF, DXF, atau teks sebelum menempatkannya; nonaktif: gunakan opsi yang terakhir dipilih On: one locked object showing the document, updated when it changes. Aktif: satu objek terkunci yang menampilkan dokumen, diperbarui saat dokumen berubah. Off: an editable copy of its art, embedded in this document. Nonaktif: salinan karya seninya yang dapat diedit, tersemat dalam dokumen ini. Images: link to the file instead of only embedding it. Gambar: tautkan ke file alih-alih hanya menyematkannya. diff --git a/crates/ui-egui/src/i18n/it.tsv b/crates/ui-egui/src/i18n/it.tsv index 2b5a6bf3da..1d76436e5c 100644 --- a/crates/ui-egui/src/i18n/it.tsv +++ b/crates/ui-egui/src/i18n/it.tsv @@ -3653,6 +3653,8 @@ digits Malayalam Malayalam Swap Fill and Stroke (Shift+X) Inverti riempimento e traccia (Shift+X) Swap start and end arrowheads Inverti punte di freccia iniziale e finale Swap the selected object for the file, keeping its place and transform Sostituisci l'oggetto selezionato con il file, mantenendone posizione e trasformazione + Show Import Options Mostra opzioni di importazione + Ask for each PDF, DXF or text file's import options before placing it; off, use the options chosen last Chiedi le opzioni di importazione di ogni file PDF, DXF o di testo prima di inserirlo; disattivato: usa le ultime opzioni scelte Stylistic Alternates Alternative stilistiche Swash Ornamentale Swatch Conflict Conflitto campioni diff --git a/crates/ui-egui/src/i18n/ja.tsv b/crates/ui-egui/src/i18n/ja.tsv index 87ec6c8b05..0359a10dc0 100644 --- a/crates/ui-egui/src/i18n/ja.tsv +++ b/crates/ui-egui/src/i18n/ja.tsv @@ -4054,6 +4054,8 @@ file-filter Templates テンプレート Superscript 上付き文字 Swap Fill and Stroke (Shift+X) 塗りと線を入れ替え (Shift+X) Swap the selected object for the file, keeping its place and transform 選択したオブジェクトを、位置と変形を保ったままファイルに置き換え + Show Import Options 読み込みオプションを表示 + Ask for each PDF, DXF or text file's import options before placing it; off, use the options chosen last 配置する前に PDF、DXF、テキストファイルごとの読み込みオプションを確認。オフのときは前回選んだオプションを使用 Swatch Libraries Menu スウォッチライブラリメニュー System Layout システムレイアウト Tabular Lining 等幅ライニング diff --git a/crates/ui-egui/src/i18n/ka.tsv b/crates/ui-egui/src/i18n/ka.tsv index 8c42eafaf1..bf8263a61f 100644 --- a/crates/ui-egui/src/i18n/ka.tsv +++ b/crates/ui-egui/src/i18n/ka.tsv @@ -2488,6 +2488,8 @@ digits Malayalam მალაიალამური Keep a link to an image file instead of only embedding it გამოსახულების ფაილის ბმულის შენარჩუნება მხოლოდ ჩაშენების ნაცვლად Place onto a new locked, dimmed template layer below the current layer ახალ ჩაკეტილ, ჩამქრალ შაბლონის შრეზე განთავსება მიმდინარე შრის ქვემოთ Swap the selected object for the file, keeping its place and transform მონიშნული ობიექტის ფაილით ჩანაცვლება მისი ადგილისა და გარდაქმნის შენარჩუნებით + Show Import Options იმპორტის პარამეტრების ჩვენება + Ask for each PDF, DXF or text file's import options before placing it; off, use the options chosen last განთავსებამდე თითოეული PDF, DXF ან ტექსტური ფაილის იმპორტის პარამეტრების მოთხოვნა; გამორთულისას ბოლოს არჩეული პარამეტრები გამოიყენება On: one locked object showing the document, updated when it changes. ჩართული: ერთი ჩაკეტილი ობიექტი, რომელიც დოკუმენტს აჩვენებს და მისი ცვლილებისას ახლდება. Off: an editable copy of its art, embedded in this document. გამორთული: მისი ნახატის რედაქტირებადი ასლი, ჩაშენებული ამ დოკუმენტში. Images: link to the file instead of only embedding it. გამოსახულებები: ფაილის ბმული მხოლოდ ჩაშენების ნაცვლად. diff --git a/crates/ui-egui/src/i18n/ko.tsv b/crates/ui-egui/src/i18n/ko.tsv index 4fd8001ea7..bd847b8784 100644 --- a/crates/ui-egui/src/i18n/ko.tsv +++ b/crates/ui-egui/src/i18n/ko.tsv @@ -4042,6 +4042,8 @@ file-filter Templates 템플릿 Superscript 위 첨자 Swap Fill and Stroke (Shift+X) 칠과 선 바꾸기 (Shift+X) Swap the selected object for the file, keeping its place and transform 선택한 개체를 파일로 바꾸되 위치와 변형은 유지합니다 + Show Import Options 가져오기 옵션 표시 + Ask for each PDF, DXF or text file's import options before placing it; off, use the options chosen last 배치하기 전에 각 PDF, DXF 또는 텍스트 파일의 가져오기 옵션을 묻습니다. 끄면 마지막으로 선택한 옵션을 사용합니다 Swatch Libraries Menu 견본 라이브러리 메뉴 System Layout 시스템 레이아웃 Tabular Lining 고정폭 라이닝 diff --git a/crates/ui-egui/src/i18n/nl.tsv b/crates/ui-egui/src/i18n/nl.tsv index 31872f060e..50eeee28e8 100644 --- a/crates/ui-egui/src/i18n/nl.tsv +++ b/crates/ui-egui/src/i18n/nl.tsv @@ -3084,6 +3084,8 @@ digits Malayalam Malayalam Keep a link to an image file instead of only embedding it Een koppeling met een afbeeldingsbestand behouden in plaats van het alleen in te sluiten Place onto a new locked, dimmed template layer below the current layer Plaatsen op een nieuwe, vergrendelde, gedimde sjabloonlaag onder de huidige laag Swap the selected object for the file, keeping its place and transform Het geselecteerde object vervangen door het bestand, met behoud van positie en transformatie + Show Import Options Importopties tonen + Ask for each PDF, DXF or text file's import options before placing it; off, use the options chosen last Vóór het plaatsen naar de importopties van elk PDF-, DXF- of tekstbestand vragen; uit: de laatst gekozen opties gebruiken On: one locked object showing the document, updated when it changes. Aan: één vergrendeld object dat het document weergeeft, bijgewerkt wanneer het verandert. Off: an editable copy of its art, embedded in this document. Uit: een bewerkbare kopie van de artwork, ingesloten in dit document. Images: link to the file instead of only embedding it. Afbeeldingen: koppel aan het bestand in plaats van het alleen in te sluiten. diff --git a/crates/ui-egui/src/i18n/pl.tsv b/crates/ui-egui/src/i18n/pl.tsv index 77b1425e5f..0f89f870f8 100644 --- a/crates/ui-egui/src/i18n/pl.tsv +++ b/crates/ui-egui/src/i18n/pl.tsv @@ -2697,6 +2697,8 @@ digits Malayalam Malajalam Keep a link to an image file instead of only embedding it Zachowaj łącze do pliku obrazu zamiast tylko go osadzać Place onto a new locked, dimmed template layer below the current layer Umieść na nowej zablokowanej, przyciemnionej warstwie szablonu pod bieżącą warstwą Swap the selected object for the file, keeping its place and transform Zamień zaznaczony obiekt na plik, zachowując jego miejsce i przekształcenie + Show Import Options Pokaż opcje importu + Ask for each PDF, DXF or text file's import options before placing it; off, use the options chosen last Pytaj o opcje importu każdego pliku PDF, DXF lub tekstowego przed umieszczeniem; wył.: używaj ostatnio wybranych opcji On: one locked object showing the document, updated when it changes. Wł.: jeden zablokowany obiekt pokazujący dokument, aktualizowany przy jego zmianie. Off: an editable copy of its art, embedded in this document. Wył.: edytowalna kopia jego kompozycji, osadzona w tym dokumencie. Images: link to the file instead of only embedding it. Obrazy: połącz z plikiem zamiast tylko go osadzać. diff --git a/crates/ui-egui/src/i18n/pt-br.tsv b/crates/ui-egui/src/i18n/pt-br.tsv index caed79af2a..df0ea2aae3 100644 --- a/crates/ui-egui/src/i18n/pt-br.tsv +++ b/crates/ui-egui/src/i18n/pt-br.tsv @@ -5233,6 +5233,8 @@ axis Both Ambos Summary Resumo Superscript Sobrescrito Swap the selected object for the file, keeping its place and transform Troca o objeto selecionado pelo arquivo, mantendo sua posição e transformação + Show Import Options Mostrar Opções de Importação + Ask for each PDF, DXF or text file's import options before placing it; off, use the options chosen last Pergunta as opções de importação de cada arquivo PDF, DXF ou de texto antes de colocá-lo; desligado: usa as últimas opções escolhidas Swatch Libraries Menu Menu de Bibliotecas de Amostras Create New Library… Criar Nova Biblioteca… Rename Library… Renomear Biblioteca… diff --git a/crates/ui-egui/src/i18n/ru.tsv b/crates/ui-egui/src/i18n/ru.tsv index 206b209ee2..9d1b40eb34 100644 --- a/crates/ui-egui/src/i18n/ru.tsv +++ b/crates/ui-egui/src/i18n/ru.tsv @@ -3639,6 +3639,8 @@ digits Malayalam Малаялам Swap Fill and Stroke (Shift+X) Поменять заливку и обводку (Shift+X) Swap start and end arrowheads Поменять начальную и конечную стрелки Swap the selected object for the file, keeping its place and transform Заменить выделенный объект файлом, сохранив его положение и преобразование + Show Import Options Показывать параметры импорта + Ask for each PDF, DXF or text file's import options before placing it; off, use the options chosen last Запрашивать параметры импорта каждого файла PDF, DXF или текстового файла перед помещением; если выключено, используются последние выбранные параметры Stylistic Alternates Стилистические альтернативы Swash Росчерк Swatch Conflict Конфликт образцов diff --git a/crates/ui-egui/src/i18n/sv.tsv b/crates/ui-egui/src/i18n/sv.tsv index 82e63d1667..d503d2f2e6 100644 --- a/crates/ui-egui/src/i18n/sv.tsv +++ b/crates/ui-egui/src/i18n/sv.tsv @@ -3529,6 +3529,8 @@ digits Malayalam Malayalam Swap Fill and Stroke (Shift+X) Byt plats på fyllning och kontur (Shift+X) Swap start and end arrowheads Byt plats på start- och slutpilspets Swap the selected object for the file, keeping its place and transform Ersätt det markerade objektet med filen och behåll dess placering och transformering + Show Import Options Visa importalternativ + Ask for each PDF, DXF or text file's import options before placing it; off, use the options chosen last Fråga efter importalternativ för varje PDF-, DXF- eller textfil innan den placeras; av: använd de senast valda alternativen Stylistic Alternates Stilistiska alternativ Swash Swash Swatch Conflict Färgrutekonflikt diff --git a/crates/ui-egui/src/i18n/tr.tsv b/crates/ui-egui/src/i18n/tr.tsv index 237694bff5..2f2a4325cc 100644 --- a/crates/ui-egui/src/i18n/tr.tsv +++ b/crates/ui-egui/src/i18n/tr.tsv @@ -3393,6 +3393,8 @@ digits Malayalam Malayalam Swap Fill and Stroke (Shift+X) Dolgu ve Konturu Değiştir (Shift+X) Swap start and end arrowheads Başlangıç ve bitiş ok uçlarını değiştirin Swap the selected object for the file, keeping its place and transform Seçilen nesneyi, yerini koruyarak dosyayla değiştirin ve dönüştürün + Show Import Options İçe Aktarma Seçeneklerini Göster + Ask for each PDF, DXF or text file's import options before placing it; off, use the options chosen last Her PDF, DXF veya metin dosyasını yerleştirmeden önce içe aktarma seçeneklerini sor; kapalıyken son seçilen seçenekleri kullan Stylistic Alternates Stilistik Alternatifler Swash çalkalama Swatch Conflict Renk Örneği Çatışması diff --git a/crates/ui-egui/src/i18n/uk.tsv b/crates/ui-egui/src/i18n/uk.tsv index a74a03529d..ed58cf1d61 100644 --- a/crates/ui-egui/src/i18n/uk.tsv +++ b/crates/ui-egui/src/i18n/uk.tsv @@ -3646,6 +3646,8 @@ effect Points Точки Swap Fill and Stroke (Shift+X) Змінити заливку та обведення (Shift+X) Swap start and end arrowheads Поміняти початкову та кінцеву стрілки Swap the selected object for the file, keeping its place and transform Замінити виділений об’єкт файлом, зберігши його положення та перетворення + Show Import Options Показувати параметри імпорту + Ask for each PDF, DXF or text file's import options before placing it; off, use the options chosen last Запитувати параметри імпорту кожного файлу PDF, DXF чи текстового файлу перед розміщенням; якщо вимкнено, використовуються останні вибрані параметри Stylistic Alternates Стилістичні альтернативи Swash Розчерк Swatch Conflict Конфлікт зразків diff --git a/crates/ui-egui/src/i18n/vi.tsv b/crates/ui-egui/src/i18n/vi.tsv index 16c613874c..f05ba2522f 100644 --- a/crates/ui-egui/src/i18n/vi.tsv +++ b/crates/ui-egui/src/i18n/vi.tsv @@ -4020,6 +4020,8 @@ mark-style Japanese Kiểu Nhật Swap Fill and Stroke (Shift+X) Đổi màu tô và nét (Shift+X) Swap start and end arrowheads Đổi đầu mũi tên bắt đầu và kết thúc Swap the selected object for the file, keeping its place and transform Thay đối tượng đã chọn bằng tệp, giữ nguyên vị trí và biến đổi + Show Import Options Hiện tùy chọn nhập + Ask for each PDF, DXF or text file's import options before placing it; off, use the options chosen last Hỏi tùy chọn nhập của từng tệp PDF, DXF hoặc văn bản trước khi đặt; khi tắt, dùng các tùy chọn đã chọn gần nhất Stylistic Alternates Ký tự thay thế theo phong cách Swash Swash Swatch Conflict Xung đột mẫu màu diff --git a/crates/ui-egui/src/i18n/zh-hans.tsv b/crates/ui-egui/src/i18n/zh-hans.tsv index b83bee650f..c3c62ec7a5 100644 --- a/crates/ui-egui/src/i18n/zh-hans.tsv +++ b/crates/ui-egui/src/i18n/zh-hans.tsv @@ -3468,6 +3468,8 @@ digits Malayalam 马拉雅拉姆数字 Swap Fill and Stroke (Shift+X) 切换填色和描边 (Shift+X) Swap start and end arrowheads 对调起点与终点箭头 Swap the selected object for the file, keeping its place and transform 以文件替换选择的对象,并保留其位置与变形 + Show Import Options 显示导入选项 + Ask for each PDF, DXF or text file's import options before placing it; off, use the options chosen last 置入前询问每个 PDF、DXF 或文本文件的导入选项;关闭时使用上次选择的选项 Stylistic Alternates 风格替代字 Swash 花饰字 Swatch Conflict 色板冲突 diff --git a/crates/ui-egui/src/i18n/zh-hant.tsv b/crates/ui-egui/src/i18n/zh-hant.tsv index 4637ecb811..ada63cd407 100644 --- a/crates/ui-egui/src/i18n/zh-hant.tsv +++ b/crates/ui-egui/src/i18n/zh-hant.tsv @@ -4726,6 +4726,8 @@ digits Malayalam 馬來亞拉姆數字 Swap Fill and Stroke (Shift+X) 對調填色與筆畫 (Shift+X) Swap start and end arrowheads 對調起點與終點箭頭 Swap the selected object for the file, keeping its place and transform 以檔案取代選取的物件,並保留其位置與變形 + Show Import Options 顯示讀入選項 + Ask for each PDF, DXF or text file's import options before placing it; off, use the options chosen last 置入前詢問每個 PDF、DXF 或文字檔案的讀入選項;關閉時使用上次選擇的選項 Stylistic Alternates 風格替代字 Swash 花體字 Swatch Conflict 色票衝突 diff --git a/crates/ui-egui/src/place.rs b/crates/ui-egui/src/place.rs index fff3587182..3205446c89 100644 --- a/crates/ui-egui/src/place.rs +++ b/crates/ui-egui/src/place.rs @@ -69,6 +69,11 @@ pub struct PlaceState { /// The Place dialog's lines about its files while they are read off the UI thread: the /// dialog's files, and the lines once they are read. reading_lines: Option<(Value, std::sync::mpsc::Receiver>)>, + /// The files of the loaded place cursor ask for their import options when clicked or dragged + /// (the Place dialog loaded it with Show Import Options on). + queue_asks: bool, + /// The loaded cursor's files by name, with the size a click places them at. + queue_sizes: Vec<(String, f64, f64)>, } /// A place cursor thumbnail: decoded when the cursor is loaded, uploaded when first drawn (and @@ -107,9 +112,11 @@ fn engine(app: &mut VectorcraftApp, id: &str, p: &Value) -> Result Result { place_file(app, p, Read::AtOnce) } @@ -130,8 +137,27 @@ enum Read { } fn place_file(app: &mut VectorcraftApp, p: &Value, read: Read) -> Result { - if p.get("text").is_none() && crate::dialogs::text_import::is_text(p) { - return Ok(crate::dialogs::text_import::open(app, p)); + let show = match p.get("showImportOptions") { + None | Some(Value::Null) => None, + Some(Value::Bool(b)) => Some(*b), + Some(_) => return Err("`showImportOptions` must be true or false".into()), + }; + let ask = show.unwrap_or(true); + let mut p = p.clone(); + if let Some(o) = p.as_object_mut() { + o.remove("showImportOptions"); + } + if !ask { + // Placed with the options confirmed last for the format. + if p.get("text").is_none() && crate::dialogs::text_import::is_text(&p) { + p["text"] = crate::dialogs::text_import::last_options(app); + } + if let Some(dxf) = crate::dialogs::dxf_import::last_options(app, &p) { + p["dxf"] = dxf; + } + } + if p.get("text").is_none() && crate::dialogs::text_import::is_text(&p) { + return Ok(crate::dialogs::text_import::open(app, &p)); } if p.get("path").is_none() && p.get("dataBase64").is_none() { let files: Vec = match p.get("paths").and_then(Value::as_array) { @@ -142,17 +168,21 @@ fn place_file(app: &mut VectorcraftApp, p: &Value, read: Read) -> Result) { "link": app.ui.place_link, "template": false, "replace": false, + "showImportOptions": app.ui.place_import_options, "__replace": files.len() == 1 && one_object, "__documents": linked.contains(&true), "__others": linked.contains(&false), @@ -319,15 +350,18 @@ fn fmt_ppi(v: f64) -> String { } /// Place (the dialog's OK): one file is placed centred in the view (or replaces the selection); -/// several load the place cursor. +/// several load the place cursor. With Show Import Options on, each file that has import options +/// asks for them before it is placed (the cursor's files when clicked or dragged); off, each is +/// placed with the options confirmed last for its format. pub fn confirm(app: &mut VectorcraftApp, d: &crate::state::Dialog) -> Result { let files: Vec = d.fields.get("files").and_then(Value::as_array).cloned().unwrap_or_default(); let sources: Vec = files.iter().filter_map(|f| source(app, f)).collect(); if sources.is_empty() { return Err("no files to place".into()); } - let (link, template) = (d.bool("link"), d.bool("template")); + let (link, template, ask) = (d.bool("link"), d.bool("template"), d.bool("showImportOptions")); app.ui.place_link = link; + app.ui.place_import_options = ask; app.ui.dialog = None; app.place.reading_lines = None; let r = match &sources[..] { @@ -335,6 +369,7 @@ pub fn confirm(app: &mut VectorcraftApp, d: &crate::state::Dialog) -> Result Result { let paths: Vec<&Value> = many.iter().filter_map(|s| s.get("path")).collect(); let files: Vec<&Value> = many.iter().filter(|s| s.get("path").is_none()).collect(); - queue_files(app, &json!({ "paths": paths, "files": files, "link": link, "template": template }), Read::ForUser { dropped: false }) + let mut q = json!({ "paths": paths, "files": files, "link": link, "template": template }); + if !ask { + // Each text file and DXF drawing in the cursor takes the options confirmed last. + q["text"] = crate::dialogs::text_import::last_options(app); + q["dxf"] = crate::dialogs::dxf_import::remembered(app); + } + queue_files(app, &q, Read::ForUser { dropped: false }, ask) } }; app.place.picked.clear(); @@ -355,35 +396,41 @@ pub fn confirm(app: &mut VectorcraftApp, d: &crate::state::Dialog) -> Result Result { - queue_files(app, p, Read::AtOnce) + queue_files(app, p, Read::AtOnce, false) } -fn queue_files(app: &mut VectorcraftApp, p: &Value, read: Read) -> Result { +/// `file.place.queue` with `p`; `asks`: the cursor's files ask for their import options when placed. +fn queue_files(app: &mut VectorcraftApp, p: &Value, read: Read, asks: bool) -> Result { let mut p = p.clone(); if p.get("thumbnail").is_none() { p["thumbnail"] = json!(THUMB); } if matches!(read, Read::ForUser { .. }) && crate::placing::available(app) { - let r = crate::placing::start_queue(app, &p); + let r = crate::placing::start_queue(app, &p, asks); if let Err(e) = &r { app.status(e.clone()); } return r; } let r = engine(app, "file.place.queue", &p)?; - show_thumbnails(app, &r); + show_thumbnails(app, &r, asks); Ok(r) } /// The file a click or drag of the place cursor places (`file.place` with `p`, its `rect` set): /// read off the UI thread where the files the user places are ([`crate::placing::start`]), else at -/// once. An error shows in the status bar. +/// once. When the Place dialog loaded the cursor with Show Import Options on, a file that has +/// import options asks for them first (Cancel leaves it unplaced). An error shows in the status bar. pub(crate) fn place_from_cursor(app: &mut VectorcraftApp, p: &Value) { // Read within the automation roots of whoever loaded the cursor, even on a click of the person // at the keyboard: an agent's queue reaches no file it wasn't given. let roots = app.session.place_queue_roots(); vectorcraft_engine::file_access::confine(roots.as_ref(), || { - if crate::placing::available(app) { + if app.place.queue_asks { + // Placing shows its own errors in the status bar. + let p = clicked_at_full_size(app, p); + let _ = place_file(app, &p, Read::ForUser { dropped: false }); + } else if crate::placing::available(app) { if let Err(e) = crate::placing::start(app, p, false) { app.status(e); } @@ -393,8 +440,40 @@ pub(crate) fn place_from_cursor(app: &mut VectorcraftApp, p: &Value) { }); } -/// The cursor's thumbnails from `file.place.queue`'s reply `r`. -pub(crate) fn show_thumbnails(app: &mut VectorcraftApp, r: &Value) { +/// The params of a cursor click (`rect` at the file's queued size) on a PDF or DXF file, whose +/// import options may change its size (another page, another scale): placed as its options place +/// it at the click (`at`, the center of the box the click gave), as File › Place of that one file +/// places it, instead of fitted into the box of the first page or the default scale. A drag keeps +/// its `rect`; one to exactly the queued size gives the same `rect` as a click, and is taken as one. +fn clicked_at_full_size(app: &VectorcraftApp, p: &Value) -> Value { + let mut p = p.clone(); + let Some(name) = p.get("path").or_else(|| p.get("name")).and_then(Value::as_str).map(fileio::file_name) else { return p }; + if !matches!(fileio::extension(&name).as_str(), "pdf" | "ai" | "ait" | "dxf") { + return p; + } + let r: Vec = p.get("rect").and_then(Value::as_array).map(|a| a.iter().filter_map(Value::as_f64).collect()).unwrap_or_default(); + let [x, y, w, h] = r[..] else { return p }; + // Files of the same name in the cursor must all have that size: the place tool's request names + // the file, not its place in the cursor. + let mut same_name = app.place.queue_sizes.iter().filter(|(n, ..)| *n == name).peekable(); + let queued = same_name.peek().is_some() && same_name.all(|(_, qw, qh)| (qw - w).abs() < 1e-6 && (qh - h).abs() < 1e-6); + if queued && let Some(o) = p.as_object_mut() { + o.remove("rect"); + o.insert("at".into(), json!([x + w / 2.0, y + h / 2.0])); + } + p +} + +/// The cursor's thumbnails and its files' sizes from `file.place.queue`'s reply `r` once the cursor +/// is loaded; `asks`: its files ask for their import options when placed. +pub(crate) fn show_thumbnails(app: &mut VectorcraftApp, r: &Value, asks: bool) { + app.place.queue_asks = asks; + app.place.queue_sizes = r["files"] + .as_array() + .into_iter() + .flatten() + .filter_map(|f| Some((f["name"].as_str()?.to_string(), f["width"].as_f64()?, f["height"].as_f64()?))) + .collect(); app.place.thumbs = r["files"] .as_array() .into_iter() diff --git a/crates/ui-egui/src/placing.rs b/crates/ui-egui/src/placing.rs index a627321d76..ca380f7ca8 100644 --- a/crates/ui-egui/src/placing.rs +++ b/crates/ui-egui/src/placing.rs @@ -21,10 +21,11 @@ use vectorcraft_engine::file_access::ScopeHandle; use crate::VectorcraftApp; -/// What was read: one file to place, the files to load the place cursor with, or a bitmap to paste. +/// What was read: one file to place, the files to load the place cursor with (and whether they ask +/// for their import options when placed), or a bitmap to paste. enum Read { Place(Box), - Queue(QueueRead), + Queue(QueueRead, bool), Paste(Box), } @@ -236,17 +237,18 @@ pub fn start(app: &mut VectorcraftApp, p: &Value, dropped: bool) -> Result Result { +/// `asks`: the cursor's files ask for their import options when placed ([`crate::place::show_thumbnails`]). +pub fn start_queue(app: &mut VectorcraftApp, p: &Value, asks: bool) -> Result { let job = app.session.prepare_place_queue(p).map_err(|e| e.to_string())?; let names = job.names().join(", "); - let started = spawn(app, names.clone(), names, false, move || job.read().map(Read::Queue).map_err(|e| e.to_string()))?; + let started = spawn(app, names.clone(), names, false, move || job.read().map(|r| Read::Queue(r, asks)).map_err(|e| e.to_string()))?; match started { Some(r) => Ok(r), None => { let r = app.session.execute("file.place.queue", p).map_err(|e| e.to_string()); app.sync_views(); let r = r?; - crate::place::show_thumbnails(app, &r); + crate::place::show_thumbnails(app, &r, asks); Ok(r) } } @@ -485,7 +487,9 @@ fn finish(app: &mut VectorcraftApp, p: &Pending, r: Result) { app.session.set_active(i); let r = match r { Ok(Read::Place(read)) => app.session.place_read(*read).map(|_| ()).map_err(|e| e.to_string()), - Ok(Read::Queue(read)) => app.session.place_queue_read(read).map(|r| crate::place::show_thumbnails(app, &r)).map_err(|e| e.to_string()), + Ok(Read::Queue(read, asks)) => { + app.session.place_queue_read(read).map(|r| crate::place::show_thumbnails(app, &r, asks)).map_err(|e| e.to_string()) + } Ok(Read::Paste(paste)) => return finish_paste(app, *paste), Err(e) => Err(e), }; diff --git a/crates/ui-egui/src/placing/tests.rs b/crates/ui-egui/src/placing/tests.rs index dbb37ff446..f9211a61bb 100644 --- a/crates/ui-egui/src/placing/tests.rs +++ b/crates/ui-egui/src/placing/tests.rs @@ -1173,7 +1173,7 @@ fn a_file_whose_document_was_reverted_meanwhile_is_not_placed() { for kind in ["place", "drop", "queue"] { let (mut app, _) = saved_and_changed(app(true), &format!("reverted-{kind}")); let read = |app: &mut VectorcraftApp| match kind { - "queue" => start_queue(app, &json!({"paths": [tif]})), + "queue" => start_queue(app, &json!({"paths": [tif]}), false), _ => start(app, &json!({"path": tif}), kind == "drop"), }; read(&mut app).unwrap(); diff --git a/crates/ui-egui/src/state.rs b/crates/ui-egui/src/state.rs index b28e4034a7..e108fe9e59 100644 --- a/crates/ui-egui/src/state.rs +++ b/crates/ui-egui/src/state.rs @@ -749,6 +749,14 @@ pub struct UiState { /// File → Place: Link is on (the Place dialog remembers it). #[serde(default = "yes")] pub place_link: bool, + /// File → Place: Show Import Options is on (the Place dialog remembers it). Each placed file + /// with import options (a PDF's page and box, a DXF drawing, a text file) asks for them first; + /// off, it is placed with the options last confirmed for its format. + #[serde(default = "yes")] + pub place_import_options: bool, + /// The Text Import Options confirmed last (`file.place`'s `text` options; null: never used). + #[serde(default)] + pub text_import: Value, /// The file the open dialog reads, kept out of its JSON fields (Import PDF); dropped when no /// dialog is open. #[serde(skip)] @@ -1219,6 +1227,8 @@ impl Default for UiState { swatches_show_recent_colors: true, svg_options: Value::Null, place_link: true, + place_import_options: true, + text_import: Value::Null, dialog_file: None, made_text_style: vec![], pending_fonts: vec![], diff --git a/crates/ui-egui/src/tests/place.rs b/crates/ui-egui/src/tests/place.rs index 19c39879af..cbddfbf2c1 100644 --- a/crates/ui-egui/src/tests/place.rs +++ b/crates/ui-egui/src/tests/place.rs @@ -7,6 +7,7 @@ use egui::{Pos2, Shape, vec2}; use serde_json::{Value, json}; use vectorcraft_doc::NodeKind; use vectorcraft_engine::Session; +use vectorcraft_testkit::pdf::{PdfPage, pdf}; use crate::VectorcraftApp; use crate::canvas::Xf; @@ -897,6 +898,179 @@ fn idle(app: &mut VectorcraftApp, ctx: &egui::Context, time: &mut f64, secs: f64 /// while the window sits idle in the background, follows Update Links: Automatically: the app /// wakes itself to see the new link before the change, so the change isn't taken for the file as /// first seen. +/// The text of the one selected type object. +fn selected_text(app: &VectorcraftApp) -> String { + let st = app.session.active().unwrap(); + let NodeKind::Text(t) = &st.doc.node(st.selection.objects[0]).unwrap().kind else { panic!("not type") }; + t.plain_text() +} + +/// The kind of the open dialog, if any. +fn dialog_kind(app: &VectorcraftApp) -> Option<&str> { + app.ui.dialog.as_ref().map(|d| d.kind.as_str()) +} + +const NOTES: &[u8] = b"one\r\ntwo\r\n\r\nthree\r\n"; + +/// A DXF drawing of one 100 × 50 line. +const LINE_DXF: &[u8] = b"0\nSECTION\n2\nENTITIES\n0\nLINE\n8\n0\n10\n0\n20\n0\n11\n100\n21\n50\n0\nENDSEC\n0\nEOF\n"; + +#[test] +fn show_import_options_is_a_place_option_saved_with_the_preferences() { + let mut app = app(); + let dir = Folder::new("ui-place-show-import-options"); + let notes = dir.file("notes.txt", NOTES); + app.run("file.place", json!({"paths": [notes]})).unwrap(); + let d = app.ui.dialog.as_ref().expect("the Place dialog"); + assert!(d.bool("showImportOptions"), "on by default"); + let text = crate::tests_labels::painted_text(&mut app, |app, ui| crate::dialogs::show(app, ui.ctx())); + assert!(text.contains("Show Import Options"), "{text}"); + // On: the text file asks for its import options first. + crate::dialogs::confirm(&mut app).unwrap(); + assert_eq!(dialog_kind(&app), Some(crate::dialogs::text_import::KIND)); + app.ui.dialog = None; + // Off: it is placed at once, and the next Place dialog starts off. + app.run("file.place", json!({"paths": [notes]})).unwrap(); + app.ui.dialog.as_mut().unwrap().fields.insert("showImportOptions".into(), json!(false)); + crate::dialogs::confirm(&mut app).unwrap(); + assert_eq!(dialog_kind(&app), None); + assert!(selected_text(&app).contains("three")); + assert!(!app.ui.place_import_options); + app.run("file.place", json!({"paths": [notes]})).unwrap(); + assert!(!app.ui.dialog.as_ref().unwrap().bool("showImportOptions"), "remembered"); + // `showImportOptions` given with `paths` sets the dialog's checkbox. + app.run("file.place", json!({"paths": [notes], "showImportOptions": true})).unwrap(); + assert!(app.ui.dialog.as_ref().unwrap().bool("showImportOptions")); + // Saved with the preferences; older preferences without it start on. + let saved = serde_json::to_value(&app.ui).unwrap(); + assert_eq!(saved["place_import_options"], false); + let old: crate::UiState = serde_json::from_value(json!({"place_link": false})).unwrap(); + assert!(old.place_import_options && old.text_import.is_null()); +} + +#[test] +fn with_show_import_options_off_a_file_takes_the_options_confirmed_last() { + let mut app = app(); + let b64 = vectorcraft_format::base64_encode; + // Nothing confirmed yet: file.place's defaults. + app.run("file.place", json!({"name": "a.txt", "dataBase64": b64(NOTES), "showImportOptions": false})).unwrap(); + assert_eq!(dialog_kind(&app), None); + let plain = selected_text(&app); + // Text Import Options remembers what it confirmed, and the next text file starts from it. + app.run("file.place", json!({"name": "b.txt", "dataBase64": b64(NOTES)})).unwrap(); + assert_eq!(dialog_kind(&app), Some(crate::dialogs::text_import::KIND)); + for k in ["removeLineReturns", "removeParagraphReturns", "replaceSpaces"] { + app.ui.dialog.as_mut().unwrap().fields.insert(k.into(), json!(true)); + } + app.ui.dialog.as_mut().unwrap().fields.insert("spaces".into(), json!(4)); + crate::dialogs::confirm(&mut app).unwrap(); + assert_eq!(selected_text(&app), "one two\nthree"); + assert_ne!(plain, "one two\nthree"); + app.run("file.place", json!({"name": "c.txt", "dataBase64": b64(NOTES)})).unwrap(); + let d = app.ui.dialog.as_ref().unwrap(); + assert!(d.bool("removeLineReturns") && d.bool("replaceSpaces")); + assert_eq!(d.f64("spaces", 0.0), 4.0); + app.ui.dialog = None; + app.run("file.place", json!({"name": "d.txt", "dataBase64": b64(NOTES), "showImportOptions": false})).unwrap(); + assert_eq!((dialog_kind(&app), selected_text(&app).as_str()), (None, "one two\nthree")); + // A DXF drawing: placed without asking, with the fit chosen last. + let width = |app: &VectorcraftApp| { + let st = app.session.active().unwrap(); + st.doc.node(st.selection.objects[0]).unwrap().geometric_bounds().unwrap().width() + }; + app.run("file.place", json!({"name": "line.dxf", "dataBase64": b64(LINE_DXF), "showImportOptions": false})).unwrap(); + assert_eq!(dialog_kind(&app), None); + let unfitted = width(&app); + app.ui.dxf_import = json!({"fit": true}); + app.run("file.place", json!({"name": "line.dxf", "dataBase64": b64(LINE_DXF), "showImportOptions": false})).unwrap(); + assert_eq!(dialog_kind(&app), None); + assert!(width(&app) > unfitted + 1.0, "fitted: {} vs {unfitted}", width(&app)); + app.run("file.place", json!({"name": "line.dxf", "dataBase64": b64(LINE_DXF)})).unwrap(); + assert_eq!(dialog_kind(&app), Some(crate::dialogs::dxf_import::KIND), "on: it asks"); + app.ui.dialog = None; + // A PDF with several pages: its first page, without asking. + let pages = [PdfPage::new(100.0, 100.0, "0 g 0 0 10 10 re f"), PdfPage::new(300.0, 100.0, "1 0 0 rg 0 0 30 30 re f")]; + let two = pdf(&pages, None); + app.run("file.place", json!({"name": "two.pdf", "dataBase64": b64(&two), "showImportOptions": false})).unwrap(); + assert_eq!(dialog_kind(&app), None); + assert!((width(&app) - 100.0).abs() < 0.01, "page 1: {}", width(&app)); + // An encrypted PDF still asks for its password, and then places its first page. + let locked = pdf(&pages, Some("pw")); + app.run("file.place", json!({"name": "locked.pdf", "dataBase64": b64(&locked), "showImportOptions": false})).unwrap(); + assert_eq!(app.ui.dialog.as_ref().map(|d| d.bool("__locked")), Some(true)); + app.ui.dialog.as_mut().unwrap().fields.insert("password".into(), json!("pw")); + crate::dialogs::confirm(&mut app).unwrap(); + assert_eq!(dialog_kind(&app), None, "no page to pick"); + assert!((width(&app) - 100.0).abs() < 0.01, "page 1: {}", width(&app)); + // Given `page`, it asks for the password and places that page. + app.run("file.place", json!({"name": "locked.pdf", "dataBase64": b64(&locked), "page": 2, "showImportOptions": false})).unwrap(); + app.ui.dialog.as_mut().unwrap().fields.insert("password".into(), json!("pw")); + crate::dialogs::confirm(&mut app).unwrap(); + assert!((width(&app) - 300.0).abs() < 0.01, "page 2: {}", width(&app)); + // A page past the last one is refused once the file is unlocked, not placed as another page. + app.run("file.place", json!({"name": "locked.pdf", "dataBase64": b64(&locked), "page": 9, "showImportOptions": false})).unwrap(); + app.ui.dialog.as_mut().unwrap().fields.insert("password".into(), json!("pw")); + assert!(crate::dialogs::confirm(&mut app).is_err()); + app.ui.dialog = None; + // A value that is not true or false is refused, and nothing is placed. + let before = app.session.active().unwrap().history.undo.len(); + for bad in [json!("no"), json!(0)] { + let e = app.run("file.place", json!({"name": "e.txt", "dataBase64": b64(NOTES), "showImportOptions": bad})).unwrap_err(); + assert!(e.contains("`showImportOptions` must be true or false"), "{e}"); + } + assert_eq!((dialog_kind(&app), app.session.active().unwrap().history.undo.len()), (None, before)); +} + +#[test] +fn the_place_cursors_files_ask_for_their_import_options_when_show_import_options_is_on() { + let mut app = app(); + let dir = Folder::new("ui-place-cursor-import-options"); + let a = dir.file("a.png", &png(10, 10, 72.0)); + let notes = dir.file("notes.txt", NOTES); + let click = json!({"path": notes, "rect": [10, 10, 200, 100]}); + // On: a click on the text file asks for its import options, and OK places it in the rect. + app.run("file.place", json!({"paths": [a, notes]})).unwrap(); + crate::dialogs::confirm(&mut app).unwrap(); + assert_eq!(app.session.tool_options()["count"], 2); + crate::place::place_from_cursor(&mut app, &click); + assert_eq!(dialog_kind(&app), Some(crate::dialogs::text_import::KIND)); + assert_eq!(app.ui.dialog.as_ref().unwrap().fields["__params"]["rect"], json!([10, 10, 200, 100])); + crate::dialogs::confirm(&mut app).unwrap(); + assert!(selected_text(&app).contains("three")); + // A click on a PDF places the page chosen at its own size, centered where the click put the + // first page; a drag keeps its box. + let pages = [PdfPage::new(100.0, 100.0, "0 g 0 0 10 10 re f"), PdfPage::new(300.0, 100.0, "1 0 0 rg 0 0 30 30 re f")]; + let two = dir.file("two.pdf", &pdf(&pages, None)); + app.run("file.place", json!({"paths": [a, two]})).unwrap(); + app.ui.dialog.as_mut().unwrap().fields.insert("link".into(), json!(false)); + crate::dialogs::confirm(&mut app).unwrap(); + crate::place::place_from_cursor(&mut app, &json!({"path": two, "link": false, "rect": [10, 10, 100, 100]})); + assert_eq!(dialog_kind(&app), Some("importPdf")); + assert_eq!(app.ui.dialog.as_ref().unwrap().fields["__place"], json!({"link": false, "at": [60.0, 60.0]})); + app.ui.dialog.as_mut().unwrap().fields.insert("page".into(), json!(2)); + crate::dialogs::confirm(&mut app).unwrap(); + let st = app.session.active().unwrap(); + let b = st.doc.node(st.selection.objects[0]).unwrap().geometric_bounds().unwrap(); + assert_eq!((b.width().round(), b.center()), (300.0, vectorcraft_geom::Point::new(60.0, 60.0))); + crate::place::place_from_cursor(&mut app, &json!({"path": two, "link": false, "rect": [10, 10, 50, 50]})); + assert_eq!(app.ui.dialog.as_ref().unwrap().fields["__place"]["rect"], json!([10, 10, 50, 50])); + app.ui.dialog = None; + // Off: the cursor's files are placed at once, with the options confirmed last. + app.run("file.place", json!({"paths": [a, notes]})).unwrap(); + app.ui.dialog.as_mut().unwrap().fields.insert("showImportOptions".into(), json!(false)); + crate::dialogs::confirm(&mut app).unwrap(); + assert_eq!(app.session.tool_options()["count"], 2); + crate::place::place_from_cursor(&mut app, &click); + assert_eq!(dialog_kind(&app), None); + assert!(selected_text(&app).contains("three")); + // A cursor loaded by file.place.queue places at once, as before. + app.run("file.place", json!({"paths": [a, notes]})).unwrap(); + crate::dialogs::confirm(&mut app).unwrap(); + app.run("file.place.queue", json!({"paths": [a, notes]})).unwrap(); + crate::place::place_from_cursor(&mut app, &click); + assert_eq!(dialog_kind(&app), None); +} + #[test] fn a_file_placed_while_idle_follows_its_changes() { let mut app = app(); diff --git a/docs/mcp.md b/docs/mcp.md index 99b0bfef38..73d391bc6c 100644 --- a/docs/mcp.md +++ b/docs/mcp.md @@ -6826,6 +6826,23 @@ with `pointer_gesture` (a click places at 100% with the top-left corner there, a `file.place.info` refuse, of the keys each one takes, a `link`, `replace` or `template` that is not true or false and a `thumbnail` that is not a number, naming the key, and place nothing; null is the same as leaving the key out. +In the app, `file.place` asks for a file's import options before placing it: a text file without `text` (Text Import +Options), a DXF drawing without `dxf` (DXF Import Options), and a PDF, `.ai` or `.ait` file with several pages or a +password and no `page` (Place PDF). `showImportOptions: false` places such a file without asking, with the options +last confirmed in its dialog: the text options, and the DXF fit, lineweight, centering and layer choices with the +drawing at 1:1 in its own unit. A PDF is placed on page 1 (or `page`) and its crop box, and an encrypted PDF given no +`password` still asks for the password. A `showImportOptions` that is not true or false is refused, and nothing is placed. The +Place dialog (File › Place, or `file.place {paths}`) has a Show Import Options checkbox after Link, Template and +Replace ([Place multiple files in a document](https://helpx.adobe.com/illustrator/desktop/add-and-import-files/import-other-file-types/place-multiple-files-in-a-document.html)). +It is on by default and saved with the preferences. On, each file that has import options asks for them before it is +placed, also each file of the place cursor when it is clicked or dragged (Cancel leaves that file unplaced). A click on +a PDF or DXF file places it as its options place it with `at` at the center of the box the click gave (a PDF's page at +its own size, a DXF drawing at its scale, or on the artboard there when fitted or not centered), and a drag to any other +size fits it into the dragged box (a drag to exactly the file's queued size gives the place tool the same box as a click and +is placed as one). Off, each +file is placed with the options last confirmed for its format. The default, and the options a file is placed with +when the checkbox is off, are VectorCraft's own choices. + ```json {"name":"run_command","arguments":{"command":"file.place","params":{"path":"/tmp/photo.jpg","at":[300,200]}}} {"name":"run_command","arguments":{"command":"file.place","params":{"name":"logo.svg","dataBase64":"PHN2Zy…","rect":[0,0,100,100]}}} diff --git a/docs/target-app-parity.md b/docs/target-app-parity.md index c6972468cc..e55e3b0a5e 100644 --- a/docs/target-app-parity.md +++ b/docs/target-app-parity.md @@ -1835,6 +1835,7 @@ Moved from the ROADMAP's "Shipped so far" on 2026-10-10. Update it when a featur - Placed documents' bitmaps on screen: when a bitmap made in the background is ready, the desktop app renders the canvas again only where the objects that show its file are, when the frame shown was rendered on the UI thread and that part is at most half the canvas. Bitmaps drawn or made since a frame began are kept for that frame past the 512 MiB budget, up to 2 GiB, so a view showing more than the budget does not make them again in every frame. A bitmap that would take them past 2 GiB is not kept, and its file draws exactly at that zoom. A bitmap that can't be made (the file can't be read and there is no preview) shows the light placeholder and is not asked for again. Each ready bitmap used to render the whole canvas again, and bitmaps past the budget and bitmaps that could not be made were asked for again in every frame (`crates/render/src/tests/placed_document.rs`). - Linked PDF and `.ai` pages: File › Place with Link (the default) places a page of a PDF, `.ai` or `.ait` file read as PDF pages as a placed document, as it links an image, the page and its box (`crop`) kept with the link: Links check, update and relink it, Break Link and Object › Expand give the group placing it without Link gives, every output draws the page's vectors, and Package copies the file. Without Link, or with a password or `textAs: "outlines"`, the page is embedded. EPS and PostScript `.ai` files are still embedded (`crates/engine/src/tests/placed_document.rs`). - File › Place with Bounding Box (`file.place {crop: "bounding"}`) measures the art that shows, with its strokes and effects, as an export does: hidden objects, guides and template layers do not count, a group whose only part on the page or artboard is hidden is not placed, and symbol instances are measured by their art, for `at`, `rect`, the place cursor and `file.place.info` (`crates/engine/src/tests/place.rs`). + - The Place dialog has a Show Import Options checkbox after Link, Template and Replace ([Place multiple files in a document](https://helpx.adobe.com/illustrator/desktop/add-and-import-files/import-other-file-types/place-multiple-files-in-a-document.html)). On (VectorCraft's default, saved with the preferences), each PDF, DXF or text file asks for its import options before it is placed, also in the place cursor; off (`file.place {showImportOptions: false}`), it is placed with the options last confirmed for its format, and only an encrypted PDF asks, for its password. Text Import Options now starts from the options confirmed last (`crates/ui-egui/src/tests/place.rs`). - Separations Preview shows placed and embedded raster images on the visible plates, as it shows vector art (#4153): an RGB image's pixels are separated into process inks with the proof setup's intent, a CMYK image shows its own inks, a hidden plate's ink leaves the image, and with one plate visible the image shows that plate's ink as a grey. Raster images carry no spot ink, so a spot plate shown alone shows them as paper; a colorized image shows its color's plates. Emoji and other bitmap glyphs follow the same rule. The plate-filtered pixels and their reduced-size copies are kept apart from the image's own pixels, for each set of visible plates, proof setup and color settings, and with the preview off nothing changes. Evidence: `tests::colorize::separations_preview_shows_an_rgb_images_process_inks`, `separations_preview_shows_a_cmyk_images_own_inks`, `separations_preview_keeps_an_images_pixels_per_plate_set`. Print preview fidelity remains the open gap, so scores and hours are unchanged. - Desktop print transport uses `craft-print` for CUPS and modern Windows. Sheet composition, copies, separations and colour management remain in VectorCraft. The Print dialog's Media list also carries the Japanese papers — JIS B4 257 × 364 mm, JIS B5 182 × 257 mm and JIS B6 128 × 182 mm beside the ISO B4/B5 (250 × 353 and 176 × 250 mm, kept so settings and presets saved before them keep their paper), and はがき 100 × 148 mm, 往復はがき 148 × 200 mm, 封筒 長形3号 120 × 235 mm, 長形4号 90 × 225 mm, 角形2号 240 × 332 mm, 角形3号 216 × 277 mm and 四六判 127 × 188 mm; the JSON id or the dialog label names a paper, any case and spacing (`B5 (JIS)` or `b5jis`) (`crates/pdf/src/print/settings.rs`, `crates/pdf/src/tests/print.rs`). The print-preview and physical-device fidelity gaps remain, so print scores and hours are unchanged. - A PDF, PDF-compatible `.ai` or `.ait` opened from its pages or its editing copy opens with the File Info that its XMP packet and its document information dictionary give: the title (`dc:title`, its `x-default` item first, else Info `Title`), the author, description, keywords, copyright status, notice and URL, and the created and modified dates, the XMP packet's value where both give one (`crates/pdf/src/tests/file_info.rs`, `crates/engine/src/tests/fileinfo.rs`). An SVG opens with the title of its root ``. The title is File Info's alone (`DocMetadata::title`): the document's name stays the file's name with its extension, a file that gives no title leaves File Info with that name, and exports (PDF, SVG, PNG, EPS, `.ai`) and native saves carry it; changing the title in File Info sets the document's name as before (#3626).