reamkit 1.30.0 → 1.32.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 (147) hide show
  1. package/README.md +70 -24
  2. package/dist/esm/core/bidi/index.d.ts +10 -0
  3. package/dist/esm/core/bidi/index.js +18 -2
  4. package/dist/esm/core/converter/facade.js +10 -5
  5. package/dist/esm/core/converter/project.js +4 -1
  6. package/dist/esm/core/converter/ream.js +17 -6
  7. package/dist/esm/core/converter/sheet-images.d.ts +17 -0
  8. package/dist/esm/core/converter/sheet-images.js +33 -0
  9. package/dist/esm/core/crypto/offcrypto.js +1 -1
  10. package/dist/esm/core/document-model/index.d.ts +1 -1
  11. package/dist/esm/core/document-model/types.d.ts +175 -0
  12. package/dist/esm/core/drawingml/chart-geometry.d.ts +21 -8
  13. package/dist/esm/core/drawingml/chart-geometry.js +198 -108
  14. package/dist/esm/core/drawingml/chart-parser.js +73 -2
  15. package/dist/esm/core/drawingml/colors.d.ts +6 -0
  16. package/dist/esm/core/drawingml/colors.js +23 -3
  17. package/dist/esm/core/drawingml/shape-render.d.ts +3 -1
  18. package/dist/esm/core/drawingml/shape-render.js +4 -2
  19. package/dist/esm/core/font/index.d.ts +2 -0
  20. package/dist/esm/core/font/measure.d.ts +9 -0
  21. package/dist/esm/core/font/measure.js +9 -1
  22. package/dist/esm/core/font/ttf-build.d.ts +113 -0
  23. package/dist/esm/core/font/ttf-build.js +1224 -0
  24. package/dist/esm/core/font/ttf-subset.d.ts +19 -0
  25. package/dist/esm/core/font/ttf-subset.js +14 -1
  26. package/dist/esm/core/fonts/families.js +4 -2
  27. package/dist/esm/core/fonts/provider.d.ts +17 -0
  28. package/dist/esm/core/fonts/provider.js +27 -2
  29. package/dist/esm/core/fonts/remote-fonts.js +8 -1
  30. package/dist/esm/core/fonts/scripts.js +4 -2
  31. package/dist/esm/core/ir/flow.d.ts +77 -1
  32. package/dist/esm/core/ir/sheet.d.ts +43 -0
  33. package/dist/esm/core/number-format.js +16 -11
  34. package/dist/esm/core/numbering/index.d.ts +1 -1
  35. package/dist/esm/core/numbering/state.d.ts +11 -1
  36. package/dist/esm/core/numbering/state.js +10 -1
  37. package/dist/esm/core/spreadsheet-model/index.d.ts +1 -1
  38. package/dist/esm/core/spreadsheet-model/types.d.ts +96 -0
  39. package/dist/esm/core/style-cascade/resolver.js +39 -6
  40. package/dist/esm/core/style-cascade/types.d.ts +19 -1
  41. package/dist/esm/core/style-cascade/types.js +3 -0
  42. package/dist/esm/excel/column-bands.d.ts +39 -6
  43. package/dist/esm/excel/column-bands.js +41 -17
  44. package/dist/esm/excel/comments-parser.js +19 -0
  45. package/dist/esm/excel/conditional-format.d.ts +7 -3
  46. package/dist/esm/excel/conditional-format.js +77 -39
  47. package/dist/esm/excel/formula/context.d.ts +18 -0
  48. package/dist/esm/excel/formula/eval.js +51 -0
  49. package/dist/esm/excel/formula/index.d.ts +2 -2
  50. package/dist/esm/excel/formula/lexer.d.ts +1 -1
  51. package/dist/esm/excel/formula/lexer.js +41 -0
  52. package/dist/esm/excel/formula/parser.d.ts +25 -0
  53. package/dist/esm/excel/formula/parser.js +95 -0
  54. package/dist/esm/excel/print-model.d.ts +38 -1
  55. package/dist/esm/excel/print-model.js +522 -80
  56. package/dist/esm/excel/shared-strings-parser.d.ts +9 -0
  57. package/dist/esm/excel/shared-strings-parser.js +11 -1
  58. package/dist/esm/excel/sheet-drawing.d.ts +38 -2
  59. package/dist/esm/excel/sheet-drawing.js +47 -13
  60. package/dist/esm/excel/sheet-shape-parser.d.ts +6 -2
  61. package/dist/esm/excel/sheet-shape-parser.js +18 -6
  62. package/dist/esm/excel/sheet-to-flow.d.ts +17 -0
  63. package/dist/esm/excel/sheet-to-flow.js +434 -47
  64. package/dist/esm/excel/styles-parser.d.ts +9 -0
  65. package/dist/esm/excel/styles-parser.js +80 -26
  66. package/dist/esm/excel/table-parser.js +5 -0
  67. package/dist/esm/excel/table-style-presets.d.ts +26 -0
  68. package/dist/esm/excel/table-style-presets.js +1555 -0
  69. package/dist/esm/excel/table-style.d.ts +13 -0
  70. package/dist/esm/excel/table-style.js +75 -0
  71. package/dist/esm/excel/tint.d.ts +10 -0
  72. package/dist/esm/excel/tint.js +75 -0
  73. package/dist/esm/excel/vml-drawing.d.ts +41 -3
  74. package/dist/esm/excel/vml-drawing.js +143 -10
  75. package/dist/esm/excel/worksheet-parser.js +89 -15
  76. package/dist/esm/excel/xls/biff-reader.js +10 -2
  77. package/dist/esm/excel/xlsx-reader.js +195 -14
  78. package/dist/esm/excel/xlsx-writer.js +8 -7
  79. package/dist/esm/html/html-writer.js +202 -20
  80. package/dist/esm/index.d.ts +2 -2
  81. package/dist/esm/layout/fit-pages.d.ts +11 -0
  82. package/dist/esm/layout/fit-pages.js +86 -0
  83. package/dist/esm/layout/line-paint.d.ts +58 -0
  84. package/dist/esm/layout/line-paint.js +107 -0
  85. package/dist/esm/layout/page-doc.d.ts +61 -3
  86. package/dist/esm/layout/page-doc.js +141 -1
  87. package/dist/esm/layout/styled-layout.d.ts +55 -7
  88. package/dist/esm/layout/styled-layout.js +1154 -169
  89. package/dist/esm/layout/turned-section.d.ts +38 -0
  90. package/dist/esm/layout/turned-section.js +205 -0
  91. package/dist/esm/markdown/markdown-writer.js +17 -6
  92. package/dist/esm/pdf/styled-page-emitter.js +114 -60
  93. package/dist/esm/pdf/styled-page-renderer.js +1 -1
  94. package/dist/esm/pdf-reader/cff-outline.d.ts +27 -0
  95. package/dist/esm/pdf-reader/cff-outline.js +169 -21
  96. package/dist/esm/pdf-reader/content.d.ts +53 -0
  97. package/dist/esm/pdf-reader/content.js +9 -1
  98. package/dist/esm/pdf-reader/display.d.ts +36 -0
  99. package/dist/esm/pdf-reader/display.js +66 -1
  100. package/dist/esm/pdf-reader/document.js +5 -1
  101. package/dist/esm/pdf-reader/embedded-fonts.d.ts +2 -1
  102. package/dist/esm/pdf-reader/embedded-fonts.js +32 -2
  103. package/dist/esm/pdf-reader/encodings.d.ts +8 -0
  104. package/dist/esm/pdf-reader/encodings.js +25 -3
  105. package/dist/esm/pdf-reader/face-outlines.d.ts +78 -0
  106. package/dist/esm/pdf-reader/face-outlines.js +362 -0
  107. package/dist/esm/pdf-reader/figures.d.ts +52 -0
  108. package/dist/esm/pdf-reader/figures.js +433 -0
  109. package/dist/esm/pdf-reader/flow-build.d.ts +131 -5
  110. package/dist/esm/pdf-reader/flow-build.js +345 -26
  111. package/dist/esm/pdf-reader/font.js +321 -36
  112. package/dist/esm/pdf-reader/glyf-outline.d.ts +33 -0
  113. package/dist/esm/pdf-reader/glyf-outline.js +135 -1
  114. package/dist/esm/pdf-reader/glyph-names.js +154 -1
  115. package/dist/esm/pdf-reader/layout.d.ts +93 -0
  116. package/dist/esm/pdf-reader/layout.js +1703 -214
  117. package/dist/esm/pdf-reader/page-numbers.d.ts +53 -0
  118. package/dist/esm/pdf-reader/page-numbers.js +167 -0
  119. package/dist/esm/pdf-reader/tagged.js +182 -23
  120. package/dist/esm/pdf-reader/text.d.ts +6 -3
  121. package/dist/esm/pdf-reader/text.js +98 -9
  122. package/dist/esm/pdf-reader/type1-outline.d.ts +11 -0
  123. package/dist/esm/pdf-reader/type1-outline.js +63 -8
  124. package/dist/esm/pdf-reader/vector.js +71 -1
  125. package/dist/esm/svg/svg-text.d.ts +58 -0
  126. package/dist/esm/svg/svg-text.js +322 -0
  127. package/dist/esm/svg/svg-writer.js +135 -71
  128. package/dist/esm/word/doc/doc-reader.js +6 -2
  129. package/dist/esm/word/doc/doc-text.d.ts +6 -0
  130. package/dist/esm/word/doc/doc-text.js +19 -1
  131. package/dist/esm/word/document-parser.d.ts +2 -2
  132. package/dist/esm/word/document-parser.js +11 -1
  133. package/dist/esm/word/docx-reader.js +5 -3
  134. package/dist/esm/word/docx-writer.js +218 -23
  135. package/dist/esm/word/drawing-parser.d.ts +5 -3
  136. package/dist/esm/word/drawing-parser.js +49 -8
  137. package/dist/esm/word/font-embed.d.ts +30 -0
  138. package/dist/esm/word/font-embed.js +173 -0
  139. package/dist/esm/word/font-table.d.ts +10 -0
  140. package/dist/esm/word/font-table.js +13 -1
  141. package/dist/esm/word/index.js +1 -1
  142. package/dist/esm/word/numbering-parser.d.ts +3 -1
  143. package/dist/esm/word/numbering-parser.js +2 -1
  144. package/dist/esm/word/paragraph-properties.d.ts +7 -6
  145. package/dist/esm/word/paragraph-properties.js +14 -2
  146. package/dist/esm/word/run-properties.js +26 -0
  147. package/package.json +12 -3
