reamkit 1.30.0 → 1.32.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (147) hide show
  1. package/README.md +70 -24
  2. package/dist/esm/core/bidi/index.d.ts +10 -0
  3. package/dist/esm/core/bidi/index.js +18 -2
  4. package/dist/esm/core/converter/facade.js +10 -5
  5. package/dist/esm/core/converter/project.js +4 -1
  6. package/dist/esm/core/converter/ream.js +17 -6
  7. package/dist/esm/core/converter/sheet-images.d.ts +17 -0
  8. package/dist/esm/core/converter/sheet-images.js +33 -0
  9. package/dist/esm/core/crypto/offcrypto.js +1 -1
  10. package/dist/esm/core/document-model/index.d.ts +1 -1
  11. package/dist/esm/core/document-model/types.d.ts +175 -0
  12. package/dist/esm/core/drawingml/chart-geometry.d.ts +21 -8
  13. package/dist/esm/core/drawingml/chart-geometry.js +198 -108
  14. package/dist/esm/core/drawingml/chart-parser.js +73 -2
  15. package/dist/esm/core/drawingml/colors.d.ts +6 -0
  16. package/dist/esm/core/drawingml/colors.js +23 -3
  17. package/dist/esm/core/drawingml/shape-render.d.ts +3 -1
  18. package/dist/esm/core/drawingml/shape-render.js +4 -2
  19. package/dist/esm/core/font/index.d.ts +2 -0
  20. package/dist/esm/core/font/measure.d.ts +9 -0
  21. package/dist/esm/core/font/measure.js +9 -1
  22. package/dist/esm/core/font/ttf-build.d.ts +113 -0
  23. package/dist/esm/core/font/ttf-build.js +1224 -0
  24. package/dist/esm/core/font/ttf-subset.d.ts +19 -0
  25. package/dist/esm/core/font/ttf-subset.js +14 -1
  26. package/dist/esm/core/fonts/families.js +4 -2
  27. package/dist/esm/core/fonts/provider.d.ts +17 -0
  28. package/dist/esm/core/fonts/provider.js +27 -2
  29. package/dist/esm/core/fonts/remote-fonts.js +8 -1
  30. package/dist/esm/core/fonts/scripts.js +4 -2
  31. package/dist/esm/core/ir/flow.d.ts +77 -1
  32. package/dist/esm/core/ir/sheet.d.ts +43 -0
  33. package/dist/esm/core/number-format.js +16 -11
  34. package/dist/esm/core/numbering/index.d.ts +1 -1
  35. package/dist/esm/core/numbering/state.d.ts +11 -1
  36. package/dist/esm/core/numbering/state.js +10 -1
  37. package/dist/esm/core/spreadsheet-model/index.d.ts +1 -1
  38. package/dist/esm/core/spreadsheet-model/types.d.ts +96 -0
  39. package/dist/esm/core/style-cascade/resolver.js +39 -6
  40. package/dist/esm/core/style-cascade/types.d.ts +19 -1
  41. package/dist/esm/core/style-cascade/types.js +3 -0
  42. package/dist/esm/excel/column-bands.d.ts +39 -6
  43. package/dist/esm/excel/column-bands.js +41 -17
  44. package/dist/esm/excel/comments-parser.js +19 -0
  45. package/dist/esm/excel/conditional-format.d.ts +7 -3
  46. package/dist/esm/excel/conditional-format.js +77 -39
  47. package/dist/esm/excel/formula/context.d.ts +18 -0
  48. package/dist/esm/excel/formula/eval.js +51 -0
  49. package/dist/esm/excel/formula/index.d.ts +2 -2
  50. package/dist/esm/excel/formula/lexer.d.ts +1 -1
  51. package/dist/esm/excel/formula/lexer.js +41 -0
  52. package/dist/esm/excel/formula/parser.d.ts +25 -0
  53. package/dist/esm/excel/formula/parser.js +95 -0
  54. package/dist/esm/excel/print-model.d.ts +38 -1
  55. package/dist/esm/excel/print-model.js +522 -80
  56. package/dist/esm/excel/shared-strings-parser.d.ts +9 -0
  57. package/dist/esm/excel/shared-strings-parser.js +11 -1
  58. package/dist/esm/excel/sheet-drawing.d.ts +38 -2
  59. package/dist/esm/excel/sheet-drawing.js +47 -13
  60. package/dist/esm/excel/sheet-shape-parser.d.ts +6 -2
  61. package/dist/esm/excel/sheet-shape-parser.js +18 -6
  62. package/dist/esm/excel/sheet-to-flow.d.ts +17 -0
  63. package/dist/esm/excel/sheet-to-flow.js +434 -47
  64. package/dist/esm/excel/styles-parser.d.ts +9 -0
  65. package/dist/esm/excel/styles-parser.js +80 -26
  66. package/dist/esm/excel/table-parser.js +5 -0
  67. package/dist/esm/excel/table-style-presets.d.ts +26 -0
  68. package/dist/esm/excel/table-style-presets.js +1555 -0
  69. package/dist/esm/excel/table-style.d.ts +13 -0
  70. package/dist/esm/excel/table-style.js +75 -0
  71. package/dist/esm/excel/tint.d.ts +10 -0
  72. package/dist/esm/excel/tint.js +75 -0
  73. package/dist/esm/excel/vml-drawing.d.ts +41 -3
  74. package/dist/esm/excel/vml-drawing.js +143 -10
  75. package/dist/esm/excel/worksheet-parser.js +89 -15
  76. package/dist/esm/excel/xls/biff-reader.js +10 -2
  77. package/dist/esm/excel/xlsx-reader.js +195 -14
  78. package/dist/esm/excel/xlsx-writer.js +8 -7
  79. package/dist/esm/html/html-writer.js +202 -20
  80. package/dist/esm/index.d.ts +2 -2
  81. package/dist/esm/layout/fit-pages.d.ts +11 -0
  82. package/dist/esm/layout/fit-pages.js +86 -0
  83. package/dist/esm/layout/line-paint.d.ts +58 -0
  84. package/dist/esm/layout/line-paint.js +107 -0
  85. package/dist/esm/layout/page-doc.d.ts +61 -3
  86. package/dist/esm/layout/page-doc.js +141 -1
  87. package/dist/esm/layout/styled-layout.d.ts +55 -7
  88. package/dist/esm/layout/styled-layout.js +1154 -169
  89. package/dist/esm/layout/turned-section.d.ts +38 -0
  90. package/dist/esm/layout/turned-section.js +205 -0
  91. package/dist/esm/markdown/markdown-writer.js +17 -6
  92. package/dist/esm/pdf/styled-page-emitter.js +114 -60
  93. package/dist/esm/pdf/styled-page-renderer.js +1 -1
  94. package/dist/esm/pdf-reader/cff-outline.d.ts +27 -0
  95. package/dist/esm/pdf-reader/cff-outline.js +169 -21
  96. package/dist/esm/pdf-reader/content.d.ts +53 -0
  97. package/dist/esm/pdf-reader/content.js +9 -1
  98. package/dist/esm/pdf-reader/display.d.ts +36 -0
  99. package/dist/esm/pdf-reader/display.js +66 -1
  100. package/dist/esm/pdf-reader/document.js +5 -1
  101. package/dist/esm/pdf-reader/embedded-fonts.d.ts +2 -1
  102. package/dist/esm/pdf-reader/embedded-fonts.js +32 -2
  103. package/dist/esm/pdf-reader/encodings.d.ts +8 -0
  104. package/dist/esm/pdf-reader/encodings.js +25 -3
  105. package/dist/esm/pdf-reader/face-outlines.d.ts +78 -0
  106. package/dist/esm/pdf-reader/face-outlines.js +362 -0
  107. package/dist/esm/pdf-reader/figures.d.ts +52 -0
  108. package/dist/esm/pdf-reader/figures.js +433 -0
  109. package/dist/esm/pdf-reader/flow-build.d.ts +131 -5
  110. package/dist/esm/pdf-reader/flow-build.js +345 -26
  111. package/dist/esm/pdf-reader/font.js +321 -36
  112. package/dist/esm/pdf-reader/glyf-outline.d.ts +33 -0
  113. package/dist/esm/pdf-reader/glyf-outline.js +135 -1
  114. package/dist/esm/pdf-reader/glyph-names.js +154 -1
  115. package/dist/esm/pdf-reader/layout.d.ts +93 -0
  116. package/dist/esm/pdf-reader/layout.js +1703 -214
  117. package/dist/esm/pdf-reader/page-numbers.d.ts +53 -0
  118. package/dist/esm/pdf-reader/page-numbers.js +167 -0
  119. package/dist/esm/pdf-reader/tagged.js +182 -23
  120. package/dist/esm/pdf-reader/text.d.ts +6 -3
  121. package/dist/esm/pdf-reader/text.js +98 -9
  122. package/dist/esm/pdf-reader/type1-outline.d.ts +11 -0
  123. package/dist/esm/pdf-reader/type1-outline.js +63 -8
  124. package/dist/esm/pdf-reader/vector.js +71 -1
  125. package/dist/esm/svg/svg-text.d.ts +58 -0
  126. package/dist/esm/svg/svg-text.js +322 -0
  127. package/dist/esm/svg/svg-writer.js +135 -71
  128. package/dist/esm/word/doc/doc-reader.js +6 -2
  129. package/dist/esm/word/doc/doc-text.d.ts +6 -0
  130. package/dist/esm/word/doc/doc-text.js +19 -1
  131. package/dist/esm/word/document-parser.d.ts +2 -2
  132. package/dist/esm/word/document-parser.js +11 -1
  133. package/dist/esm/word/docx-reader.js +5 -3
  134. package/dist/esm/word/docx-writer.js +218 -23
  135. package/dist/esm/word/drawing-parser.d.ts +5 -3
  136. package/dist/esm/word/drawing-parser.js +49 -8
  137. package/dist/esm/word/font-embed.d.ts +30 -0
  138. package/dist/esm/word/font-embed.js +173 -0
  139. package/dist/esm/word/font-table.d.ts +10 -0
  140. package/dist/esm/word/font-table.js +13 -1
  141. package/dist/esm/word/index.js +1 -1
  142. package/dist/esm/word/numbering-parser.d.ts +3 -1
  143. package/dist/esm/word/numbering-parser.js +2 -1
  144. package/dist/esm/word/paragraph-properties.d.ts +7 -6
  145. package/dist/esm/word/paragraph-properties.js +14 -2
  146. package/dist/esm/word/run-properties.js +26 -0
  147. package/package.json +12 -3
@@ -3,6 +3,7 @@ import { scriptForCodepoint } from "../core/fonts/scripts.js";
3
3
  import { shapeText } from "../core/font/opentype-layout.js";
4
4
  import { hasSubstitutable, substituteLetters } from "../core/font/ligatures.js";
5
5
  import { createFontMeasure } from "../core/font/measure.js";
6
+ import { pageItemBounds } from "./page-doc.js";
6
7
  import { halfPtToPt, pt } from "../core/ir/units.js";
7
8
  import { ResourceStore } from "../core/ir/resources.js";
8
9
  import { headingLevelOf } from "../core/outline.js";
@@ -16,6 +17,7 @@ import { FORCED_BREAK, breakLines } from "../core/line-breaker/knuth-plass.js";
16
17
  import { greedyBreakLines } from "../core/line-breaker/greedy.js";
17
18
  import { cjkBreakBetween, splitCjkSegment } from "../core/line-breaker/cjk.js";
18
19
  import "../core/line-breaker/index.js";
20
+ import { formatCounter } from "../core/numbering/state.js";
19
21
  import { DEFAULT_RESOLVED_PARAGRAPH, DEFAULT_RESOLVED_RUN } from "../core/style-cascade/types.js";
20
22
  import { resolveParagraphProperties, resolveRunProperties } from "../core/style-cascade/resolver.js";
21
23
  import "../core/style-cascade/index.js";
@@ -28,6 +30,7 @@ import { layoutMath, mathGlyphSegments, variantStyle } from "./math-layout.js";
28
30
  import { rectPath } from "../core/drawingml/preset-geometry.js";
29
31
  import { buildShapePaths, buildShapeTransform, buildStroke, gradientToSolid, lineEndPaths } from "../core/drawingml/shape-render.js";
30
32
  import { StructTreeBuilder } from "../pdf/struct-tree.js";
33
+ import { floatIntoFrame, turnOntoSheet } from "./turned-section.js";
31
34
  //#region src/layout/styled-layout.ts
32
35
  function parsePdfAProfile(pdfA) {
33
36
  const part = Number(pdfA.charAt(6));
@@ -39,6 +42,260 @@ function parsePdfAProfile(pdfA) {
39
42
  version: part === 1 ? "1.4" : "1.7"
40
43
  };
41
44
  }
45
+ function leadingOf(options) {
46
+ const profile = options.layoutProfile ?? "ream";
47
+ if (profile !== "ream") return profile;
48
+ return options.typesetBy === "word" ? "word" : "flat";
49
+ }
50
+ var FLAT_LEADING = {
51
+ model: "flat",
52
+ markLinePt: 0
53
+ };
54
+ /**
55
+ * The single line Word sets a family at, in 2048ths of the size: the ascent,
56
+ * the gap it adds above it, and the descent. It is the face's `hhea` line (the
57
+ * `OS/2` typo line where the face sets USE_TYPO_METRICS), not the `usWin` cell
58
+ * — Book Antiqua stands 1.206 of its size in Word, its cell is 1.243. Measured
59
+ * in Word for Mac on the families marked `*`; the rest read by the same rule
60
+ * from the faces Office ships. The layout draws most of them with a twin or a
61
+ * fallback whose own line differs (Caladea stands 1.15 of its size, Cambria
62
+ * 1.172), so a run's height comes from here, by the family it names, and the
63
+ * face in hand answers only for a family not listed.
64
+ */
65
+ var WORD_LINE_METRICS = (() => {
66
+ const arial = [
67
+ 1854,
68
+ 67,
69
+ 434
70
+ ];
71
+ const times = [
72
+ 1825,
73
+ 87,
74
+ 443
75
+ ];
76
+ const courier = [
77
+ 1705,
78
+ 0,
79
+ 615
80
+ ];
81
+ const aptos = [
82
+ 1923,
83
+ 0,
84
+ 577
85
+ ];
86
+ const franklin = [
87
+ 1877,
88
+ 0,
89
+ 445
90
+ ];
91
+ const century = [
92
+ 2019,
93
+ 0,
94
+ 443
95
+ ];
96
+ const lucida = [
97
+ 1980,
98
+ 0,
99
+ 432
100
+ ];
101
+ return new Map([
102
+ ["calibri", [
103
+ 1950,
104
+ 0,
105
+ 550
106
+ ]],
107
+ ["calibri light", [
108
+ 1950,
109
+ 0,
110
+ 550
111
+ ]],
112
+ ["carlito", [
113
+ 1950,
114
+ 0,
115
+ 550
116
+ ]],
117
+ ["cambria", [
118
+ 1946,
119
+ 0,
120
+ 455
121
+ ]],
122
+ ["arial", arial],
123
+ ["helvetica", arial],
124
+ ["liberation sans", arial],
125
+ ["arimo", arial],
126
+ ["arial narrow", [
127
+ 1916,
128
+ 0,
129
+ 434
130
+ ]],
131
+ ["arial black", [
132
+ 2254,
133
+ 0,
134
+ 634
135
+ ]],
136
+ ["times new roman", times],
137
+ ["times", times],
138
+ ["liberation serif", times],
139
+ ["tinos", times],
140
+ ["courier new", courier],
141
+ ["courier", courier],
142
+ ["liberation mono", courier],
143
+ ["cousine", courier],
144
+ ["aptos", aptos],
145
+ ["aptos narrow", aptos],
146
+ ["aptos light", aptos],
147
+ ["aptos semibold", aptos],
148
+ ["aptos extrabold", aptos],
149
+ ["aptos black", aptos],
150
+ ["georgia", [
151
+ 1878,
152
+ 0,
153
+ 449
154
+ ]],
155
+ ["verdana", [
156
+ 2059,
157
+ 0,
158
+ 430
159
+ ]],
160
+ ["tahoma", [
161
+ 2049,
162
+ 0,
163
+ 423
164
+ ]],
165
+ ["trebuchet ms", [
166
+ 1923,
167
+ 0,
168
+ 455
169
+ ]],
170
+ ["candara", [
171
+ 1484,
172
+ 452,
173
+ 564
174
+ ]],
175
+ ["consolas", [
176
+ 1521,
177
+ 350,
178
+ 527
179
+ ]],
180
+ ["constantia", [
181
+ 1538,
182
+ 452,
183
+ 510
184
+ ]],
185
+ ["corbel", [
186
+ 1523,
187
+ 425,
188
+ 525
189
+ ]],
190
+ ["century gothic", [
191
+ 2060,
192
+ 0,
193
+ 451
194
+ ]],
195
+ ["century", century],
196
+ ["century schoolbook", century],
197
+ ["garamond", [
198
+ 1765,
199
+ 0,
200
+ 539
201
+ ]],
202
+ ["book antiqua", [
203
+ 1891,
204
+ 0,
205
+ 578
206
+ ]],
207
+ ["palatino linotype", [
208
+ 2150,
209
+ 0,
210
+ 613
211
+ ]],
212
+ ["bookman old style", [
213
+ 1929,
214
+ 0,
215
+ 475
216
+ ]],
217
+ ["rockwell", [
218
+ 1937,
219
+ 0,
220
+ 468
221
+ ]],
222
+ ["franklin gothic book", franklin],
223
+ ["franklin gothic medium", franklin],
224
+ ["franklin gothic demi", franklin],
225
+ ["gill sans mt", [
226
+ 1903,
227
+ 0,
228
+ 472
229
+ ]],
230
+ ["tw cen mt", [
231
+ 1753,
232
+ 0,
233
+ 477
234
+ ]],
235
+ ["lucida sans unicode", [
236
+ 2246,
237
+ 0,
238
+ 901
239
+ ]],
240
+ ["lucida sans", lucida],
241
+ ["lucida bright", lucida],
242
+ ["lucida console", [
243
+ 1616,
244
+ 0,
245
+ 432
246
+ ]],
247
+ ["comic sans ms", [
248
+ 2257,
249
+ 0,
250
+ 597
251
+ ]],
252
+ ["impact", [
253
+ 2066,
254
+ 0,
255
+ 432
256
+ ]],
257
+ ["goudy old style", [
258
+ 1832,
259
+ 140,
260
+ 486
261
+ ]],
262
+ ["calisto mt", [
263
+ 1894,
264
+ 0,
265
+ 470
266
+ ]],
267
+ ["perpetua", [
268
+ 1679,
269
+ 0,
270
+ 668
271
+ ]],
272
+ ["symbol", [
273
+ 2059,
274
+ 0,
275
+ 450
276
+ ]],
277
+ ["wingdings", [
278
+ 1841,
279
+ 0,
280
+ 432
281
+ ]],
282
+ ["wingdings 2", [
283
+ 1727,
284
+ 0,
285
+ 432
286
+ ]],
287
+ ["wingdings 3", [
288
+ 1900,
289
+ 0,
290
+ 432
291
+ ]],
292
+ ["webdings", [
293
+ 1638,
294
+ 0,
295
+ 410
296
+ ]]
297
+ ]);
298
+ })();
42
299
  /** §17.6.4 — how heavy Word draws the rule between columns. */
