diff --git a/crates/calc/src/eval.rs b/crates/calc/src/eval.rs index 7ce2483..47a5119 100644 --- a/crates/calc/src/eval.rs +++ b/crates/calc/src/eval.rs @@ -1368,7 +1368,7 @@ impl<'h> Evaluator<'h> { _ => "v", })) } - "width" => Ev::V(Value::number(sheet.map_or(8.43, |s| (s.col_width(c.col) as f64 / 7.0 * 100.0).round() / 100.0).round())), + "width" => Ev::V(Value::number(sheet.map_or(8.43, |s| gridcraft_model::points_to_chars(s.col_width(c.col) as f64)).round())), "filename" => Ev::V(Value::text(String::new())), "sheetname" => Ev::V(Value::text(sheet.map(|s| s.name.clone()).unwrap_or_default())), "protect" => Ev::V(Value::number(if sheet.is_some_and(|s| wb.styles.get(s.style_id(c)).protection.locked) { 1.0 } else { 0.0 })), diff --git a/crates/calc/src/tests.rs b/crates/calc/src/tests.rs index ee4c4bc..8b29f63 100644 --- a/crates/calc/src/tests.rs +++ b/crates/calc/src/tests.rs @@ -1121,6 +1121,19 @@ fn trim_references() { assert_eq!(t.get("K20"), Value::Error(CellError::Calc)); } +#[test] +fn cell_width_is_the_column_width_dialog_value_rounded() { + // #735: Excel 365 values. Widths in px as `home.columnWidth {"chars"}` sets them + // (chars * 7 + 5): 10.71 -> 80, 12 -> 89, 10.57 -> 79, 2.86 -> 25; B keeps the default 8.43 (64). + let mut t = T::new(); + let sh = t.wb.sheet_mut(0).unwrap(); + for (col, px) in [(2, 80.0), (3, 89.0), (4, 79.0), (5, 25.0)] { + sh.cols.insert(col, gridcraft_model::LineInfo { size: Some(px), ..Default::default() }); + } + for (at, col) in [("H1", "B"), ("H2", "C"), ("H3", "D"), ("H4", "E"), ("H5", "F")] { + t.set(at, &format!("=CELL(\"width\",{col}1)")); + } + assert_eq!([t.num("H1"), t.num("H2"), t.num("H3"), t.num("H4"), t.num("H5")], [8.0, 11.0, 12.0, 11.0, 3.0]); /// Expected results are Excel's, from Apache POI's `DGet.xls` (rows 46-49): a criteria cell holding /// a formula under a label that names no field is evaluated for every record. #[test] diff --git a/crates/engine/src/cmd/format.rs b/crates/engine/src/cmd/format.rs index 61c9acb..c3c7fb4 100644 --- a/crates/engine/src/cmd/format.rs +++ b/crates/engine/src/cmd/format.rs @@ -1002,9 +1002,7 @@ pub fn chars_to_points(c: f64) -> f64 { } } -pub fn points_to_chars(px: f64) -> f64 { - if px <= 12.0 { (px / 12.0 * 100.0).round() / 100.0 } else { ((px - 5.0) / 7.0 * 100.0 + 0.5).trunc() / 100.0 } -} +pub use gridcraft_model::points_to_chars; /// Approximate text width in points for autofit (no font metrics in the engine: average glyph /// widths for the default font; the UI refines with real measurement when it autofits). diff --git a/crates/model/src/lib.rs b/crates/model/src/lib.rs index 1bea842..fa64aad 100644 --- a/crates/model/src/lib.rs +++ b/crates/model/src/lib.rs @@ -19,7 +19,7 @@ pub use cell::{Cell, CellPicture, Formula}; pub use features::*; pub use password::PasswordHash; pub use preserved::{Preserved, PreservedPart, PreservedRel, PreservedXml}; -pub use sheet::{DEFAULT_COL_WIDTH, DEFAULT_ROW_HEIGHT, LineIndex, Sheet, SheetViewMode, Visibility}; +pub use sheet::{DEFAULT_COL_WIDTH, DEFAULT_ROW_HEIGHT, LineIndex, Sheet, SheetViewMode, Visibility, points_to_chars}; pub use store::CellStore; pub use style::{ Alignment, BorderLine, BorderStyle, Borders, Color, Fill, Font, HAlign, NumFmt, PatternType, Style, StyleId, StyleTable, Theme, Underline, diff --git a/crates/model/src/sheet.rs b/crates/model/src/sheet.rs index e05d3bf..b404b89 100644 --- a/crates/model/src/sheet.rs +++ b/crates/model/src/sheet.rs @@ -117,6 +117,12 @@ pub struct Sheet { pub const DEFAULT_COL_WIDTH: f32 = 64.0; pub const DEFAULT_ROW_HEIGHT: f32 = 20.0; +/// Column width (points) → characters of the default font's digit width (7 px), without the +/// cell padding: the value the Column Width dialog shows. +pub fn points_to_chars(px: f64) -> f64 { + if px <= 12.0 { (px / 12.0 * 100.0).round() / 100.0 } else { ((px - 5.0) / 7.0 * 100.0 + 0.5).trunc() / 100.0 } +} + impl Sheet { pub fn new(name: impl Into) -> Sheet { Sheet {