@@ -1,5 +1,5 @@
1
1
  import { serialFromDate, timePeriodMatches } from "./formula/dates.js";
2
- import { BLANK, bool, err, num, str } from "./formula/value.js";
2
+ import { BLANK, bool, err, num, str, toNumber } from "./formula/value.js";
3
3
  import { NO_SHIFT, evaluate } from "./formula/eval.js";
4
4
  import { compileFormula, evaluateToBool } from "./formula/index.js";
5
5
  //#region src/excel/conditional-format.ts
@@ -23,7 +23,8 @@ import { compileFormula, evaluateToBool } from "./formula/index.js";
23
23
  * driving `TODAY()`/`NOW()` and the `timePeriod` windows;
24
24
  * absent ⇒ those constructs no-op (deterministic output).
25
25
  * @param sheetGrids The whole workbook, for an `expression` rule that reaches
26
- * another sheet (`Sheet2!A1`) or a defined name; absent ⇒
26
+ * another sheet (`Sheet2!A1`), a defined name or a table
27
+ * by its columns (`tblIncome[Amount]`); absent ⇒
27
28
  * same-sheet references only.
28
29
  * @param currentSheet The rule sheet's name, for resolving sheet-local names.
29
30
  * @param definedNames The workbook defined names visible to `expression` rules.
