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,7 +1,8 @@
1
1
  import { pt } from "../core/ir/units.js";
2
2
  import { EMPTY_STYLE_SHEET, resolveBodyStyles } from "../core/style-cascade/resolver.js";
3
3
  import "../core/style-cascade/index.js";
4
- import { cellPaintsVisibly, printableHeightPt, printableWidthPt, resolvePrintArea, resolvePrintTitleRows, sectionFromWorksheet, slicerTable, worksheetToBody } from "./print-model.js";
4
+ import { parseCellRef } from "./cell-reference.js";
5
+ import { cellPaintsVisibly, noteKey, printableHeightPt, printableWidthPt, resolvePrintArea, resolvePrintTitleRows, sectionFromWorksheet, slicerTable, worksheetToBody } from "./print-model.js";
5
6
  import { buildHeaderFooterContent } from "./header-footer.js";
6
7
  //#region src/excel/sheet-to-flow.ts
7
8
  var HEADER_REL = "_xlsxHeaderDefault";
@@ -15,6 +16,29 @@ var PAGE_BREAK_PARAGRAPH = {
15
16
  runs: []
16
17
  };
17
18
  /**
19
+ * A page with room for any sheet, and the sliver of margin a sheet drawn as an
20
+ * image keeps round itself, so that the rules on its outer edges are not cut
21
+ * in half.
22
+ */
23
+ var WHOLE_SHEET_PAGE_PT = 1e6;
24
+ var WHOLE_SHEET_MARGIN_PT = 2;
25
+ var WHOLE_SHEET_SECTION = {
26
+ pageSize: {
27
+ width: pt(WHOLE_SHEET_PAGE_PT),
28
+ height: pt(WHOLE_SHEET_PAGE_PT)
29
+ },
30
+ margins: {
31
+ top: pt(WHOLE_SHEET_MARGIN_PT),
32
+ right: pt(WHOLE_SHEET_MARGIN_PT),
33
+ bottom: pt(WHOLE_SHEET_MARGIN_PT),
34
+ left: pt(WHOLE_SHEET_MARGIN_PT),
35
+ header: pt(0),
36
+ footer: pt(0)
37
+ },
38
+ headers: [],
39
+ footers: []
40
+ };
41
+ /**
18
42
  * Project a {@link SheetDoc} into a {@link FlowDoc} (E-SHEET SA2): each grid sheet
19
43
  * becomes flow blocks (a table + chart/picture/shape/slicer frames, then comment
20
44
  * / control listings), with sheets after the first starting on a new page. Each
@@ -28,6 +52,8 @@ function projectSheetDoc(sheet, options = {}) {
28
52
  const body = [];
29
53
  const sheetSections = [];
30
54
  const sheetEnds = [];
55
+ const sheetNames = [];
56
+ const screen = options.screen === true;
31
57
  let firstSheetSection;
32
58
  const headersFooters = /* @__PURE__ */ new Map();
33
59
  const sheetGrids = new Map(sheet.sheets.map((s) => [s.name, s.grid]));
