documonster 0.14.0 → 0.15.0

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.
Files changed (35) hide show
  1. package/README.md +1 -1
  2. package/README_zh.md +1 -1
  3. package/dist/esm/modules/archive/compression/crc32.browser.js +6 -26
  4. package/dist/esm/modules/archive/compression/crc32.js +18 -80
  5. package/dist/esm/modules/archive/zip/stream.js +1 -8
  6. package/dist/esm/modules/excel/core/worksheet-core.js +65 -7
  7. package/dist/esm/modules/excel/core/worksheet.js +1 -1
  8. package/dist/esm/modules/excel/surface/column.js +88 -1
  9. package/dist/esm/modules/excel/utils/col-cache.js +6 -1
  10. package/dist/esm/modules/mermaid/layout/flowchart.js +527 -80
  11. package/dist/esm/modules/mermaid/parse/flowchart.js +55 -5
  12. package/dist/esm/modules/mermaid/theme.js +5 -4
  13. package/dist/esm/modules/pdf/render/layout-engine.js +138 -104
  14. package/dist/esm/modules/word/convert/markdown/markdown-import.js +218 -31
  15. package/dist/esm/modules/word/layout/layout-full.js +42 -2
  16. package/dist/iife/documonster.archive.iife.min.js +3 -3
  17. package/dist/iife/documonster.csv.iife.min.js +2 -2
  18. package/dist/iife/documonster.draw.iife.min.js +2 -2
  19. package/dist/iife/documonster.excel.iife.min.js +48 -48
  20. package/dist/iife/documonster.formula.iife.min.js +1 -1
  21. package/dist/iife/documonster.markdown.iife.min.js +1 -1
  22. package/dist/iife/documonster.mermaid.iife.min.js +3 -3
  23. package/dist/iife/documonster.pdf.iife.min.js +18 -18
  24. package/dist/iife/documonster.stream.iife.min.js +1 -1
  25. package/dist/iife/documonster.word.iife.min.js +12 -12
  26. package/dist/iife/documonster.xml.iife.min.js +1 -1
  27. package/dist/types/modules/archive/compression/crc32.browser.d.ts +6 -22
  28. package/dist/types/modules/archive/compression/crc32.d.ts +10 -18
  29. package/dist/types/modules/archive/zip/stream.d.ts +0 -1
  30. package/dist/types/modules/excel/core/worksheet-core.d.ts +42 -2
  31. package/dist/types/modules/excel/core/worksheet.d.ts +1 -1
  32. package/dist/types/modules/excel/surface/column.d.ts +83 -2
  33. package/dist/types/modules/mermaid/layout/flowchart.d.ts +12 -1
  34. package/dist/types/modules/word/layout/layout-model.d.ts +8 -0
  35. package/package.json +13 -13
package/README.md CHANGED
@@ -211,7 +211,7 @@ const buffer = await Workbook.toBuffer(wb);
211
211
 
212
212
  ```html
213
213
  <!-- Script tag (no bundler) — one IIFE per module, each under the shared `Documonster` global -->
214
- <script src="https://unpkg.com/documonster@0.14.0/dist/iife/documonster.excel.iife.min.js"></script>
214
+ <script src="https://unpkg.com/documonster@0.15.0/dist/iife/documonster.excel.iife.min.js"></script>
215
215
  <script>
216
216
  const { Workbook, Cell } = Documonster.Excel;
217
217
  const wb = Workbook.create();
package/README_zh.md CHANGED
@@ -198,7 +198,7 @@ const buffer = await Workbook.toBuffer(wb);
198
198
 
199
199
  ```html
200
200
  <!-- Script 标签(无需打包工具)— 每个模块一个 IIFE,共享同一个 `Documonster` 全局 -->
201
- <script src="https://unpkg.com/documonster@0.14.0/dist/iife/documonster.excel.iife.min.js"></script>
201
+ <script src="https://unpkg.com/documonster@0.15.0/dist/iife/documonster.excel.iife.min.js"></script>
202
202
  <script>
203
203
  const { Workbook, Cell } = Documonster.Excel;
