diff --git a/ROADMAP.md b/ROADMAP.md index 2ebf4c311..9ec3981b3 100644 --- a/ROADMAP.md +++ b/ROADMAP.md @@ -468,6 +468,7 @@ Newest first. - **2026-10-20** · The Object menu is in InDesign's order and groups; Paths has Join, Open Path and Close Path (Convert Shape lists only shapes, as InDesign's does) and a Convert Point submenu (Plain, Corner, Smooth, Symmetrical) is added. No readiness percentage change. - **2026-10-14** · Numbering & Section Options opened on a page that doesn't start a section is New Section, as in InDesign: Start Section on, 1 offered, and the start numbers stay editable (typing one chooses it). No readiness percentage change. - **2026-10-20** · Layout › Create Guides puts its guides on both pages of the spread in view, as InDesign does; "This page" in Apply to narrows them to one page. No readiness percentage change. +- **2026-10-21** · Object › Fitting › Clear Frame Fitting Options (`object.clearFittingOptions`): the frames' auto-fit, crop amounts and reference point go back to their defaults, and the picture stays where it is. The Fitting submenu is grouped as InDesign's. Found in the Learn Skills Daily course, chapter 11. No readiness percentage change. - **2026-10-21** · Properties › Character and Paragraph: ••• (More Options) expands the section in place, as InDesign's: Character adds vertical and horizontal scale, baseline shift, skew, language and OpenType, and All Caps, Small Caps, Superscript, Subscript, Underline and Strikethrough; Paragraph adds the left, right, first line and last line indents, space before and after, the drop cap's lines and characters, Align to Baseline Grid and Hyphenate. It used to open the Character or Paragraph panel; the language field moved under ••• as in InDesign. Found in the Learn Skills Daily course, chapter 16. No readiness percentage change. - **2026-10-21** · The text Control panel has InDesign's two modes: A (Character Formatting Controls: font and style, size and leading, All Caps/Superscript/Underline and Small Caps/Subscript/Strikethrough, kerning and tracking, vertical and horizontal scale, baseline shift and skew, character style and language) and ¶ (Paragraph Formatting Controls: the nine alignments, left/first line and right/last line indents, space before and after, the drop cap's lines and characters, paragraph style, Hyphenate and Align to Baseline Grid). It used to show a fixed mix of both. Found in the Learn Skills Daily course, chapter 16. No readiness percentage change. - **2026-10-20** · With the Type tool, the Control panel's font size, leading and tracking fields have a spinner and a list of common values, as in InDesign (auto leading in parentheses). No readiness percentage change. diff --git a/crates/engine/src/cmd/object.rs b/crates/engine/src/cmd/object.rs index 55ed79941..e85f20f33 100644 --- a/crates/engine/src/cmd/object.rs +++ b/crates/engine/src/cmd/object.rs @@ -638,6 +638,15 @@ pub fn specs() -> Vec { has_selection, fitting_options ), + cmd!( + "object.clearFittingOptions", + "Clear Frame Fitting Options", + ["Object", "Fitting"], + None, + "{ids?} — the frames' fitting options back to the defaults: no auto-fit, no crop amounts, the centre reference point; the content stays where it is", + has_selection, + clear_fitting_options + ), cmd!("object.rename", "Rename", [], None, "{id | ids?, name}", has_doc, |s, p| { let name = str_param(p, "name").unwrap_or("").to_string(); set_flag(s, p, move |i| i.name = name.clone(), false) @@ -2968,6 +2977,35 @@ fn fitting_options(s: &mut Session, p: &Value) -> Result { }) } +/// Clear Frame Fitting Options: auto-fit off, no crop, the centre reference point. The content +/// stays where it is, as in InDesign. +fn clear_fitting_options(s: &mut Session, p: &Value) -> Result { + // Locked frames (or frames on a locked layer) keep their options, as with Fit; frames whose + // options are already clear are left out; with nothing to change, no edit (and no empty undo + // step). + let doc = &s.doc()?.doc; + let set = |g: &designcraft_doc::Graphic| g.auto_fit != designcraft_doc::Fitting::None || g.crop != [0.0; 4] || g.fit_align != 4; + let ids: Vec = targets(s, p)? + .into_iter() + .filter(|id| doc.item(*id).is_some_and(|it| unlocked(doc, it) && matches!(&it.content, Content::Graphic(g) if set(g)))) + .collect(); + if ids.is_empty() { + return Ok(json!({"changed": 0})); + } + s.edit(|d, _| { + let mut n = 0; + for id in &ids { + let Some(it) = d.item_mut(*id) else { continue }; + let Content::Graphic(g) = &mut it.content else { continue }; + g.auto_fit = designcraft_doc::Fitting::None; + g.crop = [0.0; 4]; + g.fit_align = 4; + n += 1; + } + Ok(json!({"changed": n})) + }) +} + fn fit(s: &mut Session, p: &Value) -> Result { let doc = &s.doc()?.doc; let ids: Vec<_> = @@ -4373,6 +4411,38 @@ mod fitting_tests { let g = back.spreads.iter().flat_map(|sp| sp.items.iter()).find_map(|i| i.graphic().cloned()).unwrap(); assert_eq!(g.auto_fit, designcraft_doc::Fitting::FillProportionally); } + + /// Clear Frame Fitting Options puts auto-fit, crop and the reference point back to their + /// defaults and leaves the picture where it is. + #[test] + fn clear_fitting_options_resets_the_frames_options_and_keeps_the_picture() { + let mut s = Session::new(); + s.execute("file.new", &json!({})).unwrap(); + let f = s.execute("frame.create", &json!({"rect": [72, 72, 272, 172]})).unwrap()["id"].as_u64().unwrap(); + let png = designcraft_render::Rendered { width: 40, height: 20, pixels: vec![200; 40 * 20 * 4] }.to_png(); + s.execute("file.place", &json!({"base64": crate::cmd::base64_encode(&png), "name": "p.png", "frame": f})).unwrap(); + s.execute("object.fittingOptions", &json!({"ids": [f], "autoFit": true, "fitting": "fitProportionally", "crop": 10, "align": 0})).unwrap(); + let graphic = |s: &Session| s.doc().unwrap().doc.item(designcraft_doc::ItemId(f)).unwrap().graphic().unwrap().clone(); + let before = graphic(&s); + assert_ne!(before.auto_fit, designcraft_doc::Fitting::None); + s.execute("object.clearFittingOptions", &json!({"ids": [f]})).unwrap(); + let g = graphic(&s); + assert_eq!(g.auto_fit, designcraft_doc::Fitting::None); + assert_eq!(g.crop, [0.0; 4]); + assert_eq!(g.fit_align, 4, "the centre reference point"); + assert_eq!(g.xf, before.xf, "the picture stays where it is"); + let undo = s.doc().unwrap().history.undo.len(); + let r = s.execute("object.clearFittingOptions", &json!({"ids": [f]})).unwrap(); + assert_eq!((r["changed"].as_u64(), s.doc().unwrap().history.undo.len()), (Some(0), undo), "already clear: no change, no step"); + s.execute("edit.undo", &json!({})).unwrap(); + assert_eq!(graphic(&s).crop, [10.0; 4], "one undo step"); + s.execute("object.lock", &json!({"ids": [f]})).unwrap(); + let undo = s.doc().unwrap().history.undo.len(); + let r = s.execute("object.clearFittingOptions", &json!({"ids": [f]})).unwrap(); + assert_eq!(r["changed"], 0); + assert_eq!(graphic(&s).crop, [10.0; 4], "a locked frame keeps its options"); + assert_eq!(s.doc().unwrap().history.undo.len(), undo, "no empty undo step"); + } } #[cfg(test)] diff --git a/crates/ui-egui/src/i18n/ar.rs b/crates/ui-egui/src/i18n/ar.rs index ccef65f75..7b6999066 100644 --- a/crates/ui-egui/src/i18n/ar.rs +++ b/crates/ui-egui/src/i18n/ar.rs @@ -37,6 +37,7 @@ pub(super) const TABLE: &[(&str, &str)] = &[ ("Join", "وصل"), ("Convert Point", "تحويل النقطة"), ("New Section", "قسم جديد"), + ("Clear Frame Fitting Options", "مسح خيارات ملاءمة الإطار"), ("Restart Numbering", "إعادة الترقيم"), ("Continue Numbering", "متابعة الترقيم"), ("Keep Options…", "خيارات الإبقاء…"), diff --git a/crates/ui-egui/src/i18n/cs.rs b/crates/ui-egui/src/i18n/cs.rs index f548213f3..74a224965 100644 --- a/crates/ui-egui/src/i18n/cs.rs +++ b/crates/ui-egui/src/i18n/cs.rs @@ -40,6 +40,7 @@ pub(super) const TABLE: &[(&str, &str)] = &[ ("Plain", "Obyčejný"), ("Symmetrical", "Symetrický"), ("New Section", "Nový oddíl"), + ("Clear Frame Fitting Options", "Vymazat volby přizpůsobení rámečku"), ("Restart Numbering", "Začít číslování znovu"), ("Continue Numbering", "Pokračovat v číslování"), ("Keep Options…", "Volby zachování…"), diff --git a/crates/ui-egui/src/i18n/de.rs b/crates/ui-egui/src/i18n/de.rs index 20968bf37..0d1d705cb 100644 --- a/crates/ui-egui/src/i18n/de.rs +++ b/crates/ui-egui/src/i18n/de.rs @@ -39,6 +39,7 @@ pub(super) const TABLE: &[(&str, &str)] = &[ ("Plain", "Einfach"), ("Symmetrical", "Symmetrisch"), ("New Section", "Neuer Abschnitt"), + ("Clear Frame Fitting Options", "Rahmeneinpassungsoptionen löschen"), ("Restart Numbering", "Nummerierung neu beginnen"), ("Continue Numbering", "Nummerierung fortführen"), ("Keep Options…", "Umbruchoptionen …"), diff --git a/crates/ui-egui/src/i18n/it.rs b/crates/ui-egui/src/i18n/it.rs index 68f77596a..e649e2128 100644 --- a/crates/ui-egui/src/i18n/it.rs +++ b/crates/ui-egui/src/i18n/it.rs @@ -35,6 +35,7 @@ pub(super) const TABLE: &[(&str, &str)] = &[ ("Join", "Unisci"), ("Convert Point", "Converti punto"), ("New Section", "Nuova sezione"), + ("Clear Frame Fitting Options", "Cancella opzioni di adattamento cornice"), ("Restart Numbering", "Riavvia numerazione"), ("Continue Numbering", "Continua numerazione"), ("Keep Options…", "Opzioni di separazione…"), diff --git a/crates/ui-egui/src/i18n/ja.rs b/crates/ui-egui/src/i18n/ja.rs index 25f8a3273..70d0954d6 100644 --- a/crates/ui-egui/src/i18n/ja.rs +++ b/crates/ui-egui/src/i18n/ja.rs @@ -36,6 +36,7 @@ pub(super) const TABLE: &[(&str, &str)] = &[ ("Join", "連結"), ("Convert Point", "ポイントを切り換え"), ("New Section", "新規セクション"), + ("Clear Frame Fitting Options", "フレーム調整オプションを消去"), ("Restart Numbering", "番号の振り直し"), ("Continue Numbering", "番号の継続"), ("Keep Options…", "段落分離禁止オプション…"), diff --git a/crates/ui-egui/src/i18n/ka.rs b/crates/ui-egui/src/i18n/ka.rs index 04f6913bb..41c5c7bc9 100644 --- a/crates/ui-egui/src/i18n/ka.rs +++ b/crates/ui-egui/src/i18n/ka.rs @@ -36,6 +36,7 @@ pub(super) const TABLE: &[(&str, &str)] = &[ ("Corner Shape", "კუთხის ფორმა"), ("Corner Size and Shape:", "კუთხეების ზომა და ფორმა:"), ("New Section", "ახალი სექცია"), + ("Clear Frame Fitting Options", "ჩარჩოს მორგების პარამეტრების გასუფთავება"), ("Restart Numbering", "ნუმერაციის თავიდან დაწყება"), ("Continue Numbering", "ნუმერაციის გაგრძელება"), ("Keep Options…", "შენარჩუნების პარამეტრები…"), diff --git a/crates/ui-egui/src/i18n/pl.rs b/crates/ui-egui/src/i18n/pl.rs index 5a81d3397..974eb15ee 100644 --- a/crates/ui-egui/src/i18n/pl.rs +++ b/crates/ui-egui/src/i18n/pl.rs @@ -38,6 +38,7 @@ pub(super) const TABLE: &[(&str, &str)] = &[ ("Plain", "Zwykły"), ("Symmetrical", "Symetryczny"), ("New Section", "Nowa sekcja"), + ("Clear Frame Fitting Options", "Wyczyść opcje dopasowania ramki"), ("Restart Numbering", "Zacznij numerowanie od nowa"), ("Continue Numbering", "Kontynuuj numerowanie"), ("Keep Options…", "Opcje utrzymywania…"), diff --git a/crates/ui-egui/src/i18n/pt_br.rs b/crates/ui-egui/src/i18n/pt_br.rs index 92be714c0..17218ccf6 100644 --- a/crates/ui-egui/src/i18n/pt_br.rs +++ b/crates/ui-egui/src/i18n/pt_br.rs @@ -38,6 +38,7 @@ pub(super) const TABLE: &[(&str, &str)] = &[ ("Join", "Unir"), ("Convert Point", "Converter ponto"), ("New Section", "Nova seção"), + ("Clear Frame Fitting Options", "Limpar opções de ajuste do quadro"), ("Restart Numbering", "Reiniciar numeração"), ("Continue Numbering", "Continuar numeração"), ("Keep Options…", "Opções de manutenção…"), diff --git a/crates/ui-egui/src/i18n/ru.rs b/crates/ui-egui/src/i18n/ru.rs index 988d8e680..2717f37cd 100644 --- a/crates/ui-egui/src/i18n/ru.rs +++ b/crates/ui-egui/src/i18n/ru.rs @@ -42,6 +42,7 @@ pub(super) const TABLE: &[(&str, &str)] = &[ ("Plain", "Простая"), ("Symmetrical", "Симметричная"), ("New Section", "Новый раздел"), + ("Clear Frame Fitting Options", "Очистить параметры подгонки фрейма"), ("Restart Numbering", "Начать нумерацию заново"), ("Continue Numbering", "Продолжить нумерацию"), ("Keep Options…", "Параметры удержания…"), diff --git a/crates/ui-egui/src/i18n/table.rs b/crates/ui-egui/src/i18n/table.rs index 0e4552fdf..46869165c 100644 --- a/crates/ui-egui/src/i18n/table.rs +++ b/crates/ui-egui/src/i18n/table.rs @@ -1,5 +1,15 @@ /// English → [German, French, Spanish, Japanese, Simplified Chinese]. pub(super) const TABLE: &[(&str, [&str; 5])] = &[ + ( + "Clear Frame Fitting Options", + [ + "Rahmeneinpassungsoptionen löschen", + "Effacer les options d’ajustement du bloc", + "Borrar opciones de encaje de marco", + "フレーム調整オプションを消去", + "清除框架适合选项", + ], + ), ( "Character Formatting Controls", [ diff --git a/crates/ui-egui/src/i18n/uk.rs b/crates/ui-egui/src/i18n/uk.rs index 88b142517..0b3700ad1 100644 --- a/crates/ui-egui/src/i18n/uk.rs +++ b/crates/ui-egui/src/i18n/uk.rs @@ -36,6 +36,7 @@ pub(super) const TABLE: &[(&str, &str)] = &[ ("Join", "Зʼєднати"), ("Convert Point", "Перетворити точку"), ("New Section", "Новий розділ"), + ("Clear Frame Fitting Options", "Очистити параметри припасування фрейму"), ("Restart Numbering", "Почати нумерацію знову"), ("Continue Numbering", "Продовжити нумерацію"), ("Keep Options…", "Параметри утримання…"), diff --git a/crates/ui-egui/src/menus/tables.rs b/crates/ui-egui/src/menus/tables.rs index 97ae2fd5a..25ddc1a4b 100644 --- a/crates/ui-egui/src/menus/tables.rs +++ b/crates/ui-egui/src/menus/tables.rs @@ -337,10 +337,13 @@ pub const MENUS: &[(&str, &[&str])] = &[ ">Fitting", "ui:fit.fillFrameProportionally", "ui:fit.contentProportionally", + "-", "ui:fit.frameToContent", "ui:fit.contentToFrame", "ui:fit.centerContent", "-", + "cmd:object.clearFittingOptions", + "-", "ui:app.fittingOptions", "<", ">Content", diff --git a/crates/ui-egui/src/menus/tests.rs b/crates/ui-egui/src/menus/tests.rs index 5f7f5b93d..4c4c20da5 100644 --- a/crates/ui-egui/src/menus/tests.rs +++ b/crates/ui-egui/src/menus/tests.rs @@ -104,6 +104,30 @@ fn view_items_are_in_the_view_menu_as_in_indesign() { assert_eq!(ui_label("view.togglePreview").and_then(|c| c.1), Some("W")); } +/// Object › Fitting in InDesign's groups: the proportional fits, the frame and content fits, +/// Clear Frame Fitting Options, then the dialog. +#[test] +fn the_fitting_menu_is_grouped_as_indesigns() { + let items = MENUS.iter().find(|(t, _)| *t == "Object").map(|(_, items)| *items).unwrap(); + let start = items.iter().position(|e| *e == ">Fitting").unwrap() + 1; + let end = start + items[start..].iter().position(|e| *e == "<").unwrap(); + assert_eq!( + &items[start..end], + [ + "ui:fit.fillFrameProportionally", + "ui:fit.contentProportionally", + "-", + "ui:fit.frameToContent", + "ui:fit.contentToFrame", + "ui:fit.centerContent", + "-", + "cmd:object.clearFittingOptions", + "-", + "ui:app.fittingOptions", + ] + ); +} + /// The submenu paths of a menu bar menu ("View/Screen Mode"), as Edit › Menus keys them. fn submenu_paths(title: &str) -> Vec { let items = MENUS.iter().find(|(t, _)| *t == title).map(|(_, items)| *items).unwrap_or_default();