@@ -31,21 +32,40 @@ import { compileFormula, evaluateToBool } from "./formula/index.js";
31
32
  function buildConditionalFormatter(conditionalFormats, styles, cells, resolveText, date1904 = false, now, sheetGrids, currentSheet, definedNames) {
32
33
  if (!conditionalFormats || conditionalFormats.length === 0) return void 0;
33
34
  const dxfs = styles.dxfs ?? [];
35
+ const hasExpr = conditionalFormats.some((cf) => cf.rules.some((r) => r.type === "expression"));
36
+ const formulaStops = conditionalFormats.some((cf) => cf.rules.some((r) => (r.type === "colorScale" || r.type === "dataBar" || r.type === "iconSet") && r.cfvos.some((c) => c.val !== void 0 && !Number.isFinite(Number(c.val)))));
37
+ const nowSerial = now !== void 0 ? serialFromDate(now, date1904) : void 0;
38
+ const cross = (hasExpr || formulaStops) && sheetGrids ? {
39
+ sheets: [...sheetGrids].map(([name, ws]) => ({
40
+ name,
41
+ cells: ws.cells,
42
+ ...ws.tables ? { tables: ws.tables } : {}
43
+ })),
44
+ ...currentSheet !== void 0 ? { currentSheet } : {},
45
+ definedNames: definedNames ?? []
46
+ } : void 0;
47
+ const ctx = hasExpr || formulaStops ? buildEvalContext(cells, resolveText, nowSerial, date1904, cross) : void 0;
48
+ const stop = ctx ? (formula) => {
49
+ const compiled = compileFormula(formula.startsWith("=") ? formula.slice(1) : formula);
50
+ if (!compiled) return void 0;
51
+ const n = toNumber(evaluate(compiled.ast, ctx, NO_SHIFT), ctx);
52
+ return typeof n === "number" && Number.isFinite(n) ? n : void 0;
53
+ } : void 0;
34
54
  const flat = [];
35
55
  for (const cf of conditionalFormats) for (const rule of cf.rules) if (rule.type === "colorScale") flat.push({
36
56
  ranges: cf.ranges,
37
57
  rule,
38
- scale: resolveColorScale(rule, cf.ranges, cells)
58
+ scale: resolveColorScale(rule, cf.ranges, cells, stop)
39
59
  });
40
60
  else if (rule.type === "dataBar") flat.push({
41
61
  ranges: cf.ranges,
42
62
  rule,
43
- bar: resolveDataBar(rule, cf.ranges, cells)
63
+ bar: resolveDataBar(rule, cf.ranges, cells, stop)
44
64
  });
45
65
  else if (rule.type === "iconSet") flat.push({
46
66
  ranges: cf.ranges,
47
67
  rule,
48
- iconThresholds: resolveIconThresholds(rule, cf.ranges, cells)
68
+ iconThresholds: resolveIconThresholds(rule, cf.ranges, cells, stop)
49
69
  });
50
70
  else if (rule.type === "top10") flat.push({
51
71
  ranges: cf.ranges,
@@ -78,17 +98,6 @@ function buildConditionalFormatter(conditionalFormats, styles, cells, resolveTex
78
98
  rule
79
99
  });
80
100
  flat.sort((a, b) => a.rule.priority - b.rule.priority);
81
- const hasExpr = flat.some((f) => f.rule.type === "expression");
82
- const nowSerial = now !== void 0 ? serialFromDate(now, date1904) : void 0;
83
- const cross = hasExpr && sheetGrids ? {
84
- sheets: [...sheetGrids].map(([name, ws]) => ({
85
- name,
86
- cells: ws.cells
87
- })),
88
- ...currentSheet !== void 0 ? { currentSheet } : {},
89
- definedNames: definedNames ?? []
90
- } : void 0;
91
- const ctx = hasExpr ? buildEvalContext(cells, resolveText, nowSerial, date1904, cross) : void 0;
92
101
  return (row, col, value, text, blank) => {
93
102
  if (!hasExpr && value === void 0 && (text === void 0 || text.length === 0)) return;
94
103
  const dupKey = blank === true ? void 0 : dupKeyOf(value, text);
@@ -124,6 +133,7 @@ function buildConditionalFormatter(conditionalFormats, styles, cells, resolveTex
124
133
  if (!icon && value !== void 0 && iconThresholds) {
125
134
  const bucket = iconBucket(iconThresholds, value);
126
135
  icon = iconToCell(rule.iconSet, iconThresholds.length, bucket, rule.reverse ?? false);
136
+ if (rule.showValue === false) hideValue = true;
127
137
  }
128
138
  break;
129
139
  case "top10":
@@ -206,12 +216,34 @@ function buildEvalContext(cells, resolveText, nowSerial, date1904, cross) {
206
216
  const key = dn.name.toLowerCase();
207
217
  if (dn.localSheetId !== void 0 || !nameMap.has(key)) nameMap.set(key, dn);
208
218
  }
219
+ const tables = /* @__PURE__ */ new Map();
220
+ const onThisSheet = [];
221
+ cross.sheets.forEach((s, i) => {
222
+ for (const t of s.tables ?? []) {
223
+ const table = {
224
+ ...i !== currentIdx ? { sheet: i } : {},
225
+ ref: {
226
+ r0: t.ref.startRow,
227
+ c0: t.ref.startColumn,
228
+ r1: t.ref.endRow,
229
+ c1: t.ref.endColumn
230
+ },
231
+ headerRowCount: t.headerRowCount,
232
+ totalsRowCount: t.totalsRowCount ?? 0,
233
+ columns: t.columns ?? []
234
+ };
235
+ if (t.name !== void 0) tables.set(t.name.toLowerCase(), table);
236
+ if (i === currentIdx) onThisSheet.push(table);
237
+ }
238
+ });
209
239
  const resolving = /* @__PURE__ */ new Set();
210
240
  const ctx = {
211
241
  ...base,
212
242
  sheetIndex: (name) => sheetByName.get(name.toLowerCase()),
213
243
  getCellOn: (sheet, row, col) => sheetData(sheet).byKey.get(row * COLS + col) ?? BLANK,
214
244
  eachCellOn: (sheet, rect, visit) => eachInMap(sheetData(sheet), rect, visit),
245
+ table: (name) => tables.get(name.toLowerCase()),
246
+ tableAt: (row, col) => onThisSheet.find((t) => row >= t.ref.r0 && row <= t.ref.r1 && col >= t.ref.c0 && col <= t.ref.c1),
215
247
  resolveName: (rawName) => {
216
248
  const key = rawName.toLowerCase();
217
249
  const dn = nameMap.get(key);
@@ -222,7 +254,6 @@ function buildEvalContext(cells, resolveText, nowSerial, date1904, cross) {
222
254
  if (ast.k === "num") return num(ast.v);
223
255
  if (ast.k === "str") return str(ast.v);
224
256
  if (ast.k === "bool") return bool(ast.v);
225
- if (ast.k !== "cell" && ast.k !== "range") return void 0;
226
257
  resolving.add(key);
227
258
  try {
228
259
  return evaluate(ast, ctx, NO_SHIFT);
@@ -358,7 +389,7 @@ function collectRangeValues(cells, ranges) {
358
389
  }
359
390
  return out;
360
391
  }
361
- function resolveColorScale(rule, ranges, cells) {
392
+ function resolveColorScale(rule, ranges, cells, stop) {
362
393
  const vals = collectRangeValues(cells, ranges);
363
394
  if (vals.length === 0) return void 0;
364
395
  const sorted = [...vals].sort((a, b) => a - b);
@@ -366,7 +397,7 @@ function resolveColorScale(rule, ranges, cells) {
366
397
  const vmax = sorted[sorted.length - 1];
367
398
  const thresholds = [];
368
399
  for (const [i, cfvo] of rule.cfvos.entries()) {
369
- const t = resolveCfvo(cfvo, vmin, vmax, sorted, i === rule.cfvos.length - 1);
400
+ const t = resolveCfvo(cfvo, vmin, vmax, sorted, i === rule.cfvos.length - 1, stop);
370
401
  if (t === void 0) return void 0;
371
402
  thresholds.push(t);
372
403
  }
@@ -383,24 +414,26 @@ function resolveColorScale(rule, ranges, cells) {
383
414
  colors
384
415
  };
385
416
  }
386
- function resolveCfvo(cfvo, vmin, vmax, sorted, isLast = false) {
417
+ function resolveCfvo(cfvo, vmin, vmax, sorted, isLast = false, stop) {
418
+ const given = () => {
419
+ const n = Number(cfvo.val);
420
+ if (Number.isFinite(n)) return n;
421
+ return cfvo.val !== void 0 ? stop?.(cfvo.val) : void 0;
422
+ };
387
423
  switch (cfvo.type) {
388
424
  case "min":
389
425
  case "autoMin": return vmin;
390
426
  case "max":
391
427
  case "autoMax": return vmax;
392
- case "num": return Number.isFinite(Number(cfvo.val)) ? Number(cfvo.val) : void 0;
393
- case "formula": {
394
- const n = Number(cfvo.val);
395
- return Number.isFinite(n) ? n : isLast ? vmax : vmin;
396
- }
428
+ case "num": return given();
429
+ case "formula": return given() ?? (isLast ? vmax : vmin);
397
430
  case "percent": {
398
- const p = Number(cfvo.val);
399
- return Number.isFinite(p) ? vmin + (vmax - vmin) * (p / 100) : void 0;
431
+ const p = given();
432
+ return p !== void 0 ? vmin + (vmax - vmin) * (p / 100) : void 0;
400
433
  }
401
434
  case "percentile": {
402
- const p = Number(cfvo.val);
403
- return Number.isFinite(p) ? percentile(sorted, p) : void 0;
435
+ const p = given();
436
+ return p !== void 0 ? percentile(sorted, p) : void 0;
404
437
  }
405
438
  }
406
439
  }
@@ -445,50 +478,55 @@ function rgbHex([r, g, b]) {
445
478
  const h = (n) => Math.max(0, Math.min(255, Math.round(n))).toString(16).padStart(2, "0").toUpperCase();
446
479
  return `${h(r)}${h(g)}${h(b)}`;
447
480
  }
448
- function resolveDataBar(rule, ranges, cells) {
481
+ function resolveDataBar(rule, ranges, cells, stop) {
449
482
  const vals = collectRangeValues(cells, ranges);
450
483
  if (vals.length === 0) return void 0;
451
484
  const sorted = [...vals].sort((a, b) => a - b);
452
485
  const vmin = sorted[0];
453
486
  const vmax = sorted[sorted.length - 1];
454
- const lower = resolveCfvo(rule.cfvos[0], vmin, vmax, sorted);
455
- const upper = resolveCfvo(rule.cfvos[1], vmin, vmax, sorted, true);
487
+ const lower = resolveCfvo(rule.cfvos[0], vmin, vmax, sorted, false, stop);
488
+ const upper = resolveCfvo(rule.cfvos[1], vmin, vmax, sorted, true, stop);
456
489
  if (lower === void 0 || upper === void 0) return void 0;
457
490
  return {
458
491
  lower,
459
492
  upper,
460
493
  colorHex: rule.colorHex,
461
494
  negativeColorHex: "FF0000",
462
- minLen: (rule.minLength ?? 0) / 100,
463
- maxLen: (rule.maxLength ?? 100) / 100
495
+ minLen: (rule.minLength ?? 10) / 100,
496
+ maxLen: (rule.maxLength ?? 90) / 100,
497
+ solid: rule.gradient === false
464
498
  };
465
499
  }
466
500
  function dataBarBar(bar, value) {
501
+ const solid = bar.solid ? { solid: true } : {};
467
502
  if (bar.lower < 0 && bar.upper > 0) {
468
503
  const axis = -bar.lower / (bar.upper - bar.lower);
469
504
  if (value >= 0) return {
470
505
  fraction: value / bar.upper * (1 - axis),
471
506
  colorHex: bar.colorHex,
472
- startFraction: axis
507
+ startFraction: axis,
508
+ ...solid
473
509
  };
474
510
  const w = Math.abs(value) / Math.abs(bar.lower) * axis;
475
511
  return {
476
512
  fraction: w,
477
513
  colorHex: bar.negativeColorHex,
478
514
  startFraction: axis - w,
479
- negative: true
515
+ negative: true,
516
+ ...solid
480
517
  };
481
518
  }
482
519
  return {
483
520
  fraction: dataBarFraction(bar, value),
484
- colorHex: bar.colorHex
521
+ colorHex: bar.colorHex,
522
+ ...solid
485
523
  };
486
524
  }
487
525
  function dataBarFraction(bar, value) {
488
526
  const t = bar.upper > bar.lower ? Math.min(1, Math.max(0, (value - bar.lower) / (bar.upper - bar.lower))) : value >= bar.upper ? 1 : 0;
489
527
  return bar.minLen + t * (bar.maxLen - bar.minLen);
490
528
  }
491
- function resolveIconThresholds(rule, ranges, cells) {
529
+ function resolveIconThresholds(rule, ranges, cells, stop) {
492
530
  const vals = collectRangeValues(cells, ranges);
493
531
  if (vals.length === 0) return void 0;
494
532
  const sorted = [...vals].sort((a, b) => a - b);
@@ -496,7 +534,7 @@ function resolveIconThresholds(rule, ranges, cells) {
496
534
  const vmax = sorted[sorted.length - 1];
497
535
  const out = [];
498
536
  for (const cfvo of rule.cfvos) {
499
- const t = resolveCfvo(cfvo, vmin, vmax, sorted);
537
+ const t = resolveCfvo(cfvo, vmin, vmax, sorted, false, stop);
500
538
  if (t === void 0) return void 0;
501
539
  out.push(t);
502
540
  }
@@ -45,4 +45,22 @@ export interface EvalContext {
45
45
  * Rect, optionally on another sheet) or a literal scalar; undefined ⇒ `#NAME?`.
46
46
  */
47
47
  readonly resolveName?: (name: string) => FValue | undefined;
48
+ /**
49
+ * §18.5 — a table by its name (case-insensitive), for a structured reference
50
+ * (`tblIncome[Amount]`); undefined ⇒ `#REF!`.
51
+ */
52
+ readonly table?: (name: string) => FormulaTable | undefined;
53
+ /** The table a cell stands in, for a reference that names none (`[@Amount]`). */
54
+ readonly tableAt?: (row: number, col: number) => FormulaTable | undefined;
55
+ }
56
+ /** What a structured reference needs of a table (§18.5.1.2). */
57
+ export interface FormulaTable {
58
+ /** The sheet it is on, when not the formula's own; undefined ⇒ the formula's sheet. */
59
+ readonly sheet?: number;
60
+ /** Its whole range — headers, data and totals. */
61
+ readonly ref: Rect;
62
+ readonly headerRowCount: number;
63
+ readonly totalsRowCount: number;
64
+ /** Its columns' names, left to right. */
65
+ readonly columns: ReadonlyArray<string>;
48
66
  }
@@ -56,6 +56,7 @@ function evaluate(ast, ctx, shift) {
56
56
  };
57
57
  }
58
58
  case "name": return ctx.resolveName?.(ast.name) ?? err("#NAME?");
59
+ case "sref": return structuredRef(ast.ref, ctx, shift);
59
60
  case "array": return {
60
61
  t: "arr",
61
62
  rows: ast.rows.map((row) => row.map((el) => deref(evaluate(el, ctx, shift), ctx)))
@@ -69,6 +70,56 @@ function evaluate(ast, ctx, shift) {
69
70
  case "call": return callFn(ast.name, ast.args, (a) => evaluate(a, ctx, shift), ctx, shift);
70
71
  }
71
72
  }
73
+ /**
74
+ * §18.17.6.4 — the cells a structured reference names: the parts of the table
75
+ * it asks for (its data rows when it names none), across the columns it names
76
+ * (all of them when it names none). `#This Row` is the current cell's row.
77
+ *
78
+ * @param ref The reference.
79
+ * @param ctx The evaluation context, which knows the tables.
80
+ * @param shift The current cell, for `#This Row` and for a reference naming no table.
81
+ * @returns The range, or `#REF!` for a table, column or part that is not there.
82
+ */
83
+ function structuredRef(ref, ctx, shift) {
84
+ const table = ref.table.length > 0 ? ctx.table?.(ref.table) : shift.curRow !== void 0 && shift.curCol !== void 0 ? ctx.tableAt?.(shift.curRow, shift.curCol) : void 0;
85
+ if (!table) return err("#REF!");
86
+ const { r0, c0, r1, c1 } = table.ref;
87
+ const headerEnd = r0 + table.headerRowCount - 1;
88
+ const totalsStart = r1 - table.totalsRowCount + 1;
89
+ const rows = (area) => {
90
+ switch (area) {
91
+ case "#All": return [r0, r1];
92
+ case "#Data": return [headerEnd + 1, totalsStart - 1];
93
+ case "#Headers": return table.headerRowCount > 0 ? [r0, headerEnd] : void 0;
94
+ case "#Totals": return table.totalsRowCount > 0 ? [totalsStart, r1] : void 0;
95
+ case "#This Row": return shift.curRow !== void 0 ? [shift.curRow, shift.curRow] : void 0;
96
+ }
97
+ };
98
+ const spans = (ref.areas.length > 0 ? ref.areas : ["#Data"]).map(rows);
99
+ if (spans.some((s) => s === void 0 || s[1] < s[0])) return err("#REF!");
100
+ const top = Math.min(...spans.map((s) => s[0]));
101
+ const bottom = Math.max(...spans.map((s) => s[1]));
102
+ let left = c0;
103
+ let right = c1;
104
+ if (ref.columns) {
105
+ const at = (name) => table.columns.findIndex((c) => c.toLowerCase() === name.toLowerCase());
106
+ const a = at(ref.columns[0]);
107
+ const b = at(ref.columns[1]);
108
+ if (a < 0 || b < 0) return err("#REF!");
109
+ left = c0 + Math.min(a, b);
110
+ right = c0 + Math.max(a, b);
111
+ }
112
+ return {
113
+ t: "ref",
114
+ rect: {
115
+ r0: top,
116
+ c0: left,
117
+ r1: bottom,
118
+ c1: right
119
+ },
120
+ ...table.sheet !== void 0 ? { sheet: table.sheet } : {}
121
+ };
122
+ }
72
123
  function resolveRef(ref, shift) {
73
124
  const row = ref.row.abs ? ref.row.index : ref.row.index + shift.dRow;
74
125
  const col = ref.col.abs ? ref.col.index : ref.col.index + shift.dCol;
@@ -1,10 +1,10 @@
1
1
  import { Ast } from './parser.js';
2
2
  import { EvalContext } from './context.js';
3
3
  import { Shift } from './eval.js';
4
- export type { EvalContext } from './context.js';
4
+ export type { EvalContext, FormulaTable } from './context.js';
5
5
  export type { Shift } from './eval.js';
6
6
  export type { Scalar, FValue, Rect, FErr } from './value.js';
7
- export { num, str, bool, err, BLANK } from './value.js';
7
+ export { num, str, bool, err, BLANK, toNumber } from './value.js';
8
8
  export { serialFromDate, serialToParts, timePeriodMatches } from './dates.js';
9
9
  export { NO_SHIFT, evaluate } from './eval.js';
10
10
  /**
@@ -1,5 +1,5 @@
1
1
  /** The lexical categories the {@link tokenize} pass emits. */
2
- export type TokenKind = 'num' | 'str' | 'err' | 'word' | 'sheetq' | 'op' | 'eof';
2
+ export type TokenKind = 'num' | 'str' | 'err' | 'word' | 'sheetq' | 'sref' | 'op' | 'eof';
3
3
  /** One token: its {@link TokenKind} and the matched source text. */
4
4
  export interface Token {
5
5
  readonly kind: TokenKind;
@@ -21,6 +21,29 @@ function isWordPart(ch) {
21
21
  return isWordStart(ch) || isDigit(ch) || ch === ".";
22
22
  }
23
23
  /**
24
+ * Where the bracket group that opens at `start` closes: past its matching `]`,
25
+ * nested groups included. A `'` escapes the character after it — a column
26
+ * named `Q[1]` is written `[Q'[1']]`.
27
+ *
28
+ * @param src The formula source.
29
+ * @param start The index of the opening `[`.
30
+ * @returns The index just past the closing `]`.
31
+ * @throws LexError when the group never closes.
32
+ */
33
+ function bracketsEnd(src, start) {
34
+ let depth = 0;
35
+ for (let j = start; j < src.length; j++) {
36
+ const c = src[j];
37
+ if (c === "'") {
38
+ j++;
39
+ continue;
40
+ }
41
+ if (c === "[") depth++;
42
+ else if (c === "]" && --depth === 0) return j + 1;
43
+ }
44
+ throw new LexError("unclosed [");
45
+ }
46
+ /**
24
47
  * Tokenize a formula string into a flat token stream terminated by an `eof`
25
48
  * token. Whitespace separates tokens but is otherwise dropped.
26
49
  *
@@ -122,6 +145,15 @@ function tokenize(src) {
122
145
  if (isWordStart(ch)) {
123
146
  let j = i + 1;
124
147
  while (j < n && isWordPart(src[j])) j++;
148
+ if (src[j] === "[") {
149
+ const end = bracketsEnd(src, j);
150
+ out.push({
151
+ kind: "sref",
152
+ text: src.slice(i, end)
153
+ });
154
+ i = end;
155
+ continue;
156
+ }
125
157
  out.push({
126
158
  kind: "word",
127
159
  text: src.slice(i, j)
@@ -129,6 +161,15 @@ function tokenize(src) {
129
161
  i = j;
130
162
  continue;
131
163
  }
164
+ if (ch === "[") {
165
+ const end = bracketsEnd(src, i);
166
+ out.push({
167
+ kind: "sref",
168
+ text: src.slice(i, end)
169
+ });
170
+ i = end;
171
+ continue;
172
+ }
132
173
  if (ch === "<" && src[i + 1] === "=") {
133
174
  out.push({
134
175
  kind: "op",
@@ -15,6 +15,19 @@ export interface CellRef {
15
15
  readonly col: Axis;
16
16
  readonly row: Axis;
17
17
  }
18
+ /** A table's part a structured reference names (§18.17.6.4); none named ⇒ `#Data`. */
19
+ export type TableArea = '#All' | '#Data' | '#Headers' | '#Totals' | '#This Row';
20
+ /**
21
+ * §18.17.6.4 — a structured reference: the table (empty for the one the cell
22
+ * is in), the parts of it, and the columns, first to last (all of them when
23
+ * undefined). `tblIncome[Amount]` is `{ table: 'tblIncome', areas: [],
24
+ * columns: ['Amount', 'Amount'] }`.
25
+ */
26
+ export interface StructuredRef {
27
+ readonly table: string;
28
+ readonly areas: ReadonlyArray<TableArea>;
29
+ readonly columns?: readonly [string, string];
30
+ }
18
31
  /** The formula syntax tree — a discriminated union over the node kind `k`. */
19
32
  export type Ast = {
20
33
  readonly k: 'num';
@@ -40,6 +53,9 @@ export type Ast = {
40
53
  } | {
41
54
  readonly k: 'name';
42
55
  readonly name: string;
56
+ } | {
57
+ readonly k: 'sref';
58
+ readonly ref: StructuredRef;
43
59
  } | {
44
60
  readonly k: 'array';
45
61
  readonly rows: ReadonlyArray<ReadonlyArray<Ast>>;
@@ -75,3 +91,12 @@ export declare class ParseError extends Error {
75
91
  * @throws ParseError on a malformed formula (or {@link LexError} from tokenizing).
76
92
  */
77
93
  export declare function parse(src: string): Ast;
94
+ /**
95
+ * §18.17.6.4 — read a structured reference's text: `Table[Col]`, `Table[]`,
96
+ * `Table[#Totals]`, `Table[[#Headers],[A]:[C]]`, `[@Col]`, `[@[Col 2]]`.
97
+ *
98
+ * @param text The token: the table's name, if any, and its brackets.
99
+ * @returns The reference.
100
+ * @throws ParseError when the brackets name nothing a table has.
101
+ */
102
+ export declare function parseStructuredRef(text: string): StructuredRef;
@@ -148,6 +148,10 @@ var Parser = class {
148
148
  case "sheetq":
149
149
  this.expect("!");
150
150
  return this.parseSheetCell(t.text);
151
+ case "sref": return {
152
+ k: "sref",
153
+ ref: parseStructuredRef(t.text)
154
+ };
151
155
  case "op":
152
156
  if (t.text === "(") {
153
157
  const e = this.parseExpr(0);
@@ -323,5 +327,96 @@ function columnToIndex(letters) {
323
327
  for (let i = 0; i < up.length; i++) idx = idx * 26 + (up.charCodeAt(i) - 64);
324
328
  return idx - 1;
325
329
  }
330
+ var AREAS = new Map([
331
+ ["#all", "#All"],
332
+ ["#data", "#Data"],
333
+ ["#headers", "#Headers"],
334
+ ["#totals", "#Totals"],
335
+ ["#this row", "#This Row"]
336
+ ]);
337
+ /**
338
+ * §18.17.6.4 — read a structured reference's text: `Table[Col]`, `Table[]`,
339
+ * `Table[#Totals]`, `Table[[#Headers],[A]:[C]]`, `[@Col]`, `[@[Col 2]]`.
340
+ *
341
+ * @param text The token: the table's name, if any, and its brackets.
342
+ * @returns The reference.
343
+ * @throws ParseError when the brackets name nothing a table has.
344
+ */
345
+ function parseStructuredRef(text) {
346
+ const open = text.indexOf("[");
347
+ const table = text.slice(0, open);
348
+ let inner = text.slice(open + 1, -1).trim();
349
+ const areas = [];
350
+ let columns;
351
+ if (inner.startsWith("@")) {
352
+ areas.push("#This Row");
353
+ inner = inner.slice(1).trim();
354
+ if (inner.length > 0 && !inner.startsWith("[")) inner = `[${inner}]`;
355
+ } else if (inner.length > 0 && !inner.startsWith("[")) inner = `[${inner}]`;
356
+ for (const item of splitTopLevel(inner)) {
357
+ const range = splitRange(item);
358
+ if (range) {
359
+ columns = [unescapeName(range[0]), unescapeName(range[1])];
360
+ continue;
361
+ }
362
+ const body = item.slice(1, -1);
363
+ const area = AREAS.get(body.trim().toLowerCase());
364
+ if (area) areas.push(area);
365
+ else {
366
+ const name = unescapeName(body);
367
+ columns = [name, name];
368
+ }
369
+ }
370
+ return {
371
+ table,
372
+ areas,
373
+ ...columns ? { columns } : {}
374
+ };
375
+ }
376
+ /** The bracket groups of a list `[a],[b]:[c]`, each with its `:` partner. */
377
+ function splitTopLevel(inner) {
378
+ const out = [];
379
+ let depth = 0;
380
+ let start = 0;
381
+ for (let i = 0; i < inner.length; i++) {
382
+ const c = inner[i];
383
+ if (c === "'") {
384
+ i++;
385
+ continue;
386
+ }
387
+ if (c === "[") depth++;
388
+ else if (c === "]") depth--;
389
+ else if (c === "," && depth === 0) {
390
+ out.push(inner.slice(start, i).trim());
391
+ start = i + 1;
392
+ }
393
+ }
394
+ const last = inner.slice(start).trim();
395
+ if (last.length > 0) out.push(last);
396
+ for (const item of out) if (!item.startsWith("[") || !item.endsWith("]")) throw new ParseError(`bad structured reference part ${item}`);
397
+ return out;
398
+ }
399
+ /** `[A]:[C]` → the two columns' bracketed bodies, or undefined for one group. */
400
+ function splitRange(item) {
401
+ let depth = 0;
402
+ for (let i = 0; i < item.length; i++) {
403
+ const c = item[i];
404
+ if (c === "'") {
405
+ i++;
406
+ continue;
407
+ }
408
+ if (c === "[") depth++;
409
+ else if (c === "]") depth--;
410
+ else if (c === ":" && depth === 0) {
411
+ const a = item.slice(0, i).trim();
412
+ const b = item.slice(i + 1).trim();
413
+ return [a.slice(1, -1), b.slice(1, -1)];
414
+ }
415
+ }
416
+ }
417
+ /** A column name as written in a reference, its `'` escapes undone. */
418
+ function unescapeName(name) {
419
+ return name.trim().replace(/'(.)/g, "$1");
420
+ }
326
421
  //#endregion
327
422
  export { parse };
@@ -1,7 +1,8 @@
1
- import { BodyElement, SectionProperties, Table } from '../core/document-model/index.js';
1
+ import { BodyElement, CellNoteFlag, SectionProperties, Table } from '../core/document-model/index.js';
2
2
  import { CellRange, DefinedName, ParsedWorksheet, SheetRichRun, WorksheetCell, XlsxStyles } from './index.js';
3
3
  import { SheetHyperlink, SheetSlicer } from '../core/ir/sheet.js';
4
4
  import { Loss } from '../core/ir/loss.js';
5
+ import { DrawingBox } from './column-bands.js';
5
6
  /**
6
7
  * Excel "character width" → twips: the default font's Maximum Digit Width,
7
8
  * ~7 px at 96 DPI ≈ 5.25 pt ≈ 105 twips.
@@ -36,11 +37,37 @@ export declare const COL_PADDING_TWIPS = 75;
36
37
  * @returns The rendered column width in twips.
37
38
  */
38
39
  export declare function columnTwips(chars: number, charTwips: number): number;
40
+ /**
41
+ * §18.3.1.81 — the DEFAULT column, in twips: the sheet's `defaultColWidth`
42
+ * where it declares one — a stored width, padding included — and otherwise
43
+ * `baseColWidth` digits and their padding, rounded up to Excel's step of eight
44
+ * pixels. Padding a `baseColWidth` twice once stood in for that rounding: it
45
+ * gives 47668.xlsx's 80px as well, and nothing else.
46
+ *
47
+ * @param worksheet The sheet.
48
+ * @param charTwips The Maximum Digit Width, in twips.
49
+ * @returns The default column width in twips.
50
+ */
51
+ export declare function defaultColumnTwips(worksheet: ParsedWorksheet, charTwips: number): number;
39
52
  /**
40
53
  * Excel's default column width is 8.43 "characters" ≈ 64px ≈ 960 twips. Used for
41
54
  * columns without an explicit `<col width="..">`.
42
55
  */
43
56
  export declare const DEFAULT_COL_TWIPS = 960;
57
+ /**
58
+ * §18.3.1.13 — the unit a sheet's columns are measured in: the Maximum Digit
59
+ * Width of the workbook's Normal font, in twips — the font's own where the
60
+ * workbook names it, the face we render with where it names none. A drawing's
61
+ * anchor counts columns in the same unit, or it lands beside the cells it is
62
+ * anchored to: 45540_form_Footer.xlsx sets its form in 宋体 12, whose digit
63
+ * is 8px, and anchored at Excel's 7px its check boxes stood a column off
64
+ * their labels.
65
+ *
66
+ * @param styles The workbook's styles (its first font is the Normal style's).
67
+ * @param digitWidthPt The render font's digit width, for a workbook that names no font.
68
+ * @returns The unit, in twips.
69
+ */
70
+ export declare function columnUnitTwips(styles: XlsxStyles, digitWidthPt?: number): number;
44
71
  /**
45
72
  * The row height a sheet that declares none inherits from the workbook's
46
73
  * Normal font. Sizes outside Excel's table scale from its 10pt entry and snap
@@ -94,6 +121,8 @@ export declare function printableWidthPt(worksheet: ParsedWorksheet): number;
94
121
  */
95
122
  export declare function printableHeightPt(worksheet: ParsedWorksheet): number;
96
123
  interface PrintModelOptions {
124
+ readonly screen?: boolean;
125
+ readonly image?: boolean;
97
126
  readonly printArea?: CellRange;
98
127
  readonly gridLines: boolean;
99
128
  readonly titleRows?: {
@@ -114,10 +143,16 @@ interface PrintModelOptions {
114
143
  readonly bandSink?: {
115
144
  lefts: Array<number>;
116
145
  };
146
+ readonly headingSink?: {
147
+ dxPt: number;
148
+ dyPt: number;
149
+ };
150
+ readonly noteFlags?: ReadonlyMap<string, CellNoteFlag>;
117
151
  readonly drawingExtentPt?: {
118
152
  readonly widthPt: number;
119
153
  readonly heightPt: number;
120
154
  };
155
+ readonly drawingBoxesPt?: ReadonlyArray<DrawingBox>;
121
156
  readonly losses?: Array<Loss>;
122
157
  }
123
158
  /**
@@ -149,6 +184,8 @@ export declare function worksheetToBody(worksheet: ParsedWorksheet, sharedString
149
184
  * @returns The resolved display string.
150
185
  */
151
186
  export declare function resolveCellText(cell: WorksheetCell, sharedStrings: ReadonlyArray<string>, styles: XlsxStyles, date1904: boolean): string;
187
+ /** The key {@link PrintModelOptions.noteFlags} is looked up by: absolute row and column. */
188
+ export declare function noteKey(row: number, col: number): string;
152
189
  /**
153
190
  * Whether an EMPTY cell still draws something of its own — a fill or a border.
154
191
  *