204
204
  const wb = Workbook.create();
@@ -1,33 +1,12 @@
1
1
  /**
2
- * CRC32 calculation utility for ZIP files (Browser version)
2
+ * CRC32 for ZIP files — browser variant.
3
3
  *
4
- * Uses lookup table optimization - no Node.js dependencies
4
+ * The portable lookup-table implementation, with no `zlib` and no Node built-in of any kind. `crc32.ts` is
5
+ * the sibling that uses native `zlib.crc32`.
5
6
  *
6
- * The polynomial used is the standard CRC-32 IEEE 802.3:
7
+ * The polynomial is the standard CRC-32 IEEE 802.3:
7
8
  * x^32 + x^26 + x^23 + x^22 + x^16 + x^12 + x^11 + x^10 + x^8 + x^7 + x^5 + x^4 + x^2 + x + 1
8
- * Represented as 0xEDB88320 in reversed (LSB-first) form
9
- */
10
- export { crc32JS as crc32, crc32UpdateJS as crc32Update, crc32Finalize } from "./crc32.base.js";
11
- /**
12
- * Ensure CRC32 is ready (no-op in browser, for API compatibility)
13
- */
14
- export async function ensureCrc32() {
15
- // No-op in browser
16
- }
17
- /**
18
- * Synchronously ensure CRC32 is ready (no-op in browser).
19
- * Exists for API compatibility with the Node.js version.
20
- */
21
- export function ensureZlibSync() {
22
- // No-op in browser — CRC32 always uses JS implementation
23
- }
24
- /**
25
- * Calculate CRC32 incrementally (useful for streaming)
26
- * Call with initial crc of 0xffffffff, then finalize with crc32Finalize
27
- *
28
- * @param crc - Current CRC value (start with 0xffffffff)
29
- * @param data - Input data chunk
30
- * @returns Updated CRC value (not finalized)
9
+ * Represented as 0xEDB88320 in reversed (LSB-first) form.
31
10
  *
32
11
  * @example
33
12
  * ```ts
@@ -37,3 +16,4 @@ export function ensureZlibSync() {
37
16
  * const checksum = crc32Finalize(crc);
38
17
  * ```
39
18
  */
19
+ export { crc32JS as crc32, crc32UpdateJS as crc32Update, crc32Finalize } from "./crc32.base.js";
@@ -1,61 +1,20 @@
1
1
  /**
2
- * CRC32 calculation utility for ZIP files
2
+ * CRC32 for ZIP files — Node variant.
3
3
  *
4
- * - Node.js: Uses native zlib.crc32 (C++ implementation, ~100x faster)
5
- * - Browser: Uses lookup table optimization
4
+ * `zlib` is imported **statically**, and that is the invariant this file exists to hold. Loading it lazily
5
+ * leaves a window in which CRC32 runs on the portable lookup table instead, and the window cannot be closed
6
+ * synchronously from an ES module. `crc32.browser.ts` is the sibling that has no `zlib` and uses the table.
6
7
  *
7
- * The polynomial used is the standard CRC-32 IEEE 802.3:
8
+ * `zlib.crc32` has existed since Node 22.2 and `engines.node` is `>=22.13`, so it needs no capability probe.
9
+ *
10
+ * The polynomial is the standard CRC-32 IEEE 802.3:
8
11
  * x^32 + x^26 + x^23 + x^22 + x^16 + x^12 + x^11 + x^10 + x^8 + x^7 + x^5 + x^4 + x^2 + x + 1
9
- * Represented as 0xEDB88320 in reversed (LSB-first) form
10
- */
11
- import { crc32JS, crc32UpdateJS, crc32Finalize } from "./crc32.base.js";
12
- import { isNode } from "../../../utils/env.js";
13
- // Lazy-loaded zlib module for Node.js
14
- let _zlib = null;
15
- let _zlibLoading = null;
16
- let _zlibInitStarted = false;
17
- /**
18
- * Lazily initialize zlib loading in Node.js.
19
- * Called on first use rather than at module load time.
12
+ * Represented as 0xEDB88320 in reversed (LSB-first) form.
20
13
  */
