diff --git a/crates/ui-egui/src/dialogs/file_ui/print.rs b/crates/ui-egui/src/dialogs/file_ui/print.rs
--- a/crates/ui-egui/src/dialogs/file_ui/print.rs
+++ b/crates/ui-egui/src/dialogs/file_ui/print.rs
@@ -141,20 +141,16 @@ fn unit_pt(units: &str) -> f64 {
     }
 }
 
-/// A number field in `units` for a value kept in points; true when edited.
+/// A nonnegative length field (paper and print size) in `units` for a value kept in points; true
+/// when edited.
 fn length_field(ui: &mut egui::Ui, label: &str, pt: &mut f64, units: &str) -> bool {
-    length_field_prec(ui, label, pt, units, 0)
-}
-
-/// Edits a point-valued length in the selected units, with the requested decimal precision.
-/// Retains the nonnegative 0..=10000 display-unit range used by existing size fields.
-fn length_field_prec(ui: &mut egui::Ui, label: &str, pt: &mut f64, units: &str, decimals: u32) -> bool {
-    length_field_range(ui, label, pt, units, decimals, 0.0..=10000.0)
+    length_field_range(ui, label, pt, units, 0.0..=10000.0)
 }
 
 /// Edits a length stored in print points (72 per inch), returning true for a finite edit.
 /// `range` is in the displayed `units`; a signed range permits negative Top/Left offsets.
