xls-codec 2.0.2 → 4.0.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 (50) hide show
  1. package/README.md +12 -10
  2. package/dist/biff/ptg.cjs +160 -7
  3. package/dist/biff/ptg.d.cts +2 -2
  4. package/dist/biff/ptg.d.ts +2 -2
  5. package/dist/biff/ptg.js +161 -9
  6. package/dist/biff/strings.cjs +6 -0
  7. package/dist/biff/strings.d.cts +3 -1
  8. package/dist/biff/strings.d.ts +3 -1
  9. package/dist/biff/strings.js +6 -1
  10. package/dist/biff/substreams.cjs +5 -0
  11. package/dist/biff/substreams.d.cts +2 -2
  12. package/dist/biff/substreams.d.ts +2 -2
  13. package/dist/biff/substreams.js +5 -1
  14. package/dist/biff/xf-colors.cjs +59 -8
  15. package/dist/biff/xf-colors.d.cts +2 -2
  16. package/dist/biff/xf-colors.d.ts +2 -2
  17. package/dist/biff/xf-colors.js +59 -9
  18. package/dist/biff/xf-writer.d.cts +1 -1
  19. package/dist/biff/xf-writer.d.ts +1 -1
  20. package/dist/content.cjs +3 -3
  21. package/dist/content.js +3 -3
  22. package/dist/index.cjs +2 -0
  23. package/dist/index.d.cts +3 -3
  24. package/dist/index.d.ts +3 -3
  25. package/dist/index.js +3 -3
  26. package/dist/{print-names-DUlpVE00.d.ts → print-names-C-PQ2vAy.d.ts} +1 -1
  27. package/dist/{print-names-D-njuzVw.d.cts → print-names-DyYloloV.d.cts} +1 -1
  28. package/dist/ptg-CCBbJLZJ.d.cts +49 -0
  29. package/dist/ptg-CCBbJLZJ.d.ts +49 -0
  30. package/dist/{substreams-D7dQiJbp.d.ts → substreams-CmyZMtuM.d.ts} +3 -1
  31. package/dist/{substreams-Ddtvn_Vr.d.cts → substreams-Cpy5Fi3d.d.cts} +3 -1
  32. package/dist/workbook/globals-writer.d.cts +2 -2
  33. package/dist/workbook/globals-writer.d.ts +2 -2
  34. package/dist/workbook/globals.cjs +137 -14
  35. package/dist/workbook/globals.d.cts +7 -7
  36. package/dist/workbook/globals.d.ts +7 -7
  37. package/dist/workbook/globals.js +138 -15
  38. package/dist/workbook/print-names.d.cts +1 -1
  39. package/dist/workbook/print-names.d.ts +1 -1
  40. package/dist/workbook/sheet.cjs +109 -6
  41. package/dist/workbook/sheet.d.cts +3 -3
  42. package/dist/workbook/sheet.d.ts +3 -3
  43. package/dist/workbook/sheet.js +110 -7
  44. package/dist/write.cjs +37 -4
  45. package/dist/write.js +39 -6
  46. package/dist/{xf-colors-CpykR3B9.d.ts → xf-colors--5oxSeI1.d.ts} +13 -6
  47. package/dist/{xf-colors-CehHZtBy.d.cts → xf-colors-BB5MKq6R.d.cts} +13 -6
  48. package/package.json +4 -4
  49. package/dist/ptg-B2K8t3js.d.cts +0 -21
  50. package/dist/ptg-B2K8t3js.d.ts +0 -21
@@ -3,6 +3,7 @@ require("../biff/record-types.cjs");
3
3
  const require_biff_records = require("../biff/records.cjs");
4
4
  const require_units = require("../units.cjs");
5
5
  const require_biff_print_setup = require("../biff/print-setup.cjs");
6
+ const require_biff_substreams = require("../biff/substreams.cjs");
6
7
  const require_biff_cursor = require("../biff/cursor.cjs");
7
8
  const require_biff_strings = require("../biff/strings.cjs");
8
9
  const require_biff_errors = require("../biff/errors.cjs");
@@ -31,8 +32,80 @@ const EMPTY_FORMULA_SHEET_CONTEXT = {
31
32
  sheets: [],
32
33
  sheetRanges: []
33
34
  };
35
+ /** The key collectFormulaGroups and its lookup agree on: a shared/array formula group's own base cell, the same (row, column) a PtgExp token elsewhere in the sheet points back to. */
36
+ function groupKey(row, column) {
37
+ return `${row},${column}`;
38
+ }
39
+ /**
40
+ * Walks every record once, looking for a Formula record immediately followed by a ShrFmla or Array record ([MS-XLS] 2.1.7.20.6's own FORMULA production, and 984826cc/c6ee7512's own "this record is preceded by a single Formula record"), and returns the shared/array expression each one carries, keyed by that Formula record's own cell -- the same (row, column) a PtgExp token names when it points back to this group (see readPtgExpBase, and readFormula below which performs the actual lookup).
41
+ *
42
+ * Built as a single upfront pass over the whole sheet rather than interleaved into readSheetRecords' own per-record loop: every Formula record that uses a shared/array formula (including the group's own base cell, which points at itself) needs this map already complete when it is reached, and although [MS-XLS] guarantees the base pair precedes every other use, resolving the whole map first removes that ordering as a correctness dependency rather than merely relying on it.
43
+ */
44
+ function collectFormulaGroups(records) {
45
+ const groups = /* @__PURE__ */ new Map();
46
+ for (let index = 0; index < records.length; index += 1) {
47
+ const record = records[index];
48
+ const next = records[index + 1];
49
+ if (record === void 0 || next === void 0) continue;
50
+ if (record.type !== 6) continue;
51
+ if (next.type === 1212) collectFormulaGroup(groups, record, next, readShrFmlaGroup);
52
+ else if (next.type === 545) collectFormulaGroup(groups, record, next, readArrayGroup);
53
+ }
54
+ return groups;
55
+ }
56
+ /**
57
+ * Reads one shared/array formula group and keys it by its base Formula record's own cell, degrading a malformed ShrFmla/Array record to "no group recovered for this base cell" rather than letting a BiffFormatError propagate out of collectFormulaGroups and abort the whole sheet read (and every other cell in it, formula or not). readCellHeader and readGroup (readShrFmlaGroup or readArrayGroup) between them make several cursor reads capable of raising that error, not only the final `cursor.take(cce)` that copies out rgce itself: an undersized record already runs out of bytes during readCellHeader's own Cell fields, or during readShrFmlaGroup/readArrayGroup's leading `cursor.skip` past their fixed header, or during the `cursor.u16()` that reads cce -- every one of those, like the `take`, is a plain read past this record's own declared bytes, and every one is caught here the same way. This is the same per-record boundary readSupBookSafely already draws for a malformed SupBook (workbook/globals.ts): before this reader ever walked a ShrFmla/Array record's own length fields, a malformed one had nothing here to trip over, so this is entirely new territory the read-every-record-once contract now needs to hold against. A cell whose Formula record points at this base through a PtgExp then resolves to no formula text at all -- exactly the same outcome "leaves formula absent for a PtgExp whose base cell has no matching ShrFmla/Array group" already documents for a dangling reference, since from resolveFormulaText's own vantage point the two cases are indistinguishable.
58
+ */
59
+ function collectFormulaGroup(groups, record, next, readGroup) {
60
+ try {
61
+ const header = readCellHeader(new require_biff_cursor.BlockCursor(record.blocks));
62
+ groups.set(groupKey(header.row, header.column), readGroup(next));
63
+ } catch (error) {
64
+ if (!(error instanceof require_biff_records.BiffFormatError)) throw error;
65
+ }
66
+ }
67
+ /** ShrFmla ([MS-XLS] 984826cc): a RefU range (6 bytes, not needed here -- the group is looked up by its base cell's own coordinates, not by re-deriving them from this range), a reserved byte, a cUse byte, then a SharedParsedFormula (458bbec0): a two-byte cce and that many bytes of rgce. Its own rgce is forbidden from containing PtgArray ([MS-XLS] 458bbec0's own "MUST NOT contain... PtgArray"), so no rgcb is read here. */
68
+ const SHRFMLA_HEADER_BYTES = 8;
69
+ function readShrFmlaGroup(record) {
70
+ const cursor = new require_biff_cursor.BlockCursor(record.blocks);
71
+ cursor.skip(SHRFMLA_HEADER_BYTES);
72
+ const cce = cursor.u16();
73
+ return {
74
+ kind: "shared",
75
+ rgce: cursor.take(cce)
76
+ };
77
+ }
78
+ /** Array ([MS-XLS] c6ee7512): a Ref range (6 bytes), a flags word (fAlwaysCalc plus reserved bits), four unused bytes, then an ArrayParsedFormula (242bcf20): a two-byte cce, that many bytes of rgce, and -- unlike ShrFmla's own SharedParsedFormula -- a real rgcb trailer, since an array formula's rgce CAN contain a PtgArray for an array-constant literal used within it (e.g. `{=A1:A3+{1;2;3}}`). rgcb's own length is never stated directly: it is whatever bytes remain in the record once the header and rgce are accounted for. */
79
+ const ARRAY_HEADER_BYTES = 12;
80
+ function readArrayGroup(record) {
81
+ const cursor = new require_biff_cursor.BlockCursor(record.blocks);
82
+ cursor.skip(ARRAY_HEADER_BYTES);
83
+ const cce = cursor.u16();
84
+ const rgce = cursor.take(cce);
85
+ const rgcbLength = require_biff_substreams.recordByteLength(record) - (14 + cce);
86
+ if (rgcbLength <= 0) return {
87
+ kind: "array",
88
+ rgce,
89
+ rgcb: void 0
90
+ };
91
+ try {
92
+ return {
93
+ kind: "array",
94
+ rgce,
95
+ rgcb: cursor.take(rgcbLength)
96
+ };
97
+ } catch (error) {
98
+ if (!(error instanceof require_biff_records.BiffFormatError)) throw error;
99
+ return {
100
+ kind: "array",
101
+ rgce,
102
+ rgcb: void 0
103
+ };
104
+ }
105
+ }
34
106
  /** Reads one worksheet substream's records. */
