reamkit 1.31.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 (83) 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/document-model/index.d.ts +1 -1
  10. package/dist/esm/core/document-model/types.d.ts +112 -0
  11. package/dist/esm/core/drawingml/chart-geometry.d.ts +21 -8
  12. package/dist/esm/core/drawingml/chart-geometry.js +198 -108
  13. package/dist/esm/core/drawingml/chart-parser.js +73 -2
  14. package/dist/esm/core/drawingml/colors.d.ts +6 -0
  15. package/dist/esm/core/drawingml/colors.js +23 -3
  16. package/dist/esm/core/drawingml/shape-render.d.ts +3 -1
  17. package/dist/esm/core/drawingml/shape-render.js +4 -2
  18. package/dist/esm/core/font/measure.d.ts +9 -0
  19. package/dist/esm/core/font/measure.js +9 -1
  20. package/dist/esm/core/fonts/families.js +4 -2
  21. package/dist/esm/core/fonts/scripts.js +4 -2
  22. package/dist/esm/core/ir/flow.d.ts +6 -0
  23. package/dist/esm/core/ir/sheet.d.ts +43 -0
  24. package/dist/esm/core/number-format.js +16 -11
  25. package/dist/esm/core/spreadsheet-model/index.d.ts +1 -1
  26. package/dist/esm/core/spreadsheet-model/types.d.ts +96 -0
  27. package/dist/esm/core/style-cascade/resolver.js +4 -2
  28. package/dist/esm/excel/column-bands.d.ts +39 -6
  29. package/dist/esm/excel/column-bands.js +41 -17
  30. package/dist/esm/excel/comments-parser.js +19 -0
  31. package/dist/esm/excel/conditional-format.d.ts +7 -3
  32. package/dist/esm/excel/conditional-format.js +77 -39
  33. package/dist/esm/excel/formula/context.d.ts +18 -0
  34. package/dist/esm/excel/formula/eval.js +51 -0
  35. package/dist/esm/excel/formula/index.d.ts +2 -2
  36. package/dist/esm/excel/formula/lexer.d.ts +1 -1
  37. package/dist/esm/excel/formula/lexer.js +41 -0
  38. package/dist/esm/excel/formula/parser.d.ts +25 -0
  39. package/dist/esm/excel/formula/parser.js +95 -0
  40. package/dist/esm/excel/print-model.d.ts +38 -1
  41. package/dist/esm/excel/print-model.js +522 -80
  42. package/dist/esm/excel/shared-strings-parser.d.ts +9 -0
  43. package/dist/esm/excel/shared-strings-parser.js +11 -1
  44. package/dist/esm/excel/sheet-drawing.d.ts +38 -2
  45. package/dist/esm/excel/sheet-drawing.js +47 -13
  46. package/dist/esm/excel/sheet-shape-parser.d.ts +6 -2
  47. package/dist/esm/excel/sheet-shape-parser.js +18 -6
  48. package/dist/esm/excel/sheet-to-flow.d.ts +17 -0
  49. package/dist/esm/excel/sheet-to-flow.js +434 -47
  50. package/dist/esm/excel/styles-parser.d.ts +9 -0
  51. package/dist/esm/excel/styles-parser.js +80 -26
  52. package/dist/esm/excel/table-parser.js +5 -0
  53. package/dist/esm/excel/table-style-presets.d.ts +26 -0
  54. package/dist/esm/excel/table-style-presets.js +1555 -0
  55. package/dist/esm/excel/table-style.d.ts +13 -0
  56. package/dist/esm/excel/table-style.js +75 -0
  57. package/dist/esm/excel/tint.d.ts +10 -0
  58. package/dist/esm/excel/tint.js +75 -0
  59. package/dist/esm/excel/vml-drawing.d.ts +41 -3
  60. package/dist/esm/excel/vml-drawing.js +143 -10
  61. package/dist/esm/excel/worksheet-parser.js +89 -15
  62. package/dist/esm/excel/xls/biff-reader.js +10 -2
  63. package/dist/esm/excel/xlsx-reader.js +195 -14
  64. package/dist/esm/excel/xlsx-writer.js +8 -7
  65. package/dist/esm/html/html-writer.js +202 -20
  66. package/dist/esm/layout/fit-pages.d.ts +11 -0
  67. package/dist/esm/layout/fit-pages.js +86 -0
  68. package/dist/esm/layout/line-paint.d.ts +58 -0
  69. package/dist/esm/layout/line-paint.js +107 -0
  70. package/dist/esm/layout/page-doc.d.ts +51 -0
  71. package/dist/esm/layout/page-doc.js +141 -1
  72. package/dist/esm/layout/styled-layout.d.ts +7 -0
  73. package/dist/esm/layout/styled-layout.js +274 -51
  74. package/dist/esm/layout/turned-section.js +14 -2
  75. package/dist/esm/markdown/markdown-writer.js +17 -6
  76. package/dist/esm/pdf/styled-page-emitter.js +49 -71
  77. package/dist/esm/pdf/styled-page-renderer.js +1 -1
  78. package/dist/esm/pdf-reader/font.js +3 -3
  79. package/dist/esm/svg/svg-text.d.ts +58 -0
  80. package/dist/esm/svg/svg-text.js +322 -0
  81. package/dist/esm/svg/svg-writer.js +135 -71
  82. package/dist/esm/word/docx-writer.js +11 -2
  83. package/package.json +5 -1