43
300
  var COLUMN_SEPARATOR_PT = .5;
44
301
  /**
@@ -55,7 +312,6 @@ var FOOTNOTE_RULE_PT = .75;
55
312
  var FOOTNOTE_RULE_GAP_ABOVE = 4;
56
313
  var FOOTNOTE_SEPARATOR_HEIGHT = 10;
57
314
  var LINE_NUMBER_GAP_PT = 18;
58
- var FLOAT_TEXT_GAP = 6;
59
315
  var MIN_WRAP_WIDTH = 36;
60
316
  function isOutOfFlowFloat(f) {
61
317
  return f !== void 0 && f.wrap !== "topAndBottom";
@@ -315,6 +571,7 @@ function layoutStyledDocument(body, options) {
315
571
  const blocks = numberedBody.map((el, idx) => {
316
572
  while (sectionIdx < sectionCtxs.length - 1 && idx >= sectionCtxs[sectionIdx].endIndex) sectionIdx++;
317
573
  const ctx = sectionCtxs[sectionIdx];
574
+ const own = ctx.sheet ? floatIntoFrame(el, ctx.sheet) : el;
318
575
  const width = ctx.columns ? ctx.columns[0].widthPt : ctx.contentWidth;
319
576
  const box = {
320
577
  pageWidthPt: ctx.pageWidth,
@@ -327,7 +584,7 @@ function layoutStyledDocument(body, options) {
327
584
  const group = bodyFrames.starts.get(idx);
328
585
  if (group) return layoutFrameBlock(group, ctx.options ?? options, fontResources, imageResources, width);
329
586
  if (bodyFrames.continued.has(idx)) return emptyBlock(options);
330
- return layoutBodyElement(el, ctx.options ?? options, fontResources, imageResources, width, ctx.pageContentHeight, box);
587
+ return layoutBodyElement(own, ctx.options ?? options, fontResources, imageResources, width, ctx.pageContentHeight, box);
331
588
  });
332
589
  for (let i = 0; i < blocks.length; i++) {
333
590
  const here = blocks[i];
@@ -359,9 +616,15 @@ function layoutStyledDocument(body, options) {
359
616
  let laid = void 0;
360
617
  if (content && n !== void 0 && !noteAssigned.deferredFootnotes.includes(id)) {
361
618
  const noteBlocks = substituteNoteNumber(content, n).map((el) => layoutBodyElement(el, sectionCtx.options ?? options, fontResources, imageResources, sectionCtx.contentWidth, sectionCtx.pageContentHeight));
619
+ const spacing = new SpacingStack(collapsesSpacing(sectionCtx.options ?? options));
362
620
  laid = {
363
621
  blocks: noteBlocks,
364
- heightPt: noteBlocks.reduce((sum, b) => sum + (b.kind === "paragraph" ? b.spacingBeforePt + b.heightPt + b.spacingAfterPt : 0), 0)
622
+ heightPt: noteBlocks.reduce((sum, b) => {
623
+ if (b.kind !== "paragraph") return sum;
624
+ const before = spacing.open(b.spacingBeforePt);
625
+ spacing.close(b.spacingAfterPt);
626
+ return sum + before + b.heightPt + b.spacingAfterPt;
627
+ }, 0)
365
628
  };
366
629
  }
367
630
  byId.set(id, laid);
@@ -435,7 +698,7 @@ function layoutStyledDocument(body, options) {
435
698
  const commentNotes = options.commentAnnotations && options.comments ? buildCommentNotes(noteAssigned.comments, options.comments) : void 0;
436
699
  const reflowParagraph = (paragraph, width, widths) => layoutParagraphBlock(paragraph, options, fontResources, imageResources, width, widths);
437
700
  return {
438
- pages: paginateSections(blocks, sectionCtxs, structBuilder, options.language ?? "en-US", notePlan, bookmarks, reflowParagraph, backgroundImageOf(options, imageResources)),
701
+ pages: paginateSections(blocks, sectionCtxs, structBuilder, options.language ?? "en-US", notePlan, bookmarks, reflowParagraph, backgroundImageOf(options, imageResources), collapsesSpacing(options), options.compatibilityMode),
439
702
  resources: options.resources ?? new ResourceStore(),
440
703
  fontResources,
441
704
  imageResources,
@@ -509,9 +772,52 @@ function buildSectionContext(section, options, headersFooters, fontResources, im
509
772
  bottom: dims.marginBottom
510
773
  }, relativeBox);
511
774
  const columns = buildColumnGeometry(section.properties.columns, contentWidth);
512
- const headerHeight = Math.max(headerSet.default.heightPt ?? 0, section.properties.titlePg === true ? headerSet.first.heightPt ?? 0 : 0, section.properties.evenAndOddHeaders === true ? headerSet.even.heightPt ?? 0 : 0);
513
- const headerBottom = headerHeight > 0 ? dims.headerOffsetPt + headerHeight : 0;
514
- const marginTop = Math.max(dims.marginTop, headerBottom);
775
+ const bodyMarginsFor = (band) => {
776
+ const header = pickBand(headerSet, band).heightPt ?? 0;
777
+ const footer = pickBand(footerSet, band).heightPt ?? 0;
778
+ return {
779
+ top: Math.max(dims.marginTop, header > 0 ? dims.headerOffsetPt + header : 0),
780
+ bottom: Math.max(dims.marginBottom, footer > 0 ? dims.footerOffsetPt + footer : 0)
781
+ };
782
+ };
783
+ const bandMargins = {
784
+ default: bodyMarginsFor("default"),
785
+ first: bodyMarginsFor("first"),
786
+ even: bodyMarginsFor("even")
787
+ };
788
+ const marginTop = bandMargins.default.top;
789
+ const marginBottom = bandMargins.default.bottom;
790
+ if (section.properties.textDirection !== void 0) {
791
+ const sheet = {
792
+ width: dims.pageWidth,
793
+ height: dims.pageHeight,
794
+ marginLeft: dims.marginLeft,
795
+ marginRight: dims.marginRight,
796
+ marginTop,
797
+ marginBottom
798
+ };
799
+ const across = sheet.height - sheet.marginTop - sheet.marginBottom;
800
+ const turnedColumns = buildColumnGeometry(section.properties.columns, across);
801
+ return {
802
+ endIndex: section.endIndex,
803
+ properties: section.properties,
804
+ pageWidth: sheet.height,
805
+ pageHeight: sheet.width,
806
+ marginLeft: sheet.marginTop,
807
+ marginTop: sheet.marginRight,
808
+ marginBottom: sheet.marginLeft,
809
+ contentWidth: across,
810
+ pageContentHeight: sheet.width - sheet.marginRight - sheet.marginLeft,
811
+ ...turnedColumns ? { columns: turnedColumns } : {},
812
+ headerSet,
813
+ footerSet,
814
+ titlePg: section.properties.titlePg === true,
815
+ evenAndOddHeaders: section.properties.evenAndOddHeaders === true,
816
+ continuous: section.properties.sectionStart === "continuous",
817
+ options: secOptions,
818
+ sheet
819
+ };
820
+ }
515
821
  return {
516
822
  endIndex: section.endIndex,
517
823
  properties: section.properties,
@@ -519,12 +825,13 @@ function buildSectionContext(section, options, headersFooters, fontResources, im
519
825
  pageHeight: dims.pageHeight,
520
826
  marginLeft: dims.marginLeft,
521
827
  marginTop,
522
- marginBottom: dims.marginBottom,
828
+ marginBottom,
523
829
  contentWidth,
524
- pageContentHeight: dims.pageHeight - marginTop - dims.marginBottom,
830
+ pageContentHeight: dims.pageHeight - marginTop - marginBottom,
525
831
  ...columns ? { columns } : {},
526
832
  headerSet,
527
833
  footerSet,
834
+ bandMargins,
528
835
  titlePg: section.properties.titlePg === true,
529
836
  evenAndOddHeaders: section.properties.evenAndOddHeaders === true,
530
837
  continuous: section.properties.sectionStart === "continuous",
@@ -572,15 +879,15 @@ function layoutHeaderSet(section, headersFooters, options, fontResources, imageR
572
879
  let measured = 0;
573
880
  const render = (c) => {
574
881
  const blocks = laidOutBlocksFor(c, options, fontResources, contentWidth, imageResources, relativeBox);
575
- measured = blocksHeight(blocks);
576
- return markPagination(drawBlocksSequentially(blocks, marginLeft, pageHeight - headerOffsetPt, pageHeight, contentWidth, void 0, bandMargins));
882
+ measured = blocksHeight(blocks, collapsesSpacing(options));
883
+ return markPagination(drawBlocksSequentially(blocks, marginLeft, pageHeight - headerOffsetPt, pageHeight, contentWidth, void 0, bandMargins, collapsesSpacing(options)));
577
884
  };
578
885
  if (contentHasPageFields(content)) {
579
- render(substitutePageFields(content, 1, 1));
886
+ render(substitutePageFields(content, "1", 1));
580
887
  return {
581
888
  commands: [],
582
889
  heightPt: measured,
583
- renderDynamic: (n, total) => render(substitutePageFields(content, n, total))
890
+ renderDynamic: (page, total) => render(substitutePageFields(content, page, total))
584
891
  };
585
892
  }
586
893
  return {
@@ -609,11 +916,61 @@ function balancedColumnHeight(bandHeightPt, colCount, roomPt) {
609
916
  if (bandHeightPt > roomPt * colCount) return 0;
610
917
  return Math.min(roomPt, bandHeightPt / colCount);
611
918
  }
612
- /** Total laid-out height of a run of blocks, paragraph spacing included. */
613
- function blocksHeight(blocks) {
919
+ /**
920
+ * §17.3.1.33 — the space between paragraphs down one story (a page, a cell, a
921
+ * band, a box). Where the document is set as Word sets it, two paragraphs
922
+ * stand the LARGER of the first's space after and the second's space before
923
+ * apart, not the two together. Measured in Word for Mac, in modes 14 and 15
924
+ * alike: 10pt after and 10pt before stand 10pt apart; 10 and 24 stand 24,
925
+ * whichever comes first; 6 and 12 stand 12 either way. An empty paragraph
926
+ * between takes its share of both sides. Elsewhere the two add up.
927
+ */
928
+ var SpacingStack = class {
929
+ /** The space after the last block, which a paragraph right below may share. */
930
+ shared = 0;
931
+ constructor(collapse) {
932
+ this.collapse = collapse;
933
+ }
934
+ /**
935
+ * @param beforePt A block's own space before.
936
+ * @returns The room to open above it.
937
+ */
938
+ open(beforePt) {
939
+ const room = this.collapse ? Math.max(0, beforePt - this.shared) : beforePt;
940
+ this.shared = 0;
941
+ return room;
942
+ }
943
+ /** @param spacingPt The space a block just left below itself, as far as a paragraph may share it. */
944
+ close(spacingPt) {
945
+ this.shared = spacingPt;
946
+ }
947
+ };
948
+ /** Whether a layout stacks paragraph spacing as Word does (see {@link SpacingStack}). */
949
+ function collapsesSpacing(options) {
950
+ return options.typesetBy === "word";
951
+ }
952
+ /** A block's space after, less the line a picture's spacing adds under it: the part a paragraph below shares. */
953
+ function sharedAfter(block) {
954
+ return block.spacingAfterPt - (block.lineExtraPt ?? 0);
955
+ }
956
+ /**
957
+ * Total laid-out height of a run of blocks, paragraph spacing included.
958
+ *
959
+ * @param blocks The blocks, in order.
960
+ * @param collapse Whether their spacing stacks as Word stacks it (see {@link SpacingStack}).
961
+ * @returns The height in points.
962
+ */
963
+ function blocksHeight(blocks, collapse = false) {
964
+ const spacing = new SpacingStack(collapse);
614
965
  return blocks.reduce((sum, b) => {
615
966
  if (b.kind !== "paragraph" && isOutOfFlowFloat(b.float)) return sum;
616
- return sum + (b.kind === "paragraph" ? b.spacingBeforePt + b.heightPt + b.spacingAfterPt : b.heightPt);
967
+ if (!("spacingBeforePt" in b)) {
968
+ spacing.close(0);
969
+ return sum + b.heightPt;
970
+ }
971
+ const before = spacing.open(b.spacingBeforePt);
972
+ spacing.close(sharedAfter(b));
973
+ return sum + before + b.heightPt + b.spacingAfterPt;
617
974
  }, 0);
618
975
  }
619
976
  function layoutFooterSet(section, headersFooters, options, fontResources, imageResources, contentWidth, marginLeft, pageHeight, footerOffsetPt, bandMargins, relativeBox) {
@@ -622,15 +979,25 @@ function layoutFooterSet(section, headersFooters, options, fontResources, imageR
622
979
  if (!ref) return { commands: [] };
623
980
  const content = headersFooters.get(ref.relationshipId);
624
981
  if (!content) return { commands: [] };
982
+ let measured = 0;
625
983
  const render = (c) => {
626
984
  const blocks = laidOutBlocksFor(c, options, fontResources, contentWidth, imageResources, relativeBox);
627
- return markPagination(drawBlocksSequentially(blocks, marginLeft, footerOffsetPt + blocksHeight(blocks), pageHeight, contentWidth, void 0, bandMargins));
985
+ const totalHeight = blocksHeight(blocks, collapsesSpacing(options));
986
+ measured = totalHeight;
987
+ return markPagination(drawBlocksSequentially(blocks, marginLeft, footerOffsetPt + totalHeight, pageHeight, contentWidth, void 0, bandMargins, collapsesSpacing(options)));
628
988
  };
629
- if (contentHasPageFields(content)) return {
630
- commands: [],
631
- renderDynamic: (n, total) => render(substitutePageFields(content, n, total))
989
+ if (contentHasPageFields(content)) {
990
+ render(substitutePageFields(content, "1", 1));
991
+ return {
992
+ commands: [],
993
+ heightPt: measured,
994
+ renderDynamic: (page, total) => render(substitutePageFields(content, page, total))
995
+ };
996
+ }
997
+ return {
998
+ commands: render(content),
999
+ heightPt: measured
632
1000
  };
633
- return { commands: render(content) };
634
1001
  };
635
1002
  return {
636
1003
  default: band("default"),
@@ -642,7 +1009,20 @@ function contentHasPageFields(content) {
642
1009
  for (const el of content) if (el.kind === "paragraph" && el.paragraph.runs.some((r) => r.field !== void 0)) return true;
643
1010
  return false;
644
1011
  }
645
- function substitutePageFields(content, pageNumber, totalPages) {
1012
+ /**
1013
+ * §17.6.12 — a page's number as its section prints it: in the numerals
1014
+ * `w:pgNumType w:fmt` names (§17.18.59), decimal where it names none. A format
1015
+ * that has no numeral for the number — a roman zero — prints it in digits.
1016
+ *
1017
+ * @param pageNumber The page's number in its section's count.
1018
+ * @param format The section's page-number format.
1019
+ * @returns The text a PAGE field shows on that page.
1020
+ */
1021
+ function pageNumberText(pageNumber, format) {
1022
+ const text = format === void 0 ? "" : formatCounter(format, pageNumber);
1023
+ return text !== "" ? text : String(pageNumber);
1024
+ }
1025
+ function substitutePageFields(content, pageText, totalPages) {
646
1026
  return content.map((el) => {
647
1027
  if (el.kind !== "paragraph") return el;
648
1028
  if (!el.paragraph.runs.some((r) => r.field !== void 0)) return el;
@@ -652,7 +1032,7 @@ function substitutePageFields(content, pageNumber, totalPages) {
652
1032
  ...el.paragraph,
653
1033
  runs: el.paragraph.runs.map((r) => r.field === void 0 ? r : {
654
1034
  ...r,
655
- text: String(r.field === "PAGE" ? pageNumber : totalPages)
1035
+ text: r.field === "PAGE" ? pageText : String(totalPages)
656
1036
  })
657
1037
  }
658
1038
  };
@@ -953,12 +1333,33 @@ function layoutBodyElement(el, options, fontResources, imageResources, contentWi
953
1333
  if (el.kind === "image") {
954
1334
  const res = el.image.resource ? imageResources?.get(el.image.resource) : void 0;
955
1335
  const meta = res?.metafile;
956
- return meta ? layoutMetafileBlock(el.image, meta, res.metafileImages, options, fontResources, contentWidth, maxHeight, box) : layoutImageBlock(el.image, imageResources, contentWidth, maxHeight, box);
1336
+ return meta ? layoutMetafileBlock(el.image, meta, res.metafileImages, options, fontResources, contentWidth, maxHeight, box) : layoutImageBlock(el.image, imageResources, contentWidth, maxHeight, box, options);
957
1337
  }
958
1338
  if (el.kind === "chart") return layoutChartBlock(el.chart, options, fontResources, contentWidth, maxHeight, imageResources);
959
1339
  return layoutShapeBlock(el.shape, options, fontResources, imageResources, contentWidth, maxHeight, box);
960
1340
  }
961
- function layoutImageBlock(image, imageResources, contentWidth, maxHeight, box) {
1341
+ /**
1342
+ * §17.3.1.33 — what a paragraph's line spacing adds under a picture that
1343
+ * stands on a line of its own. Word sets that line as tall as the picture,
1344
+ * and a spacing of more than one line adds what the extra lines of the
1345
+ * paragraph mark's font would, not a share of the picture: in Word for Mac a
1346
+ * 100pt picture's next paragraph stands 100, 102.5 and 113 points down at
1347
+ * single, 1.15 and double spacing of an 11pt mark.
1348
+ *
1349
+ * @param pp The paragraph the picture stands on, resolved.
1350
+ * @param options The render options its mark's font resolves against; absent
1351
+ * for a picture out of the flow.
1352
+ * @returns The extra points, 0 where the spacing is not a multiple above one.
1353
+ */
1354
+ function pictureLineExtra(pp, options) {
1355
+ if (!options) return 0;
1356
+ if (pp.spacingLine === void 0 || (pp.spacingLineRule ?? "auto") !== "auto") return 0;
1357
+ const multiple = Math.round(pp.spacingLine * 20) / 240;
1358
+ if (multiple <= 1) return 0;
1359
+ if (leadingOf(options) !== "flat") return (multiple - 1) * paragraphLeading(pp, options).markLinePt;
1360
+ return (multiple - 1) * resolveRunProperties({}, pp, options.styles).fontSizePt * 1.2;
1361
+ }
1362
+ function layoutImageBlock(image, imageResources, contentWidth, maxHeight, box, options) {
962
1363
  let widthPt = relativeWidth(image.relativeSize, contentWidth, box) ?? image.width;
963
1364
  let heightPt = relativeHeight(image.relativeSize, maxHeight, box) ?? (image.relativeSize?.widthPct && image.width > 0 ? image.height * (widthPt / image.width) : image.height);
964
1365
  if (widthPt > contentWidth && !image.float) {
@@ -993,6 +1394,7 @@ function layoutImageBlock(image, imageResources, contentWidth, maxHeight, box) {
993
1394
  heightPt = reservedH;
994
1395
  drawInset = box;
995
1396
  }
1397
+ const lineExtraPt = image.float ? 0 : pictureLineExtra(image.paragraphProperties, options);
996
1398
  const res = image.resource ? imageResources?.get(image.resource) : void 0;
997
1399
  const resolvedAlignment = image.paragraphProperties.alignment ?? "left";
998
1400
  return {
@@ -1010,7 +1412,8 @@ function layoutImageBlock(image, imageResources, contentWidth, maxHeight, box) {
1010
1412
  ...image.flipV ? { flipV: true } : {},
1011
1413
  ...drawInset ? { drawInset } : {},
1012
1414
  spacingBeforePt: image.paragraphProperties.spacingBefore ?? 0,
1013
- spacingAfterPt: image.paragraphProperties.spacingAfter ?? 0,
1415
+ spacingAfterPt: (image.paragraphProperties.spacingAfter ?? 0) + lineExtraPt,
1416
+ ...lineExtraPt > 0 ? { lineExtraPt } : {},
1014
1417
  ...image.altText ? { altText: image.altText } : {},
1015
1418
  ...image.float ? { float: image.float } : {}
1016
1419
  };
@@ -1054,8 +1457,10 @@ function layoutShapeBlock(shape, options, fontResources, imageResources, content
1054
1457
  const upright = text?.upright;
1055
1458
  const warp = text?.warp;
1056
1459
  if (text && text.content.length > 0) {
1057
- const innerWidth = warp ? Number.MAX_SAFE_INTEGER : vertical ? Math.max(1, heightPt - insetTopPt - insetBottomPt) : Math.max(1, widthPt - insetLeftPt - insetRightPt);
1460
+ const innerWidth = warp || text.noWrap === true ? Number.MAX_SAFE_INTEGER : vertical ? Math.max(1, heightPt - insetTopPt - insetBottomPt) : Math.max(1, widthPt - insetLeftPt - insetRightPt);
1461
+ const spacing = new SpacingStack(collapsesSpacing(options));
1058
1462
  for (const el of text.content) {
1463
+ if (el.kind !== "paragraph") spacing.close(0);
1059
1464
  if (el.kind === "image") {
1060
1465
  const res = el.image.resource ? imageResources?.get(el.image.resource) : void 0;
1061
1466
  const scaled = Math.min(1, innerWidth / Math.max(1, el.image.width));
@@ -1129,7 +1534,8 @@ function layoutShapeBlock(shape, options, fontResources, imageResources, content
1129
1534
  continue;
1130
1535
  }
1131
1536
  const blk = layoutParagraphBlock(el.paragraph, options, fontResources, imageResources, innerWidth);
1132
- const before = textLines.length > 0 ? blk.spacingBeforePt : 0;
1537
+ const opened = spacing.open(blk.spacingBeforePt);
1538
+ const before = textLines.length > 0 ? opened : 0;
1133
1539
  if (before > 0) {
1134
1540
  const last = textLines.length - 1;
1135
1541
  textLineGaps.set(last, (textLineGaps.get(last) ?? 0) + before);
@@ -1144,6 +1550,7 @@ function layoutShapeBlock(shape, options, fontResources, imageResources, content
1144
1550
  textLineGaps.set(last, (textLineGaps.get(last) ?? 0) + blk.spacingAfterPt);
1145
1551
  }
1146
1552
  textHeightPt += blk.spacingAfterPt;
1553
+ spacing.close(blk.spacingAfterPt);
1147
1554
  }
1148
1555
  }
1149
1556
  if (text?.autoFit && !vertical && textLines.length > 0) heightPt = textHeightPt + insetTopPt + insetBottomPt;
@@ -1259,6 +1666,7 @@ function layoutMetafileBlock(image, pic, blitted, options, fontResources, conten
1259
1666
  const heightPt = placed.heightPt;
1260
1667
  const layout = rotateDrawing(metafileDrawing(pic, widthPt, heightPt, options, fontResources, image.colorChange, blitted), (image.rotation60k ?? 0) / 6e4, widthPt / 2, heightPt / 2);
1261
1668
  const pp = image.paragraphProperties;
1669
+ const lineExtraPt = image.float ? 0 : pictureLineExtra(pp, options);
1262
1670
  return {
1263
1671
  kind: "chart",
1264
1672
  widthPt,
@@ -1266,7 +1674,8 @@ function layoutMetafileBlock(image, pic, blitted, options, fontResources, conten
1266
1674
  layout,
1267
1675
  resolvedAlignment: pp.alignment ?? "left",
1268
1676
  spacingBeforePt: pp.spacingBefore ?? 0,
1269
- spacingAfterPt: pp.spacingAfter ?? 0,
1677
+ spacingAfterPt: (pp.spacingAfter ?? 0) + lineExtraPt,
1678
+ ...lineExtraPt > 0 ? { lineExtraPt } : {},
1270
1679
  figureRole: "Image",
1271
1680
  ...image.altText ? { altText: image.altText } : {},
1272
1681
  ...image.float ? { float: image.float } : {}
@@ -1433,7 +1842,7 @@ function metafileDrawing(pic, widthPt, heightPt, options, fontResources, colorCh
1433
1842
  function layoutChartBlock(block, options, fontResources, contentWidth, maxHeight, imageResources) {
1434
1843
  let widthPt = block.width;
1435
1844
  let heightPt = block.height;
1436
- if (widthPt > contentWidth && widthPt > 0) {
1845
+ if (widthPt > contentWidth && widthPt > 0 && !isOutOfFlowFloat(block.float)) {
1437
1846
  const scale = contentWidth / widthPt;
1438
1847
  widthPt = contentWidth;
1439
1848
  heightPt *= scale;
@@ -1503,8 +1912,13 @@ function rectAtPath(x, y, w, h) {
1503
1912
  return new PathBuilder().moveTo(x, y).lineTo(x + w, y).lineTo(x + w, y + h).lineTo(x, y + h).close().build();
1504
1913
  }
1505
1914
  var CF_ICON_SIZE_PT = 9;
1915
+ /** How far inside its cell Excel keeps a data bar, on every side — 2px. */
1916
+ var DATA_BAR_INSET_PT = 1.5;
1506
1917
  var CF_ICON_GUTTER_PT = 12;
1507
1918
  var CF_ICON_EMPTY_HEX = "BFBFBF";
1919
+ var NOTE_FLAG_PT = 4.5;
1920
+ var NOTE_FLAG_HEX = "FF0000";
1921
+ var NOTE_THREAD_HEX = "7030A0";
1508
1922
  var DROPDOWN_BUTTON_PT = 11;
1509
1923
  var DROPDOWN_FILL_HEX = "F0F0F0";
1510
1924
  var DROPDOWN_BORDER_HEX = "B0B0B0";
@@ -1730,8 +2144,10 @@ function collectImageResources(body, headersFooters, options) {
1730
2144
  if (el.image.resource) seen.add(el.image.resource);
1731
2145
  } else if (el.kind === "paragraph") {
1732
2146
  for (const run of el.paragraph.runs) if (run.inlineImage?.resource) seen.add(run.inlineImage.resource);
1733
- } else if (el.kind === "table") for (const row of el.table.rows) for (const cell of row.cells) visit(cell.content);
1734
- else if (el.kind === "shape") {
2147
+ } else if (el.kind === "table") {
2148
+ for (const row of el.table.rows) for (const cell of row.cells) visit(cell.content);
2149
+ visit(el.table.overlay?.drawings ?? []);
2150
+ } else if (el.kind === "shape") {
1735
2151
  const shapeImages = (sh) => {
1736
2152
  if (sh.fill.imageResource) seen.add(sh.fill.imageResource);
1737
2153
  if (sh.text) visit(sh.text.content);
@@ -2059,28 +2475,47 @@ function emitCellFloat(cf, ctx, frame) {
2059
2475
  if (!float) return;
2060
2476
  const x = floatXIn(frame, float, cf.block.widthPt);
2061
2477
  const topYUp = floatTopYUpIn(frame, float, cf.block.heightPt);
2062
- const sink = ctx.sink(float.behind === true, float.zOrder);
2063
- if (cf.block.kind === "shape") emitShapeItems(cf.block, x, topYUp - cf.block.heightPt, sink, ctx.pageHeight, void 0);
2064
- else {
2065
- const ins = cf.block.drawInset;
2066
- sink.push({
2067
- type: "image",
2068
- x: pt(x + (ins?.dxPt ?? 0)),
2069
- y: pt(ctx.pageHeight - (topYUp - (ins?.dyTopPt ?? 0))),
2070
- width: pt(ins?.widthPt ?? cf.block.widthPt),
2071
- height: pt(ins?.heightPt ?? cf.block.heightPt),
2072
- imageResourceName: cf.block.resourceName,
2073
- ...cf.block.crop ? { crop: cf.block.crop } : {},
2074
- ...cf.block.wash ? { wash: cf.block.wash } : {},
2075
- ...cf.block.alpha !== void 0 ? { alpha: cf.block.alpha } : {},
2076
- ...cf.block.rotationDeg ? { rotationDeg: cf.block.rotationDeg } : {},
2077
- ...cf.block.flipH ? { flipH: true } : {},
2078
- ...cf.block.flipV ? { flipV: true } : {}
2079
- });
2080
- if (cf.block.outline) sink.push(...pictureOutlineItems(cf.block.outline, x + (ins?.dxPt ?? 0), ctx.pageHeight - (topYUp - (ins?.dyTopPt ?? 0)), ins?.widthPt ?? cf.block.widthPt, ins?.heightPt ?? cf.block.heightPt, void 0));
2081
- }
2478
+ emitDrawing(cf.block, x, topYUp, ctx.sink(float.behind === true, float.zOrder), ctx.pageHeight, void 0);
2082
2479
  if (float.wrap !== "none") ctx.exclude(float, x, topYUp, cf.block.widthPt, cf.block.heightPt);
2083
2480
  }
2481
+ /**
2482
+ * A drawing out of the flow — a picture, a shape or a chart — in the items it
2483
+ * paints, its top-left corner at `(x, topYUp)` in the y-up cursor frame.
2484
+ *
2485
+ * @param block The drawing, laid out.
2486
+ * @param x Its left edge on the page.
2487
+ * @param topYUp Its top, y-up.
2488
+ * @param sink Where its items go.
2489
+ * @param pageHeight The page's height, for the flip into the page frame.
2490
+ * @param figId The tagged-PDF Figure its items belong to, if any.
2491
+ */
2492
+ function emitDrawing(block, x, topYUp, sink, pageHeight, figId) {
2493
+ if (block.kind === "shape") {
2494
+ emitShapeItems(block, x, topYUp - block.heightPt, sink, pageHeight, figId);
2495
+ return;
2496
+ }
2497
+ if (block.kind === "chart") {
2498
+ sink.push(...chartPageItems(block, x, topYUp - block.heightPt, pageHeight, figId));
2499
+ return;
2500
+ }
2501
+ const ins = block.drawInset;
2502
+ sink.push({
2503
+ type: "image",
2504
+ x: pt(x + (ins?.dxPt ?? 0)),
2505
+ y: pt(pageHeight - (topYUp - (ins?.dyTopPt ?? 0))),
2506
+ width: pt(ins?.widthPt ?? block.widthPt),
2507
+ height: pt(ins?.heightPt ?? block.heightPt),
2508
+ imageResourceName: block.resourceName,
2509
+ ...block.crop ? { crop: block.crop } : {},
2510
+ ...block.wash ? { wash: block.wash } : {},
2511
+ ...block.alpha !== void 0 ? { alpha: block.alpha } : {},
2512
+ ...block.rotationDeg ? { rotationDeg: block.rotationDeg } : {},
2513
+ ...block.flipH ? { flipH: true } : {},
2514
+ ...block.flipV ? { flipV: true } : {},
2515
+ ...figId !== void 0 ? { structId: figId } : {}
2516
+ });
2517
+ if (block.outline) sink.push(...pictureOutlineItems(block.outline, x + (ins?.dxPt ?? 0), pageHeight - (topYUp - (ins?.dyTopPt ?? 0)), ins?.widthPt ?? block.widthPt, ins?.heightPt ?? block.heightPt, figId));
2518
+ }
2084
2519
  function emitShapeItems(sh, x, bottomYUp, sink, pageHeight, figId) {
2085
2520
  const castsUnderPicture = sh.fillImageResourceName !== void 0 ? sh.shadow : void 0;
2086
2521
  const shadowedFill = castsUnderPicture !== void 0;
@@ -2231,9 +2666,10 @@ function bandAnchorTop(float, cursorY, pageHeight, heightPt = 0, margins) {
2231
2666
  const offset = float.posV?.offsetPt ?? 0;
2232
2667
  return float.posV?.relativeFrom === "page" ? pageHeight - offset : cursorY - offset;
2233
2668
  }
2234
- function drawBlocksSequentially(blocks, startX, startY, pageHeight, contentWidth, structId, bandMargins) {
2669
+ function drawBlocksSequentially(blocks, startX, startY, pageHeight, contentWidth, structId, bandMargins, collapse = false) {
2235
2670
  const out = [];
2236
2671
  let cursorY = startY;
2672
+ const spacing = new SpacingStack(collapse);
2237
2673
  const isBehind = (b) => "float" in b && b.float?.behind === true;
2238
2674
  const ordered = blocks.some(isBehind) ? [...blocks.filter(isBehind), ...blocks.filter((b) => !isBehind(b))] : blocks;
2239
2675
  for (const block of ordered) {
@@ -2252,18 +2688,20 @@ function drawBlocksSequentially(blocks, startX, startY, pageHeight, contentWidth
2252
2688
  emitRowChunk(out, row, tableX, cursorY, pageHeight, block.colCount);
2253
2689
  cursorY -= row.heightPt;
2254
2690
  }
2691
+ spacing.close(0);
2255
2692
  continue;
2256
2693
  }
2257
2694
  if (block.kind === "chart") {
2258
- cursorY -= block.spacingBeforePt + block.heightPt;
2695
+ cursorY -= spacing.open(block.spacingBeforePt) + block.heightPt;
2259
2696
  const offset = alignmentOffset(block.resolvedAlignment, block.widthPt, contentWidth);
2260
2697
  out.push(...chartPageItems(block, startX + offset, cursorY, pageHeight, structId));
2261
2698
  cursorY -= block.spacingAfterPt;
2699
+ spacing.close(sharedAfter(block));
2262
2700
  continue;
2263
2701
  }
2264
2702
  if (block.kind === "image") {
2265
- cursorY -= block.spacingBeforePt;
2266
2703
  const anchored = bandAnchor(block.float, block.widthPt, contentWidth, startX);
2704
+ cursorY -= anchored === void 0 ? spacing.open(block.spacingBeforePt) : block.spacingBeforePt;
2267
2705
  const offset = anchored ?? alignmentOffset(block.resolvedAlignment, block.widthPt, contentWidth);
2268
2706
  const top = anchored === void 0 || !block.float ? cursorY : bandAnchorTop(block.float, cursorY, pageHeight, block.heightPt, bandMargins);
2269
2707
  out.push({
@@ -2283,19 +2721,21 @@ function drawBlocksSequentially(blocks, startX, startY, pageHeight, contentWidth
2283
2721
  if (block.outline) out.push(...pictureOutlineItems(block.outline, startX + offset, pageHeight - top, block.widthPt, block.heightPt, structId));
2284
2722
  if (anchored === void 0) cursorY -= block.heightPt;
2285
2723
  cursorY -= block.spacingAfterPt;
2724
+ if (anchored === void 0) spacing.close(sharedAfter(block));
2286
2725
  continue;
2287
2726
  }
2288
2727
  if (block.kind === "shape") {
2289
- cursorY -= block.spacingBeforePt;
2290
2728
  const anchored = bandAnchor(block.float, block.widthPt, contentWidth, startX);
2729
+ cursorY -= anchored === void 0 ? spacing.open(block.spacingBeforePt) : block.spacingBeforePt;
2291
2730
  const offset = anchored ?? alignmentOffset(block.resolvedAlignment, block.widthPt, contentWidth);
2292
2731
  const top = anchored === void 0 || !block.float ? cursorY : bandAnchorTop(block.float, cursorY, pageHeight, block.heightPt, bandMargins);
2293
2732
  emitShapeItems(block, startX + offset, top - block.heightPt, out, pageHeight, structId);
2294
2733
  if (anchored === void 0) cursorY -= block.heightPt;
2295
2734
  cursorY -= block.spacingAfterPt;
2735
+ if (anchored === void 0) spacing.close(block.spacingAfterPt);
2296
2736
  continue;
2297
2737
  }
2298
- cursorY -= block.spacingBeforePt;
2738
+ cursorY -= spacing.open(block.spacingBeforePt);
2299
2739
  const bandTopY = cursorY;
2300
2740
  for (const line of block.lines) {
2301
2741
  const h = computeLineHeight(line, block.resolved);
@@ -2312,6 +2752,7 @@ function drawBlocksSequentially(blocks, startX, startY, pageHeight, contentWidth
2312
2752
  }
2313
2753
  out.push(...paragraphDecoration(block.resolved, startX + block.resolved.indentLeft, startX + contentWidth - block.resolved.indentRight, bandTopY, cursorY, pageHeight));
2314
2754
  cursorY -= block.spacingAfterPt;
2755
+ spacing.close(block.spacingAfterPt);
2315
2756
  }
2316
2757
  return out;
2317
2758
  }
@@ -2604,6 +3045,7 @@ function lookupFont(resources, fontKey) {
2604
3045
  }
2605
3046
  function collectFontResources(body, headersFooters, options, imageResources) {
2606
3047
  const used = /* @__PURE__ */ new Map();
3048
+ const numerals = [...new Set([options.section, ...(options.sections ?? []).map((sec) => sec.properties)].flatMap((p) => p?.pageNumberFormat !== void 0 ? [p.pageNumberFormat] : []))].map((fmt) => Array.from({ length: 200 }, (_, i) => formatCounter(fmt, i + 1)).join("")).join("");
2607
3049
  const addRun = (run, para) => {
2608
3050
  const resolved = resolveRunProperties(run.properties, para.properties, options.styles);
2609
3051
  const { fontKey, parsed } = runFontKeyAndParsed(options, resolved.fontFamily.ascii, resolved.bold, resolved.italic);
@@ -2678,7 +3120,7 @@ function collectFontResources(body, headersFooters, options, imageResources) {
2678
3120
  if (run.inlineImage && !run.text) continue;
2679
3121
  if (run.field !== void 0) {
2680
3122
  addRun({
2681
- text: `${run.text}0123456789`,
3123
+ text: `${run.text}0123456789${numerals}`,
2682
3124
  properties: run.properties
2683
3125
  }, el.paragraph);
2684
3126
  continue;
@@ -2692,17 +3134,19 @@ function collectFontResources(body, headersFooters, options, imageResources) {
2692
3134
  }
2693
3135
  addRun(run, el.paragraph);
2694
3136
  }
2695
- } else if (el.kind === "table") for (const row of el.table.rows) for (const cell of row.cells) {
2696
- if (cell.properties.hashOnOverflow === true) for (const inner of cell.content) {
2697
- if (inner.kind !== "paragraph") continue;
2698
- for (const run of inner.paragraph.runs) addRun({
2699
- text: "#",
2700
- properties: run.properties
2701
- }, inner.paragraph);
3137
+ } else if (el.kind === "table") {
3138
+ for (const row of el.table.rows) for (const cell of row.cells) {
3139
+ if (cell.properties.hashOnOverflow === true) for (const inner of cell.content) {
3140
+ if (inner.kind !== "paragraph") continue;
3141
+ for (const run of inner.paragraph.runs) addRun({
3142
+ text: "#",
3143
+ properties: run.properties
3144
+ }, inner.paragraph);
3145
+ }
3146
+ visit(cell.content);
2702
3147
  }
2703
- visit(cell.content);
2704
- }
2705
- else if (el.kind === "shape") {
3148
+ visit(el.table.overlay?.drawings ?? []);
3149
+ } else if (el.kind === "shape") {
2706
3150
  const shapeText = (sh) => {
2707
3151
  if (sh.text) visit(sh.text.content);
2708
3152
  for (const child of sh.children ?? []) shapeText(child.shape);
@@ -2793,13 +3237,10 @@ function collectFontResources(body, headersFooters, options, imageResources) {
2793
3237
  * the paragraph mark's own font (§17.3.1.31 `w:rPr` on the `w:pPr`, which is
2794
3238
  * exactly the run properties Word keeps for a paragraph that holds no runs).
2795
3239
  */
2796
- function emptyLine(paragraph, options, resolved, availableWidthPt, fontResources) {
2797
- const mark = resolveRunProperties(paragraph.properties.runProperties ?? {}, paragraph.properties, options.styles);
2798
- const fontSizePt = mark.fontSizePt;
2799
- const profile = options.layoutProfile ?? "ream";
2800
- const { variant } = options.registry.resolveByStyle(mark.bold, mark.italic);
2801
- const parsed = fontResources.get(variant)?.parsed;
2802
- const metric = profile !== "ream" && parsed ? fontLeadingPt(parsed, fontSizePt, profile) : void 0;
3240
+ function emptyLine(paragraph, options, resolved, availableWidthPt) {
3241
+ const fontSizePt = resolveRunProperties(paragraph.properties.runProperties ?? {}, paragraph.properties, options.styles).fontSizePt;
3242
+ const model = leadingOf(options);
3243
+ const metric = model === "flat" ? void 0 : markLine(paragraph.properties, options, model);
2803
3244
  return {
2804
3245
  tokens: [],
2805
3246
  contentWidthPt: 0,
@@ -2810,10 +3251,37 @@ function emptyLine(paragraph, options, resolved, availableWidthPt, fontResources
2810
3251
  isLastInParagraph: true,
2811
3252
  ...metric && metric.ascentPt + metric.descentPt > 0 ? {
2812
3253
  metricHeightPt: metric.ascentPt + metric.descentPt + metric.lineGapPt,
2813
- metricDescentPt: metric.descentPt
3254
+ metricDescentPt: metric.descentPt,
3255
+ metricTextHeightPt: metric.ascentPt + metric.descentPt + metric.lineGapPt
2814
3256
  } : {}
2815
3257
  };
2816
3258
  }
3259
+ /**
3260
+ * The single line of a paragraph mark's font (§17.3.1.29 `w:rPr` on the
3261
+ * `w:pPr`) under a model that reads faces: what an empty paragraph stands, and
3262
+ * what a spacing of more lines adds to a line with no text on it.
3263
+ *
3264
+ * @param pp The paragraph's properties.
3265
+ * @param options Render options (styles, fonts).
3266
+ * @param model The line model.
3267
+ * @returns Its ascent, descent and gap, in points.
3268
+ */
3269
+ function markLine(pp, options, model) {
3270
+ const mark = resolveRunProperties(pp.runProperties ?? {}, pp, options.styles);
3271
+ const family = mark.fontFamily.ascii;
3272
+ const { parsed } = runFontKeyAndParsed(options, family, mark.bold, mark.italic);
3273
+ return fontLeadingPt(parsed, mark.fontSizePt, model, family);
3274
+ }
3275
+ /** What a paragraph's lines are reckoned from, under the layout's model. */
3276
+ function paragraphLeading(pp, options) {
3277
+ const model = leadingOf(options);
3278
+ if (model === "flat") return FLAT_LEADING;
3279
+ const m = markLine(pp, options, model);
3280
+ return {
3281
+ model,
3282
+ markLinePt: m.ascentPt + m.descentPt + m.lineGapPt
3283
+ };
3284
+ }
2817
3285
  function layoutParagraphBlock(paragraph, options, fontResources, imageResources, contentWidth, lineWidths) {
2818
3286
  const cascaded = resolveParagraphProperties(paragraph.properties, options.styles);
2819
3287
  const baseResolved = options.gridLinePitchPt !== void 0 && cascaded.snapToGrid !== false ? {
@@ -2842,7 +3310,8 @@ function layoutParagraphBlock(paragraph, options, fontResources, imageResources,
2842
3310
  const tokens = tokenizeParagraph(paragraph, options, fontResources, imageResources, contentWidth, baseDir);
2843
3311
  const firstLineWidth = paragraphMaxWidth(resolved, contentWidth, true);
2844
3312
  const otherWidth = paragraphMaxWidth(resolved, contentWidth, false);
2845
- const runWrap = (part, firstWidth, widths) => wrap(part, firstWidth, otherWidth, resolved, options.hyphenator, options.layoutProfile ?? "ream", options.doNotExpandShiftReturn === true, widths);
3313
+ const leading = paragraphLeading(paragraph.properties, options);
3314
+ const runWrap = (part, firstWidth, widths) => wrap(part, firstWidth, otherWidth, resolved, options.hyphenator, options.layoutProfile ?? "ream", leading, options.doNotExpandShiftReturn === true, widths);
2846
3315
  const parts = splitAtColumnBreaks(tokens);
2847
3316
  const columnBreakLines = /* @__PURE__ */ new Set();
2848
3317
  let wrapped;
@@ -2858,12 +3327,13 @@ function layoutParagraphBlock(paragraph, options, fontResources, imageResources,
2858
3327
  })));
2859
3328
  }
2860
3329
  }
2861
- const lines = wrapped.length > 0 ? wrapped : [emptyLine(paragraph, options, resolved, Math.max(firstLineWidth, 0), fontResources)];
3330
+ const lines = wrapped.length > 0 ? wrapped : [emptyLine(paragraph, options, resolved, Math.max(firstLineWidth, 0))];
2862
3331
  let heightPt = 0;
2863
3332
  for (const line of lines) heightPt += computeLineHeight(line, resolved);
2864
3333
  const numbering = paragraph.properties.numbering;
2865
3334
  const breakIdx = paragraph.runs.findIndex((r) => r.pageBreak === true);
2866
- const leadingPageBreak = breakIdx >= 0 && paragraph.runs.slice(0, breakIdx + 1).every((r) => r.text === "" && r.inlineImage === void 0);
3335
+ const holds = (r) => r.text !== "" || r.inlineImage !== void 0 || r.math !== void 0;
3336
+ const leadingPageBreak = breakIdx >= 0 && paragraph.runs.slice(0, breakIdx + 1).every((r) => r.text === "" && r.inlineImage === void 0) && paragraph.runs.slice(breakIdx + 1).some(holds);
2867
3337
  const trailingPageBreak = paragraph.runs.some((r, i) => r.pageBreak === true && (!leadingPageBreak || i > breakIdx));
2868
3338
  return {
2869
3339
  kind: "paragraph",
@@ -3186,7 +3656,7 @@ function measureRunText(plan, text, face) {
3186
3656
  font: plan.font,
3187
3657
  ...plan.synthetic ? { synthetic: plan.synthetic } : {}
3188
3658
  };
3189
- const base = drawn.font.measure.textWidthPt(text, plan.fontSizePt) * (drawn.synthetic?.widthScale ?? 1);
3659
+ const base = drawn.font.measure.textWidthPt(text, plan.fontSizePt) * (drawn.synthetic?.widthScale ?? 1) * (plan.resolvedRun.widthScale ?? 1);
3190
3660
  const extra = plan.resolvedRun.letterSpacingPt;
3191
3661
  return extra === void 0 || extra === 0 ? base : base + extra * [...text].length;
3192
3662
  }
@@ -3634,7 +4104,7 @@ function splitToWidth(tok, widthPt) {
3634
4104
  }
3635
4105
  /** The fewest characters a mid-word break may leave on a line. */
3636
4106
  var MIN_SPLIT_CHARS = 6;
3637
- function lineFromRange(entries, start, breakIdx, availableWidthPt, isFirst, resolved, profile, allowEmpty = false) {
4107
+ function lineFromRange(entries, start, breakIdx, availableWidthPt, isFirst, resolved, leading, allowEmpty = false) {
3638
4108
  let st = start;
3639
4109
  let et = breakIdx;
3640
4110
  while (st < et && (entries[st].token === null || !isFirst && entries[st].token?.isSpace === true)) st++;
@@ -3662,10 +4132,12 @@ function lineFromRange(entries, start, breakIdx, availableWidthPt, isFirst, reso
3662
4132
  let maxSize = 0;
3663
4133
  let mathAscent = 0;
3664
4134
  let mathDescent = 0;
3665
- const useMetric = profile !== "ream";
3666
- let metricAscent = 0;
3667
- let metricDescent = 0;
3668
- let metricLineGap = 0;
4135
+ const useMetric = leading.model !== "flat";
4136
+ let textAscent = 0;
4137
+ let textDescent = 0;
4138
+ let markerAscent = 0;
4139
+ let markerDescent = 0;
4140
+ let pictureHeight = 0;
3669
4141
  const prints = lineTokens.some((t) => !t.isSpace);
3670
4142
  for (const t of lineTokens) {
3671
4143
  contentWidth += t.widthPt;
@@ -3676,13 +4148,22 @@ function lineFromRange(entries, start, breakIdx, availableWidthPt, isFirst, reso
3676
4148
  mathAscent = Math.max(mathAscent, t.ascentPt);
3677
4149
  mathDescent = Math.max(mathDescent, t.descentPt);
3678
4150
  } else if (useMetric && t.kind === "text") {
3679
- const m = fontLeadingPt(t.font.parsed, t.fontSizePt, profile);
3680
- if (m.ascentPt > metricAscent) metricAscent = m.ascentPt;
3681
- if (m.descentPt > metricDescent) metricDescent = m.descentPt;
3682
- if (m.lineGapPt > metricLineGap) metricLineGap = m.lineGapPt;
3683
- }
4151
+ const m = fontLeadingPt(t.font.parsed, t.fontSizePt, leading.model, wordFamilyOf(t));
4152
+ const above = m.ascentPt + m.lineGapPt;
4153
+ if (t.listMarker === true) {
4154
+ markerAscent = Math.max(markerAscent, above);
4155
+ markerDescent = Math.max(markerDescent, m.descentPt);
4156
+ } else {
4157
+ textAscent = Math.max(textAscent, above);
4158
+ textDescent = Math.max(textDescent, m.descentPt);
4159
+ }
4160
+ } else if (useMetric && t.kind === "image") pictureHeight = Math.max(pictureHeight, t.heightPt);
3684
4161
  }
3685
- const hasMetric = useMetric && metricAscent + metricDescent > 0;
4162
+ const hasText = textAscent + textDescent > 0;
4163
+ const ascent = Math.max(textAscent, markerAscent);
4164
+ const descent = hasText ? textDescent : markerDescent;
4165
+ const hasMetric = useMetric && ascent + descent + pictureHeight > 0;
4166
+ const textLine = ascent + descent > 0 ? ascent + descent : leading.markLinePt;
3686
4167
  return {
3687
4168
  tokens: lineTokens,
3688
4169
  contentWidthPt: contentWidth,
@@ -3694,8 +4175,9 @@ function lineFromRange(entries, start, breakIdx, availableWidthPt, isFirst, reso
3694
4175
  mathAscentPt: mathAscent,
3695
4176
  mathDescentPt: mathDescent,
3696
4177
  ...hasMetric ? {
3697
- metricHeightPt: metricAscent + metricDescent + metricLineGap,
3698
- metricDescentPt: metricDescent
4178
+ metricHeightPt: Math.max(ascent, pictureHeight) + descent,
4179
+ metricDescentPt: descent,
4180
+ metricTextHeightPt: textLine
3699
4181
  } : {}
3700
4182
  };
3701
4183
  }
@@ -3715,7 +4197,7 @@ function withLeadingTabWidths(tokens, resolved, firstLineWidth) {
3715
4197
  }
3716
4198
  return out;
3717
4199
  }
3718
- function wrap(tokens, firstLineWidth, otherWidth, resolved, hyphenator, profile, noExpandShiftReturn, lineWidths) {
4200
+ function wrap(tokens, firstLineWidth, otherWidth, resolved, hyphenator, profile, leading, noExpandShiftReturn, lineWidths) {
3719
4201
  if (tokens.length === 0) return [];
3720
4202
  const widths = lineWidths ?? [firstLineWidth, otherWidth];
3721
4203
  const entries = paragraphItemStream(withLeadingTabWidths(tokens, resolved, widths[0] ?? firstLineWidth), hyphenator, Math.max(0, ...widths));
@@ -3728,7 +4210,7 @@ function wrap(tokens, firstLineWidth, otherWidth, resolved, hyphenator, profile,
3728
4210
  let lineIdx = 0;
3729
4211
  for (const [i, breakIdx] of at.entries()) {
3730
4212
  const width = lineIdx < widths.length ? widths[lineIdx] : widths[widths.length - 1];
3731
- const line = lineFromRange(entries, start, breakIdx, width, isFirst, resolved, profile, i < at.length - 1);
4213
+ const line = lineFromRange(entries, start, breakIdx, width, isFirst, resolved, leading, i < at.length - 1);
3732
4214
  if (line) out.push(noExpandShiftReturn && entries[breakIdx]?.softBreak ? {
3733
4215
  ...line,
3734
4216
  noJustify: true
@@ -3748,14 +4230,55 @@ function wrap(tokens, firstLineWidth, otherWidth, resolved, hyphenator, profile,
3748
4230
  if (lines.length > 0) lines[lines.length - 1].isLastInParagraph = true;
3749
4231
  return lines;
3750
4232
  }
3751
- function fontLeadingPt(parsed, sizePt, profile) {
4233
+ /**
4234
+ * §17.3.2.26 — the family Word sets a token's characters in: East Asian text
4235
+ * in the run's `eastAsia` font, complex-script text (and a `w:rtl` run) in its
4236
+ * `cs` one, the rest in `ascii` — `hAnsi` above ASCII. The layout draws a run
4237
+ * with its ascii face and hands what that face lacks to another, so the name
4238
+ * the run gives for THESE characters is the one Word's line is looked up by:
4239
+ * a Chinese sentence in a Calibri run does not stand on Calibri's line.
4240
+ *
4241
+ * @param t The text token.
4242
+ * @returns The family's name, when the run gives one for that slot.
4243
+ */
4244
+ function wordFamilyOf(t) {
4245
+ const fonts = t.resolvedRun.fontFamily;
4246
+ if (t.resolvedRun.rtl) return fonts.cs;
4247
+ let high = false;
4248
+ for (const ch of t.text) {
4249
+ const cp = ch.codePointAt(0) ?? 0;
4250
+ if (cp <= 127) continue;
4251
+ high = true;
4252
+ const script = scriptForCodepoint(cp);
4253
+ if (script === "hebrew" || script === "arabic" || script === "thai") return fonts.cs;
4254
+ if (script === "kr" || script === "jp" || script === "sc") return fonts.eastAsia;
4255
+ }
4256
+ return high ? fonts.hAnsi ?? fonts.ascii : fonts.ascii;
4257
+ }
4258
+ /**
4259
+ * A face's single line at a size: its ascent, its descent, and the gap set
4260
+ * above the text. Under `'word'` a family Word's own line is known for (see
4261
+ * {@link WORD_LINE_METRICS}) takes that; otherwise the face in hand gives its
4262
+ * hhea line, or the OS/2 typo one when it sets USE_TYPO_METRICS.
4263
+ *
4264
+ * @param parsed The face the run is drawn with.
4265
+ * @param sizePt The run's size.
4266
+ * @param model The line model (never `'flat'`, which asks no face).
4267
+ * @param family The family the run names, when it names one.
4268
+ * @returns The three, in points; the descent as a magnitude.
4269
+ */
4270
+ function fontLeadingPt(parsed, sizePt, model, family) {
4271
+ const known = model === "word" && family !== void 0 ? WORD_LINE_METRICS.get(family.trim().toLowerCase()) : void 0;
4272
+ if (known) {
4273
+ const k = sizePt / 2048;
4274
+ return {
4275
+ ascentPt: known[0] * k,
4276
+ lineGapPt: known[1] * k,
4277
+ descentPt: known[2] * k
4278
+ };
4279
+ }
3752
4280
  const s = sizePt / parsed.unitsPerEm;
3753
4281
  const vm = parsed.vmetrics;
3754
- if (profile === "word") return {
3755
- ascentPt: vm.winAscent * s,
3756
- descentPt: vm.winDescent * s,
3757
- lineGapPt: 0
3758
- };
3759
4282
  const asc = vm.useTypoMetrics ? vm.typoAscent : parsed.ascender;
3760
4283
  const desc = vm.useTypoMetrics ? vm.typoDescent : parsed.descender;
3761
4284
  const gap = vm.useTypoMetrics ? vm.typoLineGap : parsed.lineGap;
@@ -3780,7 +4303,8 @@ function naturalLineHeight(line, p) {
3780
4303
  if (p.spacingLineRule === "atLeast" && p.spacingLine > 0) return Math.max(natural, p.spacingLine, mathNeed);
3781
4304
  const lineTwips = Math.round(p.spacingLine * 20);
3782
4305
  const multiple = lineTwips > 0 ? lineTwips / 240 : 1;
3783
- return Math.max(natural * multiple, mathNeed);
4306
+ const scaled = line.metricTextHeightPt === void 0 ? natural * multiple : natural + (multiple - 1) * line.metricTextHeightPt;
4307
+ return Math.max(scaled, mathNeed);
3784
4308
  }
3785
4309
  function lineDescent(line) {
3786
4310
  const fs = line.maxFontSizePt || 12;
@@ -3922,16 +4446,18 @@ function layoutTableBlock(table, options, fontResources, imageResources, content
3922
4446
  }
3923
4447
  aboveBordersByCol = nextAbove;
3924
4448
  const rp = table.rows[r].properties;
3925
- rows.push(rp.isHeader || rp.pageBreakBefore ? {
4449
+ rows.push(rp.isHeader || rp.pageBreakBefore || rp.sheetTopPt !== void 0 ? {
3926
4450
  ...rl,
3927
4451
  ...rp.isHeader ? { isHeader: true } : {},
3928
- ...rp.pageBreakBefore ? { breakBefore: true } : {}
4452
+ ...rp.pageBreakBefore ? { breakBefore: true } : {},
4453
+ ...rp.sheetTopPt !== void 0 ? { sheetTopPt: rp.sheetTopPt } : {}
3929
4454
  } : rl);
3930
4455
  }
3931
4456
  growRowsForMerges(rows);
3932
4457
  const heightPt = rows.reduce((s, r) => s + r.heightPt, 0);
3933
4458
  const totalWidthPt = columnWidthsPt.reduce((s, w) => s + w, 0);
3934
- const xOffsetPt = (table.properties.indentPt ?? 0) + tableXOffset(table.properties.alignment, contentWidth, totalWidthPt);
4459
+ const xOffsetPt = (table.properties.indentPt ?? 0) + tableXOffset(table.properties.alignment, contentWidth, totalWidthPt) - legacyTableOutdent(table, rows, options);
4460
+ const overlay = table.overlay ? layoutOverlay(table.overlay, options, fontResources, imageResources, contentWidth) : void 0;
3935
4461
  return {
3936
4462
  kind: "table",
3937
4463
  ...table.properties.float ? { float: table.properties.float } : {},
@@ -3939,7 +4465,40 @@ function layoutTableBlock(table, options, fontResources, imageResources, content
3939
4465
  heightPt,
3940
4466
  totalWidthPt,
3941
4467
  colCount,
3942
- xOffsetPt
4468
+ xOffsetPt,
4469
+ ...overlay ? { overlay } : {}
4470
+ };
4471
+ }
4472
+ /**
4473
+ * The drawings over a spreadsheet grid (TableOverlay), each laid out as the
4474
+ * float it is and placed by its corner in the sheet's own frame.
4475
+ *
4476
+ * @param overlay The drawings and the part of the sheet the table prints.
4477
+ * @param options The render options.
4478
+ * @param fontResources The fonts the drawings' text is set in.
4479
+ * @param imageResources The pictures they show.
4480
+ * @param contentWidth The width between the margins.
4481
+ * @returns The overlay laid out, or undefined when none of it draws.
4482
+ */
4483
+ function layoutOverlay(overlay, options, fontResources, imageResources, contentWidth) {
4484
+ const drawings = [];
4485
+ for (const el of overlay.drawings) {
4486
+ if (el.kind !== "image" && el.kind !== "shape" && el.kind !== "chart") continue;
4487
+ const block = layoutBodyElement(el, options, fontResources, imageResources, contentWidth);
4488
+ if (block.kind !== "image" && block.kind !== "shape" && block.kind !== "chart") continue;
4489
+ drawings.push({
4490
+ block,
4491
+ xPt: block.float?.posH?.offsetPt ?? 0,
4492
+ yPt: block.float?.posV?.offsetPt ?? 0
4493
+ });
4494
+ }
4495
+ if (drawings.length === 0) return void 0;
4496
+ return {
4497
+ sheetLeftPt: overlay.sheetLeftPt,
4498
+ widthPt: overlay.widthPt,
4499
+ leftPt: overlay.leftPt,
4500
+ mirrored: overlay.mirrored === true,
4501
+ drawings
3943
4502
  };
3944
4503
  }
3945
4504
  /**
@@ -3968,6 +4527,29 @@ function growRowsForMerges(rows) {
3968
4527
  };
3969
4528
  }
3970
4529
  }
4530
+ /**
4531
+ * [MS-DOCX] `compatibilityMode` — how far a table stands out past its indent
4532
+ * in a document laid out as Word 2010 and earlier lay it out. There the
4533
+ * indent places the first cell's TEXT, not the table's edge, so the table
4534
+ * stands out by that cell's left margin. Measured in Word for Mac: with
4535
+ * first-cell margins of 5.4, 15 and 20pt, the text stands at the margin in a
4536
+ * document of mode 14 and in one that states no mode, and 5.4, 15 and 20pt in
4537
+ * at mode 15. A 36pt `w:tblInd` puts the text 36pt in, not 41.4. A centred
4538
+ * table does not move. Only a Word document's table moves; a spreadsheet has
4539
+ * no such history.
4540
+ *
4541
+ * @param table The table.
4542
+ * @param rows Its laid-out rows, the first cell's margin resolved.
4543
+ * @param options Render options: whose document, and which mode.
4544
+ * @returns The points to move the table left by, 0 where it stays.
4545
+ */
4546
+ function legacyTableOutdent(table, rows, options) {
4547
+ if (options.typesetBy !== "word" || (options.compatibilityMode ?? 0) >= 15) return 0;
4548
+ if ((table.properties.alignment ?? "left") !== "left" || table.properties.float) return 0;
4549
+ const first = rows[0];
4550
+ if (!first || first.columnXOffsets[0] !== 0) return 0;
4551
+ return first.cells[0]?.padLeftPt ?? 0;
4552
+ }
3971
4553
  function tableXOffset(alignment, contentWidth, totalWidthPt) {
3972
4554
  const slack = contentWidth - totalWidthPt;
3973
4555
  if (slack <= 0 || !alignment || alignment === "left") return 0;
@@ -4126,7 +4708,7 @@ function measureSingleLine(paragraph, options, fontResources) {
4126
4708
  const { fontKey, synthetic } = runFontKeyAndParsed(options, resolved.fontFamily.ascii, resolved.bold, resolved.italic);
4127
4709
  const font = lookupFont(fontResources, fontKey);
4128
4710
  const fontSizePt = resolved.fontSizePt;
4129
- total += font.measure.textWidthPt(run.text, fontSizePt) * (synthetic?.widthScale ?? 1);
4711
+ total += font.measure.textWidthPt(run.text, fontSizePt) * (synthetic?.widthScale ?? 1) * (resolved.widthScale ?? 1);
4130
4712
  }
4131
4713
  return total;
4132
4714
  }
@@ -4166,19 +4748,30 @@ function layoutTableRow(row, rowIdx, rowCount, colCount, tableProps, columnWidth
4166
4748
  let widthPt = 0;
4167
4749
  for (let k = 0; k < span && colIdx + k < columnWidthsPt.length; k++) widthPt += columnWidthsPt[colIdx + k];
4168
4750
  const paintSpan = Math.min(span, Math.max(1, cell.properties.paintColumns ?? span));
4751
+ const paintFrom = cell.properties.paintAtEnd === true ? span - paintSpan : 0;
4169
4752
  let paintWidthPt = 0;
4170
- for (let k = 0; k < paintSpan && colIdx + k < columnWidthsPt.length; k++) paintWidthPt += columnWidthsPt[colIdx + k];
4753
+ let paintOffsetPt = 0;
4754
+ for (let k = 0; k < paintFrom + paintSpan && colIdx + k < columnWidthsPt.length; k++) if (k < paintFrom) paintOffsetPt += columnWidthsPt[colIdx + k];
4755
+ else paintWidthPt += columnWidthsPt[colIdx + k];
4171
4756
  columnXOffsets.push(cursorX);
4172
4757
  const mergeRole = rowMergeRoles[cellIdx] ?? "standalone";
4173
4758
  const leftNeighbor = cellIdx > 0 ? row.cells[cellIdx - 1].properties.borders : void 0;
4174
4759
  const cellLayout = layoutTableCell(cell, tableProps, widthPt, options, fontResources, imageResources, rowIdx, colIdx, span, rowCount, colCount, mergeRole, leftNeighbor, aboveOfSpan(aboveBordersByCol, colIdx, span));
4175
4760
  cells.push(paintWidthPt < widthPt ? {
4176
4761
  ...cellLayout,
4177
- paintWidthPt
4762
+ paintWidthPt,
4763
+ ...paintOffsetPt > 0 ? { paintOffsetPt } : {}
4178
4764
  } : cellLayout);
4179
4765
  cursorX += widthPt;
4180
4766
  colIdx += span;
4181
4767
  }
4768
+ const bands = options.typesetBy === "word" ? withBorderBands(cells, rowIdx === rowCount - 1) : void 0;
4769
+ const bandSpread = bands && (bands.topPt > 0 || bands.bottomPt > 0) ? bands : void 0;
4770
+ if (bandSpread) cells.splice(0, cells.length, ...bandSpread.cells);
4771
+ const borderBands = bandSpread ? { borderBands: {
4772
+ topPt: bandSpread.topPt,
4773
+ bottomPt: bandSpread.bottomPt
4774
+ } } : {};
4182
4775
  let heightPt = 0;
4183
4776
  for (const c of cells) {
4184
4777
  if (c.mergeRole === "start") continue;
@@ -4192,7 +4785,8 @@ function layoutTableRow(row, rowIdx, rowCount, colCount, tableProps, columnWidth
4192
4785
  columnXOffsets,
4193
4786
  rowIdx,
4194
4787
  rowCount,
4195
- ...row.properties.cantSplit ? { cantSplit: true } : {}
4788
+ ...row.properties.cantSplit ? { cantSplit: true } : {},
4789
+ ...borderBands
4196
4790
  };
4197
4791
  } else if (row.properties.height && row.properties.heightRule === "atLeast") heightPt = Math.max(heightPt, row.properties.height);
4198
4792
  return {
@@ -4201,7 +4795,42 @@ function layoutTableRow(row, rowIdx, rowCount, colCount, tableProps, columnWidth
4201
4795
  columnXOffsets,
4202
4796
  rowIdx,
4203
4797
  rowCount,
4204
- ...row.properties.cantSplit ? { cantSplit: true } : {}
4798
+ ...row.properties.cantSplit ? { cantSplit: true } : {},
4799
+ ...borderBands
4800
+ };
4801
+ }
4802
+ /**
4803
+ * §17.4.38 — a table row as Word sets it: every horizontal border takes the
4804
+ * room it is wide, the edge above a row in that row and the table's bottom
4805
+ * edge in its last one, and the text stands clear of it. Measured in Word for
4806
+ * Mac, a row of 11pt Calibri with no cell margins stands 13.44pt apart from
4807
+ * the next with no borders, 13.92 under 0.5pt ones, 14.94 under 1.5pt and
4808
+ * 16.43 under 3pt; a one-row table between two paragraphs adds its top and
4809
+ * its bottom border whole, 3pt each at 3pt. LibreOffice does the same. The
4810
+ * room is the widest border along the edge, and it goes into the cells' top
4811
+ * and bottom padding, which is what places their text and sizes the row.
4812
+ *
4813
+ * @param cells The row's laid-out cells, their borders resolved.
4814
+ * @param lastRow Whether the row is the table's last, whose bottom border is
4815
+ * the table's own.
4816
+ * @returns The cells with the room in their padding, and the room taken.
4817
+ */
4818
+ function withBorderBands(cells, lastRow) {
4819
+ let topPt = 0;
4820
+ let bottomPt = 0;
4821
+ for (const c of cells) {
4822
+ topPt = Math.max(topPt, borderWeight(c.borders.top));
4823
+ if (lastRow) bottomPt = Math.max(bottomPt, borderWeight(c.borders.bottom));
4824
+ }
4825
+ return {
4826
+ cells: cells.map((c) => ({
4827
+ ...c,
4828
+ padTopPt: c.padTopPt + topPt,
4829
+ padBottomPt: c.padBottomPt + bottomPt,
4830
+ totalHeightPt: c.totalHeightPt + topPt + bottomPt
4831
+ })),
4832
+ topPt,
4833
+ bottomPt
4205
4834
  };
4206
4835
  }
4207
4836
  /**
@@ -4223,14 +4852,16 @@ function clipCellToHeight(cell, heightPt) {
4223
4852
  let used = 0;
4224
4853
  for (const line of cell.lines) {
4225
4854
  const h = computeLineHeight(line, line.resolved);
4226
- if (kept.length > 0 && used + h > room) break;
4855
+ const below = line.metricDescentPt ?? line.maxFontSizePt * .2;
4856
+ if (kept.length > 0 && used + h - below > room) break;
4227
4857
  kept.push(line);
4228
4858
  used += h;
4229
4859
  }
4230
4860
  return {
4231
4861
  ...cell,
4232
4862
  lines: kept,
4233
- contentHeightPt: used
4863
+ contentHeightPt: used,
4864
+ ...used > room ? { clipped: true } : {}
4234
4865
  };
4235
4866
  }
4236
4867
  /**
@@ -4278,8 +4909,9 @@ function layoutTableCell(cell, tableProps, widthPt, options, fontResources, imag
4278
4909
  let contentHeightPt = 0;
4279
4910
  let clipped = false;
4280
4911
  let pendingGapPt = 0;
4912
+ const collapse = collapsesSpacing(options);
4281
4913
  const openParagraph = (spacingBeforePt) => {
4282
- const gap = pendingGapPt + spacingBeforePt;
4914
+ const gap = collapse ? Math.max(pendingGapPt, spacingBeforePt) : pendingGapPt + spacingBeforePt;
4283
4915
  pendingGapPt = 0;
4284
4916
  if (gap <= 0) return;
4285
4917
  lineGaps.set(lines.length, (lineGaps.get(lines.length) ?? 0) + gap);
@@ -4316,7 +4948,6 @@ function layoutTableCell(cell, tableProps, widthPt, options, fontResources, imag
4316
4948
  lines.push(line);
4317
4949
  contentHeightPt += computeLineHeight(line, block.resolved);
4318
4950
  }
4319
- contentHeightPt += block.spacingAfterPt;
4320
4951
  pendingGapPt = block.spacingAfterPt;
4321
4952
  } else if (el.kind === "shape" && !isOutOfFlowFloat(el.shape.float)) {
4322
4953
  const block = layoutShapeBlock(el.shape, options, fontResources, imageResources, innerWidth);
@@ -4339,6 +4970,13 @@ function layoutTableCell(cell, tableProps, widthPt, options, fontResources, imag
4339
4970
  }
4340
4971
  const colEnd = colStart + colSpan - 1;
4341
4972
  const borders = resolveCellBorders(cell.properties.borders, tableProps.borders, rowIdx, colStart, colEnd, rowCount, colCount, mergeRole, leftNeighborBorders, aboveNeighborBorders);
4973
+ const grid = tableProps.gridlines;
4974
+ const ruled = grid && !cell.properties.shading?.colorHex ? {
4975
+ ...borders.top || mergeRole === "middle" || mergeRole === "end" ? {} : { top: grid },
4976
+ ...borders.left ? {} : { left: grid },
4977
+ ...borders.bottom || rowIdx !== rowCount - 1 || mergeRole === "start" || mergeRole === "middle" ? {} : { bottom: grid },
4978
+ ...borders.right || colEnd !== colCount - 1 ? {} : { right: grid }
4979
+ } : void 0;
4342
4980
  const alongPt = turned ? lines.reduce((a, l) => Math.max(a, l.contentWidthPt), 0) : 0;
4343
4981
  const heightPt = turned ? alongPt : contentHeightPt;
4344
4982
  const totalHeightPt = mergeRole === "middle" || mergeRole === "end" ? 0 : padTopPt + heightPt + padBottomPt;
@@ -4348,11 +4986,15 @@ function layoutTableCell(cell, tableProps, widthPt, options, fontResources, imag
4348
4986
  padRightPt,
4349
4987
  padBottomPt,
4350
4988
  padLeftPt,
4351
- borders,
4989
+ borders: ruled ? {
4990
+ ...ruled,
4991
+ ...borders
4992
+ } : borders,
4352
4993
  ...cell.properties.shading?.colorHex ? { shadingColorHex: cell.properties.shading.colorHex } : {},
4353
4994
  ...cell.properties.dataBar ? { dataBar: cell.properties.dataBar } : {},
4354
4995
  ...cell.properties.icon ? { icon: cell.properties.icon } : {},
4355
4996
  ...cell.properties.sparkline ? { sparkline: cell.properties.sparkline } : {},
4997
+ ...cell.properties.noteFlag ? { noteFlag: cell.properties.noteFlag } : {},
4356
4998
  lines,
4357
4999
  ...lineGaps.size > 0 ? { lineGaps } : {},
4358
5000
  ...nestedTables.length > 0 ? { nestedTables } : {},
@@ -4492,7 +5134,7 @@ var PageAssembler = class PageAssembler {
4492
5134
  this.ctx = sectionCtxs[0];
4493
5135
  this.pageStartCtx = this.ctx;
4494
5136
  this.pageNumber = this.ctx.properties.pageNumberStart ?? 1;
4495
- this.cursorY = this.ctx.pageHeight - this.ctx.marginTop;
5137
+ this.cursorY = this.pageTopY();
4496
5138
  this.colStartY = this.cursorY;
4497
5139
  this.bandTopY = this.cursorY;
4498
5140
  }
@@ -4509,6 +5151,56 @@ var PageAssembler = class PageAssembler {
4509
5151
  current = [];
4510
5152
  pendingPageBreak = false;
4511
5153
  cursorY;
5154
+ /** Whether paragraph spacing stacks as Word stacks it (see {@link SpacingStack}). */
5155
+ collapseSpacing = false;
5156
+ /**
5157
+ * Where the last block's space after ended, on which page, and how much of
5158
+ * it a paragraph right below may share — nothing once the cursor has moved
5159
+ * on, to a new column or page or past a table.
5160
+ */
5161
+ spacingMark = void 0;
5162
+ /**
5163
+ * Whether the next block opens a section on a page of its own (the
5164
+ * document's first included), whose space before Word keeps at the top.
5165
+ */
5166
+ sectionStart = true;
5167
+ /**
5168
+ * Whether a paragraph that breaks to a new page by its own
5169
+ * `w:pageBreakBefore` keeps its space before there — Word 2010 and earlier
5170
+ * (compatibilityMode below 15, or none) keep it; Word 2013 drops it.
5171
+ */
5172
+ keepsBeforeAtOwnBreak = false;
5173
+ /**
5174
+ * §17.3.1.33 — the room to open above a block, where the spacing stacks as
5175
+ * Word stacks it (see {@link SpacingStack}): the space after the block right
5176
+ * above it shared, and nothing at all at the top of a page or column. Word
5177
+ * for Mac drops a 24pt space before there whether the paragraph ran over
5178
+ * onto the page, broke to it after a `w:br w:type="page"` or ran into the
5179
+ * next column, in every mode; it keeps it for the first paragraph of the
5180
+ * document and of a section, and — below mode 15 — for a paragraph that
5181
+ * breaks to the page by its own `w:pageBreakBefore`.
5182
+ *
5183
+ * @param beforePt A block's own space before.
5184
+ * @param opensSection Whether the block is the first of a section on a page of its own.
5185
+ * @param ownBreak Whether the block broke to this page by its own `w:pageBreakBefore`.
5186
+ * @returns The room to open above it.
5187
+ */
5188
+ openBefore(beforePt, opensSection = false, ownBreak = false) {
5189
+ const mark = this.spacingMark;
5190
+ this.spacingMark = void 0;
5191
+ if (!this.collapseSpacing) return beforePt;
5192
+ if (!this.colHasContent()) return opensSection || ownBreak && this.keepsBeforeAtOwnBreak ? beforePt : 0;
5193
+ if (!mark || mark.page !== this.current || mark.y !== this.cursorY) return beforePt;
5194
+ return Math.max(0, beforePt - mark.shared);
5195
+ }
5196
+ /** @param sharedPt The space after the block just placed, as far as a paragraph below may share it. */
5197
+ closeAfter(sharedPt) {
5198
+ this.spacingMark = {
5199
+ y: this.cursorY,
5200
+ page: this.current,
5201
+ shared: sharedPt
5202
+ };
5203
+ }
4512
5204
  /**
4513
5205
  * §17.6.4 multi-column flow: content fills column after column before the page
4514
5206
  * flushes. `colStartLen` marks where the current column's items begin in
@@ -4557,6 +5249,16 @@ var PageAssembler = class PageAssembler {
4557
5249
  * all and eleven controls wider than its page.
4558
5250
  */
4559
5251
  pageHasContent = () => this.current.length > 0 || this.floatsBehind.length > 0 || this.floatsFront.length > 0;
5252
+ /**
5253
+ * §17.10 — the body's margins on the page being laid out: below the header
5254
+ * band that page shows and above its footer band (see `bandMargins`).
5255
+ */
5256
+ pageMargins = () => this.ctx.bandMargins?.[bandForPage(this.pageInSection, this.globalPageIdx, this.ctx.titlePg, this.ctx.evenAndOddHeaders)] ?? {
5257
+ top: this.ctx.marginTop,
5258
+ bottom: this.ctx.marginBottom
5259
+ };
5260
+ /** Where the body of the page being laid out begins, y-up. */
5261
+ pageTopY = () => this.ctx.pageHeight - this.pageMargins().top;
4560
5262
  /** Overflow step: next column on this page, or a fresh page after the last. */
4561
5263
  advanceColumn = () => {
4562
5264
  if (this.ctx.columns && this.colIdx + 1 < this.ctx.columns.length) {
@@ -4652,7 +5354,7 @@ var PageAssembler = class PageAssembler {
4652
5354
  */
4653
5355
  beginBalancedBand = (bandHeightPt) => {
4654
5356
  const colCount = this.ctx.columns?.length ?? 1;
4655
- const room = this.cursorY - (this.ctx.marginBottom + this.noteReserve);
5357
+ const room = this.cursorY - (this.pageMargins().bottom + this.noteReserve);
4656
5358
  const share = colCount > 1 ? balancedColumnHeight(bandHeightPt, colCount, room) : 0;
4657
5359
  this.balanceHeightPt = share;
4658
5360
  this.balanceBottomY = share > 0 ? this.cursorY - share : void 0;
@@ -4677,6 +5379,17 @@ var PageAssembler = class PageAssembler {
4677
5379
  floatsBehind = [];
4678
5380
  floatsFront = [];
4679
5381
  /**
5382
+ * The front floats as the page paints them: over its text, when the
5383
+ * document's floats cover what is under them (StyledRenderOptions
5384
+ * floatsOverText); in the ordinary passes, under it, otherwise.
5385
+ */
5386
+ inFront(items) {
5387
+ return this.ctx.options?.floatsOverText === true ? items.map((item) => ({
5388
+ ...item,
5389
+ over: true
5390
+ })) : items;
5391
+ }
5392
+ /**
4680
5393
  * Side-wrapping floats (wrapSquare/tight/through): rectangles the body text
4681
5394
  * must flow around. Page-scoped, like the float graphics.
4682
5395
  */
@@ -4737,7 +5450,7 @@ var PageAssembler = class PageAssembler {
4737
5450
  * margin (plus any footnote reserve); inside a BALANCED band it is the line
4738
5451
  * the band's share of the height reaches, so the columns come out even.
4739
5452
  */
4740
- bottomLimit = () => Math.max(this.ctx.marginBottom + this.noteReserve, this.balanceBottomY ?? Number.NEGATIVE_INFINITY);
5453
+ bottomLimit = () => Math.max(this.pageMargins().bottom + this.noteReserve, this.balanceBottomY ?? Number.NEGATIVE_INFINITY);
4741
5454
  /**
4742
5455
  * §17.6.4 — a `continuous` section of several columns whose content fits the
4743
5456
  * page is BALANCED: Word evens the columns out rather than filling the first
@@ -4772,8 +5485,8 @@ var PageAssembler = class PageAssembler {
4772
5485
  xOffset: 0
4773
5486
  };
4774
5487
  const cl = this.colLeft();
4775
- const leftRoom = e.x0 - FLOAT_TEXT_GAP - cl;
4776
- const rightRoom = cl + full - (e.x1 + FLOAT_TEXT_GAP);
5488
+ const leftRoom = e.x0 - cl;
5489
+ const rightRoom = cl + full - e.x1;
4777
5490
  if (Math.max(leftRoom, rightRoom) < MIN_WRAP_WIDTH) return {
4778
5491
  width: full,
4779
5492
  xOffset: 0,
@@ -4806,19 +5519,22 @@ var PageAssembler = class PageAssembler {
4806
5519
  if (this.exclusions.length === 0 || block.lines.length === 0) return void 0;
4807
5520
  const h0 = computeLineHeight(block.lines[0], block.resolved);
4808
5521
  if (h0 <= 0) return void 0;
5522
+ const p = block.resolved;
5523
+ const full = this.colWidth();
5524
+ const hold = (width, xOffset, first) => Math.max(1, (xOffset > 0 ? width - p.indentRight : Math.min(width, full - p.indentRight)) - p.indentLeft - (first ? p.indentFirstLine : 0));
4809
5525
  const widths = [];
4810
5526
  let narrowed = false;
4811
5527
  let y = startY;
4812
5528
  for (let i = 0; i < 200; i++) {
4813
5529
  const g = this.lineGeometryAt(y, h0);
4814
5530
  if (g.blockedUntilYUp !== void 0) {
4815
- widths.push(this.colWidth());
5531
+ widths.push(hold(full, 0, widths.length === 0));
4816
5532
  y = g.blockedUntilYUp - h0;
4817
5533
  if (y < this.bottomLimit()) break;
4818
5534
  continue;
4819
5535
  }
4820
- widths.push(g.width);
4821
- if (g.pair) widths.push(g.pair.width);
5536
+ widths.push(hold(g.width, g.xOffset, widths.length === 0));
5537
+ if (g.pair) widths.push(hold(g.pair.width, g.pair.xOffset, false));
4822
5538
  if (g.width < this.colWidth()) narrowed = true;
4823
5539
  else if (narrowed) break;
4824
5540
  y -= h0;
@@ -4850,13 +5566,43 @@ var PageAssembler = class PageAssembler {
4850
5566
  return out;
4851
5567
  };
4852
5568
  /**
5569
+ * The first of a paragraph's lines, from `from` on, that the current column
5570
+ * no longer holds: each line is counted with the notes it brings onto the
5571
+ * page, the way the line loop places it, and an explicit column break ends
5572
+ * the column where it stands.
5573
+ *
5574
+ * @param pb The paragraph being placed.
5575
+ * @param from The first line not yet placed.
5576
+ * @returns The index of the first line that does not stay in this column;
5577
+ * `pb.lines.length` when every one does.
5578
+ */
5579
+ firstLineOverflowing = (pb, from) => {
5580
+ let y = this.cursorY;
5581
+ let reserve = 0;
5582
+ let notesOnPage = this.pageNotes.length > 0;
5583
+ const counted = /* @__PURE__ */ new Set();
5584
+ for (let i = from; i < pb.lines.length; i++) {
5585
+ if (i > from && pb.columnBreakLines?.has(i)) return i;
5586
+ const line = pb.lines[i];
5587
+ const h = computeLineHeight(line, pb.resolved);
5588
+ const notes = this.lineFootnotes(line).filter((x) => !counted.has(x.id));
5589
+ const added = notes.reduce((sum, x) => sum + x.heightPt, 0) + (!notesOnPage && notes.length > 0 ? FOOTNOTE_SEPARATOR_HEIGHT : 0);
5590
+ if (y - h < this.bottomLimit() + reserve + added) return i;
5591
+ y -= h;
5592
+ reserve += added;
5593
+ if (notes.length > 0) notesOnPage = true;
5594
+ for (const x of notes) counted.add(x.id);
5595
+ }
5596
+ return pb.lines.length;
5597
+ };
5598
+ /**
4853
5599
  * The notes band for the flushing page: separator rule + each note's blocks
4854
5600
  * stacked inside the reserved area. Tagged: each note is a Note→P element.
4855
5601
  */
4856
5602
  renderNotesBand = () => {
4857
5603
  if (this.pageNotes.length === 0) return [];
4858
5604
  const out = [];
4859
- const top = this.ctx.marginBottom + this.noteReserve;
5605
+ const top = this.pageMargins().bottom + this.noteReserve;
4860
5606
  out.push({
4861
5607
  type: "fill",
4862
5608
  x: pt(this.ctx.marginLeft),
@@ -4872,7 +5618,7 @@ var PageAssembler = class PageAssembler {
4872
5618
  const noteNode = this.builder.create("Note", this.builder.root);
4873
5619
  structId = this.builder.create("P", noteNode).id;
4874
5620
  }
4875
- out.push(...drawBlocksSequentially(note.blocks, this.ctx.marginLeft, cursor, this.ctx.pageHeight, this.ctx.contentWidth, structId));
5621
+ out.push(...drawBlocksSequentially(note.blocks, this.ctx.marginLeft, cursor, this.ctx.pageHeight, this.ctx.contentWidth, structId, void 0, this.collapseSpacing));
4876
5622
  cursor -= note.heightPt;
4877
5623
  }
4878
5624
  return out;
@@ -4895,35 +5641,38 @@ var PageAssembler = class PageAssembler {
4895
5641
  const band = bandForPage(this.pageInSection, this.globalPageIdx, this.ctx.titlePg, this.ctx.evenAndOddHeaders);
4896
5642
  const header = pickBand(this.ctx.headerSet, band);
4897
5643
  const footer = pickBand(this.ctx.footerSet, band);
5644
+ const pageText = pageNumberText(this.pageNumber, this.ctx.properties.pageNumberFormat);
4898
5645
  if (header.renderDynamic) this.dynBands.push({
4899
5646
  pageIdx: this.pages.length,
4900
- pageNumber: this.pageNumber,
5647
+ pageText,
4901
5648
  position: "header",
4902
5649
  render: header.renderDynamic
4903
5650
  });
4904
5651
  if (footer.renderDynamic) this.dynBands.push({
4905
5652
  pageIdx: this.pages.length,
4906
- pageNumber: this.pageNumber,
5653
+ pageText,
4907
5654
  position: "footer",
4908
5655
  render: footer.renderDynamic
4909
5656
  });
5657
+ const sheet = this.ctx.sheet;
5658
+ const onSheet = (items) => sheet ? items.map((item) => turnOntoSheet(item, sheet.width)) : items;
4910
5659
  this.pages.push({
4911
5660
  commands: [
4912
- ...this.pageBackgroundItems(),
4913
- ...this.pageBorderItems(),
4914
- ...this.columnSeparatorItems(),
5661
+ ...onSheet(this.pageBackgroundItems()),
5662
+ ...onSheet(this.pageBorderItems()),
5663
+ ...onSheet(this.columnSeparatorItems()),
4915
5664
  ...header.commands,
4916
- ...PageAssembler.byZ(this.floatsBehind).map((item) => ({
5665
+ ...onSheet(PageAssembler.byZ(this.floatsBehind).map((item) => ({
4917
5666
  ...item,
4918
5667
  behind: true
4919
- })),
4920
- ...this.current,
4921
- ...PageAssembler.byZ(this.floatsFront),
4922
- ...this.renderNotesBand(),
5668
+ }))),
5669
+ ...onSheet(this.current),
5670
+ ...onSheet(this.inFront(PageAssembler.byZ(this.floatsFront))),
5671
+ ...onSheet(this.renderNotesBand()),
4923
5672
  ...footer.commands
4924
5673
  ],
4925
- width: pt(this.ctx.pageWidth),
4926
- height: pt(this.ctx.pageHeight)
5674
+ width: pt(sheet?.width ?? this.ctx.pageWidth),
5675
+ height: pt(sheet?.height ?? this.ctx.pageHeight)
4927
5676
  });
4928
5677
  this.current = [];
4929
5678
  this.floatsBehind = [];
@@ -4937,7 +5686,7 @@ var PageAssembler = class PageAssembler {
4937
5686
  this.globalPageIdx++;
4938
5687
  this.pageNumber++;
4939
5688
  if ((this.ctx.properties.lineNumbering?.restart ?? "newPage") === "newPage") this.lineNumber = 0;
4940
- this.cursorY = this.ctx.pageHeight - this.ctx.marginTop;
5689
+ this.cursorY = this.pageTopY();
4941
5690
  this.colStartY = this.cursorY;
4942
5691
  this.bandTopY = this.cursorY;
4943
5692
  this.balanceBottomY = void 0;
@@ -5126,32 +5875,179 @@ var PageAssembler = class PageAssembler {
5126
5875
  this.noteReserve = 0;
5127
5876
  this.colIdx = 0;
5128
5877
  this.colStartLen = 0;
5129
- this.cursorY = this.ctx.pageHeight - this.ctx.marginTop;
5878
+ this.cursorY = this.pageTopY();
5130
5879
  this.colStartY = this.cursorY;
5131
5880
  this.bandTopY = this.cursorY;
5132
5881
  this.balanceBottomY = void 0;
5133
5882
  this.balanceHeightPt = 0;
5134
5883
  };
5135
5884
  };
5136
- function paginateSections(blocks, sectionCtxs, builder, defaultLang = "en-US", notes, bookmarkPositions, reflowParagraph, backgroundImage) {
5885
+ /**
5886
+ * §20.5 — one spreadsheet table's drawings as its pages show them
5887
+ * (TableBlock.overlay). The table lays its rows down page by page, and each
5888
+ * stretch of sheet rows a page takes unbroken is a window onto the sheet:
5889
+ * every drawing over that stretch is drawn where it stands relative to those
5890
+ * rows and cut off at the window's edges. A chart across a page break so
5891
+ * prints in two pieces, one on each page, and a drawing in the title rows on
5892
+ * every page that repeats them — which is what Excel prints.
5893
+ */
5894
+ var SheetView = class {
5895
+ runs = [];
5896
+ figures = /* @__PURE__ */ new Map();
5897
+ /**
5898
+ * @param overlay The table's drawings and the part of the sheet it prints.
5899
+ * @param tableX The table's left edge on the page.
5900
+ * @param builder The tagged-PDF structure, when the document is tagged.
5901
+ */
5902
+ constructor(overlay, tableX, builder) {
5903
+ this.overlay = overlay;
5904
+ this.tableX = tableX;
5905
+ this.builder = builder;
5906
+ }
5907
+ /**
5908
+ * A row, or a piece of one, laid down on the page in hand.
5909
+ *
5910
+ * @param sheetTop Where its top stands on the sheet; undefined for a row the sheet does not have.
5911
+ * @param topYUp Where its top stands on the page, y-up.
5912
+ * @param heightPt How tall it was laid out.
5913
+ */
5914
+ lay(sheetTop, topYUp, heightPt) {
5915
+ if (sheetTop === void 0) return;
5916
+ const last = this.runs[this.runs.length - 1];
5917
+ if (last && Math.abs(last.bottomYUp - topYUp) < .01 && Math.abs(last.sheetBottom - sheetTop) < .5) {
5918
+ last.sheetBottom = sheetTop + heightPt;
5919
+ last.bottomYUp = topYUp - heightPt;
5920
+ return;
5921
+ }
5922
+ this.runs.push({
5923
+ sheetTop,
5924
+ sheetBottom: sheetTop + heightPt,
5925
+ topYUp,
5926
+ bottomYUp: topYUp - heightPt
5927
+ });
5928
+ }
5929
+ /**
5930
+ * Draw what the page's rows show of the drawings, and start over for the
5931
+ * next page.
5932
+ *
5933
+ * @param asm The page assembler, still on the page the rows were laid on.
5934
+ */
5935
+ show(asm) {
5936
+ const o = this.overlay;
5937
+ const pageHeight = asm.ctx.pageHeight;
5938
+ for (const run of this.runs) {
5939
+ const window = {
5940
+ x: pt(this.tableX + o.leftPt),
5941
+ y: pt(pageHeight - run.topYUp),
5942
+ width: pt(o.widthPt),
5943
+ height: pt(run.topYUp - run.bottomYUp)
5944
+ };
5945
+ o.drawings.forEach((d, i) => {
5946
+ const { block } = d;
5947
+ if (d.yPt > run.sheetBottom || d.yPt + block.heightPt < run.sheetTop) return;
5948
+ const left = d.xPt - o.sheetLeftPt;
5949
+ if (left > o.widthPt || left + block.widthPt < 0) return;
5950
+ const x = this.tableX + o.leftPt + (o.mirrored ? o.widthPt - left - block.widthPt : left);
5951
+ const items = [];
5952
+ emitDrawing(block, x, run.topYUp - (d.yPt - run.sheetTop), items, pageHeight, void 0);
5953
+ const shown = items.filter((item) => {
5954
+ const box = pageItemBounds(item);
5955
+ return box.x1 >= window.x && box.x0 <= window.x + window.width && box.y1 >= window.y && box.y0 <= window.y + window.height;
5956
+ });
5957
+ if (shown.length === 0) return;
5958
+ const figId = this.figure(i, block);
5959
+ seenThrough(PageAssembler.zSink(asm.floatsFront, block.float?.zOrder), window).push(...figId === void 0 ? shown : shown.map((item) => ({
5960
+ ...item,
5961
+ structId: figId
5962
+ })));
5963
+ });
5964
+ }
5965
+ this.runs = [];
5966
+ }
5967
+ /** The tagged-PDF Figure a drawing is, made once however many pages show it. */
5968
+ figure(i, block) {
5969
+ if (!this.builder) return void 0;
5970
+ let id = this.figures.get(i);
5971
+ if (id === void 0) {
5972
+ const role = block.kind === "chart" ? block.figureRole ?? "Chart" : block.kind === "image" ? "Image" : "Shape";
5973
+ id = createFigure(this.builder, block.altText, role);
5974
+ this.figures.set(i, id);
5975
+ }
5976
+ return id;
5977
+ }
5978
+ };
5979
+ /** A sink that marks each item it takes as seen through `window` (PageItemBase.window). */
5980
+ var seenThrough = (target, window) => ({ push: (...items) => target.push(...items.map((item) => ({
5981
+ ...item,
5982
+ window
5983
+ }))) });
5984
+ /**
5985
+ * §17.3.1.14/15 — the height a paragraph needs in one column: all of its lines
5986
+ * where it keeps them together, and where it keeps with the next, the whole of
5987
+ * every paragraph in the chain it heads and as much of the block after as has
5988
+ * to stand beside it — a paragraph's first line, or its first two (all of a
5989
+ * short one) where its own widow control would not leave one alone; a table's
5990
+ * first row; a picture. The space between them counts, the chain's own space
5991
+ * before does not. A chain is decided at its head: one that follows a
5992
+ * paragraph kept with it moved with that paragraph or not at all, and asks
5993
+ * only whether its own lines stay together.
5994
+ *
5995
+ * @param blocks The section's blocks.
5996
+ * @param from The paragraph asked about.
5997
+ * @param end The section's end: nothing is kept with what lies past it.
5998
+ * @returns The height, or undefined where the paragraph asks for neither.
5999
+ */
6000
+ function keptTogetherHeight(blocks, from, end, collapse = false) {
6001
+ const first = blocks[from];
6002
+ if (first?.kind !== "paragraph") return void 0;
6003
+ const before = blocks[from - 1];
6004
+ const inChain = before?.kind === "paragraph" && before.resolved.keepNext && !before.pageBreakAfter && !first.resolved.pageBreakBefore && !first.pageBreakBefore;
6005
+ const keepsNext = first.resolved.keepNext && !inChain;
6006
+ if (!keepsNext && !first.resolved.keepLines) return void 0;
6007
+ let height = first.heightPt;
6008
+ let current = first;
6009
+ for (let i = from + 1; keepsNext && current.resolved.keepNext && !current.pageBreakAfter; i++) {
6010
+ const next = i < end ? blocks[i] : void 0;
6011
+ if (next?.kind === "paragraph") {
6012
+ if (next.resolved.pageBreakBefore || next.pageBreakBefore) break;
6013
+ height += collapse ? Math.max(current.spacingAfterPt, next.spacingBeforePt) : current.spacingAfterPt + next.spacingBeforePt;
6014
+ if (next.resolved.keepNext) {
6015
+ height += next.heightPt;
6016
+ current = next;
6017
+ continue;
6018
+ }
6019
+ const count = next.lines.length;
6020
+ const stays = next.resolved.keepLines ? count : next.resolved.widowControl ? count <= 3 ? count : 2 : 1;
6021
+ for (const line of next.lines.slice(0, stays)) height += computeLineHeight(line, next.resolved);
6022
+ } else if (next?.kind === "table" && !next.float) height += current.spacingAfterPt + (next.rows[0]?.heightPt ?? 0);
6023
+ else if (next?.kind === "image" && !next.float) height += current.spacingAfterPt + next.heightPt;
6024
+ break;
6025
+ }
6026
+ return height;
6027
+ }
6028
+ function paginateSections(blocks, sectionCtxs, builder, defaultLang = "en-US", notes, bookmarkPositions, reflowParagraph, backgroundImage, typesetByWord = false, compatibilityMode) {
5137
6029
  if (sectionCtxs.length === 0) return [];
5138
6030
  const asm = new PageAssembler(sectionCtxs, builder, notes, bookmarkPositions, backgroundImage);
6031
+ asm.collapseSpacing = typesetByWord;
6032
+ asm.keepsBeforeAtOwnBreak = (compatibilityMode ?? 0) < 15;
5139
6033
  const balanceIfEndsContinuous = (fromBlock) => {
5140
6034
  const here = asm.ctx;
5141
6035
  const next = sectionCtxs[asm.secIdx + 1];
5142
6036
  if ((here.columns?.length ?? 1) < 2) return;
5143
6037
  if (!next?.continuous) return;
5144
6038
  if (next.pageWidth !== here.pageWidth || next.pageHeight !== here.pageHeight) return;
6039
+ if (next.sheet === void 0 !== (here.sheet === void 0)) return;
5145
6040
  const end = Math.min(here.endIndex, blocks.length);
5146
- asm.beginBalancedBand(blocksHeight(blocks.slice(fromBlock, end)));
6041
+ asm.beginBalancedBand(blocksHeight(blocks.slice(fromBlock, end), typesetByWord));
5147
6042
  };
5148
6043
  balanceIfEndsContinuous(0);
6044
+ let brokenFor;
5149
6045
  for (let blockIdx = 0; blockIdx < blocks.length; blockIdx++) {
5150
6046
  while (asm.secIdx < sectionCtxs.length - 1 && blockIdx >= asm.ctx.endIndex) {
5151
6047
  const next = sectionCtxs[asm.secIdx + 1];
5152
- if (next.continuous && next.pageWidth === asm.ctx.pageWidth && next.pageHeight === asm.ctx.pageHeight) {
6048
+ if (next.continuous && next.pageWidth === asm.ctx.pageWidth && next.pageHeight === asm.ctx.pageHeight && next.sheet === void 0 === (asm.ctx.sheet === void 0)) {
5153
6049
  const end = Math.min(next.endIndex, blocks.length);
5154
- asm.startBandSection(next, blocksHeight(blocks.slice(blockIdx, end)));
6050
+ asm.startBandSection(next, blocksHeight(blocks.slice(blockIdx, end), typesetByWord));
5155
6051
  continue;
5156
6052
  }
5157
6053
  if (asm.pageHasContent() || asm.pageInSection === 0) asm.flushPage(true);
@@ -5160,7 +6056,7 @@ function paginateSections(blocks, sectionCtxs, builder, defaultLang = "en-US", n
5160
6056
  const wants = opening?.sectionStart;
5161
6057
  if (wants === "oddPage" || wants === "evenPage") {
5162
6058
  if ((opening?.pageNumberStart ?? asm.pageNumber) % 2 === 1 !== (wants === "oddPage")) {
5163
- asm.cursorY = asm.ctx.pageHeight - asm.ctx.marginTop;
6059
+ asm.cursorY = asm.pageTopY();
5164
6060
  asm.flushPage(true);
5165
6061
  }
5166
6062
  }
@@ -5168,18 +6064,43 @@ function paginateSections(blocks, sectionCtxs, builder, defaultLang = "en-US", n
5168
6064
  asm.ctx = sectionCtxs[asm.secIdx];
5169
6065
  asm.restartPageNumbers(asm.ctx);
5170
6066
  asm.pageInSection = 0;
5171
- asm.cursorY = asm.ctx.pageHeight - asm.ctx.marginTop;
6067
+ asm.cursorY = asm.pageTopY();
6068
+ asm.sectionStart = true;
5172
6069
  balanceIfEndsContinuous(blockIdx);
5173
6070
  }
5174
6071
  const block = blocks[blockIdx];
6072
+ const opensSection = asm.sectionStart;
6073
+ const anchored = "float" in block && isOutOfFlowFloat(block.float);
6074
+ if (!anchored) asm.sectionStart = false;
5175
6075
  if (asm.pendingPageBreak) {
5176
6076
  asm.pendingPageBreak = false;
5177
6077
  asm.flushPage(true);
5178
6078
  }
6079
+ if (typesetByWord && anchored && asm.pageHasContent() && brokenFor === void 0) {
6080
+ let owner = blockIdx + 1;
6081
+ while (owner < blocks.length) {
6082
+ const b = blocks[owner];
6083
+ if (!("float" in b && isOutOfFlowFloat(b.float))) break;
6084
+ owner++;
6085
+ }
6086
+ const para = blocks[owner];
6087
+ if (para?.kind === "paragraph" && para.resolved.pageBreakBefore) {
6088
+ asm.flushPage();
6089
+ brokenFor = owner;
6090
+ }
6091
+ }
5179
6092
  if (builder && !(block.kind === "paragraph" && block.list)) asm.listStack.length = 0;
5180
6093
  if (block.kind === "paragraph") {
5181
- if ((block.resolved.pageBreakBefore || block.pageBreakBefore) && asm.pageHasContent()) asm.flushPage();
5182
- asm.cursorY -= block.spacingBeforePt;
6094
+ const brokeAhead = brokenFor === blockIdx;
6095
+ brokenFor = void 0;
6096
+ const ownBreak = block.resolved.pageBreakBefore && (brokeAhead || asm.pageHasContent());
6097
+ if (!brokeAhead && (block.resolved.pageBreakBefore || block.pageBreakBefore) && asm.pageHasContent()) asm.flushPage();
6098
+ const columnInUse = asm.colHasContent();
6099
+ const opensDocument = opensSection && asm.pages.length === 0 && asm.secIdx === 0;
6100
+ const beforePt = asm.collapseSpacing && opensDocument && block.resolved.spacingBeforeAuto !== void 0 ? 0 : block.spacingBeforePt;
6101
+ asm.cursorY -= asm.openBefore(beforePt, opensSection, ownBreak);
6102
+ const kept = keptTogetherHeight(blocks, blockIdx, asm.ctx.endIndex, typesetByWord);
6103
+ if (kept !== void 0 && columnInUse && asm.exclusions.length === 0 && kept > asm.cursorY - asm.bottomLimit()) asm.advanceColumn();
5183
6104
  let pb = block;
5184
6105
  const colW = asm.colWidth();
5185
6106
  if (reflowParagraph && pb.source && Math.abs(colW - asm.builtWidth()) > .5) pb = reflowParagraph(pb.source, colW);
@@ -5187,8 +6108,26 @@ function paginateSections(blocks, sectionCtxs, builder, defaultLang = "en-US", n
5187
6108
  const widths = asm.lineWidthsFor(pb, asm.cursorY);
5188
6109
  if (widths) pb = reflowParagraph(pb.source, colW, widths);
5189
6110
  }
5190
- const borderTopY = asm.cursorY;
5191
- const borderPage = asm.pages.length;
6111
+ let borderTopY = asm.cursorY;
6112
+ let borderPage = asm.pages.length;
6113
+ const keptColumnEnd = (from) => {
6114
+ const n = pb.lines.length;
6115
+ if (!pb.resolved.widowControl || n < 2 || asm.exclusions.length > 0) return void 0;
6116
+ let end = asm.firstLineOverflowing(pb, from);
6117
+ if (end >= n) return void 0;
6118
+ if (n - end === 1) end--;
6119
+ if (from === 0 && end === 1) end = 0;
6120
+ if (end <= from) return asm.colHasContent() ? from : from + 1;
6121
+ return end;
6122
+ };
6123
+ const advanceBefore = (lineIdx) => {
6124
+ asm.advanceColumn();
6125
+ if (lineIdx === 0) {
6126
+ borderTopY = asm.cursorY;
6127
+ borderPage = asm.pages.length;
6128
+ }
6129
+ };
6130
+ let columnEnd = keptColumnEnd(0);
5192
6131
  let structId;
5193
6132
  let markerLblId;
5194
6133
  const firstLine = pb.lines[0];
@@ -5205,13 +6144,21 @@ function paginateSections(blocks, sectionCtxs, builder, defaultLang = "en-US", n
5205
6144
  let firstLineOfBlock = true;
5206
6145
  let pairPending;
5207
6146
  for (const [lineIdx, line] of pb.lines.entries()) {
5208
- if (pb.columnBreakLines?.has(lineIdx) && asm.colHasContent()) asm.advanceColumn();
6147
+ if (pb.columnBreakLines?.has(lineIdx) && asm.colHasContent()) {
6148
+ advanceBefore(lineIdx);
6149
+ columnEnd = keptColumnEnd(lineIdx);
6150
+ }
6151
+ if (lineIdx === columnEnd && asm.colHasContent()) {
6152
+ advanceBefore(lineIdx);
6153
+ columnEnd = keptColumnEnd(lineIdx);
6154
+ }
5209
6155
  const h = computeLineHeight(line, pb.resolved);
5210
6156
  let newNotes = asm.lineFootnotes(line);
5211
6157
  const addedReserve = (sub) => sub.reduce((sum, x) => sum + x.heightPt, 0) + (asm.pageNotes.length === 0 && sub.length > 0 ? FOOTNOTE_SEPARATOR_HEIGHT : 0);
5212
6158
  if (asm.cursorY - h < asm.bottomLimit() + addedReserve(newNotes) && asm.colHasContent()) {
5213
- asm.advanceColumn();
6159
+ advanceBefore(lineIdx);
5214
6160
  newNotes = asm.lineFootnotes(line);
6161
+ columnEnd = keptColumnEnd(lineIdx);
5215
6162
  }
5216
6163
  if (newNotes.length > 0) {
5217
6164
  asm.noteReserve += addedReserve(newNotes);
@@ -5291,6 +6238,7 @@ function paginateSections(blocks, sectionCtxs, builder, defaultLang = "en-US", n
5291
6238
  }
5292
6239
  if (asm.pages.length === borderPage) asm.current.push(...paragraphDecoration(pb.resolved, asm.colLeft() + pb.resolved.indentLeft, asm.colLeft() + asm.colWidth() - pb.resolved.indentRight, borderTopY, asm.cursorY, asm.ctx.pageHeight));
5293
6240
  asm.cursorY -= pb.spacingAfterPt;
6241
+ asm.closeAfter(pb.spacingAfterPt);
5294
6242
  if (pb.pageBreakAfter) asm.pendingPageBreak = true;
5295
6243
  } else if (block.kind === "image") {
5296
6244
  const figId = builder ? createFigure(builder, block.altText, "Image") : void 0;
@@ -5321,12 +6269,13 @@ function paginateSections(blocks, sectionCtxs, builder, defaultLang = "en-US", n
5321
6269
  emitImageAt(fx, fy, PageAssembler.zSink(block.float.behind ? asm.floatsBehind : asm.floatsFront, block.float.zOrder));
5322
6270
  if (block.float.wrap !== "none") asm.excludeFloat(block.float, fx, fy, block.widthPt, block.heightPt);
5323
6271
  } else {
5324
- asm.cursorY -= block.spacingBeforePt;
6272
+ asm.cursorY -= asm.openBefore(block.spacingBeforePt, opensSection);
5325
6273
  if (asm.cursorY - block.heightPt < asm.bottomLimit() && asm.colHasContent()) asm.advanceColumn();
5326
6274
  asm.cursorY -= block.heightPt;
5327
6275
  const offset = alignmentOffset(block.resolvedAlignment, block.widthPt, asm.colWidth());
5328
6276
  emitImageAt(asm.colLeft() + offset, asm.cursorY + block.heightPt, asm.current);
5329
6277
  asm.cursorY -= block.spacingAfterPt;
6278
+ asm.closeAfter(sharedAfter(block));
5330
6279
  }
5331
6280
  } else if (block.kind === "shape") {
5332
6281
  const figId = builder ? createFigure(builder, block.altText, "Shape") : void 0;
@@ -5337,12 +6286,13 @@ function paginateSections(blocks, sectionCtxs, builder, defaultLang = "en-US", n
5337
6286
  emitShapeAt(fx, fy - block.heightPt, PageAssembler.zSink(block.float.behind ? asm.floatsBehind : asm.floatsFront, block.float.zOrder));
5338
6287
  if (block.float.wrap !== "none") asm.excludeFloat(block.float, fx, fy, block.widthPt, block.heightPt);
5339
6288
  } else {
5340
- asm.cursorY -= block.spacingBeforePt;
6289
+ asm.cursorY -= asm.openBefore(block.spacingBeforePt, opensSection);
5341
6290
  if (asm.cursorY - block.heightPt < asm.bottomLimit() && asm.colHasContent()) asm.advanceColumn();
5342
6291
  asm.cursorY -= block.heightPt;
5343
6292
  const offset = alignmentOffset(block.resolvedAlignment, block.widthPt, asm.colWidth());
5344
6293
  emitShapeAt(asm.colLeft() + offset, asm.cursorY, asm.current);
5345
6294
  asm.cursorY -= block.spacingAfterPt;
6295
+ asm.closeAfter(block.spacingAfterPt);
5346
6296
  }
5347
6297
  } else if (block.kind === "chart") {
5348
6298
  const figId = builder ? createFigure(builder, block.altText, block.figureRole ?? "Chart") : void 0;
@@ -5355,12 +6305,13 @@ function paginateSections(blocks, sectionCtxs, builder, defaultLang = "en-US", n
5355
6305
  emitChartAt(fx, fy - block.heightPt, PageAssembler.zSink(block.float.behind ? asm.floatsBehind : asm.floatsFront, block.float.zOrder));
5356
6306
  if (block.float.wrap !== "none") asm.excludeFloat(block.float, fx, fy, block.widthPt, block.heightPt);
5357
6307
  } else {
5358
- asm.cursorY -= block.spacingBeforePt;
6308
+ asm.cursorY -= asm.openBefore(block.spacingBeforePt, opensSection);
5359
6309
  if (asm.cursorY - block.heightPt < asm.bottomLimit() && asm.colHasContent()) asm.advanceColumn();
5360
6310
  asm.cursorY -= block.heightPt;
5361
6311
  const offset = alignmentOffset(block.resolvedAlignment, block.widthPt, asm.colWidth());
5362
6312
  emitChartAt(asm.colLeft() + offset, asm.cursorY, asm.current);
5363
6313
  asm.cursorY -= block.spacingAfterPt;
6314
+ asm.closeAfter(sharedAfter(block));
5364
6315
  }
5365
6316
  } else {
5366
6317
  const colCount = block.rows.reduce((max, r) => Math.max(max, r.cells.reduce((s, c) => Math.max(s, c.colStart + c.colSpan), 0)), 0);
@@ -5383,6 +6334,7 @@ function paginateSections(blocks, sectionCtxs, builder, defaultLang = "en-US", n
5383
6334
  const headerHeightPt = headerRows.reduce((s, r) => s + r.heightPt, 0);
5384
6335
  const tableNode = builder ? builder.create("Table", builder.root) : void 0;
5385
6336
  const tableX = asm.colLeft() + block.xOffsetPt;
6337
+ const sheetView = block.overlay ? new SheetView(block.overlay, tableX, builder) : void 0;
5386
6338
  for (let ri = 0; ri < block.rows.length; ri++) {
5387
6339
  const row = block.rows[ri];
5388
6340
  const isLeadingHeader = ri < headerRows.length;
@@ -5403,20 +6355,25 @@ function paginateSections(blocks, sectionCtxs, builder, defaultLang = "en-US", n
5403
6355
  const room = asm.cursorY - asm.bottomLimit();
5404
6356
  const page = asm.ctx.pageContentHeight;
5405
6357
  const chunks = !row.cantSplit && row.heightPt > room && asm.colHasContent() && room > 0 ? splitRowIntoChunks(row, room, page) : row.heightPt > page ? splitRowIntoChunks(row, page) : [row];
6358
+ let chunkOffsetPt = 0;
5406
6359
  for (let ci = 0; ci < chunks.length; ci++) {
5407
6360
  const chunk = chunks[ci];
5408
- const forcedBreak = ci === 0 && row.breakBefore && !isLeadingHeader && asm.pageHasContent();
6361
+ const forcedBreak = ci === 0 && row.breakBefore && (ri === 0 || !isLeadingHeader) && asm.pageHasContent();
5409
6362
  const overflow = asm.cursorY - chunk.heightPt < asm.bottomLimit() && asm.colHasContent();
5410
6363
  if (forcedBreak || overflow) {
6364
+ sheetView?.show(asm);
5411
6365
  if (forcedBreak) asm.flushPage();
5412
6366
  else asm.advanceColumn();
5413
6367
  if (!isLeadingHeader && headerRows.length > 0 && asm.cursorY - headerHeightPt - chunk.heightPt >= asm.bottomLimit()) for (const hr of headerRows) {
5414
6368
  emitRowChunk(asm.current, hr, tableX, asm.cursorY, asm.ctx.pageHeight, colCount, void 0);
6369
+ sheetView?.lay(hr.sheetTopPt, asm.cursorY, hr.heightPt);
5415
6370
  asm.cursorY -= hr.heightPt;
5416
6371
  }
5417
6372
  }
5418
6373
  const mergedHeights = chunks.length === 1 && row.cells.some((c) => c.mergeRole === "start") ? row.cells.map((c) => c.mergeRole === "start" ? mergedBoxHeightPt(block.rows, ri, c.colStart, asm.cursorY - asm.bottomLimit()) : void 0) : void 0;
5419
6374
  emitRowChunk(asm.current, chunk, tableX, asm.cursorY, asm.ctx.pageHeight, colCount, cellStructIds, mergedHeights, cellAnchorCtx(asm));
6375
+ sheetView?.lay(row.sheetTopPt !== void 0 ? row.sheetTopPt + chunkOffsetPt : void 0, asm.cursorY, chunk.heightPt);
6376
+ chunkOffsetPt += chunk.heightPt;
5420
6377
  asm.cursorY -= chunk.heightPt;
5421
6378
  if (ci === chunks.length - 1 && ri < block.rows.length - 1) {
5422
6379
  const next = block.rows[ri + 1];
@@ -5427,12 +6384,13 @@ function paginateSections(blocks, sectionCtxs, builder, defaultLang = "en-US", n
5427
6384
  }
5428
6385
  }
5429
6386
  }
6387
+ sheetView?.show(asm);
5430
6388
  }
5431
6389
  }
5432
6390
  asm.flushPage();
5433
6391
  if (asm.pages.length === 0) asm.flushPage(true);
5434
6392
  for (const d of asm.dynBands) {
5435
- const cmds = d.render(d.pageNumber, asm.pages.length);
6393
+ const cmds = d.render(d.pageText, asm.pages.length);
5436
6394
  const page = asm.pages[d.pageIdx];
5437
6395
  if (d.position === "header") page.commands.unshift(...cmds);
5438
6396
  else page.commands.push(...cmds);
@@ -5507,7 +6465,7 @@ function emitRowChunk(out, row, marginLeft, cursorY, pageHeight, colCount, cellS
5507
6465
  }
5508
6466
  out.push({
5509
6467
  type: "fill",
5510
- x: pt(marginLeft + (row.columnXOffsets[i] ?? 0)),
6468
+ x: pt(marginLeft + (row.columnXOffsets[i] ?? 0) + (cell.paintOffsetPt ?? 0)),
5511
6469
  y: pt(pageHeight - rowBottom - row.heightPt - FILL_SEAM_PT),
5512
6470
  width: pt(width),
5513
6471
  height: pt(row.heightPt + FILL_SEAM_PT),
@@ -5522,16 +6480,19 @@ function emitRowChunk(out, row, marginLeft, cursorY, pageHeight, colCount, cellS
5522
6480
  const structId = cellStructIds?.[i];
5523
6481
  if (cell.dataBar && cell.mergeRole !== "middle" && cell.mergeRole !== "end") {
5524
6482
  const start = Math.max(0, Math.min(1, cell.dataBar.startFraction ?? 0));
5525
- const barWidth = cell.widthPt * Math.max(0, Math.min(1, cell.dataBar.fraction));
6483
+ const inner = Math.max(0, cell.widthPt - 2 * DATA_BAR_INSET_PT);
6484
+ const barWidth = inner * Math.max(0, Math.min(1, cell.dataBar.fraction));
5526
6485
  if (barWidth > 0) {
5527
- const barX = cellX + cell.widthPt * start;
5528
- const barBottomYUp = rowBottom;
6486
+ const barX = cellX + DATA_BAR_INSET_PT + inner * start;
6487
+ const boxHeightPt = Math.max(row.heightPt, mergedHeights?.[i] ?? row.heightPt);
6488
+ const barHeightPt = Math.max(0, boxHeightPt - 2 * DATA_BAR_INSET_PT);
6489
+ const barBottomYUp = rowBottom + row.heightPt - boxHeightPt + DATA_BAR_INSET_PT;
5529
6490
  out.push({
5530
6491
  type: "shape",
5531
6492
  shape: {
5532
- paths: [rectAtPath(0, 0, barWidth, row.heightPt)],
6493
+ paths: [rectAtPath(0, 0, barWidth, barHeightPt)],
5533
6494
  fillColorHex: cell.dataBar.colorHex,
5534
- fillGradient: {
6495
+ ...cell.dataBar.solid ? {} : { fillGradient: {
5535
6496
  kind: "linear",
5536
6497
  angle: cell.dataBar.negative ? 180 : 0,
5537
6498
  stops: [{
@@ -5541,7 +6502,7 @@ function emitRowChunk(out, row, marginLeft, cursorY, pageHeight, colCount, cellS
5541
6502
  offset: 1,
5542
6503
  colorHex: "FFFFFF"
5543
6504
  }]
5544
- },
6505
+ } },
5545
6506
  transform: flipTransform([
5546
6507
  1,
5547
6508
  0,
@@ -5598,6 +6559,28 @@ function emitRowChunk(out, row, marginLeft, cursorY, pageHeight, colCount, cellS
5598
6559
  });
5599
6560
  }
5600
6561
  }
6562
+ if (cell.noteFlag && cell.mergeRole !== "middle" && cell.mergeRole !== "end") {
6563
+ const left = cell.paintOffsetPt ?? 0;
6564
+ const right = left + (cell.paintWidthPt ?? cell.widthPt);
6565
+ const side = Math.min(NOTE_FLAG_PT, row.heightPt, right - left);
6566
+ const top = row.heightPt;
6567
+ const corner = cell.noteFlag.atLeft ? new PathBuilder().moveTo(left, top).lineTo(left + side, top).lineTo(left, top - side) : new PathBuilder().moveTo(right - side, top).lineTo(right, top).lineTo(right, top - side);
6568
+ out.push({
6569
+ type: "shape",
6570
+ shape: {
6571
+ paths: [corner.close().build()],
6572
+ fillColorHex: cell.noteFlag.kind === "thread" ? NOTE_THREAD_HEX : NOTE_FLAG_HEX,
6573
+ transform: flipTransform([
6574
+ 1,
6575
+ 0,
6576
+ 0,
6577
+ 1,
6578
+ cellX,
6579
+ rowBottom
6580
+ ], pageHeight)
6581
+ }
6582
+ });
6583
+ }
5601
6584
  if (cell.sparkline && cell.sparkline.values.length > 0 && cell.mergeRole !== "middle" && cell.mergeRole !== "end") {
5602
6585
  const inset = 1.5;
5603
6586
  const sw = cell.widthPt - 2 * inset;
@@ -5623,7 +6606,7 @@ function emitRowChunk(out, row, marginLeft, cursorY, pageHeight, colCount, cellS
5623
6606
  });
5624
6607
  }
5625
6608
  }
5626
- emitCellBorders(out, cell, cellX, rowBottom, row.heightPt, pageHeight, row.rowIdx, row.rowCount, colCount);
6609
+ emitCellBorders(out, cell, cellX, rowBottom, row.heightPt, pageHeight, row.rowIdx, row.rowCount, colCount, row.borderBands);
5627
6610
  const diagDown = cell.borders.diagonalDown;
5628
6611
  const diagUp = cell.borders.diagonalUp;
5629
6612
  if ((diagDown || diagUp) && cell.mergeRole !== "middle" && cell.mergeRole !== "end") {
@@ -5840,17 +6823,19 @@ function splitRowIntoChunks(row, capacity, restCapacity = capacity) {
5840
6823
  return out.length > 0 ? out : [row];
5841
6824
  }
5842
6825
  var FILL_SEAM_PT = .07;
5843
- function emitCellBorders(out, cell, cellX, cellY, rowHeight, pageHeight, rowIdx, rowCount, colCount) {
6826
+ function emitCellBorders(out, cell, cellX, cellY, rowHeight, pageHeight, rowIdx, rowCount, colCount, bands) {
5844
6827
  const pushSide = (side, border) => {
5845
6828
  if (!border || border.style === "none") return;
5846
6829
  const sz = border.width ?? DEFAULT_BORDER_SIZE_EIGHTH * EIGHTH_PT;
6830
+ const lowered = side === "top" ? (bands?.topPt ?? 0) / 2 : 0;
6831
+ const raised = side === "bottom" ? (bands?.bottomPt ?? 0) / 2 : 0;
5847
6832
  out.push({
5848
6833
  type: "border",
5849
6834
  side,
5850
- x: pt(cellX),
5851
- y: pt(pageHeight - cellY - rowHeight),
6835
+ x: pt(side === "left" ? cellX : cellX + (cell.paintOffsetPt ?? 0)),
6836
+ y: pt(pageHeight - cellY - rowHeight + lowered),
5852
6837
  width: pt(cell.paintWidthPt ?? cell.widthPt),
5853
- height: pt(rowHeight),
6838
+ height: pt(rowHeight - lowered - raised),
5854
6839
  borderSizePt: sz,
5855
6840
  borderColorHex: border.colorHex ?? "000000",
5856
6841
  ...border.style !== "single" ? { borderStyle: border.style } : {}