35
107
  function readSheetRecords(records, sharedStrings, formulaSheets = EMPTY_FORMULA_SHEET_CONTEXT) {
108
+ const formulaGroups = collectFormulaGroups(records);
36
109
  const cells = [];
37
110
  const rows = [];
38
111
  const columns = [];
@@ -88,7 +161,7 @@ function readSheetRecords(records, sharedStrings, formulaSheets = EMPTY_FORMULA_
88
161
  cells.push(readLabel(record));
89
162
  break;
90
163
  case 6:
91
- cells.push(readFormula(record, stringResultAfter(records, index), formulaSheets));
164
+ cells.push(readFormula(record, stringResultAfter(records, index), formulaSheets, formulaGroups));
92
165
  break;
93
166
  case 161:
94
167
  setup = readSetup(record);
@@ -345,7 +418,7 @@ function readMulRk(record) {
345
418
  }
346
419
  /** Both Mul records put colLast after their variable-length array, so the entry count follows from the record's own length: total = rw + colFirst + N*entry + colLast. */
347
420
  function mulEntryCount(record, entryBytes) {
348
- const total = record.blocks.reduce((sum, block) => sum + block.length, 0);
421
+ const total = require_biff_substreams.recordByteLength(record);
349
422
  const payload = total - MUL_FIXED_BYTES;
350
423
  if (payload < 0 || payload % entryBytes !== 0) throw new require_biff_records.BiffFormatError(`multiple-cell record of ${total} bytes does not hold a whole number of ${entryBytes}-byte entries`);
351
424
  return payload / entryBytes;
@@ -417,12 +490,14 @@ function readLabel(record) {
417
490
  /** The Formula record's own flags field ([MS-XLS] 2.4.127) and calculation cache, between the cached value and the compiled expression -- neither read for its own content; see the two-byte and four-byte skips in readFormula. */
418
491
  const FORMULA_FLAGS_BYTES = 2;
419
492
  const FORMULA_CALC_CACHE_BYTES = 4;
493
+ /** The Cell (6 bytes) and FormulaValue (8 bytes) fields readFormula has already consumed by the time it reaches cce, plus the flags and calculation-cache fields above and the cce field itself (2 bytes) -- what's left of the record past `FORMULA_HEADER_BYTES + cce` is the CellParsedFormula's own rgcb trailer. */
494
+ const FORMULA_HEADER_BYTES = 22;
420
495
  /**
421
- * Formula ([MS-XLS] 2.4.127): a Cell, an eight-byte FormulaValue, flags, a calculation cache, then a CellParsedFormula -- a two-byte cce followed by exactly that many bytes of compiled Ptg tokens ([MS-XLS] 2.5.198.3).
496
+ * Formula ([MS-XLS] 2.4.127): a Cell, an eight-byte FormulaValue, flags, a calculation cache, then a CellParsedFormula -- a two-byte cce, that many bytes of compiled Ptg tokens ([MS-XLS] 2.5.198.3), and (whenever rgce contains a PtgArray -- an inline array-constant literal like `=SUM({1,2,3})`, unrelated to whether the cell itself is CSE-array-entered) an RgbExtra trailer of whatever bytes remain in the record.
422
497
  *
423
- * Both the cached value and the expression are read: the value from the FormulaValue exactly as before, and the expression by handing the token bytes to biff/ptg.ts's parseFormulaText, which resolves the whole Ptg vocabulary this reader supports and returns undefined for the constructs it does not (a shared formula, an array formula, a defined name, a natural-language reference, a genuinely external 3D reference -- see that module's own boundary). `formula` is attached only when it resolves; a cell it does not resolve for keeps exactly the behaviour this reader always had, its cached value present and `formula` absent.
498
+ * Both the cached value and the expression are read: the value from the FormulaValue exactly as before, and the expression by resolveFormulaText below, which joins a lone PtgExp against the shared/array formula group collectFormulaGroups found for it and otherwise hands the token bytes straight to biff/ptg.ts's parseFormulaText. `formula` is attached only when it resolves; a cell it does not resolve for keeps exactly the behaviour this reader always had, its cached value present and `formula` absent.
424
499
  */
425
- function readFormula(record, next, formulaSheets) {
500
+ function readFormula(record, next, formulaSheets, formulaGroups) {
426
501
  const cursor = new require_biff_cursor.BlockCursor(record.blocks);
427
502
  const header = readCellHeader(cursor);
428
503
  const bytes = cursor.take(8);
@@ -434,7 +509,18 @@ function readFormula(record, next, formulaSheets) {
434
509
  cursor.skip(FORMULA_FLAGS_BYTES);
435
510
  cursor.skip(FORMULA_CALC_CACHE_BYTES);
436
511
  const cce = cursor.u16();
437
- const formula = require_biff_ptg.parseFormulaText(cursor.take(cce), formulaSheets);
512
+ let rgce;
513
+ let rgcb;
514
+ try {
515
+ rgce = cursor.take(cce);
516
+ const rgcbLength = require_biff_substreams.recordByteLength(record) - (FORMULA_HEADER_BYTES + cce);
517
+ rgcb = rgcbLength > 0 ? cursor.take(rgcbLength) : void 0;
518
+ } catch (error) {
519
+ if (!(error instanceof require_biff_records.BiffFormatError)) throw error;
520
+ rgce = void 0;
521
+ rgcb = void 0;
522
+ }
523
+ const formula = rgce === void 0 ? void 0 : resolveFormulaText(rgce, rgcb, header, formulaSheets, formulaGroups);
438
524
  return formula === void 0 ? {
439
525
  ...header,
440
526
  value,
@@ -446,6 +532,23 @@ function readFormula(record, next, formulaSheets) {
446
532
  formula
447
533
  };
448
534
  }
535
+ /**
536
+ * A Formula record's rgce resolves one of three ways: a lone PtgExp pointing back to a shared-formula base cell, whose real expression (a ShrFmla's SharedParsedFormula) is expanded relative to THIS cell's own position; a lone PtgExp pointing back to an array-formula base cell, whose real expression (an Array's ArrayParsedFormula) is identical for every cell in the range and is returned as-is, with no CSE bracing (see ArrayFormulaGroup's own comment for why); or an ordinary rgce, handed to parseFormulaText as-is (with this record's own rgcb, for an inline array-constant literal). A PtgExp with no matching group -- a dangling or malformed reference this reader cannot join -- resolves to undefined exactly like any other unsupported construct.
537
+ *
538
+ * All three branches share the same hazard and are wrapped in one catch below: a token's own embedded length (a PtgStr's character count is the reachable case, [MS-XLS] 2.5.240's cch, read straight from the file with nothing to cross-check it against) can claim more bytes than the buffer parseFormulaText's cursor was actually handed, running that cursor past its end -- for the group-joining branches because `group.rgce` was extracted from a DIFFERENT, well-formed record whose own cce correctly bounds it, and for the ordinary-rgce branch because THIS record's own cce can just as easily be too short for the token it claims to end after (or a token inside an otherwise-correctly-bounded rgce can lie the same way `group.rgce`'s can). Neither case is a bug in this reader's own token walking; both are file-controlled malformed input, and left uncaught either would propagate out of readFormula and abort every other cell on the sheet along with the one cell whose formula this is -- exactly the failure collectFormulaGroup already prevents for a group whose OWN record is malformed, extended here to malformed token content reached through any of the three paths above.
539
+ */
540
+ function resolveFormulaText(rgce, rgcb, header, formulaSheets, formulaGroups) {
541
+ const base = require_biff_ptg.readPtgExpBase(rgce);
542
+ try {
543
+ if (base === void 0) return require_biff_ptg.parseFormulaText(rgce, formulaSheets, { rgcb });
544
+ const group = formulaGroups.get(groupKey(base.row, base.column));
545
+ if (group === void 0) return;
546
+ return group.kind === "shared" ? require_biff_ptg.parseFormulaText(group.rgce, formulaSheets, { relativeTo: header }) : require_biff_ptg.parseFormulaText(group.rgce, formulaSheets, { rgcb: group.rgcb });
547
+ } catch (error) {
548
+ if (!(error instanceof require_biff_records.BiffFormatError)) throw error;
549
+ return;
550
+ }
551
+ }
449
552
  /** The non-numeric readings of a FormulaValue ([MS-XLS] 2.5.133), selected by its first byte. */
450
553
  function taggedFormulaValue(view, next) {
451
554
  switch (view.getUint8(0)) {
@@ -1,6 +1,6 @@
1
1
  import { n as SetupFields } from "../print-setup-B_ihDvm5.cjs";
2
- import { t as FormulaSheetContext } from "../ptg-B2K8t3js.cjs";
3
- import { t as RecordGroup } from "../substreams-Ddtvn_Vr.cjs";
2
+ import { r as FormulaSheetContext } from "../ptg-CCBbJLZJ.cjs";
3
+ import { t as RecordGroup } from "../substreams-Cpy5Fi3d.cjs";
4
4
  //#region src/workbook/sheet.d.ts
5
5
  /** A cell's value as its own record carries it, before number-format classification decides whether a number is really a date, a percentage, or an amount of money. */
6
6
  type RawCellValue = {
@@ -27,7 +27,7 @@ interface RawCell {
27
27
  readonly value: RawCellValue;
28
28
  /** True when the value is a Formula record's CACHED result rather than a literal. */
29
29
  readonly fromFormula: boolean;
30
- /** The formula's own text, recovered from its compiled Ptg token stream ([MS-XLS] 2.5.198), when every token in it is one this reader resolves -- absent for a shared-formula member, an array formula, a defined-name or natural-language reference, or a 3D reference into a genuinely external workbook (see biff/ptg.ts). */
30
+ /** The formula's own text, recovered from its compiled Ptg token stream ([MS-XLS] 2.5.198), when every token in it is one this reader resolves -- absent for a defined-name or natural-language reference, or a data table (see biff/ptg.ts). */
31
31
  readonly formula?: string;
32
32
  }
33
33
  interface RawRow {
@@ -1,6 +1,6 @@
1
1
  import { n as SetupFields } from "../print-setup-B_ihDvm5.js";
2
- import { t as FormulaSheetContext } from "../ptg-B2K8t3js.js";
3
- import { t as RecordGroup } from "../substreams-D7dQiJbp.js";
2
+ import { r as FormulaSheetContext } from "../ptg-CCBbJLZJ.js";
3
+ import { t as RecordGroup } from "../substreams-CmyZMtuM.js";
4
4
  //#region src/workbook/sheet.d.ts
5
5
  /** A cell's value as its own record carries it, before number-format classification decides whether a number is really a date, a percentage, or an amount of money. */
6
6
  type RawCellValue = {
@@ -27,7 +27,7 @@ interface RawCell {
27
27
  readonly value: RawCellValue;
28
28
  /** True when the value is a Formula record's CACHED result rather than a literal. */
29
29
  readonly fromFormula: boolean;
30
- /** The formula's own text, recovered from its compiled Ptg token stream ([MS-XLS] 2.5.198), when every token in it is one this reader resolves -- absent for a shared-formula member, an array formula, a defined-name or natural-language reference, or a 3D reference into a genuinely external workbook (see biff/ptg.ts). */
30
+ /** The formula's own text, recovered from its compiled Ptg token stream ([MS-XLS] 2.5.198), when every token in it is one this reader resolves -- absent for a defined-name or natural-language reference, or a data table (see biff/ptg.ts). */
31
31
  readonly formula?: string;
32
32
  }
33
33
  interface RawRow {
@@ -2,10 +2,11 @@ import "../biff/record-types.js";
2
2
  import { BiffFormatError } from "../biff/records.js";
3
3
  import { columnWidthToPoints, inchesToPoints, twipsToPoints } from "../units.js";
4
4
  import { unpackSetupFlags } from "../biff/print-setup.js";
5
+ import { recordByteLength } from "../biff/substreams.js";
5
6
  import { BlockCursor } from "../biff/cursor.js";
6
7
  import { readXLUnicodeString } from "../biff/strings.js";
7
8
  import { errorTextOf } from "../biff/errors.js";
8
- import { parseFormulaText } from "../biff/ptg.js";
9
+ import { parseFormulaText, readPtgExpBase } from "../biff/ptg.js";
9
10
  import { decodeRkNumber } from "../biff/rk.js";
10
11
  //#region src/workbook/sheet.ts
11
12
  /** Row record flag bits, in the 32-bit field following unused1 ([MS-XLS] 2.4.221). */
@@ -30,8 +31,80 @@ const EMPTY_FORMULA_SHEET_CONTEXT = {
30
31
  sheets: [],
31
32
  sheetRanges: []
32
33
  };
34
+ /** The key collectFormulaGroups and its lookup agree on: a shared/array formula group's own base cell, the same (row, column) a PtgExp token elsewhere in the sheet points back to. */
35
+ function groupKey(row, column) {
36
+ return `${row},${column}`;
37
+ }
38
+ /**
39
+ * Walks every record once, looking for a Formula record immediately followed by a ShrFmla or Array record ([MS-XLS] 2.1.7.20.6's own FORMULA production, and 984826cc/c6ee7512's own "this record is preceded by a single Formula record"), and returns the shared/array expression each one carries, keyed by that Formula record's own cell -- the same (row, column) a PtgExp token names when it points back to this group (see readPtgExpBase, and readFormula below which performs the actual lookup).
40
+ *
41
+ * Built as a single upfront pass over the whole sheet rather than interleaved into readSheetRecords' own per-record loop: every Formula record that uses a shared/array formula (including the group's own base cell, which points at itself) needs this map already complete when it is reached, and although [MS-XLS] guarantees the base pair precedes every other use, resolving the whole map first removes that ordering as a correctness dependency rather than merely relying on it.
42
+ */
43
+ function collectFormulaGroups(records) {
44
+ const groups = /* @__PURE__ */ new Map();
45
+ for (let index = 0; index < records.length; index += 1) {
46
+ const record = records[index];
47
+ const next = records[index + 1];
48
+ if (record === void 0 || next === void 0) continue;
49
+ if (record.type !== 6) continue;
50
+ if (next.type === 1212) collectFormulaGroup(groups, record, next, readShrFmlaGroup);
51
+ else if (next.type === 545) collectFormulaGroup(groups, record, next, readArrayGroup);
52
+ }
53
+ return groups;
54
+ }
55
+ /**
56
+ * Reads one shared/array formula group and keys it by its base Formula record's own cell, degrading a malformed ShrFmla/Array record to "no group recovered for this base cell" rather than letting a BiffFormatError propagate out of collectFormulaGroups and abort the whole sheet read (and every other cell in it, formula or not). readCellHeader and readGroup (readShrFmlaGroup or readArrayGroup) between them make several cursor reads capable of raising that error, not only the final `cursor.take(cce)` that copies out rgce itself: an undersized record already runs out of bytes during readCellHeader's own Cell fields, or during readShrFmlaGroup/readArrayGroup's leading `cursor.skip` past their fixed header, or during the `cursor.u16()` that reads cce -- every one of those, like the `take`, is a plain read past this record's own declared bytes, and every one is caught here the same way. This is the same per-record boundary readSupBookSafely already draws for a malformed SupBook (workbook/globals.ts): before this reader ever walked a ShrFmla/Array record's own length fields, a malformed one had nothing here to trip over, so this is entirely new territory the read-every-record-once contract now needs to hold against. A cell whose Formula record points at this base through a PtgExp then resolves to no formula text at all -- exactly the same outcome "leaves formula absent for a PtgExp whose base cell has no matching ShrFmla/Array group" already documents for a dangling reference, since from resolveFormulaText's own vantage point the two cases are indistinguishable.
57
+ */
58
+ function collectFormulaGroup(groups, record, next, readGroup) {
59
+ try {
60
+ const header = readCellHeader(new BlockCursor(record.blocks));
61
+ groups.set(groupKey(header.row, header.column), readGroup(next));
62
+ } catch (error) {
63
+ if (!(error instanceof BiffFormatError)) throw error;
64
+ }
65
+ }
66
+ /** ShrFmla ([MS-XLS] 984826cc): a RefU range (6 bytes, not needed here -- the group is looked up by its base cell's own coordinates, not by re-deriving them from this range), a reserved byte, a cUse byte, then a SharedParsedFormula (458bbec0): a two-byte cce and that many bytes of rgce. Its own rgce is forbidden from containing PtgArray ([MS-XLS] 458bbec0's own "MUST NOT contain... PtgArray"), so no rgcb is read here. */
67
+ const SHRFMLA_HEADER_BYTES = 8;
68
+ function readShrFmlaGroup(record) {
69
+ const cursor = new BlockCursor(record.blocks);
70
+ cursor.skip(SHRFMLA_HEADER_BYTES);
71
+ const cce = cursor.u16();
72
+ return {
73
+ kind: "shared",
74
+ rgce: cursor.take(cce)
75
+ };
76
+ }
77
+ /** Array ([MS-XLS] c6ee7512): a Ref range (6 bytes), a flags word (fAlwaysCalc plus reserved bits), four unused bytes, then an ArrayParsedFormula (242bcf20): a two-byte cce, that many bytes of rgce, and -- unlike ShrFmla's own SharedParsedFormula -- a real rgcb trailer, since an array formula's rgce CAN contain a PtgArray for an array-constant literal used within it (e.g. `{=A1:A3+{1;2;3}}`). rgcb's own length is never stated directly: it is whatever bytes remain in the record once the header and rgce are accounted for. */
78
+ const ARRAY_HEADER_BYTES = 12;
79
+ function readArrayGroup(record) {
80
+ const cursor = new BlockCursor(record.blocks);
81
+ cursor.skip(ARRAY_HEADER_BYTES);
82
+ const cce = cursor.u16();
83
+ const rgce = cursor.take(cce);
84
+ const rgcbLength = recordByteLength(record) - (14 + cce);
85
+ if (rgcbLength <= 0) return {
86
+ kind: "array",
87
+ rgce,
88
+ rgcb: void 0
89
+ };
90
+ try {
91
+ return {
92
+ kind: "array",
93
+ rgce,
94
+ rgcb: cursor.take(rgcbLength)
95
+ };
96
+ } catch (error) {
97
+ if (!(error instanceof BiffFormatError)) throw error;
98
+ return {
99
+ kind: "array",
100
+ rgce,
101
+ rgcb: void 0
102
+ };
103
+ }
104
+ }
33
105
  /** Reads one worksheet substream's records. */
34
106
  function readSheetRecords(records, sharedStrings, formulaSheets = EMPTY_FORMULA_SHEET_CONTEXT) {
107
+ const formulaGroups = collectFormulaGroups(records);
35
108
  const cells = [];
36
109
  const rows = [];
37
110
  const columns = [];
@@ -87,7 +160,7 @@ function readSheetRecords(records, sharedStrings, formulaSheets = EMPTY_FORMULA_
87
160
  cells.push(readLabel(record));
88
161
  break;
89
162
  case 6:
90
- cells.push(readFormula(record, stringResultAfter(records, index), formulaSheets));
163
+ cells.push(readFormula(record, stringResultAfter(records, index), formulaSheets, formulaGroups));
91
164
  break;
92
165
  case 161:
93
166
  setup = readSetup(record);
@@ -344,7 +417,7 @@ function readMulRk(record) {
344
417
  }
345
418
  /** Both Mul records put colLast after their variable-length array, so the entry count follows from the record's own length: total = rw + colFirst + N*entry + colLast. */
346
419
  function mulEntryCount(record, entryBytes) {
347
- const total = record.blocks.reduce((sum, block) => sum + block.length, 0);
420
+ const total = recordByteLength(record);
348
421
  const payload = total - MUL_FIXED_BYTES;
349
422
  if (payload < 0 || payload % entryBytes !== 0) throw new BiffFormatError(`multiple-cell record of ${total} bytes does not hold a whole number of ${entryBytes}-byte entries`);
350
423
  return payload / entryBytes;
@@ -416,12 +489,14 @@ function readLabel(record) {
416
489
  /** The Formula record's own flags field ([MS-XLS] 2.4.127) and calculation cache, between the cached value and the compiled expression -- neither read for its own content; see the two-byte and four-byte skips in readFormula. */
417
490
  const FORMULA_FLAGS_BYTES = 2;
418
491
  const FORMULA_CALC_CACHE_BYTES = 4;
492
+ /** The Cell (6 bytes) and FormulaValue (8 bytes) fields readFormula has already consumed by the time it reaches cce, plus the flags and calculation-cache fields above and the cce field itself (2 bytes) -- what's left of the record past `FORMULA_HEADER_BYTES + cce` is the CellParsedFormula's own rgcb trailer. */
493
+ const FORMULA_HEADER_BYTES = 22;
419
494
  /**
420
- * Formula ([MS-XLS] 2.4.127): a Cell, an eight-byte FormulaValue, flags, a calculation cache, then a CellParsedFormula -- a two-byte cce followed by exactly that many bytes of compiled Ptg tokens ([MS-XLS] 2.5.198.3).
495
+ * Formula ([MS-XLS] 2.4.127): a Cell, an eight-byte FormulaValue, flags, a calculation cache, then a CellParsedFormula -- a two-byte cce, that many bytes of compiled Ptg tokens ([MS-XLS] 2.5.198.3), and (whenever rgce contains a PtgArray -- an inline array-constant literal like `=SUM({1,2,3})`, unrelated to whether the cell itself is CSE-array-entered) an RgbExtra trailer of whatever bytes remain in the record.
421
496
  *
422
- * Both the cached value and the expression are read: the value from the FormulaValue exactly as before, and the expression by handing the token bytes to biff/ptg.ts's parseFormulaText, which resolves the whole Ptg vocabulary this reader supports and returns undefined for the constructs it does not (a shared formula, an array formula, a defined name, a natural-language reference, a genuinely external 3D reference -- see that module's own boundary). `formula` is attached only when it resolves; a cell it does not resolve for keeps exactly the behaviour this reader always had, its cached value present and `formula` absent.
497
+ * Both the cached value and the expression are read: the value from the FormulaValue exactly as before, and the expression by resolveFormulaText below, which joins a lone PtgExp against the shared/array formula group collectFormulaGroups found for it and otherwise hands the token bytes straight to biff/ptg.ts's parseFormulaText. `formula` is attached only when it resolves; a cell it does not resolve for keeps exactly the behaviour this reader always had, its cached value present and `formula` absent.
423
498
  */
424
- function readFormula(record, next, formulaSheets) {
499
+ function readFormula(record, next, formulaSheets, formulaGroups) {
425
500
  const cursor = new BlockCursor(record.blocks);
426
501
  const header = readCellHeader(cursor);
427
502
  const bytes = cursor.take(8);
@@ -433,7 +508,18 @@ function readFormula(record, next, formulaSheets) {
433
508
  cursor.skip(FORMULA_FLAGS_BYTES);
434
509
  cursor.skip(FORMULA_CALC_CACHE_BYTES);
435
510
  const cce = cursor.u16();
436
- const formula = parseFormulaText(cursor.take(cce), formulaSheets);
511
+ let rgce;
512
+ let rgcb;
513
+ try {
514
+ rgce = cursor.take(cce);
515
+ const rgcbLength = recordByteLength(record) - (FORMULA_HEADER_BYTES + cce);
516
+ rgcb = rgcbLength > 0 ? cursor.take(rgcbLength) : void 0;
517
+ } catch (error) {
518
+ if (!(error instanceof BiffFormatError)) throw error;
519
+ rgce = void 0;
520
+ rgcb = void 0;
521
+ }
522
+ const formula = rgce === void 0 ? void 0 : resolveFormulaText(rgce, rgcb, header, formulaSheets, formulaGroups);
437
523
  return formula === void 0 ? {
438
524
  ...header,
439
525
  value,
@@ -445,6 +531,23 @@ function readFormula(record, next, formulaSheets) {
445
531
  formula
446
532
  };
447
533
  }
534
+ /**
535
+ * A Formula record's rgce resolves one of three ways: a lone PtgExp pointing back to a shared-formula base cell, whose real expression (a ShrFmla's SharedParsedFormula) is expanded relative to THIS cell's own position; a lone PtgExp pointing back to an array-formula base cell, whose real expression (an Array's ArrayParsedFormula) is identical for every cell in the range and is returned as-is, with no CSE bracing (see ArrayFormulaGroup's own comment for why); or an ordinary rgce, handed to parseFormulaText as-is (with this record's own rgcb, for an inline array-constant literal). A PtgExp with no matching group -- a dangling or malformed reference this reader cannot join -- resolves to undefined exactly like any other unsupported construct.
536
+ *
537
+ * All three branches share the same hazard and are wrapped in one catch below: a token's own embedded length (a PtgStr's character count is the reachable case, [MS-XLS] 2.5.240's cch, read straight from the file with nothing to cross-check it against) can claim more bytes than the buffer parseFormulaText's cursor was actually handed, running that cursor past its end -- for the group-joining branches because `group.rgce` was extracted from a DIFFERENT, well-formed record whose own cce correctly bounds it, and for the ordinary-rgce branch because THIS record's own cce can just as easily be too short for the token it claims to end after (or a token inside an otherwise-correctly-bounded rgce can lie the same way `group.rgce`'s can). Neither case is a bug in this reader's own token walking; both are file-controlled malformed input, and left uncaught either would propagate out of readFormula and abort every other cell on the sheet along with the one cell whose formula this is -- exactly the failure collectFormulaGroup already prevents for a group whose OWN record is malformed, extended here to malformed token content reached through any of the three paths above.
538
+ */
539
+ function resolveFormulaText(rgce, rgcb, header, formulaSheets, formulaGroups) {
540
+ const base = readPtgExpBase(rgce);
541
+ try {
542
+ if (base === void 0) return parseFormulaText(rgce, formulaSheets, { rgcb });
543
+ const group = formulaGroups.get(groupKey(base.row, base.column));
544
+ if (group === void 0) return;
545
+ return group.kind === "shared" ? parseFormulaText(group.rgce, formulaSheets, { relativeTo: header }) : parseFormulaText(group.rgce, formulaSheets, { rgcb: group.rgcb });
546
+ } catch (error) {
547
+ if (!(error instanceof BiffFormatError)) throw error;
548
+ return;
549
+ }
550
+ }
448
551
  /** The non-numeric readings of a FormulaValue ([MS-XLS] 2.5.133), selected by its first byte. */
449
552
  function taggedFormulaValue(view, next) {
450
553
  switch (view.getUint8(0)) {
package/dist/write.cjs CHANGED
@@ -90,9 +90,18 @@ function buildPalettePlan(sheets) {
90
90
  const hex = (0, document_schema_js.colorToRgbHex)(color);
91
91
  if (!colorByHex.has(hex)) colorByHex.set(hex, color);
92
92
  };
93
+ const recordFill = (fill) => {
94
+ if (fill === void 0) return;
95
+ if (fill.kind === "solid") {
96
+ record(fill.color);
97
+ return;
98
+ }
99
+ record(fill.foregroundColor);
100
+ record(fill.backgroundColor);
101
+ };
93
102
  for (const sheet of sheets) for (const cell of sheet.cells) {
94
103
  if (!require_written_cells.writesCellRecord(cell)) continue;
95
- record(cell.background);
104
+ recordFill(cell.background);
96
105
  record(cell.borders?.left?.color);
97
106
  record(cell.borders?.right?.color);
98
107
  record(cell.borders?.top?.color);
@@ -149,12 +158,36 @@ function resolveWriteEdge(border, icvOf) {
149
158
  icv: icvOf(border.color)
150
159
  };
151
160
  }
161
+ /** A ContentCellFill's own fillPattern/fillForegroundIcv/fillBackgroundIcv triple, resolved for whichever of 'solid'/'pattern' the cell states -- undefined input resolves to FLSNULL with both colours Automatic, matching the pre-#951 undecorated case exactly. A 'pattern' fill leaving one of its own colours unstated writes that colour Automatic too, the inverse of xf-colors.ts's own resolveFillBackground treating an unresolvable icv the same way on read. */
162
+ function resolveFillFields(fill, icvOf) {
163
+ if (fill === void 0) return {
164
+ fillPattern: 0,
165
+ fillForegroundIcv: 64,
166
+ fillBackgroundIcv: 65
167
+ };
168
+ switch (fill.kind) {
169
+ case "solid": return {
170
+ fillPattern: 1,
171
+ fillForegroundIcv: icvOf(fill.color),
172
+ fillBackgroundIcv: 65
173
+ };
174
+ case "pattern": {
175
+ const fillPattern = require_biff_xf_colors.PATTERN_TYPE_TO_FILL_PATTERN.get(fill.patternType);
176
+ if (fillPattern === void 0) throw new require_biff_write_errors.BiffWriteError(`xls-codec cannot write a '${fill.patternType}' cell fill: [MS-XLS]'s own FillPattern enumeration has no member for it, that pattern name belonging only to WordprocessingML's ST_Shd half of ContentCellPatternType's shared vocabulary`);
177
+ return {
178
+ fillPattern,
179
+ fillForegroundIcv: fill.foregroundColor === void 0 ? 64 : icvOf(fill.foregroundColor),
180
+ fillBackgroundIcv: fill.backgroundColor === void 0 ? 65 : icvOf(fill.backgroundColor)
181
+ };
182
+ }
183
+ default: throw new require_biff_write_errors.BiffWriteError(`xls-codec cannot write a cell fill with kind '${(0, document_schema_js.unrecognizedFillKind)(fill)}': ContentCellFillSchema's discriminated union only defines 'solid' and 'pattern'`);
184
+ }
185
+ }
152
186
  /** A cell's own decoration, resolved into the raw XfDecorationFields the CellXF payload packs -- undefined for a cell with neither a background nor any border, so it shares the workbook's plain undecorated XF exactly as it did before decoration existed. The "has decoration at all" question is written-cells.ts's, since the writer's own record-emission predicate turns on the identical answer. */
153
187
  function resolveDecorationForCell(cell, icvOf) {
154
188
  if (!require_written_cells.cellCarriesFormatting(cell)) return;
155
189
  return {
156
- fillPattern: cell.background === void 0 ? 0 : 1,
157
- fillForegroundIcv: cell.background === void 0 ? 64 : icvOf(cell.background),
190
+ ...resolveFillFields(cell.background, icvOf),
158
191
  left: resolveWriteEdge(cell.borders?.left, icvOf),
159
192
  right: resolveWriteEdge(cell.borders?.right, icvOf),
160
193
  top: resolveWriteEdge(cell.borders?.top, icvOf),
@@ -165,7 +198,7 @@ function resolveDecorationForCell(cell, icvOf) {
165
198
  function signatureOfCellXf(formatId, alignment, verticalAlignment, decoration) {
166
199
  let signature = `f${formatId}|a${alignment ?? ""}|v${verticalAlignment ?? ""}`;
167
200
  if (decoration === void 0) return signature;
168
- signature += `|p${decoration.fillPattern}:${decoration.fillForegroundIcv}|l${decoration.left.style}:${decoration.left.icv}|r${decoration.right.style}:${decoration.right.icv}|t${decoration.top.style}:${decoration.top.icv}|b${decoration.bottom.style}:${decoration.bottom.icv}`;
201
+ signature += `|p${decoration.fillPattern}:${decoration.fillForegroundIcv}:${decoration.fillBackgroundIcv}|l${decoration.left.style}:${decoration.left.icv}|r${decoration.right.style}:${decoration.right.icv}|t${decoration.top.style}:${decoration.top.icv}|b${decoration.bottom.style}:${decoration.bottom.icv}`;
169
202
  return signature;
170
203
  }
171
204
  /**
package/dist/write.js CHANGED
@@ -1,5 +1,5 @@
1
1
  import { SUMMARY_INFORMATION_STREAM } from "./container.js";
2
- import { DEFAULT_PALETTE_HEX_TO_ICV, borderStyleTokenFor } from "./biff/xf-colors.js";
2
+ import { DEFAULT_PALETTE_HEX_TO_ICV, PATTERN_TYPE_TO_FILL_PATTERN, borderStyleTokenFor } from "./biff/xf-colors.js";
3
3
  import { BiffWriteError } from "./biff/write-errors.js";
4
4
  import { printNameEntriesFor } from "./workbook/print-names.js";
5
5
  import { layoutMetadataToSummaryInformation as layoutMetadataToSummaryInformation$1 } from "./metadata.js";
@@ -7,7 +7,7 @@ import { GENERAL_CELL_XF_INDEX, buildWorkbookGlobals } from "./workbook/globals-
7
7
  import { cellCarriesFormatting, writesCellRecord } from "./written-cells.js";
8
8
  import { buildWorksheetSubstream } from "./workbook/sheet-writer.js";
9
9
  import { hasSummaryInformationFields, writeCompoundFile, writeSummaryInformationStream } from "archive-codec";
10
- import { colorToRgbHex, flattenTree } from "document-schema.js";
10
+ import { colorToRgbHex, flattenTree, unrecognizedFillKind } from "document-schema.js";
11
11
  import { BUILTIN_NUMBER_FORMATS } from "excel-number-format";
12
12
  //#region src/write.ts
13
13
  const WORKBOOK_STREAM_NAME = "Workbook";
@@ -89,9 +89,18 @@ function buildPalettePlan(sheets) {
89
89
  const hex = colorToRgbHex(color);
90
90
  if (!colorByHex.has(hex)) colorByHex.set(hex, color);
91
91
  };
92
+ const recordFill = (fill) => {
93
+ if (fill === void 0) return;
94
+ if (fill.kind === "solid") {
95
+ record(fill.color);
96
+ return;
97
+ }
98
+ record(fill.foregroundColor);
99
+ record(fill.backgroundColor);
100
+ };
92
101
  for (const sheet of sheets) for (const cell of sheet.cells) {
93
102
  if (!writesCellRecord(cell)) continue;
94
- record(cell.background);
103
+ recordFill(cell.background);
95
104
  record(cell.borders?.left?.color);
96
105
  record(cell.borders?.right?.color);
97
106
  record(cell.borders?.top?.color);
@@ -148,12 +157,36 @@ function resolveWriteEdge(border, icvOf) {
148
157
  icv: icvOf(border.color)
149
158
  };
150
159
  }
160
+ /** A ContentCellFill's own fillPattern/fillForegroundIcv/fillBackgroundIcv triple, resolved for whichever of 'solid'/'pattern' the cell states -- undefined input resolves to FLSNULL with both colours Automatic, matching the pre-#951 undecorated case exactly. A 'pattern' fill leaving one of its own colours unstated writes that colour Automatic too, the inverse of xf-colors.ts's own resolveFillBackground treating an unresolvable icv the same way on read. */
161
+ function resolveFillFields(fill, icvOf) {
162
+ if (fill === void 0) return {
163
+ fillPattern: 0,
164
+ fillForegroundIcv: 64,
165
+ fillBackgroundIcv: 65
166
+ };
167
+ switch (fill.kind) {
168
+ case "solid": return {
169
+ fillPattern: 1,
170
+ fillForegroundIcv: icvOf(fill.color),
171
+ fillBackgroundIcv: 65
172
+ };
173
+ case "pattern": {
174
+ const fillPattern = PATTERN_TYPE_TO_FILL_PATTERN.get(fill.patternType);
175
+ if (fillPattern === void 0) throw new BiffWriteError(`xls-codec cannot write a '${fill.patternType}' cell fill: [MS-XLS]'s own FillPattern enumeration has no member for it, that pattern name belonging only to WordprocessingML's ST_Shd half of ContentCellPatternType's shared vocabulary`);
176
+ return {
177
+ fillPattern,
178
+ fillForegroundIcv: fill.foregroundColor === void 0 ? 64 : icvOf(fill.foregroundColor),
179
+ fillBackgroundIcv: fill.backgroundColor === void 0 ? 65 : icvOf(fill.backgroundColor)
180
+ };
181
+ }
182
+ default: throw new BiffWriteError(`xls-codec cannot write a cell fill with kind '${unrecognizedFillKind(fill)}': ContentCellFillSchema's discriminated union only defines 'solid' and 'pattern'`);
183
+ }
184
+ }
151
185
  /** A cell's own decoration, resolved into the raw XfDecorationFields the CellXF payload packs -- undefined for a cell with neither a background nor any border, so it shares the workbook's plain undecorated XF exactly as it did before decoration existed. The "has decoration at all" question is written-cells.ts's, since the writer's own record-emission predicate turns on the identical answer. */
152
186
  function resolveDecorationForCell(cell, icvOf) {
153
187
  if (!cellCarriesFormatting(cell)) return;
154
188
  return {
155
- fillPattern: cell.background === void 0 ? 0 : 1,
156
- fillForegroundIcv: cell.background === void 0 ? 64 : icvOf(cell.background),
189
+ ...resolveFillFields(cell.background, icvOf),
157
190
  left: resolveWriteEdge(cell.borders?.left, icvOf),
158
191
  right: resolveWriteEdge(cell.borders?.right, icvOf),
159
192
  top: resolveWriteEdge(cell.borders?.top, icvOf),
@@ -164,7 +197,7 @@ function resolveDecorationForCell(cell, icvOf) {
164
197
  function signatureOfCellXf(formatId, alignment, verticalAlignment, decoration) {
165
198
  let signature = `f${formatId}|a${alignment ?? ""}|v${verticalAlignment ?? ""}`;
166
199
  if (decoration === void 0) return signature;
167
- signature += `|p${decoration.fillPattern}:${decoration.fillForegroundIcv}|l${decoration.left.style}:${decoration.left.icv}|r${decoration.right.style}:${decoration.right.icv}|t${decoration.top.style}:${decoration.top.icv}|b${decoration.bottom.style}:${decoration.bottom.icv}`;
200
+ signature += `|p${decoration.fillPattern}:${decoration.fillForegroundIcv}:${decoration.fillBackgroundIcv}|l${decoration.left.style}:${decoration.left.icv}|r${decoration.right.style}:${decoration.right.icv}|t${decoration.top.style}:${decoration.top.icv}|b${decoration.bottom.style}:${decoration.bottom.icv}`;
168
201
  return signature;
169
202
  }
170
203
  /**
@@ -1,10 +1,12 @@
1
1
  import { t as BlockCursor } from "./cursor-VMtw9uVP.js";
2
- import { Alignment, Color, ContentBorder } from "document-schema.js";
2
+ import { Alignment, Color, ContentBorder, ContentCellFill, ContentCellPatternType } from "document-schema.js";
3
3
  //#region src/biff/xf-colors.d.ts
4
4
  /** FLSNULL: no fill pattern -- the cell's fill colour fields carry no meaning. */
5
5
  declare const FILL_PATTERN_NONE = 0;
6
- /** FLSSOLID: a solid fill, the only pattern this package maps onto ContentSheetCell.background -- "If this value is 1 ... then only icvFore is rendered" ([MS-XLS] CellXF). Every other pattern (50%/75%/25% gray, the stripe and crosshatch families, ...) is a real information-loss case this reader does not approximate: see resolveFillBackground below. */
6
+ /** FLSSOLID: a solid fill -- "If this value is 1 ... then only icvFore is rendered" ([MS-XLS] CellXF). */
7
7
  declare const FILL_PATTERN_SOLID = 1;
8
+ /** The inverse of FILL_PATTERN_TO_PATTERN_TYPE, built from it rather than restated by hand so the two can never drift apart. Every ContentCellPatternType this package's own writer is ever asked to state has an entry, since the SpreadsheetML half of the shared vocabulary is exactly FILL_PATTERN_TO_PATTERN_TYPE's own value set -- the WordprocessingML-only members (the percentN family and the stripe/cross families ST_Shd names) are absent, FillPattern having no equivalent for them at all. */
9
+ declare const PATTERN_TYPE_TO_FILL_PATTERN: ReadonlyMap<ContentCellPatternType, number>;
8
10
  /** alc (a CellXF/StyleXF payload's word1, bits 0-2) -> ContentSheetCell.alignment, or undefined for ALCGEN (the value-kind default this field being absent already requests) and for the three HorizAlign members (ALCFILL/ALCCONTCTR/ALCDIST) Alignment has no member for -- matching ooxml.js's readHorizontalAlignment policy of only the four direct members surviving. */
9
11
  declare function resolveHorizontalAlignment(alc: number): Alignment | undefined;
10
12
  /** ContentSheetCell.alignment -> the alc token to pack into word1 -- undefined maps to ALCGEN, the "use the value-kind default" token every genuinely unaligned cell already carried before this module modelled alignment at all. */
@@ -56,12 +58,17 @@ interface XfBorderEdge {
56
58
  declare function resolveBorderEdge(edge: XfBorderEdge, palette: readonly Color[] | undefined): ContentBorder | undefined;
57
59
  /** The inverse of resolveBorderEdge's style resolution: picks the BorderStyle token carrying a ContentBorder's own pattern at the closest named weight, bucketing a solid/dashed border's widthPt back to a weight through document-schema.js's own shared quantisation -- the same one resolveBorderEdge's widths came out of, and the same one ooxml.js's borderToXlsxStyle buckets xlsx's string tokens through. */
58
60
  declare function borderStyleTokenFor(border: ContentBorder): number;
59
- /** A solid fill's own foreground colour resolved to a real background, or undefined for every other FillPattern value -- FLSNULL (no fill at all) and every pattern beyond solid (50%/75%/25% gray, the stripe and crosshatch family) alike. A non-solid pattern is a real information-loss case rather than an oversight: ContentSheetCell.background models one flat colour, and approximating a striped or crosshatched fill as its foreground colour alone would misrepresent what the cell actually shows -- see xls-codec's README for this package's own stated judgment call. */
60
- declare function resolveFillBackground(fillPattern: number, foregroundIcv: number, palette: readonly Color[] | undefined): Color | undefined;
61
- /** Every decoration field the trailing payload's word2/word3/word4 carry ([MS-XLS] 2.4.353's own CellXF/StyleXF "Data" field), read or write side alike: which fill pattern (if any) and its foreground colour, and each of the four sides' own border style plus colour. Diagonal borders (dgDiag/grbitDiag/icvDiag) are out of this package's scope -- ContentCellBordersSchema has no diagonal member -- and are always read as absent / always written as none. */
61
+ /**
62
+ * Resolves a cell's own FillPattern/icvFore/icvBack triple to a real ContentCellFill (ExaDev/documents.js#951), or undefined for FLSNULL (no fill at all), for a reserved/unrecognised FillPattern value, or for FLSSOLID when its own icvFore does not resolve to a fixed RGB value (an "Automatic" or otherwise unmapped icv, which leaves nothing to state a solid fill's colour as).
63
+ *
64
+ * FLSSOLID resolves to a 'solid' fill of icvFore alone -- "If this value is 1 ... then only icvFore is rendered" ([MS-XLS] CellXF), so icvBack carries no meaning for it and is never consulted. Every other named FillPattern resolves to a real 'pattern' fill via FILL_PATTERN_TO_PATTERN_TYPE, carrying whichever of icvFore/icvBack resolves to a real colour (either may be an "Automatic" icv this package cannot express as a fixed RGB value, matching ContentCellFillSchema's own "a colour can defer instead of asserting" convention).
65
+ */
66
+ declare function resolveFillBackground(fillPattern: number, foregroundIcv: number, backgroundIcv: number, palette: readonly Color[] | undefined): ContentCellFill | undefined;
67
+ /** Every decoration field the trailing payload's word2/word3/word4 carry ([MS-XLS] 2.4.353's own CellXF/StyleXF "Data" field), read or write side alike: which fill pattern (if any) and its foreground/background colours, and each of the four sides' own border style plus colour. fillBackgroundIcv carries no meaning for a solid fill (icvFore alone is rendered) but is real for every other named pattern, where it is the colour the pattern's gaps show through. Diagonal borders (dgDiag/grbitDiag/icvDiag) are out of this package's scope -- ContentCellBordersSchema has no diagonal member -- and are always read as absent / always written as none. */
62
68
  interface XfDecorationFields {
63
69
  readonly fillPattern: number;
64
70
  readonly fillForegroundIcv: number;
71
+ readonly fillBackgroundIcv: number;
65
72
  readonly left: XfBorderEdge;
66
73
  readonly right: XfBorderEdge;
67
74
  readonly top: XfBorderEdge;
@@ -88,4 +95,4 @@ declare function readLongRgbColor(cursor: BlockCursor): Color;
88
95
  /** The inverse of readLongRgbColor: a colour's own red/green/blue/reserved bytes, rounded to the nearest byte (the same rounding colorToRgbHex applies) -- exact for any colour this package itself constructed via rgbHexToColor, which is what write.ts's own palette-colour interning does. */
89
96
  declare function longRgbBytesOf(color: Color): readonly [number, number, number, number];
90
97
  //#endregion
91
- export { readLongRgbColor as A, XfAlignmentFields as C, horizAlignTokenFor as D, borderStyleTokenFor as E, resolveVerticalAlignment as F, unpackXfAlignment as I, unpackXfDecoration as L, resolveFillBackground as M, resolveHorizontalAlignment as N, longRgbBytesOf as O, resolveIcvColor as P, vertAlignTokenFor as R, UNDECORATED_XF_FIELDS as S, XfDecorationFields as T, FILL_PATTERN_SOLID as _, BORDER_STYLE_DOUBLE as a, PALETTE_BASE_ICV as b, BORDER_STYLE_MEDIUM_DASHDOT as c, BORDER_STYLE_NONE as d, BORDER_STYLE_SLANT_DASHDOT as f, FILL_PATTERN_NONE as g, DEFAULT_PALETTE_HEX_TO_ICV as h, BORDER_STYLE_DOTTED as i, resolveBorderEdge as j, packXfDecorationWords as k, BORDER_STYLE_MEDIUM_DASHDOTDOT as l, BORDER_STYLE_THIN as m, BORDER_STYLE_DASHDOTDOT as n, BORDER_STYLE_HAIR as o, BORDER_STYLE_THICK as p, BORDER_STYLE_DASHED as r, BORDER_STYLE_MEDIUM as s, BORDER_STYLE_DASHDOT as t, BORDER_STYLE_MEDIUM_DASHED as u, ICV_AUTOMATIC_BACKGROUND as v, XfBorderEdge as w, PALETTE_ENTRY_COUNT as x, ICV_AUTOMATIC_FOREGROUND as y };
98
+ export { packXfDecorationWords as A, UNDECORATED_XF_FIELDS as C, borderStyleTokenFor as D, XfDecorationFields as E, resolveIcvColor as F, resolveVerticalAlignment as I, unpackXfAlignment as L, resolveBorderEdge as M, resolveFillBackground as N, horizAlignTokenFor as O, resolveHorizontalAlignment as P, unpackXfDecoration as R, PATTERN_TYPE_TO_FILL_PATTERN as S, XfBorderEdge as T, FILL_PATTERN_SOLID as _, BORDER_STYLE_DOUBLE as a, PALETTE_BASE_ICV as b, BORDER_STYLE_MEDIUM_DASHDOT as c, BORDER_STYLE_NONE as d, BORDER_STYLE_SLANT_DASHDOT as f, FILL_PATTERN_NONE as g, DEFAULT_PALETTE_HEX_TO_ICV as h, BORDER_STYLE_DOTTED as i, readLongRgbColor as j, longRgbBytesOf as k, BORDER_STYLE_MEDIUM_DASHDOTDOT as l, BORDER_STYLE_THIN as m, BORDER_STYLE_DASHDOTDOT as n, BORDER_STYLE_HAIR as o, BORDER_STYLE_THICK as p, BORDER_STYLE_DASHED as r, BORDER_STYLE_MEDIUM as s, BORDER_STYLE_DASHDOT as t, BORDER_STYLE_MEDIUM_DASHED as u, ICV_AUTOMATIC_BACKGROUND as v, XfAlignmentFields as w, PALETTE_ENTRY_COUNT as x, ICV_AUTOMATIC_FOREGROUND as y, vertAlignTokenFor as z };