reamkit 1.31.0 → 1.32.1

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 (98) hide show
  1. package/README.md +10 -0
  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 +1 -0
  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 +56 -12
  10. package/dist/esm/core/document-model/index.d.ts +1 -1
  11. package/dist/esm/core/document-model/types.d.ts +112 -0
  12. package/dist/esm/core/drawingml/chart-geometry.d.ts +36 -13
  13. package/dist/esm/core/drawingml/chart-geometry.js +372 -146
  14. package/dist/esm/core/drawingml/chart-parser.d.ts +19 -0
  15. package/dist/esm/core/drawingml/chart-parser.js +126 -18
  16. package/dist/esm/core/drawingml/colors.d.ts +6 -0
  17. package/dist/esm/core/drawingml/colors.js +23 -3
  18. package/dist/esm/core/drawingml/shape-render.d.ts +3 -1
  19. package/dist/esm/core/drawingml/shape-render.js +4 -2
  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/fonts/families.js +4 -2
  23. package/dist/esm/core/fonts/scripts.js +4 -2
  24. package/dist/esm/core/ir/flow.d.ts +6 -0
  25. package/dist/esm/core/ir/sheet.d.ts +43 -0
  26. package/dist/esm/core/number-format.d.ts +40 -15
  27. package/dist/esm/core/number-format.js +295 -83
  28. package/dist/esm/core/opc/tag-scan.d.ts +15 -0
  29. package/dist/esm/core/opc/tag-scan.js +41 -0
  30. package/dist/esm/core/opc/xml-entities.js +39 -2
  31. package/dist/esm/core/spreadsheet-model/index.d.ts +1 -1
  32. package/dist/esm/core/spreadsheet-model/types.d.ts +96 -0
  33. package/dist/esm/core/style-cascade/resolver.js +4 -2
  34. package/dist/esm/excel/column-bands.d.ts +39 -6
  35. package/dist/esm/excel/column-bands.js +41 -17
  36. package/dist/esm/excel/comments-parser.js +19 -0
  37. package/dist/esm/excel/conditional-format.d.ts +7 -3
  38. package/dist/esm/excel/conditional-format.js +77 -39
  39. package/dist/esm/excel/formula/context.d.ts +18 -0
  40. package/dist/esm/excel/formula/eval.js +51 -0
  41. package/dist/esm/excel/formula/functions.js +37 -14
  42. package/dist/esm/excel/formula/index.d.ts +2 -2
  43. package/dist/esm/excel/formula/lexer.d.ts +1 -1
  44. package/dist/esm/excel/formula/lexer.js +41 -0
  45. package/dist/esm/excel/formula/parser.d.ts +25 -0
  46. package/dist/esm/excel/formula/parser.js +95 -0
  47. package/dist/esm/excel/print-model.d.ts +38 -1
  48. package/dist/esm/excel/print-model.js +536 -83
  49. package/dist/esm/excel/shared-strings-parser.d.ts +9 -0
  50. package/dist/esm/excel/shared-strings-parser.js +11 -1
  51. package/dist/esm/excel/sheet-drawing.d.ts +38 -2
  52. package/dist/esm/excel/sheet-drawing.js +47 -13
  53. package/dist/esm/excel/sheet-shape-parser.d.ts +6 -2
  54. package/dist/esm/excel/sheet-shape-parser.js +18 -6
  55. package/dist/esm/excel/sheet-to-flow.d.ts +17 -0
  56. package/dist/esm/excel/sheet-to-flow.js +434 -47
  57. package/dist/esm/excel/styles-parser.d.ts +9 -0
  58. package/dist/esm/excel/styles-parser.js +80 -26
  59. package/dist/esm/excel/table-parser.js +5 -0
  60. package/dist/esm/excel/table-style-presets.d.ts +26 -0
  61. package/dist/esm/excel/table-style-presets.js +1555 -0
  62. package/dist/esm/excel/table-style.d.ts +13 -0
  63. package/dist/esm/excel/table-style.js +75 -0
  64. package/dist/esm/excel/tint.d.ts +10 -0
  65. package/dist/esm/excel/tint.js +75 -0
  66. package/dist/esm/excel/vml-drawing.d.ts +41 -3
  67. package/dist/esm/excel/vml-drawing.js +144 -11
  68. package/dist/esm/excel/worksheet-parser.js +89 -15
  69. package/dist/esm/excel/xls/biff-reader.js +10 -2
  70. package/dist/esm/excel/xlsx-reader.js +234 -19
  71. package/dist/esm/excel/xlsx-writer.js +8 -7
  72. package/dist/esm/html/html-writer.js +202 -20
  73. package/dist/esm/layout/fit-pages.d.ts +11 -0
  74. package/dist/esm/layout/fit-pages.js +86 -0
  75. package/dist/esm/layout/line-paint.d.ts +58 -0
  76. package/dist/esm/layout/line-paint.js +107 -0
  77. package/dist/esm/layout/page-doc.d.ts +51 -0
  78. package/dist/esm/layout/page-doc.js +141 -1
  79. package/dist/esm/layout/styled-layout.d.ts +7 -0
  80. package/dist/esm/layout/styled-layout.js +275 -52
  81. package/dist/esm/layout/turned-section.js +14 -2
  82. package/dist/esm/markdown/markdown-writer.js +75 -13
  83. package/dist/esm/pdf/styled-page-emitter.js +49 -71
  84. package/dist/esm/pdf/styled-page-renderer.js +1 -1
  85. package/dist/esm/pdf-reader/annot-draw.js +2 -2
  86. package/dist/esm/pdf-reader/annots.js +3 -1
  87. package/dist/esm/pdf-reader/font.js +4 -4
  88. package/dist/esm/pdf-reader/layout.js +1 -1
  89. package/dist/esm/svg/svg-text.d.ts +58 -0
  90. package/dist/esm/svg/svg-text.js +322 -0
  91. package/dist/esm/svg/svg-writer.js +135 -71
  92. package/dist/esm/word/document-parser.js +1 -1
  93. package/dist/esm/word/docx-reader.js +13 -8
  94. package/dist/esm/word/docx-to-pdf.js +3 -3
  95. package/dist/esm/word/docx-writer.js +11 -2
  96. package/dist/esm/word/font-table.js +89 -10
  97. package/dist/esm/word/numbering-parser.js +1 -1
  98. package/package.json +7 -3
@@ -1,5 +1,6 @@
1
1
  import { eighthPtToPt, halfPtToPt, pt, twipsToPt } from "../core/ir/units.js";
2
2
  import { FEATURES } from "../core/ir/features.js";
3
+ import { firstStrongDirection } from "../core/bidi/index.js";
3
4
  import { applyNumberFormat, generalToWidth, numberFormatColorHex } from "../core/number-format.js";
4
5
  import { parseAreaRef, parseTitleRowRange } from "./defined-name-ref.js";
5
6
  import "./index.js";
@@ -35,27 +36,67 @@ function columnTwips(chars, charTwips) {
35
36
  return columnTwipsOf(chars, charTwips);
36
37
  }