21
- function ensureZlibLoading() {
22
- if (_zlibInitStarted) {
23
- return;
24
- }
25
- _zlibInitStarted = true;
26
- if (isNode()) {
27
- _zlibLoading = import("zlib")
28
- .then(module => {
29
- _zlib = module.default ?? module;
30
- return _zlib;
31
- })
32
- .catch(() => {
33
- _zlib = null;
34
- return null;
35
- });
36
- }
37
- }
38
- /**
39
- * Synchronously ensure zlib is loaded for Node.js.
40
- * Used by the sync deflate path where the async dynamic import may not have
41
- * resolved yet. Falls back to `require()` which is synchronous in Node.js.
42
- */
43
- export function ensureZlibSync() {
44
- if (_zlib || !isNode()) {
45
- return;
46
- }
47
- try {
48
- // oxlint-disable-next-line typescript/no-require-imports
49
- _zlib = require("zlib");
50
- _zlibInitStarted = true;
51
- }
52
- catch {
53
- // Bundler or non-Node environment — JS fallback will be used
54
- }
55
- }
14
+ import { crc32 as nativeCrc32 } from "zlib";
15
+ import { crc32Finalize } from "./crc32.base.js";
56
16
  /**
57
17
  * Calculate CRC32 checksum for the given data
58
- * Uses native zlib.crc32 in Node.js for ~100x better performance
59
18
  *
60
19
  * @param data - Input data as Uint8Array or Buffer
61
20
  * @returns CRC32 checksum as unsigned 32-bit integer
@@ -68,28 +27,13 @@ export function ensureZlibSync() {
68
27
  * ```
69
28
  */
70
29
  export function crc32(data) {
71
- ensureZlibLoading();
72
- // Use native zlib.crc32 if available (Node.js)
73
- if (_zlib && typeof _zlib.crc32 === "function") {
74
- return _zlib.crc32(data) >>> 0;
75
- }
76
- // Fallback to JS implementation
77
- return crc32JS(data);
78
- }
79
- /**
80
- * Ensure zlib is loaded (for use before calling crc32)
81
- */
82
- export async function ensureCrc32() {
83
- ensureZlibLoading();
84
- if (_zlibLoading) {
85
- await _zlibLoading;
86
- }
30
+ return nativeCrc32(data) >>> 0;
87
31
  }
88
32
  /**
89
33
  * Calculate CRC32 incrementally (useful for streaming)
90
34
  * Call with initial crc of 0xffffffff, then finalize with crc32Finalize
91
- * In Node.js, this uses native zlib.crc32 when available for performance.
92
- * The internal CRC state remains the same as the JS table implementation:
35
+ *
36
+ * The internal state matches the lookup-table implementation, so the two are interchangeable:
93
37
  * - initial state: 0xffffffff
94
38
  * - finalize: xor with 0xffffffff
95
39
  *
@@ -106,17 +50,11 @@ export async function ensureCrc32() {
106
50
  * ```
107
51
  */
108
52
  export function crc32Update(crc, data) {
109
- ensureZlibLoading();
110
- // If available, use native zlib.crc32 but preserve our internal state shape.
111
- // zlib.crc32 returns a finalized CRC value and can accept the previous finalized
112
- // CRC as the second parameter (chainable). Our internal state is the inverted
113
- // (non-finalized) CRC, so convert with xor before/after.
114
- if (_zlib && typeof _zlib.crc32 === "function") {
115
- const prevFinal = (crc ^ 0xffffffff) >>> 0;
116
- const nextFinal = _zlib.crc32(data, prevFinal) >>> 0;
117
- return (nextFinal ^ 0xffffffff) >>> 0;
118
- }
119
- return crc32UpdateJS(crc, data);
53
+ // `zlib.crc32` takes and returns a *finalized* CRC and chains through its second argument. The state here
54
+ // is the inverted (non-finalized) form, so convert on the way in and out.
55
+ const prevFinal = (crc ^ 0xffffffff) >>> 0;
56
+ const nextFinal = nativeCrc32(data, prevFinal) >>> 0;
57
+ return (nextFinal ^ 0xffffffff) >>> 0;
120
58
  }