@@ -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 = [];
@@ -709,22 +837,35 @@ function worksheetToBody(worksheet, sharedStrings, styles, date1904, print) {
709
837
  textBudget -= text.length;
710
838
  const xf = ws ? styles.cellXfs[ws.styleIndex ?? 0] : defaultStyleAt(absR, absC, styles.cellXfs);
711
839
  let runProps = cellRunProps(xf);
840
+ let normalColored = (xf ? styles.fonts[xf.fontId] : styles.fonts[0])?.colorRef === normalColorRef;
712
841
  if (ws?.type === "n" && xf?.numFmtId) {
713
842
  const fmtColor = numberFormatColorHex(ws.rawValue, xf.numFmtId, styles.numFmts);
714
- if (fmtColor !== void 0 && fmtColor !== runProps.colorHex) runProps = {
715
- ...runProps,
716
- colorHex: fmtColor
717
- };
843
+ if (fmtColor !== void 0 && fmtColor !== runProps.colorHex) {
844
+ runProps = {
845
+ ...runProps,
846
+ colorHex: fmtColor
847
+ };
848
+ normalColored = false;
849
+ }
718
850
  }
719
- const alignment = alignmentFromXf(xf, ws?.type);
851
+ const alignment = alignmentFromXf(xf, ws?.type, text);
852
+ const flow = flowAlignment(alignment, rtl);
720
853
  let shading = xf ? shadingFromXf(xf, styles) : void 0;
721
854
  const tableFmt = tableFormatByCell.get(key(absR, absC));
722
855
  if (!shading && tableFmt?.shading) shading = tableFmt.shading;
723
- if (tableFmt?.fontColorHex) runProps = {
856
+ if (tableFmt?.fontColorHex && normalColored) runProps = {
724
857
  ...runProps,
725
858
  colorHex: tableFmt.fontColorHex
726
859
  };
860
+ if (tableFmt?.bold && !runProps.bold) runProps = {
861
+ ...runProps,
862
+ bold: true
863
+ };
727
864
  let borders = xf ? scaleBorders(bordersFromXf(xf, styles), printScale) : void 0;
865
+ if (tableFmt?.borders) borders = {
866
+ ...scaleBorders(mapXlsxBorder(tableFmt.borders), printScale),
867
+ ...borders
868
+ };
728
869
  let dataBar;
729
870
  let icon;
730
871
  const sparkline = sparklineByCell.get(key(absR, absC));
@@ -751,7 +892,7 @@ function worksheetToBody(worksheet, sharedStrings, styles, date1904, print) {
751
892
  const rotation = xf?.alignment?.textRotation;
752
893
  const rotated = rotation !== void 0 && rotation !== 0 && text.length > 0;
753
894
  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]) {
895
+ 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
896
  let availTwips = columnWidths[c];
756
897
  let cc = c + 1;
757
898
  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 +939,7 @@ function worksheetToBody(worksheet, sharedStrings, styles, date1904, print) {
798
939
  };
799
940
  }
800
941
  }
942
+ const noteFlag = print.noteFlags?.get(noteKey(absR, absC));
801
943
  const dropdown = dropdownRanges.length > 0 && rangesCover(dropdownRanges, absR, absC);
802
944
  const href = print.hyperlinks && print.hyperlinks.length > 0 ? hyperlinkUrlAt(print.hyperlinks, absR, absC) : void 0;
803
945
  const visibleEndCol = merge ? Math.min(merge.endColumn, colWindowEnd) : 0;
@@ -820,15 +962,19 @@ function worksheetToBody(worksheet, sharedStrings, styles, date1904, print) {
820
962
  ...overflowRightRule ? { right: overflowRightRule } : {}
821
963
  } } : {},
822
964
  ...dropdown ? { dropdown: true } : {},
965
+ ...noteFlag ? { noteFlag } : {},
823
966
  ...!wrapText && !rotated ? { noWrap: true } : {},
824
967
  ...!wrapText && !rotated && !shrinkToFit && !merge && ws?.type === "n" && (xf?.numFmtId ?? 0) !== 0 && text.length > 0 && tooWideToShow(text, charTwips(xf, styles, textTwipsUnit), columnWidths[c]) ? { hashOnOverflow: true } : {},