@@ -38,19 +64,28 @@ function projectSheetDoc(sheet, options = {}) {
38
64
  for (let sheetIdx = 0; sheetIdx < sheet.sheets.length; sheetIdx++) {
39
65
  const ws = sheet.sheets[sheetIdx];
40
66
  if (ws.hidden) continue;
41
- if (!sheetPrintsAnything(ws, sheet.styles) && ws !== fallbackSheet) {
67
+ if (!screen && !sheetPrintsAnything(ws, sheet.styles) && ws !== fallbackSheet) {
42
68
  printableSheets--;
43
69
  continue;
44
70
  }
45
71
  const scaleSink = { value: 1 };
46
72
  const bandSink = { lefts: [0] };
47
- const drawingExtentPt = drawingReachPt(ws);
48
- const printArea = resolvePrintArea(sheet.definedNames, sheetIdx);
49
- const titleRows = resolvePrintTitleRows(sheet.definedNames, sheetIdx);
73
+ const headingSink = {
74
+ dxPt: 0,
75
+ dyPt: 0
76
+ };
77
+ const notesShown = screen || ws.grid.pageSetup?.cellComments === "asDisplayed";
78
+ const drawingExtentPt = drawingReachPt(ws, notesShown, screen);
79
+ const controlBlocks = controlShapeBlocks(ws.formControls, ws.activeXControls);
80
+ const noteBlocks = notesShown ? (ws.comments ?? []).flatMap((comment) => noteShapes(comment, ws.grid.rightToLeft === true)) : [];
81
+ const printArea = screen ? void 0 : resolvePrintArea(sheet.definedNames, sheetIdx);
82
+ const titleRows = screen ? void 0 : resolvePrintTitleRows(sheet.definedNames, sheetIdx);
50
83
  const gridBody = worksheetToBody(ws.grid, sheet.sharedStrings, sheet.styles, sheet.date1904, {
84
+ ...screen ? { screen } : {},
85
+ ...screen && options.wholeSheetPages === true ? { image: true } : {},
51
86
  ...printArea ? { printArea } : {},
52
87
  ...titleRows ? { titleRows } : {},
53
- gridLines: ws.grid.printOptions?.gridLines === true,
88
+ gridLines: screen ? ws.grid.showGridLines !== false : ws.grid.printOptions?.gridLines === true,
54
89
  sheetGrids,
55
90
  sheetName: ws.name,
56
91
  definedNames: sheet.definedNames,
@@ -60,19 +95,23 @@ function projectSheetDoc(sheet, options = {}) {
60
95
  ...options.digitWidthPt !== void 0 ? { digitWidthPt: options.digitWidthPt } : {},
61
96
  ...sheet.styles.fonts[0]?.sizePt !== void 0 ? { defaultFontPt: sheet.styles.fonts[0].sizePt } : {},
62
97
  ...options.losses ? { losses: options.losses } : {},
98
+ ...screen && ws.comments && ws.comments.length > 0 ? { noteFlags: noteFlagsOf(ws.comments) } : {},
63
99
  scaleSink,
64
100
  bandSink,
65
- ...drawingExtentPt ? { drawingExtentPt } : {}
101
+ headingSink,
102
+ ...drawingExtentPt ? { drawingExtentPt } : {},
103
+ ...screen ? {} : { drawingBoxesPt: drawingBoxes(ws, [...controlBlocks, ...noteBlocks]) }
66
104
  });
67
- const sheetSection = withHeaderFooter(sectionFromWorksheet(ws.grid), ws, headersFooters, scaleSink.value, sheet.styles.fonts[0]?.sizePt, printed, options.fileName, sheet.themePalette, options.now);
105
+ const sheetSection = screen ? options.wholeSheetPages === true ? WHOLE_SHEET_SECTION : sectionFromWorksheet(ws.grid) : withHeaderFooter(sectionFromWorksheet(ws.grid), ws, headersFooters, scaleSink.value, sheet.styles.fonts[0]?.sizePt, printed, options.fileName, sheet.themePalette, options.now);
68
106
  if (printed === 0) firstSheetSection = sheetSection;
69
107
  sheetSections.push(sheetSection);
108
+ sheetNames.push(ws.name);
70
109
  if (options.sheetHeadings === true) body.push({
71
110
  kind: "paragraph",
72
111
  paragraph: {
73
112
  properties: {
74
113
  outlineLevel: 0,
75
- ...printed > 0 ? { pageBreakBefore: true } : {}
114
+ ...printed > 0 && !screen ? { pageBreakBefore: true } : {}
76
115
  },
77
116
  runs: [{
78
117
  text: ws.name,
@@ -80,37 +119,52 @@ function projectSheetDoc(sheet, options = {}) {
80
119
  }]
81
120
  }
82
121
  });
83
- else if (printed > 0) body.push({
122
+ else if (printed > 0 && !screen) body.push({
84
123
  kind: "paragraph",
85
124
  paragraph: PAGE_BREAK_PARAGRAPH
86
125
  });
87
126
  const drawings = [];
88
- for (const ref of ws.charts ?? []) drawings.push({
89
- kind: "chart",
90
- chart: {
91
- ...anchorFloat(ref.xPt, ref.yPt, scaleSink.value),
92
- chartRelId: ref.chartPartPath,
93
- width: pt(ref.widthPt * scaleSink.value),
94
- height: pt(ref.heightPt * scaleSink.value),
95
- paragraphProperties: {}
96
- }
97
- });
98
- for (const img of ws.images ?? []) drawings.push({
99
- kind: "image",
100
- image: {
101
- ...anchorFloat(img.xPt, img.yPt, scaleSink.value),
102
- resource: img.resourceId,
103
- width: pt(img.widthPt * scaleSink.value),
104
- height: pt(img.heightPt * scaleSink.value),
105
- paragraphProperties: {}
106
- }
107
- });
108
- for (const shape of ws.shapes ?? []) drawings.push({
109
- kind: "shape",
110
- shape: centreShape(scaleShape(shape, scaleSink.value), ws.grid, drawingExtentPt, scaleSink.value)
111
- });
112
- const controlBlocks = controlShapeBlocks(ws.formControls, ws.activeXControls);
113
- const drawingBands = gridBody.length === 0 ? bandDrawings(controlBlocks, printableHeightPt(ws.grid), scaleSink.value, DOWN).flatMap((row) => bandDrawings(row, printableWidthPt(ws.grid), scaleSink.value)) : [controlBlocks];
127
+ const pageFilling = ws.grid.chartSheet === true && !screen;
128
+ for (const ref of ws.charts ?? []) {
129
+ if (ref.screenOnly && !screen) continue;
130
+ drawings.push({
131
+ kind: "chart",
132
+ chart: pageFilling ? {
133
+ ...anchorFloat(0, 0, 1),
134
+ chartRelId: ref.chartPartPath,
135
+ width: pt(printableWidthPt(ws.grid)),
136
+ height: pt(printableHeightPt(ws.grid)),
137
+ paragraphProperties: {}
138
+ } : {
139
+ ...anchorFloat(ref.xPt, ref.yPt, scaleSink.value),
140
+ chartRelId: ref.chartPartPath,
141
+ width: pt(ref.widthPt * scaleSink.value),
142
+ height: pt(ref.heightPt * scaleSink.value),
143
+ paragraphProperties: {}
144
+ }
145
+ });
146
+ }
147
+ for (const img of ws.images ?? []) {
148
+ if (img.screenOnly && !screen) continue;
149
+ drawings.push({
150
+ kind: "image",
151
+ image: {
152
+ ...anchorFloat(img.xPt, img.yPt, scaleSink.value),
153
+ resource: img.resourceId,
154
+ width: pt(img.widthPt * scaleSink.value),
155
+ height: pt(img.heightPt * scaleSink.value),
156
+ paragraphProperties: {}
157
+ }
158
+ });
159
+ }
160
+ for (const [i, shape] of (ws.shapes ?? []).entries()) {
161
+ if (!screen && ws.screenOnlyShapes?.has(i)) continue;
162
+ drawings.push({
163
+ kind: "shape",
164
+ shape: screen ? shape : scaleShape(shape, scaleSink.value)
165
+ });
166
+ }
167
+ const drawingBands = gridBody.length === 0 && !screen ? bandDrawings(controlBlocks, printableHeightPt(ws.grid), scaleSink.value, DOWN).flatMap((row) => bandDrawings(row, printableWidthPt(ws.grid), scaleSink.value)) : [controlBlocks];
114
168
  for (let band = 0; band < drawingBands.length; band++) {
115
169
  if (band > 0) drawings.push({
116
170
  kind: "paragraph",
@@ -118,22 +172,43 @@ function projectSheetDoc(sheet, options = {}) {
118
172
  });
119
173
  for (const shape of drawingBands[band]) drawings.push({
120
174
  kind: "shape",
121
- shape: centreShape(scaleShape(shape, scaleSink.value), ws.grid, drawingExtentPt, scaleSink.value)
175
+ shape: screen ? shape : scaleShape(shape, scaleSink.value)
122
176
  });
123
177
  }
124
- body.push(...withDrawingsByBand(drawings, gridBody, bandSink.lefts));
178
+ for (const shape of noteBlocks) drawings.push({
179
+ kind: "shape",
180
+ shape: screen ? shape : scaleShape(shape, scaleSink.value)
181
+ });
182
+ const { grid, loose } = screen ? {
183
+ grid: gridBody,
184
+ loose: drawings
185
+ } : intoOverlays(gridBody, drawings, printArea !== void 0);
186
+ const placed = withDrawingsByBand(screen ? loose : loose.map((el) => el.kind === "shape" ? {
187
+ ...el,
188
+ shape: centreShape(el.shape, ws.grid, drawingExtentPt, scaleSink.value)
189
+ } : el), grid, bandSink.lefts).map((el) => headingSink.dxPt > 0 || headingSink.dyPt > 0 ? nudgeFloat(el, headingSink.dxPt, headingSink.dyPt) : el);
190
+ body.push(...ws.grid.rightToLeft === true ? mirrorDrawings(placed, screen ? void 0 : printableWidthPt(ws.grid), (drawingExtentPt?.widthPt ?? 0) * scaleSink.value) : placed);
125
191
  for (const slicer of ws.slicers ?? []) body.push({
126
192
  kind: "table",
127
193
  table: slicerTable(slicer)
128
194
  });
129
- const printComments = ws.grid.pageSetup?.cellComments === "atEnd" || ws.grid.pageSetup?.cellComments === "asDisplayed";
130
- if (ws.comments && ws.comments.length > 0) if (printComments) body.push(...commentBlocks(ws.comments));
131
- else options.losses?.push({
195
+ const listComments = !screen && ws.grid.pageSetup?.cellComments === "atEnd";
196
+ if (ws.comments && ws.comments.length > 0) if (listComments) body.push(...commentBlocks(ws.comments));
197
+ else if (!notesShown) options.losses?.push({
132
198
  severity: "dropped",
133
199
  feature: "comments",
134
200
  detail: `${ws.comments.length} cell note(s) not printed — <pageSetup cellComments> is "${ws.grid.pageSetup?.cellComments ?? "none"}"`,
135
201
  where: `sheet "${ws.name}"`
136
202
  });
203
+ else {
204
+ const hidden = ws.comments.filter((c) => c.shown === void 0).length;
205
+ if (hidden > 0) options.losses?.push({
206
+ severity: "dropped",
207
+ feature: "comments",
208
+ detail: screen ? `${hidden} cell note(s) not shown — a hidden note shows only while its cell is pointed at` : `${hidden} cell note(s) not printed — "asDisplayed" prints the notes the sheet shows`,
209
+ where: `sheet "${ws.name}"`
210
+ });
211
+ }
137
212
  if (ws.formControls && ws.formControls.length > 0) body.push(...formControlBlocks(ws.formControls));
138
213
  if (ws.activeXControls && ws.activeXControls.length > 0) body.push(...activeXBlocks(ws.activeXControls));
139
214
  sheetEnds.push(body.length);
@@ -149,9 +224,10 @@ function projectSheetDoc(sheet, options = {}) {
149
224
  feature: "images",
150
225
  detail: `${String(sheet.metafilePictures)} picture(s) not rendered — a WMF/EMF/PICT metafile is replayed, not embedded; the anchor keeps its space`
151
226
  });
152
- const sections = sheetSections.length > 1 ? sheetSections.map((properties, i) => ({
227
+ const sections = sheetSections.length > 1 || screen ? sheetSections.map((properties, i) => ({
153
228
  properties,
154
- endIndex: sheetEnds[i]
229
+ endIndex: sheetEnds[i],
230
+ ...screen ? { sheet: { name: sheetNames[i] } } : {}
155
231
  })) : [];
156
232
  return {
157
233
  kind: "flow",
@@ -162,7 +238,169 @@ function projectSheetDoc(sheet, options = {}) {
162
238
  resources: sheet.resources,
163
239
  ...sheet.chartData && sheet.chartData.size > 0 ? { charts: sheet.chartData } : {},
164
240
  ...headersFooters.size > 0 ? { headersFooters } : {},
165
- ...sheet.info ? { info: sheet.info } : {}
241
+ ...sheet.info ? { info: sheet.info } : {},
242
+ floatsOverText: true
243
+ };
244
+ }
245
+ /**
246
+ * E-SHEET W7 — what a window flags: each cell that carries a note or a
247
+ * comment, with what hovering over it shows. A cell with a threaded comment is
248
+ * flagged as one, and reads as its conversation — Excel writes a legacy
249
+ * placeholder note beside every thread ("[Threaded comment] Your version of
250
+ * Excel allows you to read…"), which is no part of what anyone wrote.
251
+ *
252
+ * @param comments The sheet's legacy notes and threaded comments.
253
+ * @returns The flags by absolute cell (print-model `noteKey`).
254
+ */
255
+ function noteFlagsOf(comments) {
256
+ const byCell = /* @__PURE__ */ new Map();
257
+ for (const c of comments) {
258
+ let cell;
259
+ try {
260
+ cell = parseCellRef(c.ref);
261
+ } catch {
262
+ continue;
263
+ }
264
+ const k = noteKey(cell.row, cell.column);
265
+ const entry = byCell.get(k) ?? {
266
+ notes: [],
267
+ thread: []
268
+ };
269
+ (c.threaded ? entry.thread : entry.notes).push(c.author ? `${c.author}: ${c.text}` : c.text);
270
+ byCell.set(k, entry);
271
+ }
272
+ const out = /* @__PURE__ */ new Map();
273
+ for (const [k, { notes, thread }] of byCell) out.set(k, thread.length > 0 ? {
274
+ kind: "thread",
275
+ text: thread.join("\n")
276
+ } : {
277
+ kind: "note",
278
+ text: notes.join("\n")
279
+ });
280
+ return out;
281
+ }
282
+ var NOTE_INSET_PT = 1.5;
283
+ var NOTE_SHADOW_PT = 2;
284
+ var NOTE_LINE_PT = .75;
285
+ var NOTE_FACE = "Tahoma";
286
+ var NOTE_SIZE_PT = 9;
287
+ /**
288
+ * A shown note as Excel draws it: a pale box at the place its VML anchor
289
+ * names, holding the note's text run by run (the bold "Author:" line first),
290
+ * with a hard shadow down and to the right — and a thin line from the box to
291
+ * its cell's top-right corner, ending there in a small arrowhead.
292
+ *
293
+ * Built in the file's column order like every other drawing. On a sheet that
294
+ * reads from the right mirrorDrawings moves both, and the line, which has a
295
+ * direction the box has not, is turned round here so it still meets the
296
+ * cell's corner — the corner, too, is mirrored.
297
+ *
298
+ * @param comment The note, with the box its shape shows, if it shows one.
299
+ * @param rtl Whether the sheet reads from the right.
300
+ * @returns The line then the box (the box over the line), or nothing.
301
+ */
302
+ function noteShapes(comment, rtl) {
303
+ const box = comment.shown;
304
+ if (!box) return [];
305
+ const runs = comment.runs ?? [{ text: comment.author ? `${comment.author}:\n${comment.text}` : comment.text }];
306
+ const frame = {
307
+ ...anchorFloat(box.xPt, box.yPt, 1),
308
+ width: pt(box.widthPt),
309
+ height: pt(box.heightPt),
310
+ geometry: {
311
+ kind: "preset",
312
+ preset: "rect"
313
+ },
314
+ fill: {
315
+ kind: "solid",
316
+ colorHex: box.fillHex
317
+ },
318
+ line: {
319
+ width: pt(NOTE_LINE_PT),
320
+ colorHex: box.lineHex
321
+ },
322
+ ...box.shadow ? { shadow: {
323
+ dxPt: NOTE_SHADOW_PT,
324
+ dyPt: NOTE_SHADOW_PT,
325
+ blurPt: 0,
326
+ colorHex: "000000",
327
+ alpha: 1
328
+ } } : {},
329
+ text: {
330
+ content: noteParagraphs(runs, box.textAlign),
331
+ insetLeft: pt(NOTE_INSET_PT),
332
+ insetTop: pt(NOTE_INSET_PT),
333
+ insetRight: pt(NOTE_INSET_PT),
334
+ insetBottom: pt(NOTE_INSET_PT),
335
+ anchor: "t"
336
+ },
337
+ paragraphProperties: {}
338
+ };
339
+ const cx = box.cornerXPt;
340
+ const cy = box.cornerYPt;
341
+ const sx = Math.min(Math.max(cx, box.xPt), box.xPt + box.widthPt);
342
+ const sy = Math.min(Math.max(cy, box.yPt), box.yPt + box.heightPt);
343
+ if (sx === cx && sy === cy) return [frame];
344
+ const flipH = sx > cx !== rtl;
345
+ const flipV = sy > cy;
346
+ return [{
347
+ ...anchorFloat(Math.min(sx, cx), Math.min(sy, cy), 1),
348
+ width: pt(Math.abs(cx - sx)),
349
+ height: pt(Math.abs(cy - sy)),
350
+ geometry: {
351
+ kind: "preset",
352
+ preset: "line"
353
+ },
354
+ fill: { kind: "none" },
355
+ line: {
356
+ width: pt(NOTE_LINE_PT),
357
+ colorHex: box.lineHex,
358
+ tailEnd: {
359
+ type: "triangle",
360
+ width: "sm",
361
+ length: "sm"
362
+ }
363
+ },
364
+ ...flipH || flipV ? { transform: {
365
+ ...flipH ? { flipH: true } : {},
366
+ ...flipV ? { flipV: true } : {}
367
+ } } : {},
368
+ paragraphProperties: {}
369
+ }, frame];
370
+ }
371
+ function noteParagraphs(runs, align) {
372
+ const lines = [[]];
373
+ for (const r of runs) r.text.split(/\r\n|\r|\n/).forEach((part, i) => {
374
+ if (i > 0) lines.push([]);
375
+ if (part.length > 0) lines[lines.length - 1].push({
376
+ text: part,
377
+ properties: noteRunProps(r)
378
+ });
379
+ });
380
+ while (lines.length > 1 && lines[lines.length - 1].length === 0) lines.pop();
381
+ return lines.map((line) => ({
382
+ kind: "paragraph",
383
+ paragraph: {
384
+ properties: align ? { alignment: align } : {},
385
+ runs: line
386
+ }
387
+ }));
388
+ }
389
+ function noteRunProps(r) {
390
+ const face = r.fontName ?? NOTE_FACE;
391
+ return {
392
+ fontFamily: {
393
+ ascii: face,
394
+ hAnsi: face,
395
+ cs: face
396
+ },
397
+ fontSizePt: pt(r.sizePt ?? NOTE_SIZE_PT),
398
+ colorHex: r.colorHex ?? "000000",
399
+ ...r.bold ? { bold: true } : {},
400
+ ...r.italic ? { italic: true } : {},
401
+ ...r.underline ? { underline: "single" } : {},
402
+ ...r.strike ? { strike: true } : {},
403
+ ...r.vertAlign ? { verticalAlign: r.vertAlign } : {}
166
404
  };
167
405
  }
168
406
  function commentBlocks(comments) {
@@ -433,6 +671,113 @@ function withDrawingsByBand(drawings, gridBody, lefts) {
433
671
  }
434
672
  return out;
435
673
  }
674
+ /**
675
+ * §20.5 — each printed table of a sheet takes the drawings over the columns it
676
+ * prints, into its overlay (TableOverlay): the layout then shows every page
677
+ * the part of them over its own rows. A table none of them lies over keeps no
678
+ * overlay.
679
+ *
680
+ * @param grid The sheet's grid, its tables framed by the print model.
681
+ * @param drawings The sheet's drawings at the print scale, in the sheet's frame.
682
+ * @param printArea Whether a print area bounds what prints.
683
+ * @returns The grid with the drawings in it, and those over none of its
684
+ * columns that still print — none, when a print area leaves them out.
685
+ */
686
+ function intoOverlays(grid, drawings, printArea) {
687
+ if (!grid.some((el) => el.kind === "table" && el.table.overlay)) return {
688
+ grid: [...grid],
689
+ loose: [...drawings]
690
+ };
691
+ const taken = /* @__PURE__ */ new Set();
692
+ return {
693
+ grid: grid.map((el) => {
694
+ if (el.kind !== "table" || !el.table.overlay) return el;
695
+ const o = el.table.overlay;
696
+ const over = drawings.filter((d) => {
697
+ const x = floatLeftPt(d);
698
+ const w = floatWidthPt(d) ?? 0;
699
+ return x !== void 0 && x < o.sheetLeftPt + o.widthPt && x + w > o.sheetLeftPt;
700
+ });
701
+ for (const d of over) taken.add(d);
702
+ if (over.length === 0) {
703
+ const { overlay: _none, ...table } = el.table;
704
+ return {
705
+ kind: "table",
706
+ table
707
+ };
708
+ }
709
+ return {
710
+ kind: "table",
711
+ table: {
712
+ ...el.table,
713
+ overlay: {
714
+ ...o,
715
+ drawings: over
716
+ }
717
+ }
718
+ };
719
+ }),
720
+ loose: drawings.filter((d) => !taken.has(d) && (!printArea || floatLeftPt(d) === void 0))
721
+ };
722
+ }
723
+ /**
724
+ * Where each of the sheet's printed drawings lies, unscaled, in the sheet's
725
+ * own frame — what a band keeps its rows under (PrintModelOptions
726
+ * .drawingBoxesPt).
727
+ *
728
+ * @param ws The sheet.
729
+ * @param shapes The controls and shown notes drawn as shapes, besides its own.
730
+ * @returns The boxes.
731
+ */
732
+ function drawingBoxes(ws, shapes) {
733
+ const boxes = [];
734
+ const add = (xPt, yPt, w, h) => {
735
+ if (xPt !== void 0 && yPt !== void 0) boxes.push({
736
+ xPt,
737
+ yPt,
738
+ widthPt: w,
739
+ heightPt: h
740
+ });
741
+ };
742
+ for (const c of ws.charts ?? []) if (!c.screenOnly) add(c.xPt, c.yPt, c.widthPt, c.heightPt);
743
+ for (const p of ws.images ?? []) if (!p.screenOnly) add(p.xPt, p.yPt, p.widthPt, p.heightPt);
744
+ for (const [i, shape] of [...ws.shapes ?? [], ...shapes].entries()) {
745
+ if (i < (ws.shapes ?? []).length && ws.screenOnlyShapes?.has(i)) continue;
746
+ add(shape.float?.posH?.offsetPt, shape.float?.posV?.offsetPt, shape.width, shape.height);
747
+ }
748
+ return boxes;
749
+ }
750
+ /**
751
+ * §18.3.1.87 `rightToLeft` — a drawing on a sheet that reads from the right is
752
+ * anchored to the same cells, and they stand mirrored: it keeps its size and
753
+ * its own orientation (Excel does not turn a right arrow round) but its place
754
+ * is counted from the grid's right edge. Each float is measured against the
755
+ * table it was placed beside — a band's own when the sheet bands, the first
756
+ * one for the floats that go in ahead of the grid — and on paper that table
757
+ * stands against the right margin (mirrorTable), or in the middle when the
758
+ * sheet asks to be centred.
759
+ *
760
+ * @param elements The sheet's grid and floats, as withDrawingsByBand placed them.
761
+ * @param contentWidthPt The width between the margins, or undefined for a screen.
762
+ * @param extentWidthPt How far the drawings reach, for a sheet with no table.
763
+ */
764
+ function mirrorDrawings(elements, contentWidthPt, extentWidthPt) {
765
+ let table = elements.find((el) => el.kind === "table")?.table;
766
+ const out = [];
767
+ for (const el of elements) {
768
+ if (el.kind === "table") table = el.table;
769
+ const x = floatLeftPt(el);
770
+ const w = floatWidthPt(el);
771
+ if (el.kind === "table" || x === void 0 || w === void 0) {
772
+ out.push(el);
773
+ continue;
774
+ }
775
+ const gridWidth = table ? table.grid.reduce((sum, c) => sum + c, 0) : extentWidthPt;
776
+ const left = contentWidthPt === void 0 ? 0 : table?.properties.alignment === "center" ? (contentWidthPt - gridWidth) / 2 : contentWidthPt - gridWidth;
777
+ out.push(shiftFloatLeft(el, x - (left + gridWidth - x - w)));
778
+ }
779
+ return out;
780
+ }
436
781
  /** A float's horizontal anchor, for the block kinds a sheet anchors. */
437
782
  function floatLeftPt(el) {
438
783
  return (el.kind === "chart" ? el.chart.float : el.kind === "image" ? el.image.float : el.kind === "shape" ? el.shape.float : void 0)?.posH?.offsetPt;
@@ -443,6 +788,40 @@ function floatWidthPt(el) {
443
788
  if (el.kind === "image") return el.image.width;
444
789
  if (el.kind === "shape") return el.shape.width;
445
790
  }
791
+ /** The same block with its anchor moved `dx` right and `dy` down. */
792
+ function nudgeFloat(el, dx, dy) {
793
+ const moved = (block) => {
794
+ const f = block.float;
795
+ if (!f) return block;
796
+ return {
797
+ ...block,
798
+ float: {
799
+ ...f,
800
+ ...f.posH ? { posH: {
801
+ ...f.posH,
802
+ offsetPt: pt((f.posH.offsetPt ?? 0) + dx)
803
+ } } : {},
804
+ ...f.posV ? { posV: {
805
+ ...f.posV,
806
+ offsetPt: pt((f.posV.offsetPt ?? 0) + dy)
807
+ } } : {}
808
+ }
809
+ };
810
+ };
811
+ if (el.kind === "chart") return {
812
+ ...el,
813
+ chart: moved(el.chart)
814
+ };
815
+ if (el.kind === "image") return {
816
+ ...el,
817
+ image: moved(el.image)
818
+ };
819
+ if (el.kind === "shape") return {
820
+ ...el,
821
+ shape: moved(el.shape)
822
+ };
823
+ return el;
824
+ }
446
825
  /** The same block with its horizontal anchor measured from `by` points later. */
447
826
  function shiftFloatLeft(el, by) {
448
827
  if (by === 0) return el;
@@ -650,21 +1029,29 @@ function activeXLabel(c) {
650
1029
  * past the last value is still printed, and on a sheet whose values sit in a
651
1030
  * handful of cells it is the drawing that decides the page.
652
1031
  */
653
- function drawingReachPt(ws) {
1032
+ function drawingReachPt(ws, notesShown, screen) {
654
1033
  let widthPt = 0;
655
1034
  let heightPt = 0;
656
- for (const shape of ws.shapes ?? []) {
1035
+ for (const [i, shape] of (ws.shapes ?? []).entries()) {
1036
+ if (!screen && ws.screenOnlyShapes?.has(i)) continue;
657
1037
  widthPt = Math.max(widthPt, (shape.float?.posH?.offsetPt ?? 0) + shape.width);
658
1038
  heightPt = Math.max(heightPt, (shape.float?.posV?.offsetPt ?? 0) + shape.height);
659
1039
  }
660
1040
  for (const c of ws.charts ?? []) {
1041
+ if (c.screenOnly && !screen) continue;
661
1042
  widthPt = Math.max(widthPt, (c.xPt ?? 0) + c.widthPt);
662
1043
  heightPt = Math.max(heightPt, (c.yPt ?? 0) + c.heightPt);
663
1044
  }
664
1045
  for (const p of ws.images ?? []) {
1046
+ if (p.screenOnly && !screen) continue;
665
1047
  widthPt = Math.max(widthPt, (p.xPt ?? 0) + p.widthPt);
666
1048
  heightPt = Math.max(heightPt, (p.yPt ?? 0) + p.heightPt);
667
1049
  }
1050
+ for (const c of notesShown ? ws.comments ?? [] : []) {
1051
+ if (!c.shown) continue;
1052
+ widthPt = Math.max(widthPt, c.shown.xPt + c.shown.widthPt + NOTE_SHADOW_PT);
1053
+ heightPt = Math.max(heightPt, c.shown.yPt + c.shown.heightPt + NOTE_SHADOW_PT);
1054
+ }
668
1055
  return widthPt > 0 || heightPt > 0 ? {
669
1056
  widthPt,
670
1057
  heightPt
@@ -685,7 +1072,7 @@ function centreShape(shape, worksheet, extent, scale) {
685
1072
  const size = section.pageSize;
686
1073
  const margins = section.margins;
687
1074
  if (!size || !margins) return shape;
688
- const dx = options.horizontalCentered ? (size.width - margins.left - margins.right - extent.widthPt * scale) / 2 : 0;
1075
+ const dx = options.horizontalCentered && worksheet.rightToLeft !== true ? (size.width - margins.left - margins.right - extent.widthPt * scale) / 2 : 0;
689
1076
  const dy = options.verticalCentered ? (size.height - margins.top - margins.bottom - extent.heightPt * scale) / 2 : 0;
690
1077
  if (dx <= 0 && dy <= 0) return shape;
691
1078
  const posH = shape.float?.posH;
@@ -37,3 +37,12 @@ export declare function parseXlsxStyles(data: Uint8Array, theme?: ThemePalette):
37
37
  * @returns The format; empty when the node states nothing.
38
38
  */
39
39
  export declare function parseDxf(item: unknown, colors: WorkbookColors): Dxf;
40
+ /**
41
+ * A `<color>` of this workbook as 6-hex: its `rgb`, its `indexed` entry or its
42
+ * `theme` slot, with the `tint` applied (§18.8.3).
43
+ *
44
+ * @param node The `<color>` element, parsed.
45
+ * @param colors What the workbook's colours point at.
46
+ * @returns The colour, or undefined for one that names nothing reachable.
47
+ */
48
+ export declare function workbookColorHex(node: unknown, colors: WorkbookColors): string | undefined;