37
38
  /**
38
- * §18.3.1.81 — the DEFAULT column, in twips, from whichever of the two the
39
- * sheet declares.
39
+ * How far the first `count` rows (or columns) of a sheet reach, in twips: each
40
+ * the default unless the sheet gives it a size of its own.
40
41
  *
41
- * The two are not the same number. `defaultColWidth` "includes margin padding
42
- * and extra padding for gridlines"; `baseColWidth` is the bare character count
43
- * and explicitly excludes them, so deriving a default from it adds the padding
44
- * once to reach a defaultColWidth and once more to render it. 47668.xlsx says
45
- * `baseColWidth="10"` and caches its picture's extent at 9753600 EMU = 768pt
46
- * over 12 columns plus 48pt — 60pt, or 80px, a column. Padding it once gives
47
- * 75px and squeezed that picture by 6%. LibreOffice measures ~79px here, so
48
- * both references agree against us.
42
+ * @param count How many tracks, from the first.
43
+ * @param defaultTwips The size of a track the sheet says nothing about.
44
+ * @param sized The tracks it does size, by 0-based index.
45
+ * @returns Their total.
46
+ */
47
+ /**
48
+ * §20.5 — the part of the sheet a printed table shows, for the drawings over
49
+ * it (TableOverlay); empty until the drawings are put in. Nothing on a screen,
50
+ * which draws them over the whole sheet as it is.
51
+ *
52
+ * @param sheetLeftPt Where the table's first column stands on the sheet.
53
+ * @param columns The widths of the sheet's columns it prints.
54
+ * @param leftPt Where those begin in the table — past a row-number column.
55
+ * @returns The frame to spread into the table, or nothing.
56
+ */
57
+ function sheetFrame(sheetLeftPt, columns, leftPt) {
58
+ if (sheetLeftPt === void 0) return {};
59
+ return { overlay: {
60
+ sheetLeftPt: pt(sheetLeftPt),
61
+ widthPt: pt(columns.reduce((sum, w) => sum + w, 0)),
62
+ leftPt: pt(leftPt),
63
+ drawings: []
64
+ } };
65
+ }
66
+ /** The drawings' boxes at the print scale. */
67
+ function scaledBoxes(boxes, scale) {
68
+ return (boxes ?? []).map((b) => ({
69
+ xPt: b.xPt * scale,
70
+ yPt: b.yPt * scale,
71
+ widthPt: b.widthPt * scale,
72
+ heightPt: b.heightPt * scale
73
+ }));
74
+ }
75
+ function tracksBeforeTwips(count, defaultTwips, sized) {
76
+ let total = count * defaultTwips;
77
+ const seen = /* @__PURE__ */ new Set();
78
+ for (const t of sized) {
79
+ if (t.index >= count || seen.has(t.index)) continue;
80
+ seen.add(t.index);
81
+ total += t.twips - defaultTwips;
82
+ }
83
+ return total;
84
+ }
85
+ /**
86
+ * §18.3.1.81 — the DEFAULT column, in twips: the sheet's `defaultColWidth`
87
+ * where it declares one — a stored width, padding included — and otherwise
88
+ * `baseColWidth` digits and their padding, rounded up to Excel's step of eight
89
+ * pixels. Padding a `baseColWidth` twice once stood in for that rounding: it
90
+ * gives 47668.xlsx's 80px as well, and nothing else.
49
91
  *
50
- * @param worksheet The sheet.
51
- * @param charTwips The Maximum Digit Width, in twips.
52
- * @param fallbackChars The width to use when the sheet declares neither.
92
+ * @param worksheet The sheet.
93
+ * @param charTwips The Maximum Digit Width, in twips.
53
94
  * @returns The default column width in twips.
54
95
  */
