diff --git a/crates/engine/src/cmd/format/font.rs b/crates/engine/src/cmd/format/font.rs index 5b5fd8eea..c977fcb6f 100644 --- a/crates/engine/src/cmd/format/font.rs +++ b/crates/engine/src/cmd/format/font.rs @@ -55,12 +55,18 @@ pub(super) fn size(s: &mut Session, p: &Value) -> Result { format_run(s, &move |r| r.size = Some(v)) } -/// PowerPoint's font size list (grow/shrink steps through it). -pub const SIZES: [f64; 20] = [8.0, 9.0, 10.0, 10.5, 11.0, 12.0, 14.0, 16.0, 18.0, 20.0, 24.0, 28.0, 32.0, 36.0, 40.0, 44.0, 48.0, 54.0, 60.0, 66.0]; +/// PowerPoint's font size list, as the Font Size box lists it (grow/shrink steps through it). +pub const SIZES: [f64; 24] = + [8.0, 9.0, 10.0, 10.5, 11.0, 12.0, 14.0, 16.0, 18.0, 20.0, 24.0, 28.0, 32.0, 36.0, 40.0, 44.0, 48.0, 54.0, 60.0, 66.0, 72.0, 80.0, 88.0, 96.0]; +/// Grow steps up the list, then 8 points at a time past its top; shrink steps back down the same +/// way. A size past the top used to shrink straight to 66 (#2059). fn step(cur: f64, up: bool) -> f64 { + let top = SIZES[SIZES.len() - 1]; if up { SIZES.iter().copied().find(|v| *v > cur + 0.01).unwrap_or((cur + 8.0).min(4000.0)) + } else if cur - 0.01 > top { + (cur - 8.0).max(top) } else { SIZES.iter().rev().copied().find(|v| *v < cur - 0.01).unwrap_or((cur - 1.0).max(1.0)) } diff --git a/crates/engine/src/tests.rs b/crates/engine/src/tests.rs index 4f40b2dfb..d4939d043 100644 --- a/crates/engine/src/tests.rs +++ b/crates/engine/src/tests.rs @@ -6921,6 +6921,37 @@ fn master_body_placeholder_format_keeps_per_level_formatting() { assert_eq!(shown(&s), vec![Some(28.0), Some(24.0)]); } +/// #2059: Decrease Font Size (Ctrl+[, Cmd+Shift+,) steps down one size at a time from any size: +/// PowerPoint's list up to 96, then 8 points at a time above it, as Increase Font Size steps up. +/// A size above 66 used to shrink straight to 66. +#[test] +fn shrinking_a_large_font_steps_down_one_size_at_a_time() { + let mut s = session(); + let r = s.execute("shape.insert", &json!({"preset": "rect", "rect": [80.0, 80.0, 420.0, 160.0], "text": "Big"})).unwrap(); + let id = deckcraft_model::ShapeId(u32::try_from(r["id"].as_u64().unwrap()).unwrap()); + let size = |s: &Session| s.doc().unwrap().shape(id).unwrap().text.as_ref().unwrap().paragraphs[0].runs[0].props.size; + s.execute("format.size", &json!({"size": 120})).unwrap(); + let mut down = vec![]; + for _ in 0..9 { + s.execute("format.shrink", &json!({})).unwrap(); + down.push(size(&s).unwrap()); + } + assert_eq!(down, [112.0, 104.0, 96.0, 88.0, 80.0, 72.0, 66.0, 60.0, 54.0]); + let mut up = vec![]; + for _ in 0..7 { + s.execute("format.grow", &json!({})).unwrap(); + up.push(size(&s).unwrap()); + } + assert_eq!(up, [60.0, 66.0, 72.0, 80.0, 88.0, 96.0, 104.0]); + // A size between list steps goes to the next one down, and one past the top steps 8 at most. + s.execute("format.size", &json!({"size": 99})).unwrap(); + s.execute("format.shrink", &json!({})).unwrap(); + assert_eq!(size(&s), Some(96.0)); + s.execute("format.size", &json!({"size": 150})).unwrap(); + s.execute("format.shrink", &json!({})).unwrap(); + assert_eq!(size(&s), Some(142.0)); +} + /// A shape named many times in one edit is edited once and not copied per mention. #[test] fn repeated_target_ids_edit_the_shape_once() { diff --git a/crates/ui-egui/src/ribbon/home.rs b/crates/ui-egui/src/ribbon/home.rs index 267f161d3..78dc637f7 100644 --- a/crates/ui-egui/src/ribbon/home.rs +++ b/crates/ui-egui/src/ribbon/home.rs @@ -197,7 +197,7 @@ fn font_group(app: &mut SlideApp, ui: &mut Ui, st: &Value) { }); let size_text = size.map(|s| widgets::fmt_num(s, "")).unwrap_or_default(); dropdown(ui, "Font size", &size_text, 52.0, en, |ui| { - for s in deckcraft_engine::cmd::format::SIZES.iter().chain([72.0, 80.0, 88.0, 96.0].iter()) { + for s in deckcraft_engine::cmd::format::SIZES.iter() { if ui.button(widgets::fmt_num(*s, "")).clicked() { run(app, "format.size", json!({"size": s})); ui.close();