-fn length_field_range(ui: &mut egui::Ui, label: &str, pt: &mut f64, units: &str, decimals: u32, range: std::ops::RangeInclusive<f32>) -> bool {
+/// Shows the unit's own decimals, as Photoshop does, so 2.5 in or 6.35 cm are kept (#9704).
+fn length_field_range(ui: &mut egui::Ui, label: &str, pt: &mut f64, units: &str, range: std::ops::RangeInclusive<f32>) -> bool {
     let t = Tokens::get(ui.ctx());
     let k = unit_pt(units);
     let mut v = (*pt / k) as f32;
@@ -162,6 +158,7 @@ fn length_field_range(ui: &mut egui::Ui, label: &str, pt: &mut f64, units: &str,
     ui.horizontal(|ui| {
         ui.label(egui::RichText::new(label).color(t.text_dim));
         let before = v;
+        let decimals = crate::widgets::unit_decimals(units);
         crate::widgets::value_field_prec(ui, &mut v, range, units, 70.0, decimals).accessible_name(tl!(&label));
         if (v - before).abs() > 1e-4 && v.is_finite() {
             *pt = f64::from(v) * k;
@@ -208,8 +205,8 @@ fn print_paper_controls(ui: &mut egui::Ui, f: &mut Map<String, Value>) {
         let units = s(f, "units", "cm");
         let changed = ui
             .horizontal(|ui| {
-                let wc = length_field_prec(ui, tl!("Paper Width"), &mut w, &units, 2);
-                let hc = length_field_prec(ui, tl!("Paper Height"), &mut h, &units, 2);
+                let wc = length_field(ui, tl!("Paper Width"), &mut w, &units);
+                let hc = length_field(ui, tl!("Paper Height"), &mut h, &units);
                 wc || hc
             })
             .inner;
@@ -535,7 +532,7 @@ pub(super) fn print_body(app: &mut PhotocraftApp, ui: &mut egui::Ui, f: &mut Map
                         let mut pt =
                             centered_layout.map_or_else(|| n(f, key, 0.0) * 72.0, |l| if key == "top" { l.paper.1 - l.rect.1 - l.rect.3 } else { l.rect.0 });
                         let limit = (photocraft_engine::print_cmds::MAX_POSITION_INCHES * 72.0 / unit_pt(&units)) as f32;
-                        if length_field_range(ui, label, &mut pt, &units, 2, -limit..=limit) {
+                        if length_field_range(ui, label, &mut pt, &units, -limit..=limit) {
                             let limit = photocraft_engine::print_cmds::MAX_POSITION_INCHES;
                             f.insert(key.into(), json!((pt / 72.0).clamp(-limit, limit)));
                         }
@@ -549,12 +546,14 @@ pub(super) fn print_body(app: &mut PhotocraftApp, ui: &mut egui::Ui, f: &mut Map
                 let (mut w2, mut h2) = (w, h);
                 let fit = b(f, "scaleToFit", false);
                 if !fit {
-                    // Two decimals: Height and Width set fractional scales.
+                    // Written only when edited: the exact scale Height or Width set must survive
+                    // the field's rounded display.
                     ui.horizontal(|ui| {
                         ui.label(egui::RichText::new(tl!("Scale")).color(t.text_dim));
                         let mut v = n(f, "scale", 100.0) as f32;
-                        crate::widgets::value_field(ui, &mut v, 1.0..=1000.0, "%", 64.0).accessible_name(tl!("Scale"));
-                        f.insert("scale".into(), json!((f64::from(v) * 100.0).round() / 100.0));
+                        if crate::widgets::value_field(ui, &mut v, 1.0..=1000.0, "%", 64.0).accessible_name(tl!("Scale")).changed() {
+                            f.insert("scale".into(), json!((f64::from(v) * 100.0).round() / 100.0));
+                        }
                     });
                 }
                 ui.horizontal(|ui| {
@@ -568,7 +567,8 @@ pub(super) fn print_body(app: &mut PhotocraftApp, ui: &mut egui::Ui, f: &mut Map
                         None
                     };
                     if let Some(k) = k.filter(|k| k.is_finite() && *k > 0.0) {
-                        f.insert("scale".into(), json!(((l.scale * k * 100.0) * 100.0).round() / 100.0));
+                        // Unrounded, so the typed size prints exactly.
+                        f.insert("scale".into(), json!(l.scale * k * 100.0));
                         f.insert("scaleToFit".into(), json!(false));
                     }
                 });
diff --git a/crates/ui-egui/src/dialogs/file_ui/tests.rs b/crates/ui-egui/src/dialogs/file_ui/tests.rs
--- a/crates/ui-egui/src/dialogs/file_ui/tests.rs
+++ b/crates/ui-egui/src/dialogs/file_ui/tests.rs
@@ -504,6 +504,40 @@ fn print_dialog_does_not_inherit_scripted_send_override() {
     }
 }
 
+/// #9704: Width and Height take a fractional size, such as a 2.5 in card, and keep it as typed.
+#[test]
+fn print_size_keeps_a_typed_fractional_width() {
+    use egui::accesskit::Role;
+    use egui_kittest::{Harness, kittest::Queryable};
+    let mut app = PhotocraftApp::new(photocraft_engine::Session::new(), crate::Services::default());
+    // 30 × 15 in: wide enough that a scale rounded to 0.01 % would move 2.5 in to 2.499 in.
+    app.run("file.new", json!({"width": 3000, "height": 1500, "resolution": 100})).unwrap();
+    let mut h = Harness::builder().with_size(egui::vec2(1400.0, 900.0)).build_ui_state(|ui, app| crate::dialogs::show(app, ui.ctx()), app);
+    PhotocraftApp::setup_context(&h.ctx, crate::theme::ThemeKind::ALL[0]);
+    // Opened once the fonts are set up: the harness draws a frame as it is built.
+    let id = open_print(h.state_mut());
+    let f = &mut h.state_mut().ui.dialog_mut(id).unwrap().fields;
+    f.insert("units".into(), json!("in"));
+    f.insert("dryRun".into(), json!(true));
+    h.run_steps(3);
+    h.get_by_role_and_label(Role::SpinButton, "Width").click();
+    h.run_steps(1);
+    for c in "2.5".chars() {
+        h.event(egui::Event::Text(c.to_string()));
+        h.run_steps(1);
+    }
+    // Tab leaves the field (Enter would confirm the dialog).
+    h.key_press(egui::Key::Tab);
+    h.run_steps(3);
+    assert_eq!(h.get_by_role_and_label(Role::SpinButton, "Width").value().as_deref(), Some("2.5"));
+    let app = h.state_mut();
+    let doc = app.session.active().unwrap().doc.clone();
+    let f = app.ui.dialog_mut(id).unwrap().fields.clone();
+    let l = photocraft_engine::print_cmds::layout(&doc, &print_params(&f), "file.print").unwrap();
+    assert!((l.rect.2 - 2.5 * 72.0).abs() < 1e-6, "printed width {} pt", l.rect.2);
+    assert!((l.rect.3 - 1.25 * 72.0).abs() < 1e-6, "printed height {} pt", l.rect.3);
+}
+
 /// The Orientation buttons show the driver paper's shape: landscape when it is wider than tall.
 #[test]
 fn orientation_follows_the_driver_paper() {