55
- function defaultColumnTwips(worksheet, charTwips, fallbackChars) {
96
+ function defaultColumnTwips(worksheet, charTwips) {
56
97
  if (worksheet.defaultColWidthChars !== void 0) return columnTwipsOf(worksheet.defaultColWidthChars, charTwips);
57
- if (worksheet.baseColWidthChars !== void 0) return columnTwipsOf(worksheet.baseColWidthChars, charTwips) + 150;
58
- return columnTwipsOf(fallbackChars, charTwips);
98
+ const px = ((worksheet.baseColWidthChars ?? DEFAULT_BASE_COL_CHARS) * charTwips + 75) / TWIPS_PER_PIXEL;
99
+ return Math.ceil(px / 8) * 8 * TWIPS_PER_PIXEL;
59
100
  }
60
101
  function columnTwipsOf(chars, charTwips) {
61
102
  const mdwPx = charTwips / TWIPS_PER_PIXEL;
@@ -68,14 +109,8 @@ function columnTwipsOf(chars, charTwips) {
68
109
  * four times as much.
69
110
  */
70
111
  var EXCEL_CELL_INSET_PT = 1.5;
71
- /**
72
- * The width behind {@link DEFAULT_COL_TWIPS}, for a non-Calibri unit.
73
- *
74
- * In the STORED unit, which is what {@link columnTwips} reads: Excel's default
75
- * column is "8.43 characters" in its own interface and `width="9.140625"` in
76
- * every file that writes it out, because the stored form carries the padding.
77
- */
78
- var DEFAULT_COL_CHARS = 9.140625;
112
+ /** §18.3.1.81 `baseColWidth` — the digits a default column holds when the sheet does not say. */
113
+ var DEFAULT_BASE_COL_CHARS = 8;
79
114
  /**
80
115
  * The column-width unit in twips: the Maximum Digit Width of the font the
81
116
  * document is rendered in (§18.3.1.13), or Excel's own 7 px when the caller has
@@ -89,12 +124,8 @@ var DEFAULT_COL_CHARS = 9.140625;
89
124
  * are listed beside their originals because a file naming one is measured in
90
125
  * the other's unit by every reader that has to substitute.
91
126
  *
92
- * Every entry here is measured. The CJK defaults are deliberately absent: the
93
- * evidence says MS Pゴシック 11 is 8px — Japanese Excel's default column is
94
- * 72px, and §18.3.1.13's forward formula turns that into exactly the
95
- * `width="9"` 54524.xlsx writes for its default band — but I have no copy of
96
- * the face to measure, and fitting 0.55 to it moved 50299.xlsx from 16 pages
97
- * to 22 against LibreOffice's 17. A face we cannot measure keeps the old 7px.
127
+ * Every Latin entry here is measured. A third number, where there is one, is
128
+ * what Excel adds in whole pixels to the digit it measures (see the CJK entry).
98
129
  */
99
130
  var DIGIT_EM = [
100
131
  [/^(calibri|carlito)$/i, 1063 / 2048],
@@ -102,9 +133,45 @@ var DIGIT_EM = [
102
133
  [/^(times new roman|liberation serif|tinos)$/i, 1024 / 2048],
103
134
  [/^(tahoma)$/i, 1118 / 2048],
104
135
  [/^(verdana|dejavu sans)$/i, 1303 / 2048],
105
- [/* @__PURE__ */ new RegExp("^(新細明體|細明體|pmingliu|mingliu|標楷體|dfkai-sb|宋体|新宋体|simsun|nsimsun|黑体|simhei|微软雅黑|microsoft yahei|微軟正黑體|microsoft jhenghei|MS [P]?明朝|MS [P]?ゴシック|ms [p]?mincho|ms [p]?gothic|meiryo|メイリオ|游ゴシック|yu gothic|굴림체?|gulim(che)?|돋움체?|dotum(che)?|바탕체?|batang(che)?|맑은 고딕|malgun gothic)$", "iu"), 1024 / 2048]
136
+ [
137
+ /* @__PURE__ */ new RegExp("^(新細明體|細明體|pmingliu|mingliu|標楷體|dfkai-sb|宋体|新宋体|simsun|nsimsun|黑体|simhei|微软雅黑|microsoft yahei|微軟正黑體|microsoft jhenghei|MS [P]?明朝|MS [P]?ゴシック|ms [p]?mincho|ms [p]?gothic|meiryo|メイリオ|游ゴシック|yu gothic|굴림체?|gulim(che)?|돋움체?|dotum(che)?|바탕체?|batang(che)?|맑은 고딕|malgun gothic)$", "iu"),
138
+ 1024 / 2048,
139
+ 1
140
+ ]
106
141
  ];
107
142
  /**
143
+ * The space advance, as a fraction of the em, of the faces a spreadsheet names
144
+ * for its normal style — §18.8.1 measures a cell's indent in them. Read off
145
+ * each face's `hmtx` for U+0020, the metric twins beside their originals; a
146
+ * face not listed takes a quarter em, which is where most spaces sit.
147
+ */
148
+ var SPACE_EM = [
149
+ [/^(calibri|carlito)$/i, 463 / 2048],
150
+ [/^(arial|helvetica|liberation sans|arimo|arial unicode ms)$/i, 569 / 2048],
151
+ [/^(times new roman|liberation serif|tinos)$/i, 512 / 2048],
152
+ [/^(cambria|caladea)$/i, 220 / 1e3],
153
+ [/^(tahoma)$/i, 640 / 2048],
154
+ [/^(verdana|dejavu sans)$/i, 720 / 2048]
155
+ ];
156
+ /**
157
+ * One level of §18.8.1's `indent`, in twips: "an increment of 1 represents 3
158
+ * spaces … of the normal style font". It was counted in DIGITS, three of them,
159
+ * which is twice as far — simple-monthly-budget.xlsx indents its "Amount"
160
+ * headings two levels and we set them 31.5pt in where LibreOffice sets them
161
+ * 15pt (it measures 7.49pt a level in Calibri 11, 8.39pt in Arial 10: three
162
+ * spaces of each).
163
+ *
164
+ * @param styles The workbook's styles; the first font is the normal style's.
165
+ * @returns The width of one indent level, in twips.
166
+ */
167
+ function indentLevelTwips(styles) {
168
+ const font = styles.fonts[0];
169
+ const size = font?.sizePt !== void 0 && Number.isFinite(font.sizePt) && font.sizePt > 0 ? font.sizePt : DEFAULT_FONT_PT;
170
+ const name = font?.name?.trim() ?? "";
171
+ const em = SPACE_EM.find(([re]) => re.test(name))?.[1] ?? .25;
172
+ return Math.round(3 * em * size * TWIPS_PER_POINT);
173
+ }
174
+ /**
108
175
  * §18.3.1.13 — the Maximum Digit Width, in twips, of the normal style's font.
109
176
  *
110
177
  * "The number of characters of the maximum digit width of the numbers 0, 1,
@@ -123,11 +190,29 @@ var DIGIT_EM = [
123
190
  */
124
191
  function maximumDigitTwips(name, sizePt) {
125
192
  const size = sizePt !== void 0 && Number.isFinite(sizePt) && sizePt > 0 ? sizePt : 11;
126
- const em = DIGIT_EM.find(([re]) => re.test(name.trim()))?.[1];
127
- if (em === void 0) return 105;
128
- const px = Math.trunc(em * size * (96 / 72));
193
+ const face = DIGIT_EM.find(([re]) => re.test(name.trim()));
194
+ if (face === void 0) return 105;
195
+ const [, em, extraPx = 0] = face;
196
+ const px = Math.trunc(em * size * (96 / 72)) + extraPx;
129
197
  return px > 0 ? px * TWIPS_PER_PIXEL : 105;
130
198
  }
199
+ /**
200
+ * §18.3.1.13 — the unit a sheet's columns are measured in: the Maximum Digit
201
+ * Width of the workbook's Normal font, in twips — the font's own where the
202
+ * workbook names it, the face we render with where it names none. A drawing's
203
+ * anchor counts columns in the same unit, or it lands beside the cells it is
204
+ * anchored to: 45540_form_Footer.xlsx sets its form in 宋体 12, whose digit
205
+ * is 8px, and anchored at Excel's 7px its check boxes stood a column off
206
+ * their labels.
207
+ *
208
+ * @param styles The workbook's styles (its first font is the Normal style's).
209
+ * @param digitWidthPt The render font's digit width, for a workbook that names no font.
210
+ * @returns The unit, in twips.
211
+ */
212
+ function columnUnitTwips(styles, digitWidthPt) {
213
+ const font = styles.fonts[0];
214
+ return font?.name === void 0 ? digitTwips(digitWidthPt) : maximumDigitTwips(font.name, font.sizePt);
215
+ }
131
216
  function digitTwips(digitWidthPt) {
132
217
  if (digitWidthPt === void 0 || !Number.isFinite(digitWidthPt) || digitWidthPt <= 0) return 105;
133
218
  return digitWidthPt * TWIPS_PER_POINT;
@@ -398,6 +483,11 @@ function scaleRunFont(props, scale) {
398
483
  };
399
484
  }
400
485
  var PRINT_GRIDLINE_HEX = "C0C0C0";
486
+ var SCREEN_GRIDLINE = {
487
+ style: "single",
488
+ width: pt(.5),
489
+ colorHex: "D4D4D4"
490
+ };
401
491
  var CF_NUMBER_FORMAT_ID = 1000001;
402
492
  var MAX_SHEET_TEXT_CHARS = 1e6;
403
493
  /**
@@ -417,8 +507,18 @@ var MAX_SHEET_TEXT_CHARS = 1e6;
417
507
  * blank or empty-print-area sheet.
418
508
  */
419
509
  function worksheetToBody(worksheet, sharedStrings, styles, date1904, print) {
510
+ const body = gridBody(worksheet, sharedStrings, styles, date1904, print);
511
+ if (worksheet.rightToLeft !== true) return body;
512
+ return body.map((el) => el.kind === "table" ? {
513
+ ...el,
514
+ table: mirrorTable(el.table, print.screen === true)
515
+ } : el);
516
+ }
517
+ function gridBody(worksheet, sharedStrings, styles, date1904, print) {
420
518
  if (worksheet.maxRow < 0 || worksheet.maxColumn < 0) return [];
421
- const charTwipsUnit = styles.fonts[0]?.name === void 0 ? digitTwips(print.digitWidthPt) : maximumDigitTwips(styles.fonts[0].name, styles.fonts[0].sizePt);
519
+ const rtl = worksheet.rightToLeft === true;
520
+ const indentTwips = indentLevelTwips(styles);
521
+ const charTwipsUnit = columnUnitTwips(styles, print.digitWidthPt);
422
522
  const textTwipsUnit = 105;
423
523
  const rowStyleAt = /* @__PURE__ */ new Map();
424
524
  for (const r of worksheet.rowStyles ?? []) rowStyleAt.set(r.row, r.styleIndex);
@@ -449,7 +549,7 @@ function worksheetToBody(worksheet, sharedStrings, styles, date1904, print) {
449
549
  }
450
550
  if (usedRow >= 0 && usedCol >= 0 && print.drawingExtentPt) {
451
551
  const wantTwips = Math.round(print.drawingExtentPt.widthPt * TWIPS_PER_POINT);
452
- const defTwips = defaultColumnTwips(worksheet, charTwipsUnit, DEFAULT_COL_CHARS);
552
+ const defTwips = defaultColumnTwips(worksheet, charTwipsUnit);
453
553
  const widthAt = (abs) => {
454
554
  for (const col of worksheet.columns) {
455
555
  if (abs < col.min - 1 || abs > col.max - 1) continue;
@@ -462,6 +562,15 @@ function worksheetToBody(worksheet, sharedStrings, styles, date1904, print) {
462
562
  acc += widthAt(col);
463
563
  if (col > usedCol) usedCol = col;
464
564
  }
565
+ const wantRowTwips = Math.round(print.drawingExtentPt.heightPt * TWIPS_PER_POINT);
566
+ const defRowTwips = Math.round((worksheet.defaultRowHeightPt ?? defaultRowHeightPtFor(print.defaultFontPt)) * TWIPS_PER_POINT);
567
+ const heightAt = new Map(worksheet.rowHeights.map((h) => [h.row, h.hidden ? 0 : Math.round(h.heightPt * TWIPS_PER_POINT)]));
568
+ const rowReach = usedRow + PAINT_REACH_ROWS;
569
+ let down = 0;
570
+ for (let row = 0; row <= rowReach && down <= wantRowTwips; row++) {
571
+ down += heightAt.get(row) ?? defRowTwips;
572
+ if (row > usedRow) usedRow = row;
573
+ }
465
574
  }
466
575
  if (usedRow >= 0 && usedCol >= 0) {
467
576
  const colReach = usedCol + PAINT_REACH_COLUMNS;
@@ -476,7 +585,7 @@ function worksheetToBody(worksheet, sharedStrings, styles, date1904, print) {
476
585
  }
477
586
  if (usedRow < 0 && usedCol < 0) {
478
587
  const pageTwips = sheetContentWidthTwips(worksheet);
479
- const defTwips = defaultColumnTwips(worksheet, charTwipsUnit, DEFAULT_COL_CHARS);
588
+ const defTwips = defaultColumnTwips(worksheet, charTwipsUnit);
480
589
  let acc = 0;
481
590
  let colLimit = 0;
482
591
  while (colLimit < PAINT_REACH_COLUMNS && acc < pageTwips) {
@@ -501,7 +610,7 @@ function worksheetToBody(worksheet, sharedStrings, styles, date1904, print) {
501
610
  });
502
611
  return [];
503
612
  }
504
- usedCol += overflowColumnsPastUsedRange(worksheet, usedCol, sharedStrings, styles, date1904, charTwipsUnit);
613
+ usedCol += overflowColumnsPastUsedRange(worksheet, usedCol, sharedStrings, styles, date1904, charTwipsUnit, print.screen === true, print.image === true);
505
614
  let rowStart = 0;
506
615
  let colStart = 0;
507
616
  let rowEnd = usedRow;
@@ -569,7 +678,7 @@ function worksheetToBody(worksheet, sharedStrings, styles, date1904, print) {
569
678
  const cEnd = Math.min(m.endColumn, colWindowEnd);
570
679
  for (let r = Math.max(m.startRow, rowStart); r <= rEnd; r++) for (let c = Math.max(m.startColumn, colStart); c <= cEnd; c++) if (!(r === m.startRow && c === m.startColumn)) insideMerge.add(key(r, c));
571
680
  }
572
- const defaultColTwips = defaultColumnTwips(worksheet, charTwipsUnit, DEFAULT_COL_CHARS);
681
+ const defaultColTwips = defaultColumnTwips(worksheet, charTwipsUnit);
573
682
  const columnWidths = new Array(colCount).fill(defaultColTwips);
574
683
  const hiddenCols = /* @__PURE__ */ new Set();
575
684
  for (const col of worksheet.columns) {
@@ -607,7 +716,7 @@ function worksheetToBody(worksheet, sharedStrings, styles, date1904, print) {
607
716
  if (manualColBreaks.has(c + colStart)) fitColBreaks.add(i);
608
717
  });
609
718
  const drawing = print.drawingExtentPt;
610
- const printScale = computePrintScale(worksheet, Math.max(totalGridTwips, Math.round((drawing?.widthPt ?? 0) * TWIPS_PER_POINT)), sheetContentWidthTwips(worksheet), Math.max(totalGridHeightTwips, Math.round((drawing?.heightPt ?? 0) * TWIPS_PER_POINT)), sheetContentHeightTwips(worksheet), {
719
+ const printScale = print.screen ? 1 : computePrintScale(worksheet, Math.max(totalGridTwips, Math.round((drawing?.widthPt ?? 0) * TWIPS_PER_POINT)), sheetContentWidthTwips(worksheet), Math.max(totalGridHeightTwips, Math.round((drawing?.heightPt ?? 0) * TWIPS_PER_POINT)), sheetContentHeightTwips(worksheet), {
611
720
  rowTwips: fitRowTwips,
612
721
  rowBreaks: fitRowBreaks,
613
722
  titleTwips: fitTitleTwips,
@@ -616,7 +725,20 @@ function worksheetToBody(worksheet, sharedStrings, styles, date1904, print) {
616
725
  });
617
726
  const scaled = printScale < .999;
618
727
  if (print.scaleSink) print.scaleSink.value = printScale;
619
- const breakRows = new Set(honouredBreaks(worksheet, "rows"));
728
+ let sheetTopTwips = print.screen ? void 0 : tracksBeforeTwips(rowStart, defaultRowTwips, worksheet.rowHeights.map((h) => ({
729
+ index: h.row,
730
+ twips: h.hidden ? 0 : Math.round(h.heightPt * TWIPS_PER_POINT)
731
+ }))) * printScale;
732
+ const sheetLeftPt = print.screen ? void 0 : twipsToPt(Math.round(tracksBeforeTwips(colStart, defaultColTwips, worksheet.columns.flatMap((col) => {
733
+ const twips = col.hidden ? 0 : columnTwips(col.widthChars, charTwipsUnit);
734
+ const out = [];
735
+ for (let abs = col.min - 1; abs <= Math.min(col.max - 1, colStart - 1); abs++) out.push({
736
+ index: abs,
737
+ twips
738
+ });
739
+ return out;
740
+ })) * printScale));
741
+ const breakRows = new Set(print.screen ? [] : honouredBreaks(worksheet, "rows"));
620
742
  let textBudget = MAX_SHEET_TEXT_CHARS;
621
743
  let textBudgetReported = false;
622
744
  const runPropsByXf = /* @__PURE__ */ new Map();
@@ -630,17 +752,23 @@ function worksheetToBody(worksheet, sharedStrings, styles, date1904, print) {
630
752
  return props;
631
753
  };
632
754
  const paraPropsByAlignment = /* @__PURE__ */ new Map();
633
- const cellParaProps = (alignment) => {
634
- let props = paraPropsByAlignment.get(alignment);
755
+ const cellParaProps = (alignment, rtlText) => {
756
+ const memoKey = `${alignment ?? ""}|${rtlText ? "rtl" : ""}`;
757
+ let props = paraPropsByAlignment.get(memoKey);
635
758
  if (props === void 0) {
636
- props = alignment ? { alignment } : {};
637
- paraPropsByAlignment.set(alignment, props);
759
+ const stored = rtlText ? crossedOver(alignment) : alignment;
760
+ props = {
761
+ ...stored ? { alignment: stored } : {},
762
+ ...rtlText ? { bidi: true } : {}
763
+ };
764
+ paraPropsByAlignment.set(memoKey, props);
638
765
  }
639
766
  return props;
640
767
  };
641
768
  const cfFormatter = buildConditionalFormatter(worksheet.conditionalFormats, styles, worksheet.cells, (cell) => resolveCellText(cell, sharedStrings, styles, date1904), date1904, print.now, print.sheetGrids, print.sheetName, print.definedNames);
642
769
  const sparklineByCell = buildSparklineLookup(worksheet, print);
643
770
  const tableFormatByCell = buildTableFormatLookup(worksheet);
771
+ const normalColorRef = styles.fonts[0]?.colorRef;
644
772
  const dropdownRanges = listDropdownRanges(worksheet.dataValidations);
645
773
  const bandEndOfCol = /* @__PURE__ */ new Map();
646
774
  {
@@ -653,7 +781,7 @@ function worksheetToBody(worksheet, sharedStrings, styles, date1904, print) {
653
781
  }
654
782
  const wide = worksheet.fitToPage ? worksheet.pageSetup?.fitToWidth ?? 1 : 1;
655
783
  const width = sheetContentWidthTwips(worksheet);
656
- if (colCount > 1 && bandsAcross(worksheet, wide) && (total > width || breaks.size > 0)) for (const band of computeColumnBands(widthsForBands, width, breaks)) for (let i = band.start; i <= band.end; i++) bandEndOfCol.set(visibleCols[i] ?? i, visibleCols[band.end] ?? band.end);
784
+ if (!print.screen && colCount > 1 && bandsAcross(worksheet, wide) && (total > width || breaks.size > 0)) for (const band of computeColumnBands(widthsForBands, width, breaks)) for (let i = band.start; i <= band.end; i++) bandEndOfCol.set(visibleCols[i] ?? i, visibleCols[band.end] ?? band.end);
657
785
  }
658
786
  const rows = [];
659
787
  const rowNumbers = [];
@@ -685,12 +813,23 @@ function worksheetToBody(worksheet, sharedStrings, styles, date1904, print) {
685
813
  }
686
814
  const ws = cellMatrix[r]?.[c];
687
815
  let text = ws ? resolveCellText(ws, sharedStrings, styles, date1904) : "";
816
+ let generalOverflow = false;
688
817
  if (ws?.type === "n" && ws.rawValue.length > 0 && (styles.cellXfs[ws.styleIndex ?? 0]?.numFmtId ?? 0) === 0) {
689
818
  const unit = charTwips(styles.cellXfs[ws.styleIndex ?? 0], styles, textTwipsUnit);
690
819
  const digit = charWidthUnits("0");
691
820
  if (unit > 0 && digit > 0) {
692
- const room = columnWidths[c] / unit;
693
- text = generalToWidth(ws.rawValue, (candidate) => estimateChars(candidate) / digit <= room);
821
+ let widthTwips = columnWidths[c];
822
+ if (merge) {
823
+ widthTwips = 0;
824
+ for (let a = merge.startColumn; a <= Math.min(merge.endColumn, colWindowEnd); a++) {
825
+ const i = a - colStart;
826
+ if (i >= 0 && i < colCount && !hiddenCols.has(i)) widthTwips += columnWidths[i];
827
+ }
828
+ }
829
+ const room = (widthTwips - 75) / unit;
830
+ const fits = (candidate) => estimateChars(candidate) / digit <= room;
831
+ text = generalToWidth(ws.rawValue, fits);
832
+ generalOverflow = !fits(text);
694
833
  }
695
834
  }
696
835
  const cfText = text.length > 0 ? text : void 0;
@@ -709,22 +848,35 @@ function worksheetToBody(worksheet, sharedStrings, styles, date1904, print) {
709
848
  textBudget -= text.length;
710
849
  const xf = ws ? styles.cellXfs[ws.styleIndex ?? 0] : defaultStyleAt(absR, absC, styles.cellXfs);
711
850
  let runProps = cellRunProps(xf);
851
+ let normalColored = (xf ? styles.fonts[xf.fontId] : styles.fonts[0])?.colorRef === normalColorRef;
712
852
  if (ws?.type === "n" && xf?.numFmtId) {
713
853
  const fmtColor = numberFormatColorHex(ws.rawValue, xf.numFmtId, styles.numFmts);
714
- if (fmtColor !== void 0 && fmtColor !== runProps.colorHex) runProps = {
715
- ...runProps,
716
- colorHex: fmtColor
717
- };
854
+ if (fmtColor !== void 0 && fmtColor !== runProps.colorHex) {
855
+ runProps = {
856
+ ...runProps,
857
+ colorHex: fmtColor
858
+ };
859
+ normalColored = false;
860
+ }
718
861
  }
719
- const alignment = alignmentFromXf(xf, ws?.type);
862
+ const alignment = alignmentFromXf(xf, ws?.type, text);
863
+ const flow = flowAlignment(alignment, rtl);
720
864
  let shading = xf ? shadingFromXf(xf, styles) : void 0;
721
865
  const tableFmt = tableFormatByCell.get(key(absR, absC));
722
866
  if (!shading && tableFmt?.shading) shading = tableFmt.shading;
723
- if (tableFmt?.fontColorHex) runProps = {
867
+ if (tableFmt?.fontColorHex && normalColored) runProps = {
724
868
  ...runProps,
725
869
  colorHex: tableFmt.fontColorHex
726
870
  };
871
+ if (tableFmt?.bold && !runProps.bold) runProps = {
872
+ ...runProps,
873
+ bold: true
874
+ };
727
875
  let borders = xf ? scaleBorders(bordersFromXf(xf, styles), printScale) : void 0;
876
+ if (tableFmt?.borders) borders = {
877
+ ...scaleBorders(mapXlsxBorder(tableFmt.borders), printScale),
878
+ ...borders
879
+ };
728
880
  let dataBar;
729
881
  let icon;
730
882
  const sparkline = sparklineByCell.get(key(absR, absC));
@@ -751,7 +903,7 @@ function worksheetToBody(worksheet, sharedStrings, styles, date1904, print) {
751
903
  const rotation = xf?.alignment?.textRotation;
752
904
  const rotated = rotation !== void 0 && rotation !== 0 && text.length > 0;
753
905
  const shrinkToFit = xf?.alignment?.shrinkToFit === true && !wrapText && !rotated;
754
- if (text.length > 0 && !merge && !wrapText && !rotated && !shrinkToFit && ws && (ws.type === "s" || ws.type === "str" || ws.type === "inlineStr") && (alignment === "left" || alignment === "center") && estimateChars(text) * charTwips(xf, styles, textTwipsUnit) > columnWidths[c]) {
906
+ if (text.length > 0 && !merge && !wrapText && !rotated && !shrinkToFit && ws && (ws.type === "s" || ws.type === "str" || ws.type === "inlineStr") && (flow === "left" || flow === "center") && estimateChars(text) * charTwips(xf, styles, textTwipsUnit) > columnWidths[c]) {
755
907
  let availTwips = columnWidths[c];
756
908
  let cc = c + 1;
757
909
  const neighbourIsFree = (col) => !cellHasContent(cellMatrix[r]?.[col]) && !insideMerge.has(key(absR, col + colStart)) && !mergeOrigins.has(key(absR, col + colStart)) && (!cellPaintsSomething(cellMatrix[r]?.[col], styles) || spanPreservesPaint(cellMatrix[r]?.[col], styles, shading, borders)) && !sparklineByCell.has(key(absR, col + colStart)) && !(dropdownRanges.length > 0 && rangesCover(dropdownRanges, absR, col + colStart));
@@ -798,6 +950,7 @@ function worksheetToBody(worksheet, sharedStrings, styles, date1904, print) {
798
950
  };
799
951
  }
800
952
  }
953
+ const noteFlag = print.noteFlags?.get(noteKey(absR, absC));
801
954
  const dropdown = dropdownRanges.length > 0 && rangesCover(dropdownRanges, absR, absC);
802
955
  const href = print.hyperlinks && print.hyperlinks.length > 0 ? hyperlinkUrlAt(print.hyperlinks, absR, absC) : void 0;
803
956
  const visibleEndCol = merge ? Math.min(merge.endColumn, colWindowEnd) : 0;
@@ -820,15 +973,19 @@ function worksheetToBody(worksheet, sharedStrings, styles, date1904, print) {
820
973
  ...overflowRightRule ? { right: overflowRightRule } : {}
821
974
  } } : {},
822
975
  ...dropdown ? { dropdown: true } : {},
976
+ ...noteFlag ? { noteFlag } : {},
823
977
  ...!wrapText && !rotated ? { noWrap: true } : {},
824
- ...!wrapText && !rotated && !shrinkToFit && !merge && ws?.type === "n" && (xf?.numFmtId ?? 0) !== 0 && text.length > 0 && tooWideToShow(text, charTwips(xf, styles, textTwipsUnit), columnWidths[c]) ? { hashOnOverflow: true } : {},
978
+ ...!wrapText && !rotated && !shrinkToFit && !merge && ws?.type === "n" && text.length > 0 && (generalOverflow || (xf?.numFmtId ?? 0) !== 0 && tooWideToShow(text, charTwips(xf, styles, textTwipsUnit), columnWidths[c])) ? { hashOnOverflow: true } : {},
825
979
  verticalAlign: verticalAlignOf(xf)
826
980
  };
827
981
  const indentLevels = xf?.alignment?.indent ?? 0;
828
- const baseParaProps = cellParaProps(alignment);
829
- const paragraphProps = indentLevels > 0 ? {
982
+ const rtlText = textDirection(xf, text) === "rtl";
983
+ const baseParaProps = cellParaProps(alignment, rtlText);
984
+ const indentPt = twipsToPt(indentLevels * indentTwips);
985
+ const indentAtRight = alignment === "right" !== rtlText;
986
+ const paragraphProps = indentLevels > 0 && alignment !== "center" ? {
830
987
  ...baseParaProps,
831
- indentLeft: twipsToPt(indentLevels * 3 * 105)
988
+ ...indentAtRight ? { indentRight: indentPt } : { indentLeft: indentPt }
832
989
  } : baseParaProps;
833
990
  const richRuns = ws && ws.type === "s" && print.sharedStringRuns ? print.sharedStringRuns[Number(ws.rawValue)] : void 0;
834
991
  const cellRuns = richRuns && richRuns.length > 0 ? richRuns.map((rr) => ({
@@ -847,7 +1004,7 @@ function worksheetToBody(worksheet, sharedStrings, styles, date1904, print) {
847
1004
  runs
848
1005
  }
849
1006
  }));
850
- if (text.length > 0 && !merge && !wrapText && !rotated && !shrinkToFit && c > 0 && alignment === "right" && estimateChars(text) * charTwips(xf, styles, textTwipsUnit) > columnWidths[c]) leftOverflow.push({
1007
+ if (text.length > 0 && !merge && !wrapText && !rotated && !shrinkToFit && c > 0 && flow === "right" && estimateChars(text) * charTwips(xf, styles, textTwipsUnit) > columnWidths[c]) leftOverflow.push({
851
1008
  index: cells.length,
852
1009
  column: c,
853
1010
  needTwips: estimateChars(text) * charTwips(xf, styles, textTwipsUnit) - columnWidths[c],
@@ -884,17 +1041,27 @@ function worksheetToBody(worksheet, sharedStrings, styles, date1904, print) {
884
1041
  };
885
1042
  if (isTitleRow && titleRowIndex < 0) titleRowIndex = rows.length;
886
1043
  rowNumbers.push(absR + 1);
887
- rows.push({
1044
+ if (sheetTopTwips === void 0) rows.push({
888
1045
  properties: rowProps,
889
1046
  cells
890
1047
  });
1048
+ else {
1049
+ rows.push({
1050
+ properties: {
1051
+ ...rowProps,
1052
+ sheetTopPt: twipsToPt(sheetTopTwips)
1053
+ },
1054
+ cells
1055
+ });
1056
+ sheetTopTwips += rowHeightTwips ?? defaultRowTwips;
1057
+ }
891
1058
  }
892
1059
  const thin = {
893
1060
  style: "single",
894
1061
  width: eighthPtToPt(2),
895
1062
  colorHex: PRINT_GRIDLINE_HEX
896
1063
  };
897
- const centered = worksheet.printOptions?.horizontalCentered === true;
1064
+ const centered = !print.screen && worksheet.printOptions?.horizontalCentered === true;
898
1065
  const tableProperties = {
899
1066
  defaultCellMargins: {
900
1067
  left: pt(EXCEL_CELL_INSET_PT * printScale),
@@ -903,7 +1070,8 @@ function worksheetToBody(worksheet, sharedStrings, styles, date1904, print) {
903
1070
  bottom: pt(0)
904
1071
  },
905
1072
  layout: "fixed",
906
- ...print.gridLines ? { borders: {
1073
+ ...print.gridLines && print.screen ? { gridlines: SCREEN_GRIDLINE } : {},
1074
+ ...print.gridLines && !print.screen ? { borders: {
907
1075
  top: thin,
908
1076
  bottom: thin,
909
1077
  left: thin,
@@ -914,6 +1082,23 @@ function worksheetToBody(worksheet, sharedStrings, styles, date1904, print) {
914
1082
  ...centered ? { alignment: "center" } : {}
915
1083
  };
916
1084
  const contentWidthTwips = sheetContentWidthTwips(worksheet);
1085
+ const headingColTwips = scaled ? Math.max(1, Math.round(460 * printScale)) : 460;
1086
+ const headings = !print.screen && worksheet.printOptions?.headings ? {
1087
+ dxPt: twipsToPt(headingColTwips),
1088
+ dyPt: (worksheet.defaultRowHeightPt ?? defaultRowHeightPtFor(print.defaultFontPt)) * printScale,
1089
+ band: {
1090
+ colTwips: headingColTwips,
1091
+ ...scaled ? {
1092
+ fontPt: (print.defaultFontPt ?? DEFAULT_FONT_PT) * printScale,
1093
+ lineWidthPt: .5 * printScale
1094
+ } : {}
1095
+ }
1096
+ } : void 0;
1097
+ const reportHeadings = () => {
1098
+ if (!headings || !print.headingSink) return;
1099
+ print.headingSink.dxPt = headings.dxPt;
1100
+ print.headingSink.dyPt = headings.dyPt;
1101
+ };
917
1102
  const colBreaksLocal = /* @__PURE__ */ new Set();
918
1103
  for (const brk of worksheet.colBreaks ?? []) {
919
1104
  const local = brk - colStart;
@@ -922,14 +1107,23 @@ function worksheetToBody(worksheet, sharedStrings, styles, date1904, print) {
922
1107
  const fitWide = worksheet.fitToPage ? worksheet.pageSetup?.fitToWidth ?? 1 : 1;
923
1108
  const bandWidths = scaledColumnWidths(visibleWidths, printScale, scaled);
924
1109
  const bandTotal = bandWidths.reduce((sum, w) => sum + w, 0);
925
- if (colCount > 1 && bandsAcross(worksheet, fitWide) && (bandTotal > contentWidthTwips || colBreaksLocal.size > 0)) {
1110
+ if (!print.screen && colCount > 1 && bandsAcross(worksheet, fitWide) && (bandTotal > contentWidthTwips || colBreaksLocal.size > 0)) {
926
1111
  const bands = computeColumnBands(bandWidths, contentWidthTwips, colBreaksLocal);
927
1112
  if (bands.length > 1) {
928
1113
  if (print.bandSink) print.bandSink.lefts = bands.map((band) => twipsToPt(bandWidths.slice(0, band.start).reduce((sum, w) => sum + w, 0)));
929
- return bandedTables(rows, bandWidths, bands, tableProperties, titleRowIndex, Math.round((print.drawingExtentPt?.widthPt ?? 0) * TWIPS_PER_POINT * printScale), worksheet.printOptions?.headings ? {
930
- colStart,
931
- rowNumbers
1114
+ const banded = bandedTables(rows, bandWidths, bands, tableProperties, titleRowIndex, Math.round((print.drawingExtentPt?.widthPt ?? 0) * TWIPS_PER_POINT * printScale), headings ? {
1115
+ columns: visibleCols.map((c) => c + colStart),
1116
+ rowNumbers,
1117
+ band: {
1118
+ ...headings.band,
1119
+ lettersPt: headings.dyPt
1120
+ }
1121
+ } : void 0, sheetLeftPt !== void 0 ? {
1122
+ sheetLeftPt,
1123
+ drawings: scaledBoxes(print.drawingBoxesPt, printScale)
932
1124
  } : void 0);
1125
+ reportHeadings();
1126
+ return banded;
933
1127
  }
934
1128
  }
935
1129
  const frozen = worksheet.pane && (worksheet.pane.frozenRows > 0 || worksheet.pane.frozenCols > 0) ? {
@@ -939,23 +1133,30 @@ function worksheetToBody(worksheet, sharedStrings, styles, date1904, print) {
939
1133
  if (titleRowIndex > 0 && titleRowIndex < rows.length) {
940
1134
  const titleStart = titleRowIndex;
941
1135
  const grid = bandWidths.map((w) => twipsToPt(w));
1136
+ const overlay = sheetFrame(sheetLeftPt, grid, 0);
942
1137
  return [{
943
1138
  kind: "table",
944
1139
  table: {
945
1140
  properties: tableProperties,
946
1141
  grid,
947
- rows: rows.slice(0, titleStart)
1142
+ rows: rows.slice(0, titleStart),
1143
+ ...overlay
948
1144
  }
949
1145
  }, {
950
1146
  kind: "table",
951
1147
  table: {
952
1148
  properties: tableProperties,
953
1149
  grid,
954
- rows: rows.slice(titleStart)
1150
+ rows: rows.slice(titleStart),
1151
+ ...overlay
955
1152
  }
956
1153
  }];
957
1154
  }
958
- const headed = worksheet.printOptions?.headings ? withHeadingBand(rows, bandWidths, colStart, rowNumbers) : void 0;
1155
+ const headed = headings ? withHeadingBand(rows, bandWidths, visibleCols.map((c) => c + colStart), rowNumbers, {
1156
+ ...headings.band,
1157
+ lettersPt: headings.dyPt
1158
+ }) : void 0;
1159
+ if (headed) reportHeadings();
959
1160
  return [{
960
1161
  kind: "table",
961
1162
  table: {
@@ -964,7 +1165,8 @@ function worksheetToBody(worksheet, sharedStrings, styles, date1904, print) {
964
1165
  frozen
965
1166
  } : tableProperties,
966
1167
  grid: (headed?.widths ?? bandWidths).map((w) => twipsToPt(w)),
967
- rows: headed?.rows ?? rows
1168
+ rows: headed?.rows ?? rows,
1169
+ ...sheetFrame(sheetLeftPt, bandWidths.map((w) => twipsToPt(w)), headed ? twipsToPt(headingColTwips) : 0)
968
1170
  }
969
1171
  }];
970
1172
  }
@@ -1070,20 +1272,49 @@ function applyCfOverride(base, o) {
1070
1272
  * the sheets where it matters most, since a column of figures is the common
1071
1273
  * case.
1072
1274
  */
1073
- function alignmentFromXf(xf, type) {
1275
+ function alignmentFromXf(xf, type, text = "") {
1074
1276
  const explicit = xf?.alignment ? mapAlignment(xf.alignment.horizontal) : void 0;
1075
1277
  if (explicit) return explicit;
1076
- return generalAlignment(type);
1278
+ return generalAlignment(type, textDirection(xf, text));
1077
1279
  }
1078
- function generalAlignment(type) {
1280
+ /**
1281
+ * §18.8.1 General: numbers to the right, logical values centred, and text to
1282
+ * the side it STARTS from — the left for a word that reads from the left, the
1283
+ * right for one that reads from the right. Excel sets "שלום" against the right
1284
+ * edge of a General cell on any sheet, and the sheet's own direction does not
1285
+ * enter into it: the numbers on a right-to-left sheet still sit at the right.
1286
+ */
1287
+ function generalAlignment(type, direction) {
1079
1288
  switch (type) {
1080
1289
  case "n":
1081
1290
  case "d": return "right";
1082
1291
  case "b":
1083
1292
  case "e": return "center";
1084
- default: return "left";
1293
+ default: return direction === "rtl" ? "right" : "left";
1085
1294
  }
1086
1295
  }
1296
+ /**
1297
+ * The direction a cell's text reads in: its own §18.8.1 `readingOrder`, or
1298
+ * else "context" — that of its first strong character.
1299
+ */
1300
+ function textDirection(xf, text) {
1301
+ return xf?.alignment?.readingOrder ?? firstStrongDirection(text) ?? "ltr";
1302
+ }
1303
+ /**
1304
+ * The side a cell's text runs ON to, in the file's own column order. The grid
1305
+ * is built A first whatever the sheet's direction and turned round at the end
1306
+ * (mirrorTable), so on a sheet that reads from the right a cell set against
1307
+ * its right edge is one whose text runs FORWARD — into B, which stands to the
1308
+ * left of A — and the overflow has to reason about the mirror of what the
1309
+ * cell shows.
1310
+ */
1311
+ function flowAlignment(alignment, rtl) {
1312
+ return rtl ? crossedOver(alignment) : alignment;
1313
+ }
1314
+ /** Left for right and right for left; every other alignment is its own mirror. */
1315
+ function crossedOver(alignment) {
1316
+ return alignment === "left" ? "right" : alignment === "right" ? "left" : alignment;
1317
+ }
1087
1318
  function mapAlignment(h) {
1088
1319
  if (!h) return void 0;
1089
1320
  if (h === "left") return "left";
@@ -1268,6 +1499,10 @@ function mapBorderStyle(style) {
1268
1499
  function key(row, col) {
1269
1500
  return `${row},${col}`;
1270
1501
  }
1502
+ /** The key {@link PrintModelOptions.noteFlags} is looked up by: absolute row and column. */
1503
+ function noteKey(row, col) {
1504
+ return key(row, col);
1505
+ }
1271
1506
  function listDropdownRanges(dvs) {
1272
1507
  if (!dvs || dvs.length === 0) return [];
1273
1508
  const out = [];
@@ -1345,6 +1580,147 @@ function collectSeriesValues(cells, area, print) {
1345
1580
  for (let r = area.startRow; r <= area.endRow; r++) for (let col = area.startColumn; col <= area.endColumn; col++) out.push(byKey.get(key(r, col)) ?? null);
1346
1581
  return out;
1347
1582
  }
1583
+ /** §18.8.41 — the order a table style's regions apply in: a later one wins. */
1584
+ var TABLE_STYLE_ORDER = [
1585
+ "wholeTable",
1586
+ "firstColumnStripe",
1587
+ "secondColumnStripe",
1588
+ "firstRowStripe",
1589
+ "secondRowStripe",
1590
+ "lastColumn",
1591
+ "firstColumn",
1592
+ "headerRow",
1593
+ "totalRow",
1594
+ "firstHeaderCell",
1595
+ "lastHeaderCell",
1596
+ "firstTotalCell",
1597
+ "lastTotalCell"
1598
+ ];
1599
+ /**
1600
+ * §18.8.40 — what a table's style makes of each of its cells. Each region
1601
+ * the style formats is laid over the table in the order §18.8.41 gives —
1602
+ * the whole table, its column and row stripes, its last and first columns,
1603
+ * its header and total rows, their corner cells — and a later region's fill,
1604
+ * weight, colour and edges win over an earlier one's. A region's `left`,
1605
+ * `right`, `top` and `bottom` rule its own outer edges, its `vertical` and
1606
+ * `horizontal` the lines between its cells. Stripes run through the data rows
1607
+ * only, in bands of their own size, the first band at the first data row;
1608
+ * the first and last columns, and the stripes, only where the table turns them
1609
+ * on.
1610
+ *
1611
+ * @param t The table, its style resolved.
1612
+ * @param out Where each cell's format goes, by absolute key.
1613
+ */
1614
+ function styledTableFormats(t, out) {
1615
+ const style = t.style;
1616
+ if (!style) return;
1617
+ const { startRow: r0, endRow: r1, startColumn: c0, endColumn: c1 } = t.ref;
1618
+ const dataTop = r0 + t.headerRowCount;
1619
+ const dataBottom = r1 - (t.totalsRowCount ?? 0);
1620
+ const bands = (from, to, first, second, which) => {
1621
+ const spans = [];
1622
+ let at = from;
1623
+ for (let i = 0; at <= to; i++) {
1624
+ const size = i % 2 === 0 ? first : second;
1625
+ if (i % 2 === which) spans.push([at, Math.min(to, at + size - 1)]);
1626
+ at += size;
1627
+ }
1628
+ return spans;
1629
+ };
1630
+ const rowSize = [style.firstRowStripe?.size ?? 1, style.secondRowStripe?.size ?? 1];
1631
+ const colSize = [style.firstColumnStripe?.size ?? 1, style.secondColumnStripe?.size ?? 1];
1632
+ const regionsOf = (type) => {
1633
+ const whole = {
1634
+ top: r0,
1635
+ bottom: r1,
1636
+ left: c0,
1637
+ right: c1
1638
+ };
1639
+ switch (type) {
1640
+ case "wholeTable": return [whole];
1641
+ case "firstRowStripe":
1642
+ case "secondRowStripe": return t.showRowStripes && dataBottom >= dataTop ? bands(dataTop, dataBottom, rowSize[0], rowSize[1], type === "firstRowStripe" ? 0 : 1).map(([top, bottom]) => ({
1643
+ top,
1644
+ bottom,
1645
+ left: c0,
1646
+ right: c1
1647
+ })) : [];
1648
+ case "firstColumnStripe":
1649
+ case "secondColumnStripe": return t.showColumnStripes && dataBottom >= dataTop ? bands(c0, c1, colSize[0], colSize[1], type === "firstColumnStripe" ? 0 : 1).map(([left, right]) => ({
1650
+ top: dataTop,
1651
+ bottom: dataBottom,
1652
+ left,
1653
+ right
1654
+ })) : [];
1655
+ case "lastColumn": return t.showLastColumn ? [{
1656
+ ...whole,
1657
+ left: c1
1658
+ }] : [];
1659
+ case "firstColumn": return t.showFirstColumn ? [{
1660
+ ...whole,
1661
+ right: c0
1662
+ }] : [];
1663
+ case "headerRow": return t.headerRowCount > 0 ? [{
1664
+ ...whole,
1665
+ bottom: dataTop - 1
1666
+ }] : [];
1667
+ case "totalRow": return dataBottom < r1 ? [{
1668
+ ...whole,
1669
+ top: dataBottom + 1
1670
+ }] : [];
1671
+ case "firstHeaderCell": return t.headerRowCount > 0 && t.showFirstColumn ? [{
1672
+ top: r0,
1673
+ bottom: dataTop - 1,
1674
+ left: c0,
1675
+ right: c0
1676
+ }] : [];
1677
+ case "lastHeaderCell": return t.headerRowCount > 0 && t.showLastColumn ? [{
1678
+ top: r0,
1679
+ bottom: dataTop - 1,
1680
+ left: c1,
1681
+ right: c1
1682
+ }] : [];
1683
+ case "firstTotalCell": return dataBottom < r1 && t.showFirstColumn ? [{
1684
+ top: dataBottom + 1,
1685
+ bottom: r1,
1686
+ left: c0,
1687
+ right: c0
1688
+ }] : [];
1689
+ case "lastTotalCell": return dataBottom < r1 && t.showLastColumn ? [{
1690
+ top: dataBottom + 1,
1691
+ bottom: r1,
1692
+ left: c1,
1693
+ right: c1
1694
+ }] : [];
1695
+ }
1696
+ };
1697
+ for (const type of TABLE_STYLE_ORDER) {
1698
+ const dxf = style[type]?.dxf;
1699
+ if (!dxf) continue;
1700
+ const fill = dxf.fill?.patternType === "none" ? void 0 : dxf.fill?.bgColorHex ?? dxf.fill?.fgColorHex;
1701
+ const b = dxf.border;
1702
+ for (const region of regionsOf(type)) for (let r = region.top; r <= region.bottom; r++) for (let c = region.left; c <= region.right; c++) {
1703
+ const k = key(r, c);
1704
+ const was = out.get(k);
1705
+ const edges = { ...was?.borders };
1706
+ const top = r === region.top ? b?.top : b?.horizontal;
1707
+ const bottom = r === region.bottom ? b?.bottom : b?.horizontal;
1708
+ const left = c === region.left ? b?.left : b?.vertical;
1709
+ const right = c === region.right ? b?.right : b?.vertical;
1710
+ if (top) edges.top = top;
1711
+ if (bottom) edges.bottom = bottom;
1712
+ if (left) edges.left = left;
1713
+ if (right) edges.right = right;
1714
+ out.set(k, {
1715
+ ...was,
1716
+ ...fill !== void 0 ? { shading: { colorHex: fill } } : {},
1717
+ ...dxf.font?.colorHex !== void 0 ? { fontColorHex: dxf.font.colorHex } : {},
1718
+ ...dxf.font?.bold !== void 0 ? { bold: dxf.font.bold } : {},
1719
+ ...Object.keys(edges).length > 0 ? { borders: edges } : {}
1720
+ });
1721
+ }
1722
+ }
1723
+ }
1348
1724
  function isPivotTotal(t) {
1349
1725
  return t !== void 0 && t !== "data" && t !== "blank";
1350
1726
  }
@@ -1370,7 +1746,8 @@ function buildTableFormatLookup(worksheet) {
1370
1746
  for (let c = ref.startColumn; c <= ref.endColumn; c++) out.set(key(r, c), fmt);
1371
1747
  }
1372
1748
  };
1373
- for (const t of worksheet.tables ?? []) band(t.ref, t.headerRowCount, t);
1749
+ for (const t of worksheet.tables ?? []) if (t.style) styledTableFormats(t, out);
1750
+ else band(t.ref, t.headerRowCount, t);
1374
1751
  for (const p of worksheet.pivotTables ?? []) band(p.ref, p.firstDataRow, p, (off) => isPivotTotal(p.rowItemTypes?.[off]));
1375
1752
  for (const p of worksheet.pivotTables ?? []) {
1376
1753
  if (!p.colItemTypes || p.headerHex === void 0) continue;
@@ -1466,14 +1843,17 @@ function cellPaintsSomething(cell, styles) {
1466
1843
  }
1467
1844
  /**
1468
1845
  * How many empty columns past the used range the last column's text needs to
1469
- * run into, bounded by the printable width (Excel stops at the page edge too).
1846
+ * run into, bounded by the printable width (Excel stops at the page edge too)
1847
+ * — or, on a `screen`, which has no edge, only by the column cap; an `image`
1848
+ * of the sheet gives it none, its edge being the used range's.
1470
1849
  *
1471
1850
  * Only the last used column can want them — anywhere else the grid already has
1472
1851
  * neighbours. Zero for the overwhelming majority of sheets, which keeps their
1473
1852
  * projection exactly as it was.
1474
1853
  */
1475
- function overflowColumnsPastUsedRange(worksheet, usedCol, sharedStrings, styles, date1904, charTwipsUnit) {
1476
- const defaultTwips = defaultColumnTwips(worksheet, charTwipsUnit, DEFAULT_COL_CHARS);
1854
+ function overflowColumnsPastUsedRange(worksheet, usedCol, sharedStrings, styles, date1904, charTwipsUnit, screen, image) {
1855
+ if (image) return 0;
1856
+ const defaultTwips = defaultColumnTwips(worksheet, charTwipsUnit);
1477
1857
  const widthOf = (abs) => {
1478
1858
  for (const col of worksheet.columns) {
1479
1859
  if (abs < col.min - 1 || abs > col.max - 1) continue;
@@ -1493,16 +1873,16 @@ function overflowColumnsPastUsedRange(worksheet, usedCol, sharedStrings, styles,
1493
1873
  const xf = styles.cellXfs[cell.styleIndex ?? 0];
1494
1874
  const align = xf?.alignment;
1495
1875
  if (align?.wrapText || align?.shrinkToFit || align?.textRotation) continue;
1496
- if (alignmentFromXf(xf, cell.type) !== "left") continue;
1876
+ const text = resolveCellText(cell, sharedStrings, styles, date1904);
1877
+ if (flowAlignment(alignmentFromXf(xf, cell.type, text), worksheet.rightToLeft === true) !== "left") continue;
1497
1878
  let room = 0;
1498
1879
  for (let abs = cell.column; abs <= usedCol; abs++) room += widthOf(abs);
1499
- const text = resolveCellText(cell, sharedStrings, styles, date1904);
1500
1880
  needTwips = Math.max(needTwips, estimateChars(text) * charTwips(xf, styles, 105) - room);
1501
1881
  }
1502
1882
  if (needTwips <= 0) return 0;
1503
1883
  let gridTwips = 0;
1504
1884
  for (let abs = 0; abs <= usedCol; abs++) gridTwips += widthOf(abs);
1505
- const limit = sheetContentWidthTwips(worksheet);
1885
+ const limit = screen ? Infinity : sheetContentWidthTwips(worksheet);
1506
1886
  const MAX_OVERFLOW_COLS = 256;
1507
1887
  let extra = 0;
1508
1888
  while (needTwips > 0 && extra < MAX_OVERFLOW_COLS) {
@@ -1671,6 +2051,79 @@ function absorbLeftwards(cells, cellColumns, candidates, styles, neighbourAt) {
1671
2051
  }
1672
2052
  }
1673
2053
  /**
2054
+ * §18.3.1.87 `rightToLeft` — the table turned round, the way Excel draws a
2055
+ * sheet that reads from the right: column A last in every row and so at the
2056
+ * right edge, and each cell's left and right borders crossed over with it
2057
+ * (its diagonals too). What a cell shows is untouched — its text keeps the
2058
+ * side its alignment names, and its overflow already runs the right way (see
2059
+ * flowAlignment). On paper the sheet stands against the right margin, as
2060
+ * Excel prints it, unless it asks to be centred.
2061
+ *
2062
+ * @param table The grid as built, column A first.
2063
+ * @param screen Whether it is drawn for a window, which has no margin to meet.
2064
+ */
2065
+ function mirrorTable(table, screen) {
2066
+ const { frozen, ...properties } = table.properties;
2067
+ return {
2068
+ ...table,
2069
+ properties: {
2070
+ ...properties,
2071
+ ...frozen && frozen.rows > 0 ? { frozen: {
2072
+ rows: frozen.rows,
2073
+ cols: 0
2074
+ } } : {},
2075
+ ...!screen && properties.alignment !== "center" ? { alignment: "right" } : {}
2076
+ },
2077
+ grid: [...table.grid].reverse(),
2078
+ rows: table.rows.map((row) => ({
2079
+ ...row,
2080
+ cells: [...row.cells].reverse().map(mirrorCell)
2081
+ })),
2082
+ ...table.overlay ? { overlay: {
2083
+ ...table.overlay,
2084
+ leftPt: pt(table.grid.reduce((sum, w) => sum + w, 0) - table.overlay.leftPt - table.overlay.widthPt),
2085
+ mirrored: true
2086
+ } } : {}
2087
+ };
2088
+ }
2089
+ function mirrorCell(cell) {
2090
+ const p = cell.properties;
2091
+ const span = p.colSpan ?? 1;
2092
+ const paintAtEnd = p.paintColumns !== void 0 && p.paintColumns < span;
2093
+ if (!p.borders && !p.dataBar && !p.noteFlag && !paintAtEnd) return cell;
2094
+ return {
2095
+ ...cell,
2096
+ properties: {
2097
+ ...p,
2098
+ ...p.borders ? { borders: mirrorBorders(p.borders) } : {},
2099
+ ...p.dataBar ? { dataBar: mirrorDataBar(p.dataBar) } : {},
2100
+ ...p.noteFlag ? { noteFlag: {
2101
+ ...p.noteFlag,
2102
+ atLeft: !p.noteFlag.atLeft
2103
+ } } : {},
2104
+ ...paintAtEnd ? { paintAtEnd: true } : {}
2105
+ }
2106
+ };
2107
+ }
2108
+ function mirrorBorders(b) {
2109
+ const { left, right, diagonalUp, diagonalDown, ...rest } = b;
2110
+ return {
2111
+ ...rest,
2112
+ ...right ? { left: right } : {},
2113
+ ...left ? { right: left } : {},
2114
+ ...diagonalUp ? { diagonalDown: diagonalUp } : {},
2115
+ ...diagonalDown ? { diagonalUp: diagonalDown } : {}
2116
+ };
2117
+ }
2118
+ function mirrorDataBar(bar) {
2119
+ const start = bar.startFraction ?? 0;
2120
+ return {
2121
+ ...bar,
2122
+ startFraction: Math.max(0, 1 - start - bar.fraction),
2123
+ negative: !bar.negative
2124
+ };
2125
+ }
2126
+ /**
1674
2127
  * Whether spanning a cell's overflowing text across `neighbour` would leave the
1675
2128
  * page looking the same as drawing over it would.
1676
2129
  *
@@ -1805,4 +2258,4 @@ function slicerParagraph(text, runProps) {
1805
2258
  };
1806
2259
  }
1807
2260
  //#endregion
1808
- export { cellPaintsVisibly, columnTwips, printableHeightPt, printableWidthPt, resolveCellText, resolvePrintArea, resolvePrintTitleRows, sectionFromWorksheet, slicerTable, worksheetToBody };
2261
+ export { cellPaintsVisibly, columnTwips, columnUnitTwips, defaultColumnTwips, noteKey, printableHeightPt, printableWidthPt, resolveCellText, resolvePrintArea, resolvePrintTitleRows, sectionFromWorksheet, slicerTable, worksheetToBody };