@rowsncolumns/toolkit 24.5.1 → 24.5.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -2,15 +2,17 @@ import type { GridRange } from "@rowsncolumns/common-types";
2
2
  import type { Column, FilterSpec, SortSpecs, TableTotalsRowFunction } from "@rowsncolumns/spreadsheet";
3
3
  export declare const getRangeFromAddressRef: (ref: string) => GridRange | undefined;
4
4
  /**
5
- * Convert an xlsx column width (in units of max-digit-width of the
6
- * default workbook font, Calibri 11 → MDW=7px) to pixels.
5
+ * OOXML `<col width>` (and `<sheetFormatPr defaultColWidth>`) → pixels.
7
6
  *
8
- * Excel's effective rendered pixel count includes a 5-pixel padding
9
- * baked into the column box: `pixels = floor(width × MDW + padding)`.
10
- * The default width "8.43" renders as 64 px (8.43 × 7 + 5 ≈ 64.01),
11
- * matching Excel exactly. The "pure" ECMA-376 §18.3.1.13 formula omits
12
- * the 5px padding; we keep the +5 form so our pixel output matches
13
- * Excel's rendered widths and round-trips with our exporter.
7
+ * The stored width is already in *padded* character units: Excel writes
8
+ * `Truncate((chars × MDW + 5) / MDW × 256) / 256` — the default 8.43-character
9
+ * column is stored as 9.140625 — and renders it back as
10
+ * `Truncate(((256 × width + Truncate(128 / MDW)) / 256) × MDW)`
11
+ * (ECMA-376 §18.3.1.13), i.e. 9.140625 → 64 px. Adding the 5 px padding a
12
+ * second time (the previous `floor(width × 7 + 5)`) drew every Excel-authored
13
+ * column ~5 px wider than Excel does and grew widths by 0.71 characters per
14
+ * import/export cycle (the exporter stores `px / 7`). MDW is 7 px for the
15
+ * Calibri 11 / Aptos Narrow 12 defaults at 96 dpi.
14
16
  */
15
17
  export declare function excelColumnWidthToPixels(width: number): number;
16
18
  export declare function findMax(arr: number[]): number;
