From 7b5b86469ed49efd3f88b559e91705fd6b19eafb Mon Sep 17 00:00:00 2001 From: Christopher Overbeck Date: Wed, 16 Sep 2026 19:20:47 -0500 Subject: [PATCH] fix(dimension): round printed values half away from zero, scale alternate units, stack alternate limits AutoCAD prints 0.4255 as 0.426; Rust's formatter rounds the binary value, which sits a hair below the decimal tie, and printed 0.425. Decimal dimension text, tolerances and limits now settle the tie the way the drawings do. Alternate units multiplied the raw measurement instead of the displayed DIMLFAC-scaled value, so a paper-space dimension's bracketed value was off by the viewport factor, and alternate limits were written high/low with a slash where AutoCAD stacks them like the primary limits. --- src/entities/dimension.rs | 57 +++++++++++++++++++++++++++++++++------ 1 file changed, 49 insertions(+), 8 deletions(-) diff --git a/src/entities/dimension.rs b/src/entities/dimension.rs index ad60e3434..a1eeeaa40 100644 --- a/src/entities/dimension.rs +++ b/src/entities/dimension.rs @@ -6195,7 +6195,7 @@ fn dimension_text_parts( // Alternate units appended in brackets when DIMALT is on (linear only). let primary = if !is_angular { - match alternate_units_text(dim.measurement(), style) { + match alternate_units_text(display_measurement(dim) * effective_dimlfac(style), style) { Some(alt) => format!("{} [{}]", primary, alt), None => primary, } @@ -6260,8 +6260,8 @@ fn limits_text(dim: &Dimension, style: Option<&DimStyle>, is_angular: bool) -> O /// writes `13.950` over `13.926` with DIMTZIN 8, not `13.95` over `13.926`. fn limit_pair(high: f64, low: f64, s: &DimStyle) -> (String, String) { let dimtdec = s.dimtdec.max(0) as usize; - let mut high = format!("{:.*}", dimtdec, high); - let mut low = format!("{:.*}", dimtdec, low); + let mut high = decimal_text(high, dimtdec); + let mut low = decimal_text(low, dimtdec); if s.dimtzin & 8 != 0 { let droppable = |value: &str| match value.find('.') { Some(_) => value.len() - value.trim_end_matches('0').len(), @@ -6290,7 +6290,7 @@ fn limit_pair(high: f64, low: f64, s: &DimStyle) -> (String, String) { fn tolerance_formatter(s: &DimStyle) -> impl Fn(f64) -> String + '_ { let dimtdec = s.dimtdec.max(0) as usize; move |v: f64| { - let raw = format!("{:.*}", dimtdec, v); + let raw = decimal_text(v, dimtdec); swap_decimal_sep(&apply_linear_zero_suppression(&raw, s.dimtzin), s.dimdsep) } } @@ -6387,7 +6387,7 @@ fn alternate_units_text(measurement: f64, style: Option<&DimStyle>) -> Option String { - let raw = format!("{:.*}", alttdec, x * tolerance_factor); + let raw = decimal_text(x * tolerance_factor, alttdec); swap_decimal_sep(&apply_linear_zero_suppression(&raw, alttzin), s.dimdsep) }; if (s.dimtp - s.dimtm).abs() < 1e-12 && s.dimtp.abs() > 1e-12 { @@ -6401,11 +6401,12 @@ fn alternate_units_text(measurement: f64, style: Option<&DimStyle>) -> Option String { - let raw = format!("{:.*}", alttdec, x * tolerance_factor); + let raw = decimal_text(x * tolerance_factor, alttdec); swap_decimal_sep(&apply_linear_zero_suppression(&raw, alttzin), s.dimdsep) }; + // Alternate limits stack like the primary ones. format!( - "{}/{}", + "\\S{}^{};", fmt(measurement + s.dimtp), fmt(measurement - s.dimtm) ) @@ -6639,10 +6640,20 @@ fn format_with_unit( 5 => format_fractional(value, dec, if alternate { 0 } else { dimfrac }), 6 if alternate => format_architectural(value, dec, 2, zin), 7 if alternate => format_fractional(value, dec, 2), - _ => format!("{:.*}", dec, value), + _ => decimal_text(value, dec), } } +/// Fixed-point text rounded half away from zero, as AutoCAD prints it. Rust's +/// formatter rounds the binary value, so 0.4255 (stored a hair below) came out +/// as 0.425 where the drawing shows 0.426; a nudge well under any dimension +/// tolerance settles the tie the same way. +fn decimal_text(value: f64, decimals: usize) -> String { + let nudged = value + value.signum() * 1e-9; + let scale = 10f64.powi(decimals.min(15) as i32); + format!("{:.*}", decimals, (nudged * scale).round() / scale) +} + fn format_engineering(inches: f64, dec: usize) -> String { // Round to the printed precision *first*, then split, so inches that round // up to a full foot carry into the feet instead of printing an @@ -8185,6 +8196,36 @@ mod limits_format_tests { assert!(value.starts_with(r"\S45"), "{value}"); } + // AutoCAD rounds a printed value half away from zero; Rust's formatter + // rounds the binary value, which sits a hair below the decimal tie. + #[test] + fn decimal_text_rounds_half_away_from_zero() { + assert_eq!(decimal_text(0.4255, 3), "0.426"); + assert_eq!(decimal_text(0.4085, 3), "0.409"); + assert_eq!(decimal_text(-0.4255, 3), "-0.426"); + assert_eq!(decimal_text(2.5, 0), "3"); + assert_eq!(decimal_text(0.1234, 3), "0.123"); + } + + // Alternate units multiply the displayed (DIMLFAC-scaled) value and stack + // their limits like the primary ones. + #[test] + fn alternate_limits_stack_from_the_scaled_value() { + let mut s = style(); + s.dimlim = true; + s.dimlfac = 2.0; + s.dimalt = true; + s.dimaltf = 25.4; + s.dimaltd = 2; + s.dimalttd = 2; + s.dimaltz = 0; + s.dimalttz = 0; + s.dimtp = 0.02; + s.dimtm = 0.05; + let (value, _) = dimension_text_parts(&horizontal(), Some(&s)).unwrap(); + assert_eq!(value, r"\S20.020^19.950; [\S508.51^506.73;]"); + } + #[test] fn a_limits_stack_is_two_halves_tall() { let mut s = style();