diff --git a/crates/engine/src/file_cmds.rs b/crates/engine/src/file_cmds.rs index 79d0697e..9eeb98a2 100644 --- a/crates/engine/src/file_cmds.rs +++ b/crates/engine/src/file_cmds.rs @@ -400,9 +400,9 @@ fn open_as(s: &mut Session, p: &Value) -> Result { /// History label of an embedded place. pub const PLACE_EMBEDDED: &str = "Place Embedded"; -/// Place a file's bytes as a smart object layer, centred and (when larger than the canvas) -/// scaled down to fit, like Photoshop's Place with "Resize Image During Place". `linked` makes it -/// a linked smart object that refers to that path instead of embedding the bytes. +/// Place a file's bytes as a smart object layer, centred, and (with Preferences ▸ General ▸ +/// Resize Image During Place, on by default) scaled down to fit when larger than the canvas. +/// `linked` makes it a linked smart object that refers to that path instead of embedding the bytes. pub fn place_bytes(s: &mut Session, name: &str, bytes: Vec, linked: Option, p: &Value) -> Result { let d = s.active().ok_or(EngineError::NoDocument)?; let (cw, ch) = (d.doc.size.width as f64, d.doc.size.height as f64); @@ -417,9 +417,11 @@ pub fn place_bytes(s: &mut Session, name: &str, bytes: Vec, linked: Option (k / 100.0).max(1e-4), - None if p.get("fit").and_then(Value::as_bool) != Some(false) && (w > cw || h > ch) => (cw / w).min(ch / h), + None if fit && (w > cw || h > ch) => (cw / w).min(ch / h), None => 1.0, }; let center = match p.get("center").and_then(Value::as_array) { @@ -1302,13 +1304,19 @@ pub fn specs() -> Vec { "Place Embedded…", &["File"], None, - r##"{"path":str,"scale":%? (default: fit when larger than the canvas),"fit":bool=true,"center":[x,y]?}"##, + r##"{"path":str,"scale":%? (default: fit when larger than the canvas),"fit":bool? (default: Preferences > General > Resize Image During Place),"center":[x,y]?}"##, native_doc, |s, p| place(s, p, false) ), - spec!("file.placeLinked", "Place Linked…", &["File"], None, r##"{"path":str,"scale":%?,"fit":bool=true,"center":[x,y]?}"##, native_doc, |s, p| place( - s, p, true - )), + spec!( + "file.placeLinked", + "Place Linked…", + &["File"], + None, + r##"{"path":str,"scale":%?,"fit":bool? (default: Preferences > General > Resize Image During Place),"center":[x,y]?}"##, + native_doc, + |s, p| place(s, p, true) + ), spec!( "file.fileInfo", "File Info…", diff --git a/crates/engine/src/file_cmds/tests.rs b/crates/engine/src/file_cmds/tests.rs index 3ea6a319..e5b17a20 100644 --- a/crates/engine/src/file_cmds/tests.rs +++ b/crates/engine/src/file_cmds/tests.rs @@ -180,6 +180,23 @@ fn place_embedded_centres_fits_and_embeds() { } } +/// Preferences ▸ General ▸ Resize Image During Place: off, a larger image keeps its natural size +/// instead of being fitted to the canvas; an explicit `"fit"` still wins. +#[test] +fn resize_image_during_place_preference_controls_the_fit() { + let dir = tmp("place-fit-pref"); + let big = png(&dir, "big.png", 200, 100, "#ff0000"); + let mut s = session(100, 100, 8); + s.execute("prefs.set", json!({"path": "general.resizeImageDuringPlace", "value": false})).unwrap(); + let r = s.execute("file.placeEmbedded", json!({"path": big})).unwrap(); + assert!((r["scale"].as_f64().unwrap() - 100.0).abs() < 1e-9, "natural size: {r}"); + let b = r["bounds"].as_array().unwrap(); + assert!(b[2].as_f64().unwrap() > 100.0, "wider than the canvas: {b:?}"); + // An explicit "fit": true overrides the preference. + let r = s.execute("file.placeEmbedded", json!({"path": big, "fit": true})).unwrap(); + assert!((r["scale"].as_f64().unwrap() - 50.0).abs() < 1e-9, "fitted on request: {r}"); +} + /// A 40×20 JPEG (left half red, right half blue) tagged EXIF Orientation = 6: shown upright it /// is 20×40, red on top. fn rotated_jpeg(dir: &str) -> String { diff --git a/crates/engine/src/prefs.rs b/crates/engine/src/prefs.rs index c0c35bf5..c5fe3470 100644 --- a/crates/engine/src/prefs.rs +++ b/crates/engine/src/prefs.rs @@ -972,7 +972,6 @@ pub const SECTIONS: [(&str, &str); 18] = [ pub const HIDDEN_UNTIL_IMPLEMENTED: &[&str] = &[ "general.colorPicker", "general.beepWhenDone", - "general.resizeImageDuringPlace", "general.alwaysCreateSmartObjectsWhenPlacing", "general.animatedZoom", "general.zoomResizesWindows", diff --git a/docs/scorecard.md b/docs/scorecard.md index f0f16eca..c2616264 100644 --- a/docs/scorecard.md +++ b/docs/scorecard.md @@ -9,7 +9,7 @@ Generated by `cargo xtask scorecard` from `perf/budgets.toml`, `perf/baseline.js | [Performance](#performance) | 3 | 22 | 22 | 56 scenarios: 3 within budget, 22 over budget or failing, 9 tracked only, 15 not run, 7 not measurable yet (baseline 2026-10-05) | | [File compatibility](#file-compatibility) | 5 | 11 | 11 | io corpus: oracle ≥ 146/170, round trip ≥ 169/170; psd-tools corpus: oracle ≥ 236/309, round trip ≥ 308/309; photoshop oracles: oracle ≥ 133/256, round trip ≥ 258/256 | | [Tools](#tools) | 21 | 15 | 6 | | -| [Workspace and UI](#workspace-and-ui) | 6 | 7 | 10 | settings that do nothing: 44 of 149 (target 0) | +| [Workspace and UI](#workspace-and-ui) | 6 | 7 | 10 | settings that do nothing: 43 of 149 (target 0) | | [Type](#type) | 0 | 3 | 6 | | | [Automation](#automation) | 3 | 2 | 6 | 628 of 628 menu items live (100.0%) | | [Reliability](#reliability) | 5 | 2 | 0 | never-crash lints on 28 of 28 crates; panic_hunt present | @@ -175,9 +175,9 @@ Corpus floors (`crates/io/tests/corpus.rs`): the enforced minimums; the corpus t ## Workspace and UI -**Settings that do nothing: 44 of 149** (target 0, #204). Counted by the prefs audit (`xtask/src/scorecard.rs`): a `Preferences` field counts as read when code outside the prefs model (`prefs.rs` except its `impl Session` blocks; tests excluded; the schema-driven dialog never names a field) accesses `section.field`, or `.section` and `.field` in the same file, or calls a section method that reads `self.field`. It is a heuristic lower bound: a generic name can look read when it isn't. +**Settings that do nothing: 43 of 149** (target 0, #204). Counted by the prefs audit (`xtask/src/scorecard.rs`): a `Preferences` field counts as read when code outside the prefs model (`prefs.rs` except its `impl Session` blocks; tests excluded; the schema-driven dialog never names a field) accesses `section.field`, or `.section` and `.field` in the same file, or calls a section method that reads `self.field`. It is a heuristic lower bound: a generic name can look read when it isn't. -Unread: `general.color_picker`, `general.beep_when_done`, `general.resize_image_during_place`, `general.always_create_smart_objects_when_placing`, `general.animated_zoom`, `general.zoom_resizes_windows`, `workspace.auto_collapse_icon_panels`, `workspace.auto_show_hidden_panels`, `workspace.enable_floating_document_window_docking`, `workspace.enable_narrow_options_bar`, `tools.enable_flick_panning`, `tools.vary_round_brush_hardness_on_hud`, `tools.show_transformation_values`, `file_handling.image_previews`, `file_handling.save_in_background`, `file_handling.ignore_exif_profile_tag`, `file_handling.maximize_psd_compatibility`, `performance.cache_levels`, `performance.legacy_compositing`, `scratch_disks.disks`, `cursors.brush_preview_color`, `units_and_rulers.type_units`, `units_and_rulers.column_width`, `units_and_rulers.gutter`, `units_and_rulers.print_resolution`, `plug_ins.show_extension_panels`, `plug_ins.allow_scripts_to_connect`, `plug_ins.generator_enabled`, `type_.missing_glyph_protection`, `type_.show_font_names_in_english`, `type_.text_engine`, `type_.font_preview`, `type_.recent_fonts`, `enhanced_controls.scrubby_slider_acceleration`, `enhanced_controls.touch_gestures`, `enhanced_controls.zoom_with_trackpad_pinch`, `raw_defaults.color_space`, `raw_defaults.bit_depth`, `raw_defaults.resolution`, `raw_defaults.sharpen_for`, `raw_defaults.open_as_smart_object`, `raw_defaults.apply_auto_tone`, `integrations.allow_agent_control`, `integrations.control_port`. +Unread: `general.color_picker`, `general.beep_when_done`, `general.always_create_smart_objects_when_placing`, `general.animated_zoom`, `general.zoom_resizes_windows`, `workspace.auto_collapse_icon_panels`, `workspace.auto_show_hidden_panels`, `workspace.enable_floating_document_window_docking`, `workspace.enable_narrow_options_bar`, `tools.enable_flick_panning`, `tools.vary_round_brush_hardness_on_hud`, `tools.show_transformation_values`, `file_handling.image_previews`, `file_handling.save_in_background`, `file_handling.ignore_exif_profile_tag`, `file_handling.maximize_psd_compatibility`, `performance.cache_levels`, `performance.legacy_compositing`, `scratch_disks.disks`, `cursors.brush_preview_color`, `units_and_rulers.type_units`, `units_and_rulers.column_width`, `units_and_rulers.gutter`, `units_and_rulers.print_resolution`, `plug_ins.show_extension_panels`, `plug_ins.allow_scripts_to_connect`, `plug_ins.generator_enabled`, `type_.missing_glyph_protection`, `type_.show_font_names_in_english`, `type_.text_engine`, `type_.font_preview`, `type_.recent_fonts`, `enhanced_controls.scrubby_slider_acceleration`, `enhanced_controls.touch_gestures`, `enhanced_controls.zoom_with_trackpad_pinch`, `raw_defaults.color_space`, `raw_defaults.bit_depth`, `raw_defaults.resolution`, `raw_defaults.sharpen_for`, `raw_defaults.open_as_smart_object`, `raw_defaults.apply_auto_tone`, `integrations.allow_agent_control`, `integrations.control_port`. | Id | Target | Status | Issue | Note | |---|---|---|---|---|