@@ -1 +1 @@
1
- {"version":3,"file":"helpers.d.ts","sourceRoot":"","sources":["../../../excel-parser/helpers.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,4BAA4B,CAAC;AAC5D,OAAO,KAAK,EAEV,MAAM,EACN,UAAU,EACV,SAAS,EACT,sBAAsB,EACvB,MAAM,2BAA2B,CAAC;AAUnC,eAAO,MAAM,sBAAsB,GAAI,KAAK,MAAM,KAAG,SAAS,GAAG,SAIhE,CAAC;AAEF;;;;;;;;;;GAUG;AACH,wBAAgB,wBAAwB,CAAC,KAAK,EAAE,MAAM,UAIrD;AAED,wBAAgB,OAAO,CAAC,GAAG,EAAE,MAAM,EAAE,UAMpC;AAmBD,eAAO,MAAM,wBAAwB,GACnC,OAAO,OAAO,KACb,KAAK,IAAI,sBACwD,CAAC;AAErE;;;;;GAKG;AACH,eAAO,MAAM,sBAAsB,GACjC,QAAQ,GAAG,KACV,IAAI,CAAC,MAAM,EAAE,mBAAmB,GAAG,gBAAgB,GAAG,kBAAkB,CAmB1E,CAAC;AAqBF,wBAAgB,+BAA+B,CAC7C,UAAU,EAAE,GAAG;AACf;;;;GAIG;AACH,eAAe,CAAC,EAAE,CAAC,KAAK,EAAE,MAAM,EAAE,SAAS,EAAE,OAAO,KAAK,MAAM,GAAG,SAAS,gBA0G5E;AAED;;;;GAIG;AACH,wBAAgB,yBAAyB,CAAC,UAAU,EAAE,GAAG,GAAG,GAAG,CAAC,MAAM,CAAC,CAgBtE;AAED;;;;;;GAMG;AACH,wBAAgB,4BAA4B,CAC1C,SAAS,EAAE,GAAG,EACd,KAAK,EAAE,SAAS,EAChB,eAAe,CAAC,EAAE,CAAC,KAAK,EAAE,MAAM,EAAE,SAAS,EAAE,OAAO,KAAK,MAAM,GAAG,SAAS,GAC1E,SAAS,EAAE,CA2Cb;AAED;;GAEG;AACH,wBAAsB,aAAa,CAAC,IAAI,EAAE,IAAI,GAAG,WAAW,wBAkB3D"}
1
+ {"version":3,"file":"helpers.d.ts","sourceRoot":"","sources":["../../../excel-parser/helpers.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,4BAA4B,CAAC;AAC5D,OAAO,KAAK,EAEV,MAAM,EACN,UAAU,EACV,SAAS,EACT,sBAAsB,EACvB,MAAM,2BAA2B,CAAC;AAUnC,eAAO,MAAM,sBAAsB,GAAI,KAAK,MAAM,KAAG,SAAS,GAAG,SAIhE,CAAC;AAEF;;;;;;;;;;;;GAYG;AACH,wBAAgB,wBAAwB,CAAC,KAAK,EAAE,MAAM,UAMrD;AAED,wBAAgB,OAAO,CAAC,GAAG,EAAE,MAAM,EAAE,UAMpC;AAmBD,eAAO,MAAM,wBAAwB,GACnC,OAAO,OAAO,KACb,KAAK,IAAI,sBACwD,CAAC;AAErE;;;;;GAKG;AACH,eAAO,MAAM,sBAAsB,GACjC,QAAQ,GAAG,KACV,IAAI,CAAC,MAAM,EAAE,mBAAmB,GAAG,gBAAgB,GAAG,kBAAkB,CAmB1E,CAAC;AAqBF,wBAAgB,+BAA+B,CAC7C,UAAU,EAAE,GAAG;AACf;;;;GAIG;AACH,eAAe,CAAC,EAAE,CAAC,KAAK,EAAE,MAAM,EAAE,SAAS,EAAE,OAAO,KAAK,MAAM,GAAG,SAAS,gBA0G5E;AAED;;;;GAIG;AACH,wBAAgB,yBAAyB,CAAC,UAAU,EAAE,GAAG,GAAG,GAAG,CAAC,MAAM,CAAC,CAgBtE;AAED;;;;;;GAMG;AACH,wBAAgB,4BAA4B,CAC1C,SAAS,EAAE,GAAG,EACd,KAAK,EAAE,SAAS,EAChB,eAAe,CAAC,EAAE,CAAC,KAAK,EAAE,MAAM,EAAE,SAAS,EAAE,OAAO,KAAK,MAAM,GAAG,SAAS,GAC1E,SAAS,EAAE,CA2Cb;AAED;;GAEG;AACH,wBAAsB,aAAa,CAAC,IAAI,EAAE,IAAI,GAAG,WAAW,wBAkB3D"}
@@ -8,20 +8,23 @@ export var getRangeFromAddressRef = function (ref) {
8
8
  return selection.range;
9
9
  };
10
10
  /**
11
- * Convert an xlsx column width (in units of max-digit-width of the
12
- * default workbook font, Calibri 11 → MDW=7px) to pixels.
11
+ * OOXML `<col width>` (and `<sheetFormatPr defaultColWidth>`) → pixels.
13
12
  *
14
- * Excel's effective rendered pixel count includes a 5-pixel padding
15
- * baked into the column box: `pixels = floor(width × MDW + padding)`.
16
- * The default width "8.43" renders as 64 px (8.43 × 7 + 5 ≈ 64.01),
17
- * matching Excel exactly. The "pure" ECMA-376 §18.3.1.13 formula omits
18
- * the 5px padding; we keep the +5 form so our pixel output matches
19
- * Excel's rendered widths and round-trips with our exporter.
13
+ * The stored width is already in *padded* character units: Excel writes
14
+ * `Truncate((chars × MDW + 5) / MDW × 256) / 256` — the default 8.43-character
15
+ * column is stored as 9.140625 — and renders it back as
16
+ * `Truncate(((256 × width + Truncate(128 / MDW)) / 256) × MDW)`
17
+ * (ECMA-376 §18.3.1.13), i.e. 9.140625 → 64 px. Adding the 5 px padding a
18
+ * second time (the previous `floor(width × 7 + 5)`) drew every Excel-authored
19
+ * column ~5 px wider than Excel does and grew widths by 0.71 characters per
20
+ * import/export cycle (the exporter stores `px / 7`). MDW is 7 px for the
21
+ * Calibri 11 / Aptos Narrow 12 defaults at 96 dpi.
20
22
  */
21
23
  export function excelColumnWidthToPixels(width) {
22
- var padding = 5;
23
- var charWidth = 7;
24
- return Math.floor(width * charWidth + padding);
24
+ var maxDigitWidth = 7;
25
+ if (!Number.isFinite(width) || width <= 0)
26
+ return 0;
27
+ return Math.trunc(((256 * width + Math.trunc(128 / maxDigitWidth)) / 256) * maxDigitWidth);
25
28
  }
26
29
  export function findMax(arr) {
27
30
  var max = arr[0];
@@ -1307,7 +1307,7 @@ var buildWorksheetXml = function (_a) {
1307
1307
  var sheetFormatPrAttrs = ["defaultRowHeight=\"".concat(defaultRowHeightPt, "\"")];
1308
1308
  if (typeof explicitDefaultColumnWidthPx === "number" &&
1309
1309
  explicitDefaultColumnWidthPx > 0) {
1310
- sheetFormatPrAttrs.push("defaultColWidth=\"".concat(columnPixelToChars(explicitDefaultColumnWidthPx).toFixed(2), "\""));
1310
+ sheetFormatPrAttrs.push("defaultColWidth=\"".concat(columnPixelToChars(explicitDefaultColumnWidthPx), "\""));
1311
1311
  }
1312
1312
  var worksheetXml = "".concat(XML_DECLARATION, "<worksheet xmlns=\"").concat(WORKSHEET_NS, "\" xmlns:r=\"").concat(REL_NS, "\">").concat(sheetPrXml, "<dimension ref=\"").concat(dimension, "\"/>").concat(sheetViews, "<sheetFormatPr ").concat(sheetFormatPrAttrs.join(" "), "/>").concat(columnXml).concat(sheetDataXml).concat(protectionXml).concat(basicFilterXml).concat(mergeXml).concat(conditionalFormattingXml).concat(dataValidationsXml).concat(hyperlinksXml).concat(printSettingsXml).concat(drawingXml).concat(legacyDrawingXml).concat(controlsXml).concat(tablePartsXml).concat(extLstXml, "</worksheet>");
1313
1313
  return {
@@ -2514,8 +2514,11 @@ var buildColumnsXml = function (sheet, bounds) {
2514
2514
  continue;
2515
2515
  var attributes = ["min=\"".concat(columnIndex, "\""), "max=\"".concat(columnIndex, "\"")];
2516
2516
  if (hasExplicitSize) {
2517
+ // Full precision on purpose: the stored value is a multiple of 1/256
2518
+ // (Excel writes e.g. 9.140625), and rounding to two decimals would
2519
+ // break the exact export → import pixel fixed point.
2517
2520
  var width = columnPixelToChars(getColumnWidthPx(sheet, columnIndex - 1));
2518
- attributes.push("width=\"".concat(width.toFixed(2), "\""));
2521
+ attributes.push("width=\"".concat(width, "\""));
2519
2522
  attributes.push("customWidth=\"1\"");
2520
2523
  }
2521
2524
  if (hasHiddenFlag) {
@@ -8,7 +8,9 @@ export declare const createDownloadableFile: (filename: string, urlOrBlob: Blob
8
8
  export declare const toArrayBuffer: (data: ArrayBuffer | Uint8Array) => ArrayBuffer;
9
9
  export declare const charsToPixel: (chars: number) => number;
10
10
  export declare const pixelToCharacters: (px: number) => number;
11
+ /** Stored OOXML column width (padded character units) → rendered pixels. */
11
12
  export declare const columnCharsToPixel: (chars: number) => number;
13
+ /** Rendered pixels → stored OOXML column width (padded character units). */
12
14
  export declare const columnPixelToChars: (pixel: number) => number;
13
15
  /**
14
16
  * Get range from address reference with minimal processing
@@ -1 +1 @@
1
- {"version":3,"file":"helpers.d.ts","sourceRoot":"","sources":["../../helpers.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,aAAa,EAAE,SAAS,EAAE,MAAM,4BAA4B,CAAC;AA+B3E,eAAO,MAAM,0BAA0B,GACrC,SAAS,MAAM,GAAG,SAAS,EAC3B,YAAY,aAAa,EACzB,iBAAiB,aAAa,EAC9B,UAAU;IACR,cAAc,CAAC,EAAE,SAAS,CAAC;IAC3B,cAAc,CAAC,EAAE,OAAO,CAAC;CAC1B,KACA,MAAM,GAAG,SA8BX,CAAC;AAEF,eAAO,MAAM,gBAAgB,GAC3B,UAAU,MAAM,KACf,OAAO,CAAC,MAAM,GAAG,SAAS,CAiB5B,CAAC;AAEF,eAAO,MAAM,sBAAsB,GACjC,UAAU,MAAM,EAChB,WAAW,IAAI,GAAG,MAAM,8BAazB,CAAC;AAEF,eAAO,MAAM,aAAa,GAAI,MAAM,WAAW,GAAG,UAAU,KAAG,WAO9D,CAAC;AAIF,eAAO,MAAM,YAAY,GAAI,OAAO,MAAM,WAAiC,CAAC;AAC5E,eAAO,MAAM,iBAAiB,GAAI,IAAI,MAAM,WAA+B,CAAC;AAoB5E,eAAO,MAAM,kBAAkB,GAAI,OAAO,MAAM,WACrB,CAAC;AAE5B,eAAO,MAAM,kBAAkB,GAAI,OAAO,MAAM,WAClB,CAAC;AAE/B;;GAEG;AACH,wBAAgB,sBAAsB,CAAC,UAAU,EAAE,MAAM,GAAG,SAAS;;;;;cAwBpE;AAED;;;;GAIG;AACH,wBAAgB,WAAW,CAAC,IAAI,EAAE,IAAI,GAAG,OAAO,CAsB/C;AAED;;;;GAIG;AACH,wBAAgB,SAAS,CAAC,IAAI,EAAE,IAAI,GAAG,OAAO,CAe7C"}
1
+ {"version":3,"file":"helpers.d.ts","sourceRoot":"","sources":["../../helpers.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,aAAa,EAAE,SAAS,EAAE,MAAM,4BAA4B,CAAC;AAiC3E,eAAO,MAAM,0BAA0B,GACrC,SAAS,MAAM,GAAG,SAAS,EAC3B,YAAY,aAAa,EACzB,iBAAiB,aAAa,EAC9B,UAAU;IACR,cAAc,CAAC,EAAE,SAAS,CAAC;IAC3B,cAAc,CAAC,EAAE,OAAO,CAAC;CAC1B,KACA,MAAM,GAAG,SA8BX,CAAC;AAEF,eAAO,MAAM,gBAAgB,GAC3B,UAAU,MAAM,KACf,OAAO,CAAC,MAAM,GAAG,SAAS,CAiB5B,CAAC;AAEF,eAAO,MAAM,sBAAsB,GACjC,UAAU,MAAM,EAChB,WAAW,IAAI,GAAG,MAAM,8BAazB,CAAC;AAEF,eAAO,MAAM,aAAa,GAAI,MAAM,WAAW,GAAG,UAAU,KAAG,WAO9D,CAAC;AAIF,eAAO,MAAM,YAAY,GAAI,OAAO,MAAM,WAAiC,CAAC;AAC5E,eAAO,MAAM,iBAAiB,GAAI,IAAI,MAAM,WAA+B,CAAC;AAyB5E,4EAA4E;AAC5E,eAAO,MAAM,kBAAkB,GAAI,OAAO,MAAM,WACf,CAAC;AAElC,4EAA4E;AAC5E,eAAO,MAAM,kBAAkB,GAAI,OAAO,MAAM,WAG/C,CAAC;AAEF;;GAEG;AACH,wBAAgB,sBAAsB,CAAC,UAAU,EAAE,MAAM,GAAG,SAAS;;;;;cAwBpE;AAED;;;;GAIG;AACH,wBAAgB,WAAW,CAAC,IAAI,EAAE,IAAI,GAAG,OAAO,CAsB/C;AAED;;;;GAIG;AACH,wBAAgB,SAAS,CAAC,IAAI,EAAE,IAAI,GAAG,OAAO,CAe7C"}
@@ -2,6 +2,7 @@ import { __awaiter, __generator, __read } from "tslib";
2
2
  import { addressToCell, canUseDOM } from "@rowsncolumns/utils";
3
3
  // ---- Formula helpers ----
4
4
  import { alpha2number, number2Alpha } from "@rowsncolumns/utils";
5
+ import { excelColumnWidthToPixels } from "./excel-parser/helpers";
5
6
  var CELL_ADDR_RE = /(?:'[^']+'!?|[^'!\s]+!?)?(\$?[A-Z]+\$?\d+)(?::(\$?[A-Z]+\$?\d+))?/g;
6
7
  var parseAddr = function (addr) {
7
8
  var m = /^(\$?)([A-Z]+)(\$?)(\d+)$/.exec(addr);
@@ -98,22 +99,30 @@ export var pixelToCharacters = function (px) { return Math.floor((72 / 96) * px)
98
99
  // export const pixelToDimension = (chWidth: number) =>
99
100
  // Math.ceil((33.25 / 265) * chWidth);
100
101
  // // const dimensionToPixel = (chWidth: number) => (72 / 30) * chWidth;
101
- // Excel column-width / pixel conversion. Excel's rendered column box
102
- // includes a 5-pixel padding around the MDW (max-digit-width, 7 px for
103
- // Calibri 11), so the default width 8.43 renders at 64 px (8.43 × 7 + 5).
104
- // We keep the +5 padding form so a round-trip pixels → width → pixels is
105
- // an identity for the values Excel itself produces.
102
+ // Excel column-width / pixel conversion (ECMA-376 §18.3.1.13).
106
103
  //
107
- // pixels = floor(widthChars × 7 + 5)
108
- // widthChars = (pixels − 5) / 7
109
- //
110
- // The "pure" ECMA-376 §18.3.1.13 formula omits the 5px padding and gives
111
- // 59 for default-width columns instead of Excel's actual 64.
104
+ // OOXML `<col width>` / `<sheetFormatPr defaultColWidth>` are stored in
105
+ // *padded* character units — Excel writes `px / MDW` (the default 64 px
106
+ // column is stored as 9.140625, not 8.43) and renders it back as
107
+ // `Truncate(((256 × width + Truncate(128 / MDW)) / 256) × MDW)`, which the
108
+ // importer implements in `excelColumnWidthToPixels`. The exporter must be
109
+ // that function's inverse so export → import is a fixed point: storing
110
+ // `trunc(px × 256 / MDW) / 256` (Excel's own 1/256-character quantisation)
111
+ // makes `excelColumnWidthToPixels(columnPixelToChars(px)) === px` for every
112
+ // integer pixel width. Subtracting the 5 px cell padding here (the previous
113
+ // `(px − 5) / 7`) shrank every column by 5 px per export/import cycle once
114
+ // the importer stopped adding it. MDW is 7 px for the Calibri 11 /
115
+ // Aptos Narrow 12 defaults at 96 dpi.
116
+ var EXCEL_MAX_DIGIT_WIDTH_PX = 7;
117
+ /** Stored OOXML column width (padded character units) → rendered pixels. */
112
118
  export var columnCharsToPixel = function (chars) {
113
- return Math.floor(chars * 7 + 5);
119
+ return excelColumnWidthToPixels(chars);
114
120
  };
121
+ /** Rendered pixels → stored OOXML column width (padded character units). */
115
122
  export var columnPixelToChars = function (pixel) {
116
- return Math.max(0, (pixel - 5) / 7);
123
+ if (!Number.isFinite(pixel) || pixel <= 0)
124
+ return 0;
125
+ return Math.trunc((pixel * 256) / EXCEL_MAX_DIGIT_WIDTH_PX) / 256;
117
126
  };
118
127
  /**
119
128
  * Get range from address reference with minimal processing
@@ -2,15 +2,17 @@ import type { GridRange } from "@rowsncolumns/common-types";
2
2
  import type { Column, FilterSpec, SortSpecs, TableTotalsRowFunction } from "@rowsncolumns/spreadsheet";
3
3
  export declare const getRangeFromAddressRef: (ref: string) => GridRange | undefined;
4
4
  /**
5
- * Convert an xlsx column width (in units of max-digit-width of the
6
- * default workbook font, Calibri 11 → MDW=7px) to pixels.
5
+ * OOXML `<col width>` (and `<sheetFormatPr defaultColWidth>`) → pixels.
7
6
  *
8
- * Excel's effective rendered pixel count includes a 5-pixel padding
9
- * baked into the column box: `pixels = floor(width × MDW + padding)`.
10
- * The default width "8.43" renders as 64 px (8.43 × 7 + 5 ≈ 64.01),
11
- * matching Excel exactly. The "pure" ECMA-376 §18.3.1.13 formula omits
12
- * the 5px padding; we keep the +5 form so our pixel output matches
13
- * Excel's rendered widths and round-trips with our exporter.
7
+ * The stored width is already in *padded* character units: Excel writes
8
+ * `Truncate((chars × MDW + 5) / MDW × 256) / 256` — the default 8.43-character
9
+ * column is stored as 9.140625 — and renders it back as
10
+ * `Truncate(((256 × width + Truncate(128 / MDW)) / 256) × MDW)`
11
+ * (ECMA-376 §18.3.1.13), i.e. 9.140625 → 64 px. Adding the 5 px padding a
12
+ * second time (the previous `floor(width × 7 + 5)`) drew every Excel-authored
13
+ * column ~5 px wider than Excel does and grew widths by 0.71 characters per
14
+ * import/export cycle (the exporter stores `px / 7`). MDW is 7 px for the
15
+ * Calibri 11 / Aptos Narrow 12 defaults at 96 dpi.
14
16
  */
15
17
  export declare function excelColumnWidthToPixels(width: number): number;
16
18
  export declare function findMax(arr: number[]): number;
@@ -1 +1 @@
1
- {"version":3,"file":"helpers.d.ts","sourceRoot":"","sources":["../../../excel-parser/helpers.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,4BAA4B,CAAC;AAC5D,OAAO,KAAK,EAEV,MAAM,EACN,UAAU,EACV,SAAS,EACT,sBAAsB,EACvB,MAAM,2BAA2B,CAAC;AAUnC,eAAO,MAAM,sBAAsB,GAAI,KAAK,MAAM,KAAG,SAAS,GAAG,SAIhE,CAAC;AAEF;;;;;;;;;;GAUG;AACH,wBAAgB,wBAAwB,CAAC,KAAK,EAAE,MAAM,UAIrD;AAED,wBAAgB,OAAO,CAAC,GAAG,EAAE,MAAM,EAAE,UAMpC;AAmBD,eAAO,MAAM,wBAAwB,GACnC,OAAO,OAAO,KACb,KAAK,IAAI,sBACwD,CAAC;AAErE;;;;;GAKG;AACH,eAAO,MAAM,sBAAsB,GACjC,QAAQ,GAAG,KACV,IAAI,CAAC,MAAM,EAAE,mBAAmB,GAAG,gBAAgB,GAAG,kBAAkB,CAmB1E,CAAC;AAqBF,wBAAgB,+BAA+B,CAC7C,UAAU,EAAE,GAAG;AACf;;;;GAIG;AACH,eAAe,CAAC,EAAE,CAAC,KAAK,EAAE,MAAM,EAAE,SAAS,EAAE,OAAO,KAAK,MAAM,GAAG,SAAS,gBA0G5E;AAED;;;;GAIG;AACH,wBAAgB,yBAAyB,CAAC,UAAU,EAAE,GAAG,GAAG,GAAG,CAAC,MAAM,CAAC,CAgBtE;AAED;;;;;;GAMG;AACH,wBAAgB,4BAA4B,CAC1C,SAAS,EAAE,GAAG,EACd,KAAK,EAAE,SAAS,EAChB,eAAe,CAAC,EAAE,CAAC,KAAK,EAAE,MAAM,EAAE,SAAS,EAAE,OAAO,KAAK,MAAM,GAAG,SAAS,GAC1E,SAAS,EAAE,CA2Cb;AAED;;GAEG;AACH,wBAAsB,aAAa,CAAC,IAAI,EAAE,IAAI,GAAG,WAAW,wBAkB3D"}
1
+ {"version":3,"file":"helpers.d.ts","sourceRoot":"","sources":["../../../excel-parser/helpers.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,4BAA4B,CAAC;AAC5D,OAAO,KAAK,EAEV,MAAM,EACN,UAAU,EACV,SAAS,EACT,sBAAsB,EACvB,MAAM,2BAA2B,CAAC;AAUnC,eAAO,MAAM,sBAAsB,GAAI,KAAK,MAAM,KAAG,SAAS,GAAG,SAIhE,CAAC;AAEF;;;;;;;;;;;;GAYG;AACH,wBAAgB,wBAAwB,CAAC,KAAK,EAAE,MAAM,UAMrD;AAED,wBAAgB,OAAO,CAAC,GAAG,EAAE,MAAM,EAAE,UAMpC;AAmBD,eAAO,MAAM,wBAAwB,GACnC,OAAO,OAAO,KACb,KAAK,IAAI,sBACwD,CAAC;AAErE;;;;;GAKG;AACH,eAAO,MAAM,sBAAsB,GACjC,QAAQ,GAAG,KACV,IAAI,CAAC,MAAM,EAAE,mBAAmB,GAAG,gBAAgB,GAAG,kBAAkB,CAmB1E,CAAC;AAqBF,wBAAgB,+BAA+B,CAC7C,UAAU,EAAE,GAAG;AACf;;;;GAIG;AACH,eAAe,CAAC,EAAE,CAAC,KAAK,EAAE,MAAM,EAAE,SAAS,EAAE,OAAO,KAAK,MAAM,GAAG,SAAS,gBA0G5E;AAED;;;;GAIG;AACH,wBAAgB,yBAAyB,CAAC,UAAU,EAAE,GAAG,GAAG,GAAG,CAAC,MAAM,CAAC,CAgBtE;AAED;;;;;;GAMG;AACH,wBAAgB,4BAA4B,CAC1C,SAAS,EAAE,GAAG,EACd,KAAK,EAAE,SAAS,EAChB,eAAe,CAAC,EAAE,CAAC,KAAK,EAAE,MAAM,EAAE,SAAS,EAAE,OAAO,KAAK,MAAM,GAAG,SAAS,GAC1E,SAAS,EAAE,CA2Cb;AAED;;GAEG;AACH,wBAAsB,aAAa,CAAC,IAAI,EAAE,IAAI,GAAG,WAAW,wBAkB3D"}
@@ -7,20 +7,23 @@ export const getRangeFromAddressRef = (ref) => {
7
7
  return selection.range;
8
8
  };
9
9
  /**
10
- * Convert an xlsx column width (in units of max-digit-width of the
11
- * default workbook font, Calibri 11 → MDW=7px) to pixels.
10
+ * OOXML `<col width>` (and `<sheetFormatPr defaultColWidth>`) → pixels.
12
11
  *
13
- * Excel's effective rendered pixel count includes a 5-pixel padding
14
- * baked into the column box: `pixels = floor(width × MDW + padding)`.
15
- * The default width "8.43" renders as 64 px (8.43 × 7 + 5 ≈ 64.01),
16
- * matching Excel exactly. The "pure" ECMA-376 §18.3.1.13 formula omits
17
- * the 5px padding; we keep the +5 form so our pixel output matches
18
- * Excel's rendered widths and round-trips with our exporter.
12
+ * The stored width is already in *padded* character units: Excel writes
13
+ * `Truncate((chars × MDW + 5) / MDW × 256) / 256` — the default 8.43-character
14
+ * column is stored as 9.140625 — and renders it back as
15
+ * `Truncate(((256 × width + Truncate(128 / MDW)) / 256) × MDW)`
16
+ * (ECMA-376 §18.3.1.13), i.e. 9.140625 → 64 px. Adding the 5 px padding a
17
+ * second time (the previous `floor(width × 7 + 5)`) drew every Excel-authored
18
+ * column ~5 px wider than Excel does and grew widths by 0.71 characters per
19
+ * import/export cycle (the exporter stores `px / 7`). MDW is 7 px for the
20
+ * Calibri 11 / Aptos Narrow 12 defaults at 96 dpi.
19
21
  */
20
22
  export function excelColumnWidthToPixels(width) {
21
- const padding = 5;
22
- const charWidth = 7;
23
- return Math.floor(width * charWidth + padding);
23
+ const maxDigitWidth = 7;
24
+ if (!Number.isFinite(width) || width <= 0)
25
+ return 0;
26
+ return Math.trunc(((256 * width + Math.trunc(128 / maxDigitWidth)) / 256) * maxDigitWidth);
24
27
  }
25
28
  export function findMax(arr) {
26
29
  let max = arr[0];
@@ -967,7 +967,7 @@ const buildWorksheetXml = ({ sheet, rows, sharedStrings, sourceSharedStrings, ce
967
967
  const sheetFormatPrAttrs = [`defaultRowHeight="${defaultRowHeightPt}"`];
968
968
  if (typeof explicitDefaultColumnWidthPx === "number" &&
969
969
  explicitDefaultColumnWidthPx > 0) {
970
- sheetFormatPrAttrs.push(`defaultColWidth="${columnPixelToChars(explicitDefaultColumnWidthPx).toFixed(2)}"`);
970
+ sheetFormatPrAttrs.push(`defaultColWidth="${columnPixelToChars(explicitDefaultColumnWidthPx)}"`);
971
971
  }
972
972
  const worksheetXml = `${XML_DECLARATION}<worksheet xmlns="${WORKSHEET_NS}" xmlns:r="${REL_NS}">${sheetPrXml}<dimension ref="${dimension}"/>${sheetViews}<sheetFormatPr ${sheetFormatPrAttrs.join(" ")}/>${columnXml}${sheetDataXml}${protectionXml}${basicFilterXml}${mergeXml}${conditionalFormattingXml}${dataValidationsXml}${hyperlinksXml}${printSettingsXml}${drawingXml}${legacyDrawingXml}${controlsXml}${tablePartsXml}${extLstXml}</worksheet>`;
973
973
  return {
@@ -2208,8 +2208,11 @@ const buildColumnsXml = (sheet, bounds) => {
2208
2208
  continue;
2209
2209
  const attributes = [`min="${columnIndex}"`, `max="${columnIndex}"`];
2210
2210
  if (hasExplicitSize) {
2211
+ // Full precision on purpose: the stored value is a multiple of 1/256
2212
+ // (Excel writes e.g. 9.140625), and rounding to two decimals would
2213
+ // break the exact export → import pixel fixed point.
2211
2214
  const width = columnPixelToChars(getColumnWidthPx(sheet, columnIndex - 1));
2212
- attributes.push(`width="${width.toFixed(2)}"`);
2215
+ attributes.push(`width="${width}"`);
2213
2216
  attributes.push(`customWidth="1"`);
2214
2217
  }
2215
2218
  if (hasHiddenFlag) {
@@ -8,7 +8,9 @@ export declare const createDownloadableFile: (filename: string, urlOrBlob: Blob
8
8
  export declare const toArrayBuffer: (data: ArrayBuffer | Uint8Array) => ArrayBuffer;
9
9
  export declare const charsToPixel: (chars: number) => number;
10
10
  export declare const pixelToCharacters: (px: number) => number;
11
+ /** Stored OOXML column width (padded character units) → rendered pixels. */
11
12
  export declare const columnCharsToPixel: (chars: number) => number;
13
+ /** Rendered pixels → stored OOXML column width (padded character units). */
12
14
  export declare const columnPixelToChars: (pixel: number) => number;
13
15
  /**
14
16
  * Get range from address reference with minimal processing
@@ -1 +1 @@
1
- {"version":3,"file":"helpers.d.ts","sourceRoot":"","sources":["../../helpers.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,aAAa,EAAE,SAAS,EAAE,MAAM,4BAA4B,CAAC;AA+B3E,eAAO,MAAM,0BAA0B,GACrC,SAAS,MAAM,GAAG,SAAS,EAC3B,YAAY,aAAa,EACzB,iBAAiB,aAAa,EAC9B,UAAU;IACR,cAAc,CAAC,EAAE,SAAS,CAAC;IAC3B,cAAc,CAAC,EAAE,OAAO,CAAC;CAC1B,KACA,MAAM,GAAG,SA8BX,CAAC;AAEF,eAAO,MAAM,gBAAgB,GAC3B,UAAU,MAAM,KACf,OAAO,CAAC,MAAM,GAAG,SAAS,CAiB5B,CAAC;AAEF,eAAO,MAAM,sBAAsB,GACjC,UAAU,MAAM,EAChB,WAAW,IAAI,GAAG,MAAM,8BAazB,CAAC;AAEF,eAAO,MAAM,aAAa,GAAI,MAAM,WAAW,GAAG,UAAU,KAAG,WAO9D,CAAC;AAIF,eAAO,MAAM,YAAY,GAAI,OAAO,MAAM,WAAiC,CAAC;AAC5E,eAAO,MAAM,iBAAiB,GAAI,IAAI,MAAM,WAA+B,CAAC;AAoB5E,eAAO,MAAM,kBAAkB,GAAI,OAAO,MAAM,WACrB,CAAC;AAE5B,eAAO,MAAM,kBAAkB,GAAI,OAAO,MAAM,WAClB,CAAC;AAE/B;;GAEG;AACH,wBAAgB,sBAAsB,CAAC,UAAU,EAAE,MAAM,GAAG,SAAS;;;;;cAwBpE;AAED;;;;GAIG;AACH,wBAAgB,WAAW,CAAC,IAAI,EAAE,IAAI,GAAG,OAAO,CAsB/C;AAED;;;;GAIG;AACH,wBAAgB,SAAS,CAAC,IAAI,EAAE,IAAI,GAAG,OAAO,CAe7C"}
1
+ {"version":3,"file":"helpers.d.ts","sourceRoot":"","sources":["../../helpers.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,aAAa,EAAE,SAAS,EAAE,MAAM,4BAA4B,CAAC;AAiC3E,eAAO,MAAM,0BAA0B,GACrC,SAAS,MAAM,GAAG,SAAS,EAC3B,YAAY,aAAa,EACzB,iBAAiB,aAAa,EAC9B,UAAU;IACR,cAAc,CAAC,EAAE,SAAS,CAAC;IAC3B,cAAc,CAAC,EAAE,OAAO,CAAC;CAC1B,KACA,MAAM,GAAG,SA8BX,CAAC;AAEF,eAAO,MAAM,gBAAgB,GAC3B,UAAU,MAAM,KACf,OAAO,CAAC,MAAM,GAAG,SAAS,CAiB5B,CAAC;AAEF,eAAO,MAAM,sBAAsB,GACjC,UAAU,MAAM,EAChB,WAAW,IAAI,GAAG,MAAM,8BAazB,CAAC;AAEF,eAAO,MAAM,aAAa,GAAI,MAAM,WAAW,GAAG,UAAU,KAAG,WAO9D,CAAC;AAIF,eAAO,MAAM,YAAY,GAAI,OAAO,MAAM,WAAiC,CAAC;AAC5E,eAAO,MAAM,iBAAiB,GAAI,IAAI,MAAM,WAA+B,CAAC;AAyB5E,4EAA4E;AAC5E,eAAO,MAAM,kBAAkB,GAAI,OAAO,MAAM,WACf,CAAC;AAElC,4EAA4E;AAC5E,eAAO,MAAM,kBAAkB,GAAI,OAAO,MAAM,WAG/C,CAAC;AAEF;;GAEG;AACH,wBAAgB,sBAAsB,CAAC,UAAU,EAAE,MAAM,GAAG,SAAS;;;;;cAwBpE;AAED;;;;GAIG;AACH,wBAAgB,WAAW,CAAC,IAAI,EAAE,IAAI,GAAG,OAAO,CAsB/C;AAED;;;;GAIG;AACH,wBAAgB,SAAS,CAAC,IAAI,EAAE,IAAI,GAAG,OAAO,CAe7C"}
@@ -1,6 +1,7 @@
1
1
  import { addressToCell, canUseDOM } from "@rowsncolumns/utils";
2
2
  // ---- Formula helpers ----
3
3
  import { alpha2number, number2Alpha } from "@rowsncolumns/utils";
4
+ import { excelColumnWidthToPixels } from "./excel-parser/helpers";
4
5
  const CELL_ADDR_RE = /(?:'[^']+'!?|[^'!\s]+!?)?(\$?[A-Z]+\$?\d+)(?::(\$?[A-Z]+\$?\d+))?/g;
5
6
  const parseAddr = (addr) => {
6
7
  const m = /^(\$?)([A-Z]+)(\$?)(\d+)$/.exec(addr);
@@ -95,19 +96,29 @@ export const pixelToCharacters = (px) => Math.floor((72 / 96) * px);
95
96
  // export const pixelToDimension = (chWidth: number) =>
96
97
  // Math.ceil((33.25 / 265) * chWidth);
97
98
  // // const dimensionToPixel = (chWidth: number) => (72 / 30) * chWidth;
98
- // Excel column-width / pixel conversion. Excel's rendered column box
99
- // includes a 5-pixel padding around the MDW (max-digit-width, 7 px for
100
- // Calibri 11), so the default width 8.43 renders at 64 px (8.43 × 7 + 5).
101
- // We keep the +5 padding form so a round-trip pixels → width → pixels is
102
- // an identity for the values Excel itself produces.
99
+ // Excel column-width / pixel conversion (ECMA-376 §18.3.1.13).
103
100
  //
104
- // pixels = floor(widthChars × 7 + 5)
105
- // widthChars = (pixels − 5) / 7
106
- //
107
- // The "pure" ECMA-376 §18.3.1.13 formula omits the 5px padding and gives
108
- // 59 for default-width columns instead of Excel's actual 64.
109
- export const columnCharsToPixel = (chars) => Math.floor(chars * 7 + 5);
110
- export const columnPixelToChars = (pixel) => Math.max(0, (pixel - 5) / 7);
101
+ // OOXML `<col width>` / `<sheetFormatPr defaultColWidth>` are stored in
102
+ // *padded* character units — Excel writes `px / MDW` (the default 64 px
103
+ // column is stored as 9.140625, not 8.43) and renders it back as
104
+ // `Truncate(((256 × width + Truncate(128 / MDW)) / 256) × MDW)`, which the
105
+ // importer implements in `excelColumnWidthToPixels`. The exporter must be
106
+ // that function's inverse so export → import is a fixed point: storing
107
+ // `trunc(px × 256 / MDW) / 256` (Excel's own 1/256-character quantisation)
108
+ // makes `excelColumnWidthToPixels(columnPixelToChars(px)) === px` for every
109
+ // integer pixel width. Subtracting the 5 px cell padding here (the previous
110
+ // `(px − 5) / 7`) shrank every column by 5 px per export/import cycle once
111
+ // the importer stopped adding it. MDW is 7 px for the Calibri 11 /
112
+ // Aptos Narrow 12 defaults at 96 dpi.
113
+ const EXCEL_MAX_DIGIT_WIDTH_PX = 7;
114
+ /** Stored OOXML column width (padded character units) → rendered pixels. */
115
+ export const columnCharsToPixel = (chars) => excelColumnWidthToPixels(chars);
116
+ /** Rendered pixels → stored OOXML column width (padded character units). */
117
+ export const columnPixelToChars = (pixel) => {
118
+ if (!Number.isFinite(pixel) || pixel <= 0)
119
+ return 0;
120
+ return Math.trunc((pixel * 256) / EXCEL_MAX_DIGIT_WIDTH_PX) / 256;
121
+ };
111
122
  /**
112
123
  * Get range from address reference with minimal processing
113
124
  */
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@rowsncolumns/toolkit",
3
- "version": "24.5.1",
3
+ "version": "24.5.4",
4
4
  "author": "Vinay M",
5
5
  "license": "SEE LICENSE IN LICENSE.md",
6
6
  "main": "./dist/cjs/index.js",
@@ -45,7 +45,7 @@
45
45
  "url": "https://github.com/rowsncolumns/spreadsheet/issues"
46
46
  },
47
47
  "devDependencies": {
48
- "@rowsncolumns/utils": ">=9.5.0",
48
+ "@rowsncolumns/utils": ">=9.5.1",
49
49
  "@types/lodash-es": "^4.17.12"
50
50
  },
51
51
  "peerDependencies": {
@@ -55,9 +55,9 @@
55
55
  "dependencies": {
56
56
  "@noble/ciphers": "^0.5.3",
57
57
  "@noble/hashes": "^1.4.0",
58
- "@rowsncolumns/calculator": ">=7.2.33",
59
- "@rowsncolumns/common-types": ">=5.3.0",
60
- "@rowsncolumns/date-parser": ">=13.0.1",
58
+ "@rowsncolumns/calculator": ">=7.3.1",
59
+ "@rowsncolumns/common-types": ">=5.3.1",
60
+ "@rowsncolumns/date-parser": ">=13.0.2",
61
61
  "@zip.js/zip.js": "^2.8.17",
62
62
  "fast-xml-parser": "^5.8.0",
63
63
  "fflate": "^0.8.2",