825
968
  verticalAlign: verticalAlignOf(xf)
826
969
  };
827
970
  const indentLevels = xf?.alignment?.indent ?? 0;
828
- const baseParaProps = cellParaProps(alignment);
829
- const paragraphProps = indentLevels > 0 ? {
971
+ const rtlText = textDirection(xf, text) === "rtl";
972
+ const baseParaProps = cellParaProps(alignment, rtlText);
973
+ const indentPt = twipsToPt(indentLevels * indentTwips);
974
+ const indentAtRight = alignment === "right" !== rtlText;
975
+ const paragraphProps = indentLevels > 0 && alignment !== "center" ? {
830
976
  ...baseParaProps,
831
- indentLeft: twipsToPt(indentLevels * 3 * 105)
977
+ ...indentAtRight ? { indentRight: indentPt } : { indentLeft: indentPt }
832
978
  } : baseParaProps;
833
979
  const richRuns = ws && ws.type === "s" && print.sharedStringRuns ? print.sharedStringRuns[Number(ws.rawValue)] : void 0;
834
980
  const cellRuns = richRuns && richRuns.length > 0 ? richRuns.map((rr) => ({
@@ -847,7 +993,7 @@ function worksheetToBody(worksheet, sharedStrings, styles, date1904, print) {
847
993
  runs
848
994
  }
849
995
  }));
850
- if (text.length > 0 && !merge && !wrapText && !rotated && !shrinkToFit && c > 0 && alignment === "right" && estimateChars(text) * charTwips(xf, styles, textTwipsUnit) > columnWidths[c]) leftOverflow.push({
996
+ if (text.length > 0 && !merge && !wrapText && !rotated && !shrinkToFit && c > 0 && flow === "right" && estimateChars(text) * charTwips(xf, styles, textTwipsUnit) > columnWidths[c]) leftOverflow.push({
851
997
  index: cells.length,
852
998
  column: c,
853
999
  needTwips: estimateChars(text) * charTwips(xf, styles, textTwipsUnit) - columnWidths[c],
@@ -884,17 +1030,27 @@ function worksheetToBody(worksheet, sharedStrings, styles, date1904, print) {
884
1030
  };
885
1031
  if (isTitleRow && titleRowIndex < 0) titleRowIndex = rows.length;
886
1032
  rowNumbers.push(absR + 1);
887
- rows.push({
1033
+ if (sheetTopTwips === void 0) rows.push({
888
1034
  properties: rowProps,
889
1035
  cells
890
1036
  });
1037
+ else {
1038
+ rows.push({
1039
+ properties: {
1040
+ ...rowProps,
1041
+ sheetTopPt: twipsToPt(sheetTopTwips)
1042
+ },
1043
+ cells
1044
+ });
1045
+ sheetTopTwips += rowHeightTwips ?? defaultRowTwips;
1046
+ }
891
1047
  }
892
1048
  const thin = {
893
1049
  style: "single",
894
1050
  width: eighthPtToPt(2),
895
1051
  colorHex: PRINT_GRIDLINE_HEX
896
1052
  };
897
- const centered = worksheet.printOptions?.horizontalCentered === true;
1053
+ const centered = !print.screen && worksheet.printOptions?.horizontalCentered === true;
898
1054
  const tableProperties = {
899
1055
  defaultCellMargins: {
900
1056
  left: pt(EXCEL_CELL_INSET_PT * printScale),
@@ -903,7 +1059,8 @@ function worksheetToBody(worksheet, sharedStrings, styles, date1904, print) {
903
1059
  bottom: pt(0)
904
1060
  },
905
1061
  layout: "fixed",
906
- ...print.gridLines ? { borders: {
1062
+ ...print.gridLines && print.screen ? { gridlines: SCREEN_GRIDLINE } : {},
1063
+ ...print.gridLines && !print.screen ? { borders: {
907
1064
  top: thin,
908
1065
  bottom: thin,
909
1066
  left: thin,
@@ -914,6 +1071,23 @@ function worksheetToBody(worksheet, sharedStrings, styles, date1904, print) {
914
1071
  ...centered ? { alignment: "center" } : {}
915
1072
  };
916
1073
  const contentWidthTwips = sheetContentWidthTwips(worksheet);
1074
+ const headingColTwips = scaled ? Math.max(1, Math.round(460 * printScale)) : 460;
1075
+ const headings = !print.screen && worksheet.printOptions?.headings ? {
1076
+ dxPt: twipsToPt(headingColTwips),
1077
+ dyPt: (worksheet.defaultRowHeightPt ?? defaultRowHeightPtFor(print.defaultFontPt)) * printScale,
1078
+ band: {
1079
+ colTwips: headingColTwips,
1080
+ ...scaled ? {
1081
+ fontPt: (print.defaultFontPt ?? DEFAULT_FONT_PT) * printScale,
1082
+ lineWidthPt: .5 * printScale
1083
+ } : {}
1084
+ }
1085
+ } : void 0;
1086
+ const reportHeadings = () => {
1087
+ if (!headings || !print.headingSink) return;
1088
+ print.headingSink.dxPt = headings.dxPt;
1089
+ print.headingSink.dyPt = headings.dyPt;
1090
+ };
917
1091
  const colBreaksLocal = /* @__PURE__ */ new Set();
918
1092
  for (const brk of worksheet.colBreaks ?? []) {
919
1093
  const local = brk - colStart;
@@ -922,14 +1096,23 @@ function worksheetToBody(worksheet, sharedStrings, styles, date1904, print) {
922
1096
  const fitWide = worksheet.fitToPage ? worksheet.pageSetup?.fitToWidth ?? 1 : 1;
923
1097
  const bandWidths = scaledColumnWidths(visibleWidths, printScale, scaled);
924
1098
  const bandTotal = bandWidths.reduce((sum, w) => sum + w, 0);
925
- if (colCount > 1 && bandsAcross(worksheet, fitWide) && (bandTotal > contentWidthTwips || colBreaksLocal.size > 0)) {
1099
+ if (!print.screen && colCount > 1 && bandsAcross(worksheet, fitWide) && (bandTotal > contentWidthTwips || colBreaksLocal.size > 0)) {
926
1100
  const bands = computeColumnBands(bandWidths, contentWidthTwips, colBreaksLocal);
927
1101
  if (bands.length > 1) {
928
1102
  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
1103
+ const banded = bandedTables(rows, bandWidths, bands, tableProperties, titleRowIndex, Math.round((print.drawingExtentPt?.widthPt ?? 0) * TWIPS_PER_POINT * printScale), headings ? {
1104
+ columns: visibleCols.map((c) => c + colStart),
1105
+ rowNumbers,
1106
+ band: {
1107
+ ...headings.band,
1108
+ lettersPt: headings.dyPt
1109
+ }
1110
+ } : void 0, sheetLeftPt !== void 0 ? {
1111
+ sheetLeftPt,
1112
+ drawings: scaledBoxes(print.drawingBoxesPt, printScale)
932
1113
  } : void 0);
1114
+ reportHeadings();
1115
+ return banded;
933
1116
  }
934
1117
  }
935
1118
  const frozen = worksheet.pane && (worksheet.pane.frozenRows > 0 || worksheet.pane.frozenCols > 0) ? {
@@ -939,23 +1122,30 @@ function worksheetToBody(worksheet, sharedStrings, styles, date1904, print) {
939
1122
  if (titleRowIndex > 0 && titleRowIndex < rows.length) {
940
1123
  const titleStart = titleRowIndex;
941
1124
  const grid = bandWidths.map((w) => twipsToPt(w));
1125
+ const overlay = sheetFrame(sheetLeftPt, grid, 0);
942
1126
  return [{
943
1127
  kind: "table",
944
1128
  table: {
945
1129
  properties: tableProperties,
946
1130
  grid,
947
- rows: rows.slice(0, titleStart)
1131
+ rows: rows.slice(0, titleStart),
1132
+ ...overlay
948
1133
  }
949
1134
  }, {
950
1135
  kind: "table",
951
1136
  table: {
952
1137
  properties: tableProperties,
953
1138
  grid,
954
- rows: rows.slice(titleStart)
1139
+ rows: rows.slice(titleStart),
1140
+ ...overlay
955
1141
  }
956
1142
  }];
957
1143
  }
958
- const headed = worksheet.printOptions?.headings ? withHeadingBand(rows, bandWidths, colStart, rowNumbers) : void 0;
1144
+ const headed = headings ? withHeadingBand(rows, bandWidths, visibleCols.map((c) => c + colStart), rowNumbers, {
1145
+ ...headings.band,
1146
+ lettersPt: headings.dyPt
1147
+ }) : void 0;
1148
+ if (headed) reportHeadings();
959
1149
  return [{
960
1150
  kind: "table",
961
1151
  table: {
@@ -964,7 +1154,8 @@ function worksheetToBody(worksheet, sharedStrings, styles, date1904, print) {
964
1154
  frozen
965
1155
  } : tableProperties,
966
1156
  grid: (headed?.widths ?? bandWidths).map((w) => twipsToPt(w)),
967
- rows: headed?.rows ?? rows
1157
+ rows: headed?.rows ?? rows,
1158
+ ...sheetFrame(sheetLeftPt, bandWidths.map((w) => twipsToPt(w)), headed ? twipsToPt(headingColTwips) : 0)
968
1159
  }
969
1160
  }];
970
1161
  }
@@ -1070,20 +1261,49 @@ function applyCfOverride(base, o) {
1070
1261
  * the sheets where it matters most, since a column of figures is the common
1071
1262
  * case.
1072
1263
  */
1073
- function alignmentFromXf(xf, type) {
1264
+ function alignmentFromXf(xf, type, text = "") {
1074
1265
  const explicit = xf?.alignment ? mapAlignment(xf.alignment.horizontal) : void 0;
1075
1266
  if (explicit) return explicit;
1076
- return generalAlignment(type);
1267
+ return generalAlignment(type, textDirection(xf, text));
1077
1268
  }
1078
- function generalAlignment(type) {
1269
+ /**
1270
+ * §18.8.1 General: numbers to the right, logical values centred, and text to
1271
+ * the side it STARTS from — the left for a word that reads from the left, the
1272
+ * right for one that reads from the right. Excel sets "שלום" against the right
1273
+ * edge of a General cell on any sheet, and the sheet's own direction does not
1274
+ * enter into it: the numbers on a right-to-left sheet still sit at the right.
1275
+ */
1276
+ function generalAlignment(type, direction) {
1079
1277
  switch (type) {
1080
1278
  case "n":
1081
1279
  case "d": return "right";
1082
1280
  case "b":
1083
1281
  case "e": return "center";
1084
- default: return "left";
1282
+ default: return direction === "rtl" ? "right" : "left";
1085
1283
  }
1086
1284
  }
1285
+ /**
1286
+ * The direction a cell's text reads in: its own §18.8.1 `readingOrder`, or
1287
+ * else "context" — that of its first strong character.
1288
+ */
1289
+ function textDirection(xf, text) {
1290
+ return xf?.alignment?.readingOrder ?? firstStrongDirection(text) ?? "ltr";
1291
+ }
1292
+ /**
1293
+ * The side a cell's text runs ON to, in the file's own column order. The grid
1294
+ * is built A first whatever the sheet's direction and turned round at the end
1295
+ * (mirrorTable), so on a sheet that reads from the right a cell set against
1296
+ * its right edge is one whose text runs FORWARD — into B, which stands to the
1297
+ * left of A — and the overflow has to reason about the mirror of what the
1298
+ * cell shows.
1299
+ */
1300
+ function flowAlignment(alignment, rtl) {
1301
+ return rtl ? crossedOver(alignment) : alignment;
1302
+ }
1303
+ /** Left for right and right for left; every other alignment is its own mirror. */
1304
+ function crossedOver(alignment) {
1305
+ return alignment === "left" ? "right" : alignment === "right" ? "left" : alignment;
1306
+ }
1087
1307
  function mapAlignment(h) {
1088
1308
  if (!h) return void 0;
1089
1309
  if (h === "left") return "left";
@@ -1268,6 +1488,10 @@ function mapBorderStyle(style) {
1268
1488
  function key(row, col) {
1269
1489
  return `${row},${col}`;
1270
1490
  }
1491
+ /** The key {@link PrintModelOptions.noteFlags} is looked up by: absolute row and column. */
1492
+ function noteKey(row, col) {
1493
+ return key(row, col);
1494
+ }
1271
1495
  function listDropdownRanges(dvs) {
1272
1496
  if (!dvs || dvs.length === 0) return [];
1273
1497
  const out = [];
@@ -1345,6 +1569,147 @@ function collectSeriesValues(cells, area, print) {
1345
1569
  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
1570
  return out;
1347
1571
  }
1572
+ /** §18.8.41 — the order a table style's regions apply in: a later one wins. */
1573
+ var TABLE_STYLE_ORDER = [
1574
+ "wholeTable",
1575
+ "firstColumnStripe",
1576
+ "secondColumnStripe",
1577
+ "firstRowStripe",
1578
+ "secondRowStripe",
1579
+ "lastColumn",
1580
+ "firstColumn",
1581
+ "headerRow",
1582
+ "totalRow",
1583
+ "firstHeaderCell",
1584
+ "lastHeaderCell",
1585
+ "firstTotalCell",
1586
+ "lastTotalCell"
1587
+ ];
1588
+ /**
1589
+ * §18.8.40 — what a table's style makes of each of its cells. Each region
1590
+ * the style formats is laid over the table in the order §18.8.41 gives —
1591
+ * the whole table, its column and row stripes, its last and first columns,
1592
+ * its header and total rows, their corner cells — and a later region's fill,
1593
+ * weight, colour and edges win over an earlier one's. A region's `left`,
1594
+ * `right`, `top` and `bottom` rule its own outer edges, its `vertical` and
1595
+ * `horizontal` the lines between its cells. Stripes run through the data rows
1596
+ * only, in bands of their own size, the first band at the first data row;
1597
+ * the first and last columns, and the stripes, only where the table turns them
1598
+ * on.
1599
+ *
1600
+ * @param t The table, its style resolved.
1601
+ * @param out Where each cell's format goes, by absolute key.
1602
+ */
1603
+ function styledTableFormats(t, out) {
1604
+ const style = t.style;
1605
+ if (!style) return;
1606
+ const { startRow: r0, endRow: r1, startColumn: c0, endColumn: c1 } = t.ref;
1607
+ const dataTop = r0 + t.headerRowCount;
1608
+ const dataBottom = r1 - (t.totalsRowCount ?? 0);
1609
+ const bands = (from, to, first, second, which) => {
1610
+ const spans = [];
1611
+ let at = from;
1612
+ for (let i = 0; at <= to; i++) {
1613
+ const size = i % 2 === 0 ? first : second;
1614
+ if (i % 2 === which) spans.push([at, Math.min(to, at + size - 1)]);
1615
+ at += size;
1616
+ }
1617
+ return spans;
1618
+ };
1619
+ const rowSize = [style.firstRowStripe?.size ?? 1, style.secondRowStripe?.size ?? 1];
1620
+ const colSize = [style.firstColumnStripe?.size ?? 1, style.secondColumnStripe?.size ?? 1];
1621
+ const regionsOf = (type) => {
1622
+ const whole = {
1623
+ top: r0,
1624
+ bottom: r1,
1625
+ left: c0,
1626
+ right: c1
1627
+ };
1628
+ switch (type) {
1629
+ case "wholeTable": return [whole];
1630
+ case "firstRowStripe":
1631
+ case "secondRowStripe": return t.showRowStripes && dataBottom >= dataTop ? bands(dataTop, dataBottom, rowSize[0], rowSize[1], type === "firstRowStripe" ? 0 : 1).map(([top, bottom]) => ({
1632
+ top,
1633
+ bottom,
1634
+ left: c0,
1635
+ right: c1
1636
+ })) : [];
1637
+ case "firstColumnStripe":
1638
+ case "secondColumnStripe": return t.showColumnStripes && dataBottom >= dataTop ? bands(c0, c1, colSize[0], colSize[1], type === "firstColumnStripe" ? 0 : 1).map(([left, right]) => ({
1639
+ top: dataTop,
1640
+ bottom: dataBottom,
1641
+ left,
1642
+ right
1643
+ })) : [];
1644
+ case "lastColumn": return t.showLastColumn ? [{
1645
+ ...whole,
1646
+ left: c1
1647
+ }] : [];
1648
+ case "firstColumn": return t.showFirstColumn ? [{
1649
+ ...whole,
1650
+ right: c0
1651
+ }] : [];
1652
+ case "headerRow": return t.headerRowCount > 0 ? [{
1653
+ ...whole,
1654
+ bottom: dataTop - 1
1655
+ }] : [];
1656
+ case "totalRow": return dataBottom < r1 ? [{
1657
+ ...whole,
1658
+ top: dataBottom + 1
1659
+ }] : [];
1660
+ case "firstHeaderCell": return t.headerRowCount > 0 && t.showFirstColumn ? [{
1661
+ top: r0,
1662
+ bottom: dataTop - 1,
1663
+ left: c0,
1664
+ right: c0
1665
+ }] : [];
1666
+ case "lastHeaderCell": return t.headerRowCount > 0 && t.showLastColumn ? [{
1667
+ top: r0,
1668
+ bottom: dataTop - 1,
1669
+ left: c1,
1670
+ right: c1
1671
+ }] : [];
1672
+ case "firstTotalCell": return dataBottom < r1 && t.showFirstColumn ? [{
1673
+ top: dataBottom + 1,
1674
+ bottom: r1,
1675
+ left: c0,
1676
+ right: c0
1677
+ }] : [];
1678
+ case "lastTotalCell": return dataBottom < r1 && t.showLastColumn ? [{
1679
+ top: dataBottom + 1,
1680
+ bottom: r1,
1681
+ left: c1,
1682
+ right: c1
1683
+ }] : [];
1684
+ }
1685
+ };
1686
+ for (const type of TABLE_STYLE_ORDER) {
1687
+ const dxf = style[type]?.dxf;
1688
+ if (!dxf) continue;
1689
+ const fill = dxf.fill?.patternType === "none" ? void 0 : dxf.fill?.bgColorHex ?? dxf.fill?.fgColorHex;
1690
+ const b = dxf.border;
1691
+ for (const region of regionsOf(type)) for (let r = region.top; r <= region.bottom; r++) for (let c = region.left; c <= region.right; c++) {
1692
+ const k = key(r, c);
1693
+ const was = out.get(k);
1694
+ const edges = { ...was?.borders };
1695
+ const top = r === region.top ? b?.top : b?.horizontal;
1696
+ const bottom = r === region.bottom ? b?.bottom : b?.horizontal;
1697
+ const left = c === region.left ? b?.left : b?.vertical;
1698
+ const right = c === region.right ? b?.right : b?.vertical;
1699
+ if (top) edges.top = top;
1700
+ if (bottom) edges.bottom = bottom;
1701
+ if (left) edges.left = left;
1702
+ if (right) edges.right = right;
1703
+ out.set(k, {
1704
+ ...was,
1705
+ ...fill !== void 0 ? { shading: { colorHex: fill } } : {},
1706
+ ...dxf.font?.colorHex !== void 0 ? { fontColorHex: dxf.font.colorHex } : {},
1707
+ ...dxf.font?.bold !== void 0 ? { bold: dxf.font.bold } : {},
1708
+ ...Object.keys(edges).length > 0 ? { borders: edges } : {}
1709
+ });
1710
+ }
1711
+ }
1712
+ }
1348
1713
  function isPivotTotal(t) {
1349
1714
  return t !== void 0 && t !== "data" && t !== "blank";
1350
1715
  }
@@ -1370,7 +1735,8 @@ function buildTableFormatLookup(worksheet) {
1370
1735
  for (let c = ref.startColumn; c <= ref.endColumn; c++) out.set(key(r, c), fmt);
1371
1736
  }
1372
1737
  };
1373
- for (const t of worksheet.tables ?? []) band(t.ref, t.headerRowCount, t);
1738
+ for (const t of worksheet.tables ?? []) if (t.style) styledTableFormats(t, out);
1739
+ else band(t.ref, t.headerRowCount, t);
1374
1740
  for (const p of worksheet.pivotTables ?? []) band(p.ref, p.firstDataRow, p, (off) => isPivotTotal(p.rowItemTypes?.[off]));
1375
1741
  for (const p of worksheet.pivotTables ?? []) {
1376
1742
  if (!p.colItemTypes || p.headerHex === void 0) continue;
@@ -1466,14 +1832,17 @@ function cellPaintsSomething(cell, styles) {
1466
1832
  }
1467
1833
  /**
1468
1834
  * 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).
1835
+ * run into, bounded by the printable width (Excel stops at the page edge too)
1836
+ * — or, on a `screen`, which has no edge, only by the column cap; an `image`
1837
+ * of the sheet gives it none, its edge being the used range's.
1470
1838
  *
1471
1839
  * Only the last used column can want them — anywhere else the grid already has
1472
1840
  * neighbours. Zero for the overwhelming majority of sheets, which keeps their
1473
1841
  * projection exactly as it was.
1474
1842
  */
1475
- function overflowColumnsPastUsedRange(worksheet, usedCol, sharedStrings, styles, date1904, charTwipsUnit) {
1476
- const defaultTwips = defaultColumnTwips(worksheet, charTwipsUnit, DEFAULT_COL_CHARS);
1843
+ function overflowColumnsPastUsedRange(worksheet, usedCol, sharedStrings, styles, date1904, charTwipsUnit, screen, image) {
1844
+ if (image) return 0;
1845
+ const defaultTwips = defaultColumnTwips(worksheet, charTwipsUnit);
1477
1846
  const widthOf = (abs) => {
1478
1847
  for (const col of worksheet.columns) {
1479
1848
  if (abs < col.min - 1 || abs > col.max - 1) continue;
@@ -1493,16 +1862,16 @@ function overflowColumnsPastUsedRange(worksheet, usedCol, sharedStrings, styles,
1493
1862
  const xf = styles.cellXfs[cell.styleIndex ?? 0];
1494
1863
  const align = xf?.alignment;
1495
1864
  if (align?.wrapText || align?.shrinkToFit || align?.textRotation) continue;
1496
- if (alignmentFromXf(xf, cell.type) !== "left") continue;
1865
+ const text = resolveCellText(cell, sharedStrings, styles, date1904);
1866
+ if (flowAlignment(alignmentFromXf(xf, cell.type, text), worksheet.rightToLeft === true) !== "left") continue;
1497
1867
  let room = 0;
1498
1868
  for (let abs = cell.column; abs <= usedCol; abs++) room += widthOf(abs);
1499
- const text = resolveCellText(cell, sharedStrings, styles, date1904);
1500
1869
  needTwips = Math.max(needTwips, estimateChars(text) * charTwips(xf, styles, 105) - room);
1501
1870
  }
1502
1871
  if (needTwips <= 0) return 0;
1503
1872
  let gridTwips = 0;
1504
1873
  for (let abs = 0; abs <= usedCol; abs++) gridTwips += widthOf(abs);
1505
- const limit = sheetContentWidthTwips(worksheet);
1874
+ const limit = screen ? Infinity : sheetContentWidthTwips(worksheet);
1506
1875
  const MAX_OVERFLOW_COLS = 256;
1507
1876
  let extra = 0;
1508
1877
  while (needTwips > 0 && extra < MAX_OVERFLOW_COLS) {
@@ -1671,6 +2040,79 @@ function absorbLeftwards(cells, cellColumns, candidates, styles, neighbourAt) {
1671
2040
  }
1672
2041
  }
1673
2042
  /**
2043
+ * §18.3.1.87 `rightToLeft` — the table turned round, the way Excel draws a
2044
+ * sheet that reads from the right: column A last in every row and so at the
2045
+ * right edge, and each cell's left and right borders crossed over with it
2046
+ * (its diagonals too). What a cell shows is untouched — its text keeps the
2047
+ * side its alignment names, and its overflow already runs the right way (see
2048
+ * flowAlignment). On paper the sheet stands against the right margin, as
2049
+ * Excel prints it, unless it asks to be centred.
2050
+ *
2051
+ * @param table The grid as built, column A first.
2052
+ * @param screen Whether it is drawn for a window, which has no margin to meet.
2053
+ */
2054
+ function mirrorTable(table, screen) {
2055
+ const { frozen, ...properties } = table.properties;
2056
+ return {
2057
+ ...table,
2058
+ properties: {
2059
+ ...properties,
2060
+ ...frozen && frozen.rows > 0 ? { frozen: {
2061
+ rows: frozen.rows,
2062
+ cols: 0
2063
+ } } : {},
2064
+ ...!screen && properties.alignment !== "center" ? { alignment: "right" } : {}
2065
+ },
2066
+ grid: [...table.grid].reverse(),
2067
+ rows: table.rows.map((row) => ({
2068
+ ...row,
2069
+ cells: [...row.cells].reverse().map(mirrorCell)
2070
+ })),
2071
+ ...table.overlay ? { overlay: {
2072
+ ...table.overlay,
2073
+ leftPt: pt(table.grid.reduce((sum, w) => sum + w, 0) - table.overlay.leftPt - table.overlay.widthPt),
2074
+ mirrored: true
2075
+ } } : {}
2076
+ };
2077
+ }
2078
+ function mirrorCell(cell) {
2079
+ const p = cell.properties;
2080
+ const span = p.colSpan ?? 1;
2081
+ const paintAtEnd = p.paintColumns !== void 0 && p.paintColumns < span;
2082
+ if (!p.borders && !p.dataBar && !p.noteFlag && !paintAtEnd) return cell;
2083
+ return {
2084
+ ...cell,
2085
+ properties: {
2086
+ ...p,
2087
+ ...p.borders ? { borders: mirrorBorders(p.borders) } : {},
2088
+ ...p.dataBar ? { dataBar: mirrorDataBar(p.dataBar) } : {},
2089
+ ...p.noteFlag ? { noteFlag: {
2090
+ ...p.noteFlag,
2091
+ atLeft: !p.noteFlag.atLeft
2092
+ } } : {},
2093
+ ...paintAtEnd ? { paintAtEnd: true } : {}
2094
+ }
2095
+ };
2096
+ }
2097
+ function mirrorBorders(b) {
2098
+ const { left, right, diagonalUp, diagonalDown, ...rest } = b;
2099
+ return {
2100
+ ...rest,
2101
+ ...right ? { left: right } : {},
2102
+ ...left ? { right: left } : {},
2103
+ ...diagonalUp ? { diagonalDown: diagonalUp } : {},
2104
+ ...diagonalDown ? { diagonalUp: diagonalDown } : {}
2105
+ };
2106
+ }
2107
+ function mirrorDataBar(bar) {
2108
+ const start = bar.startFraction ?? 0;
2109
+ return {
2110
+ ...bar,
2111
+ startFraction: Math.max(0, 1 - start - bar.fraction),
2112
+ negative: !bar.negative
2113
+ };
2114
+ }
2115
+ /**
1674
2116
  * Whether spanning a cell's overflowing text across `neighbour` would leave the
1675
2117
  * page looking the same as drawing over it would.
1676
2118
  *
@@ -1805,4 +2247,4 @@ function slicerParagraph(text, runProps) {
1805
2247
  };
1806
2248
  }
1807
2249
  //#endregion
1808
- export { cellPaintsVisibly, columnTwips, printableHeightPt, printableWidthPt, resolveCellText, resolvePrintArea, resolvePrintTitleRows, sectionFromWorksheet, slicerTable, worksheetToBody };
2250
+ export { cellPaintsVisibly, columnTwips, columnUnitTwips, defaultColumnTwips, noteKey, printableHeightPt, printableWidthPt, resolveCellText, resolvePrintArea, resolvePrintTitleRows, sectionFromWorksheet, slicerTable, worksheetToBody };