121
59
  /**
122
60
  * Finalize CRC32 calculation
@@ -5,7 +5,7 @@
5
5
  * - In Node builds it uses `./compression/crc32` + `./compression/streaming-compress` (zlib-backed).
6
6
  * - In browser builds the bundler aliases those imports to their browser variants.
7
7
  */
8
- import { crc32Update, crc32Finalize, ensureZlibSync } from "#platform/modules/archive/compression/crc32";
8
+ import { crc32Update, crc32Finalize } from "#platform/modules/archive/compression/crc32";
9
9
  import { createDeflateStream, SyncDeflater, hasNativeAsyncDeflate } from "#platform/modules/archive/compression/streaming-compress";
10
10
  import { EMPTY_UINT8ARRAY } from "../core/bytes.js";
11
11
  import { DEFAULT_ZIP_LEVEL, DEFAULT_ZIP_TIMESTAMPS } from "../core/defaults.js";
@@ -77,7 +77,6 @@ export class ZipDeflateFile {
77
77
  this._inputPos = 0;
78
78
  // Synchronous compression state for pushSync() path.
79
79
  this._syncDeflater = null;
80
- this._syncZlibReady = false;
81
80
  const resolvedName = options?.path ? normalizeZipPath(name, options.path) : name;
82
81
  this.name = resolvedName;
83
82
  const modTime = options?.modTime ?? new Date();
@@ -723,12 +722,6 @@ export class ZipDeflateFile {
723
722
  if (this._finalized) {
724
723
  throw new ArchiveError("Cannot push to finalized ZipDeflateFile");
725
724
  }
726
- // Ensure native CRC32 is available before the first _writeDataSync call.
727
- // Without this, the JS fallback is ~60x slower.
728
- if (!this._syncZlibReady) {
729
- ensureZlibSync();
730
- this._syncZlibReady = true;
731
- }
732
725
  if (this._deflateWanted === null) {
733
726
  this._accumulateSampleLen(data);
734
727
  if (!this._shouldDecide(final)) {
@@ -4,7 +4,7 @@ import { Enums } from "./enums.js";
4
4
  import { rangeCreate, rangeRange } from "./range.js";
5
5
  import { rowCreate, rowFindCell, rowHasValues, rowValues, resolveColumnKeyValue } from "./row.js";
6
6
  import { invalidateSharedCellStyle, sharedCellFacet } from "./style-sharing.js";
7
- import { ExcelError, InvalidAddressError } from "../errors.js";
7
+ import { ColumnOutOfBoundsError, ExcelError, InvalidAddressError } from "../errors.js";
8
8
  import { colCache } from "../utils/col-cache.js";
9
9
  import { copyStyle } from "../utils/copy-style.js";
10
10
  // =============================================================================
@@ -34,6 +34,40 @@ export function eachColumnKey(ws, f) {
34
34
  // =============================================================================
35
35
  // Columns
36
36
  // =============================================================================
37
+ /**
38
+ * The 1-based column number for a key, a letter or a number — **without**
39
+ * creating the column record, unlike {@link getColumn}.
40
+ *
41
+ * A read addressed by column reference needs the number and nothing else (the
42
+ * cells live in the rows, not in the column record), so it resolves the
43
+ * reference through this instead of walking through a handle: `getColumn` pads
44
+ * `ws._columns` up to the column asked for, which moves `Worksheet.columns`
45
+ * and is a mutation on a read path — see `core/__tests__/read-side-effects.test.ts`.
46
+ *
47
+ * Unlike `getColumn` it also *validates*, so a reference that names no column
48
+ * fails the same way whichever form it took. Without that a numeric reference is
49
+ * the worst case of all: `0`, `-1`, `1.5` and `NaN` all index the cell array with
50
+ * something no row can hold, so the read reports an empty column rather than a
51
+ * bad argument.
52
+ */
53
+ export function findColumnNumber(ws, c) {
54
+ if (typeof c === "string") {
55
+ // Own-property only. `ws._keys` is a plain object, so a bare `ws._keys[c]`
56
+ // resolves `"toString"` / `"constructor"` against `Object.prototype` and
57
+ // yields a function, whose `.number` is `undefined` — an unreadable column
58
+ // instead of an unknown key.
59
+ const keyed = Object.prototype.hasOwnProperty.call(ws._keys, c) ? ws._keys[c] : undefined;
60
+ return keyed ? keyed.number : colCache.l2n(c);
61
+ }
62
+ if (!Number.isInteger(c)) {
63
+ throw new ColumnOutOfBoundsError(c);
64
+ }
65
+ // `n2l` owns the 1..16384 bound and throws `ColumnOutOfBoundsError` past it,
66
+ // so the numeric form is rejected exactly as `l2n` rejects `"XFE"` above
67
+ // rather than duplicating the limit here.
68
+ colCache.n2l(c);
69
+ return c;
70
+ }
37
71
  export function getColumn(ws, c) {
38
72
  let colNum;
39
73
  if (typeof c === "string") {
@@ -313,34 +347,58 @@ export function columnEachCell(c, optOrCallback, maybeCallback) {
313
347
  });
314
348
  }
315
349
  export function columnValues(c) {
350
+ return columnValuesAt(c.worksheet, c.number);
351
+ }
352
+ /**
353
+ * The values down a column, addressed by column number rather than by a handle —
354
+ * so a caller holding only a reference does not have to materialise the column
355
+ * record to read through it.
356
+ *
357
+ * `base` is the index row 1 lands on: `1` keys the array by row number and
358
+ * leaves an empty leading slot (what `Column.values` returns), `0` makes it a
359
+ * plain array (`Column.getValues`). Either way an empty cell is a hole.
360
+ */
361
+ export function columnValuesAt(ws, colNumber, base = 1) {
316
362
  const v = [];
317
363
  // Deliberately not via `columnEachCell`: its callback signature demands a
318
364
  // `CellData`, so it materialises a cell on every row of the column. This is a
319
365
  // getter, so walk the rows non-destructively instead.
320
- const rows = c.worksheet._rows;
366
+ const rows = ws._rows;
321
367
  for (let i = 0; i < rows.length; i++) {
322
368
  const row = rows[i];
323
369
  if (!row) {
324
370
  continue;
325
371
  }
326
- const cell = rowFindCell(row, c.number);
372
+ const cell = rowFindCell(row, colNumber);
327
373
  if (cell && cellType(cell) !== Enums.ValueType.Null) {
328
- v[row.number] = cellGetValue(cell);
374
+ v[row.number - 1 + base] = cellGetValue(cell);
329
375
  }
330
376
  }
331
377
  return v;
332
378
  }
333
- export function columnSetValues(c, v) {
379
+ /**
380
+ * Write values down a column, addressed by column number.
381
+ *
382
+ * The array is read as **1-based by row number** when index 0 is a hole — the
383
+ * shape {@link columnValuesAt} hands back, so a read round-trips — and as
384
+ * 0-based when index 0 is present, so a plain array starts at row 1. The two
385
+ * cannot be told apart when a 0-based array's first slot is *also* a hole; that
386
+ * ambiguity is inherent to the protocol, and `rowSetValues` shares it.
387
+ *
388
+ * Only the indices the array actually carries are written: unlike
389
+ * `rowSetValues`, which resets the row, this leaves every other cell in the
390
+ * column alone.
391
+ */
392
+ export function columnSetValuesAt(ws, colNumber, v) {
334
393
  if (!v) {
335
394
  return;
336
395
  }
337
- const colNumber = c.number;
338
396
  let offset = 0;
339
397
  if (Object.prototype.hasOwnProperty.call(v, "0")) {
340
398
  offset = 1;
341
399
  }
342
400
  v.forEach((value, index) => {
343
- cellSetValue(getCell(c.worksheet, index + offset, colNumber), value);
401
+ cellSetValue(getCell(ws, index + offset, colNumber), value);
344
402
  });
345
403
  }
346
404
  // =============================================================================
@@ -1979,4 +1979,4 @@ function _resolveOrigin(origin, rowCount) {
1979
1979
  }
1980
1980
  // Re-export the worksheet-core container layer so `@excel/worksheet` remains the
1981
1981
  // canonical import path for all worksheet operations.
1982
- export { addRow, addRows, getColumnKey, setColumnKey, deleteColumnKey, getColumn, _commitRow, findRow, findRows, getRow, getRows, eachRow, getSheetValues, findCell, getCell, getSheetName, getSheetWorkbook, getTable, getTables, rowGetCell, rowGetCellEx, rowSetValues, rowEachCell, rowSplice, rowCommit, rowSetModel, columnCreate, columnSetDefn, columnSetHeader, columnSetKey, columnEachCell, columnValues, columnSetValues, columnFromModel, columnSetNumFmt, columnSetFont, columnSetAlignment, columnSetProtection, columnSetBorder, columnSetFill, freeze, split, unfreeze, panes, setAutoFilter, autoFilter } from "./worksheet-core.js";
1982
+ export { addRow, addRows, getColumnKey, setColumnKey, deleteColumnKey, getColumn, _commitRow, findRow, findRows, getRow, getRows, eachRow, getSheetValues, findCell, getCell, getSheetName, getSheetWorkbook, getTable, getTables, rowGetCell, rowGetCellEx, rowSetValues, rowEachCell, rowSplice, rowCommit, rowSetModel, columnCreate, columnSetDefn, columnSetHeader, columnSetKey, columnEachCell, columnValues, columnSetValuesAt, columnFromModel, columnSetNumFmt, columnSetFont, columnSetAlignment, columnSetProtection, columnSetBorder, columnSetFill, freeze, split, unfreeze, panes, setAutoFilter, autoFilter } from "./worksheet-core.js";
@@ -1,6 +1,6 @@
1
1
  import { columnAddPageBreak, columnDefn, columnHidden, columnLetter, columnOutlineLevel, columnSetHidden, columnSetOutlineLevel } from "../core/column.js";
2
2
  import { invalidateSharedCellStyle } from "../core/style-sharing.js";
3
- import { getColumn, columnSetHeader, columnSetKey, columnSetNumFmt, columnSetStyle } from "../core/worksheet-core.js";
3
+ import { columnSetHeader, columnSetKey, columnSetNumFmt, columnSetStyle, columnSetValuesAt, columnValuesAt, findColumnNumber, getColumn } from "../core/worksheet-core.js";
4
4
  // --- identity ---
5
5
  /**
6
6
  * The 1-based column number for a key, letter or number.
@@ -111,6 +111,93 @@ export function setNumFmt(ws, col, value) {
111
111
  export function getDefinition(ws, col) {
112
112
  return columnDefn(getColumn(ws, col));
113
113
  }
114
+ // --- values ---
115
+ /**
116
+ * Read the values down a column as a **0-based sparse** array: the value in row
117
+ * 1 is at index `0`, row 2 at index `1`, and so on, and an empty cell is a hole
118
+ * rather than a `null`.
119
+ *
120
+ * Contrast with {@link values}, which is indexed by row *number* and therefore
121
+ * composes directly with the `Cell` namespace. That is also the one to pass back
122
+ * to {@link setValues} — see the ambiguity noted there.
123
+ *
124
+ * The read is non-destructive: it walks the rows that exist rather than asking
125
+ * each one for a cell, so it neither materialises cells down the column nor
126
+ * declares the column itself. The metadata and style accessors in this namespace
127
+ * resolve their reference through a column *record*, which pads
128
+ * `Worksheet.columns` up to the column asked for.
129
+ *
130
+ * @example
131
+ * ```typescript
132
+ * import { Column, Workbook, Worksheet } from "documonster/excel";
133
+ *
134
+ * const wb = Workbook.create();
135
+ * const ws = Workbook.addWorksheet(wb, "Sales");
136
+ * Worksheet.setColumns(ws, [{ key: "total", header: "Total" }]);
137
+ * Column.setValues(ws, "total", [10, 20, 30]);
138
+ * const [first] = Column.getValues(ws, "total"); // 10 — row 1
139
+ * ```
140
+ */
141
+ export function getValues(ws, col) {
142
+ return columnValuesAt(ws, findColumnNumber(ws, col), 0);
143
+ }
144
+ /**
145
+ * Read the values down a column as a **sparse** array indexed by **row number**:
146
+ * index `0` is always an empty leading slot, the value in row 1 is at index `1`,
147
+ * and so on. This mirrors Excel's 1-based row numbering, so the index can be
148
+ * handed straight to the `Cell` namespace, and it is the form {@link setValues}
149
+ * reads back unambiguously.
150
+ *
151
+ * Use {@link getValues} when the values are wanted as a plain 0-based array. Like
152
+ * it, this read materialises nothing.
153
+ *
154
+ * @example
155
+ * ```typescript
156
+ * import { Cell, Column, Workbook } from "documonster/excel";
157
+ *
158
+ * const wb = Workbook.create();
159
+ * const ws = Workbook.addWorksheet(wb, "Sales");
160
+ * Column.setValues(ws, "C", [120, -40, 75]);
161
+ *
162
+ * // Colour the losses red. The index *is* the row number, and `Column.getNumber`
163
+ * // turns the reference into the column number `Cell` takes.
164
+ * const colNumber = Column.getNumber(ws, "C");
165
+ * Column.values(ws, "C").forEach((value, rowNumber) => {
166
+ * if (typeof value === "number" && value < 0) {
167
+ * Cell.setFont(ws, rowNumber, colNumber, { color: { argb: "FFC00000" } });
168
+ * }
169
+ * });
170
+ * ```
171
+ */
172
+ export function values(ws, col) {
173
+ return columnValuesAt(ws, findColumnNumber(ws, col));
174
+ }
175
+ /**
176
+ * Write values down a column. Takes anything `Cell.setValue` takes — a number, a
177
+ * string, a formula, rich text, a hyperlink, a `Date` or a civil `Temporal`.
178
+ *
179
+ * The array is read as **1-based by row number** when index 0 is a hole — the
180
+ * shape {@link values} hands back, so that read round-trips — and as **0-based**
181
+ * when index 0 is present, so a plain array starts at row 1:
182
+ *
183
+ * ```typescript
184
+ * Column.setValues(ws, "A", [1, 2, 3]); // rows 1, 2, 3
185
+ * Column.setValues(ws, "A", [, 1, 2, 3]); // rows 1, 2, 3 — the same thing
186
+ * ```
187
+ *
188
+ * The two are indistinguishable when a 0-based array's own first slot is a hole,
189
+ * so pass {@link values} rather than {@link getValues} when writing a read back —
190
+ * a `getValues` array whose row 1 was empty lands a row high. `Row.setValues` has
191
+ * the same ambiguity.
192
+ *
193
+ * **This writes, it does not replace.** Only the indices the array carries are
194
+ * touched — unlike `Row.setValues`, which resets the row — so a shorter array
195
+ * does not truncate the column and a value already in a row the array skips
196
+ * survives. Clear a cell by passing `null` at its index.
197
+ */
198
+ export function setValues(ws, col, v) {
199
+ columnSetValuesAt(ws, findColumnNumber(ws, col), v);
200
+ }
114
201
  // --- printing ---
115
202
  /**
116
203
  * Add a manual vertical page break to the **right** of `col`, the equivalent of
@@ -32,7 +32,12 @@ const colCache = {
32
32
  "Z"
33
33
  ],
34
34
  _l2nFill: 0,
35
- _l2n: {},
35
+ // Prototype-less: this is keyed by caller-supplied strings, so a plain object
36
+ // resolves `"toString"` / `"constructor"` / `"valueOf"` against
37
+ // `Object.prototype` and `l2n` returns that *function* instead of throwing —
38
+ // the truthiness guards below both pass. A column reference named after an
39
+ // `Object.prototype` member then reads as a column number of `NaN`.
40
+ _l2n: Object.create(null),
36
41
  _n2l: [],
37
42
  _level(n) {
38
43
  if (n <= 26) {