reamkit 1.17.0 → 1.19.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 (34) hide show
  1. package/dist/esm/core/document-model/types.d.ts +59 -1
  2. package/dist/esm/core/drawingml/colors.js +13 -6
  3. package/dist/esm/core/images.d.ts +2 -2
  4. package/dist/esm/core/images.js +38 -0
  5. package/dist/esm/core/lzw.d.ts +17 -0
  6. package/dist/esm/core/lzw.js +83 -0
  7. package/dist/esm/core/metafile/emf.d.ts +16 -0
  8. package/dist/esm/core/metafile/emf.js +635 -0
  9. package/dist/esm/core/metafile/picture.d.ts +106 -0
  10. package/dist/esm/core/metafile/picture.js +55 -0
  11. package/dist/esm/core/metafile/wmf.d.ts +9 -0
  12. package/dist/esm/core/metafile/wmf.js +371 -0
  13. package/dist/esm/core/po-helpers.d.ts +9 -0
  14. package/dist/esm/core/po-helpers.js +9 -1
  15. package/dist/esm/core/tiff.d.ts +20 -0
  16. package/dist/esm/core/tiff.js +339 -0
  17. package/dist/esm/core/vector.d.ts +5 -0
  18. package/dist/esm/excel/print-model.js +3 -1
  19. package/dist/esm/html/html-writer.js +3 -2
  20. package/dist/esm/layout/page-doc.d.ts +46 -3
  21. package/dist/esm/layout/styled-layout.js +649 -108
  22. package/dist/esm/pdf/styled-page-emitter.js +78 -3
  23. package/dist/esm/pdf/vector-graphics.d.ts +1 -1
  24. package/dist/esm/pdf/vector-graphics.js +2 -1
  25. package/dist/esm/pdf-reader/image-decode.js +7 -75
  26. package/dist/esm/svg/svg-writer.js +1 -1
  27. package/dist/esm/word/document-parser.js +3 -0
  28. package/dist/esm/word/drawing-parser.d.ts +5 -0
  29. package/dist/esm/word/drawing-parser.js +100 -14
  30. package/dist/esm/word/paragraph-properties.js +2 -2
  31. package/dist/esm/word/run-properties.js +2 -2
  32. package/dist/esm/word/xml-helpers.d.ts +12 -0
  33. package/dist/esm/word/xml-helpers.js +23 -3
  34. package/package.json +1 -1
@@ -4,6 +4,9 @@ import { createFontMeasure } from "../core/font/measure.js";
4
4
  import { halfPtToPt, pt } from "../core/ir/units.js";
5
5
  import { ResourceStore } from "../core/ir/resources.js";
6
6
  import { prepareImage } from "../core/images.js";
7
+ import { PathBuilder, flipTransform } from "../core/vector.js";
8
+ import { isEmf, readEmf } from "../core/metafile/emf.js";
9
+ import { isWmf, readWmf } from "../core/metafile/wmf.js";
7
10
  import { segmentLevels } from "../core/bidi/segments.js";
8
11
  import { analyzeString, hasBidiCharacters } from "../core/bidi/index.js";
9
12
  import { FORCED_BREAK, breakLines } from "../core/line-breaker/knuth-plass.js";
@@ -15,7 +18,6 @@ import { resolveParagraphProperties, resolveRunProperties } from "../core/style-
15
18
  import "../core/style-cascade/index.js";
16
19
  import { applyNumbering, applyNumberingToHeadersFooters } from "../core/numbering/apply.js";
17
20
  import "../core/numbering/index.js";
18
- import { PathBuilder, flipTransform } from "../core/vector.js";
19
21
  import { buildSparkline } from "../core/drawingml/sparkline-geometry.js";
20
22
  import { arcPoint, arcToBeziers } from "../core/arc-to-bezier.js";
21
23
  import { buildChartScene, legendSeriesName } from "../core/drawingml/chart-geometry.js";
@@ -55,6 +57,35 @@ var MIN_WRAP_WIDTH = 36;
55
57
  function isOutOfFlowFloat(f) {
56
58
  return f !== void 0 && f.wrap !== "topAndBottom";
57
59
  }
60
+ /** The float's left edge, resolved from its horizontal anchor (§20.4.3.3). */
61
+ function floatXIn(frame, f, widthPt) {
62
+ const h = f.posH;
63
+ if (!h) return frame.columnLeft;
64
+ const textRight = frame.marginLeft + frame.contentWidth;
65
+ const pageRight = frame.pageLeft + frame.pageWidth;
66
+ const base = h.relativeFrom === "page" || h.relativeFrom === "leftMargin" ? frame.pageLeft : h.relativeFrom === "column" ? frame.columnLeft : h.relativeFrom === "rightMargin" ? textRight : frame.marginLeft;
67
+ const span = h.relativeFrom === "page" ? frame.pageWidth : h.relativeFrom === "column" ? frame.columnWidth : h.relativeFrom === "leftMargin" ? frame.marginLeft - frame.pageLeft : h.relativeFrom === "rightMargin" ? pageRight - textRight : frame.contentWidth;
68
+ if (h.align === "center") return base + (span - widthPt) / 2;
69
+ if (h.align === "right") return base + span - widthPt;
70
+ return base + (h.offsetPt ?? 0);
71
+ }
72
+ /** The float's TOP, y-up, resolved from its vertical anchor (§20.4.3.4). */
73
+ function floatTopYUpIn(frame, f, heightPt = 0) {
74
+ const v = f.posV;
75
+ if (!v) return frame.anchorY;
76
+ if (v.align !== void 0) {
77
+ const page = v.relativeFrom === "page";
78
+ const topYUp = page ? frame.pageTopYUp : frame.marginTopYUp;
79
+ const bottomYUp = page ? frame.pageBottomYUp : frame.marginBottomYUp;
80
+ if (v.align === "top") return topYUp;
81
+ if (v.align === "bottom") return bottomYUp + heightPt;
82
+ return bottomYUp + (topYUp - bottomYUp + heightPt) / 2;
83
+ }
84
+ if (v.relativeFrom === "page" || v.relativeFrom === "topMargin") return frame.pageTopYUp - (v.offsetPt ?? 0);
85
+ if (v.relativeFrom === "bottomMargin") return frame.marginBottomYUp - (v.offsetPt ?? 0);
86
+ if (v.relativeFrom === "margin") return frame.marginTopYUp - (v.offsetPt ?? 0);
87
+ return frame.anchorY - (v.offsetPt ?? 0);
88
+ }
58
89
  var FOOTNOTE_RULE_WIDTH = 144;
59
90
  function assignNoteNumbers(body, commentIds) {
60
91
  const footnotes = /* @__PURE__ */ new Map();
@@ -273,8 +304,8 @@ function layoutStyledDocument(body, options) {
273
304
  const pdfaProfile = options.pdfA ? parsePdfAProfile(options.pdfA) : void 0;
274
305
  const tagged = options.tagged === true || options.pdfUA === true || (pdfaProfile?.tagged ?? false);
275
306
  const structBuilder = tagged ? new StructTreeBuilder() : void 0;
276
- const fontResources = collectFontResources(numberedBody, numberedHeadersFooters, options);
277
307
  const imageResources = collectImageResources(numberedBody, numberedHeadersFooters, options);
308
+ const fontResources = collectFontResources(numberedBody, numberedHeadersFooters, options, imageResources);
278
309
  const sectionCtxs = sectionList.map((s) => buildSectionContext(s, options, numberedHeadersFooters, fontResources, imageResources));
279
310
  let sectionIdx = 0;
280
311
  const bodyFrames = frameGroups(numberedBody);
@@ -309,6 +340,7 @@ function layoutStyledDocument(body, options) {
309
340
  spacingBeforePt: 0
310
341
  };
311
342
  }
343
+ flowLinkedTextBoxes(blocks);
312
344
  const noteBlockCache = /* @__PURE__ */ new Map();
313
345
  const notePlan = options.footnotes && noteAssigned.footnotes.size > 0 ? {
314
346
  numbers: noteAssigned.footnotes,
@@ -400,7 +432,7 @@ function layoutStyledDocument(body, options) {
400
432
  const commentNotes = options.commentAnnotations && options.comments ? buildCommentNotes(noteAssigned.comments, options.comments) : void 0;
401
433
  const reflowParagraph = (paragraph, width, widths) => layoutParagraphBlock(paragraph, options, fontResources, imageResources, width, widths);
402
434
  return {
403
- pages: paginateSections(blocks, sectionCtxs, structBuilder, options.language ?? "en-US", notePlan, bookmarks, reflowParagraph, options.pageBackgroundFill?.imageResource !== void 0 ? imageResources.get(options.pageBackgroundFill.imageResource)?.resourceName : void 0),
435
+ pages: paginateSections(blocks, sectionCtxs, structBuilder, options.language ?? "en-US", notePlan, bookmarks, reflowParagraph, backgroundImageOf(options, imageResources)),
404
436
  resources: options.resources ?? new ResourceStore(),
405
437
  fontResources,
406
438
  imageResources,
@@ -577,7 +609,7 @@ function balancedColumnHeight(bandHeightPt, colCount, roomPt) {
577
609
  /** Total laid-out height of a run of blocks, paragraph spacing included. */
578
610
  function blocksHeight(blocks) {
579
611
  return blocks.reduce((sum, b) => {
580
- if (b.kind !== "paragraph" && b.kind !== "table" && isOutOfFlowFloat(b.float)) return sum;
612
+ if (b.kind !== "paragraph" && isOutOfFlowFloat(b.float)) return sum;
581
613
  return sum + (b.kind === "paragraph" ? b.spacingBeforePt + b.heightPt + b.spacingAfterPt : b.heightPt);
582
614
  }, 0);
583
615
  }
@@ -775,8 +807,98 @@ function frameShape(frame, paragraphs, widthPt, exact) {
775
807
  };
776
808
  }
777
809
  /** §17.3.1.11 → §20.4.2.3: the frame's anchor, in the terms floats are placed in. */
810
+ /**
811
+ * `wps:linkedTxbx` — carry each chain's overflow from one box to the next.
812
+ * The words all live in the chain's FIRST box; a box shows as many of its lines
813
+ * as its height holds and hands the rest on, so LinkedTextBoxes.docx reads down
814
+ * one column and continues at the top of the other. A box that ends a chain
815
+ * keeps whatever is left, overflowing as it would have anyway.
816
+ *
817
+ * The lines are broken at the first box's width, so a chain of boxes of
818
+ * different widths sets its later columns to the wrong measure; re-breaking
819
+ * needs the source paragraphs split at the carry point, which the laid-out
820
+ * lines no longer know.
821
+ */
822
+ function flowLinkedTextBoxes(blocks) {
823
+ const chains = /* @__PURE__ */ new Map();
824
+ blocks.forEach((b, i) => {
825
+ if (b.kind !== "shape" || !b.textChain) return;
826
+ const at = chains.get(b.textChain.id) ?? [];
827
+ at.push(i);
828
+ chains.set(b.textChain.id, at);
829
+ });
830
+ for (const indices of chains.values()) {
831
+ if (indices.length < 2) continue;
832
+ indices.sort((a, b) => {
833
+ return (blocks[a].textChain?.seq ?? 0) - (blocks[b].textChain?.seq ?? 0);
834
+ });
835
+ let carried = [];
836
+ indices.forEach((idx, n) => {
837
+ const box = blocks[idx];
838
+ const all = n === 0 ? boxLines(box) : carried;
839
+ const last = n === indices.length - 1;
840
+ const room = box.heightPt - box.insetTopPt - box.insetBottomPt;
841
+ const kept = [];
842
+ let used = 0;
843
+ for (const l of all) {
844
+ const h = computeLineHeight(l.line, l.line.resolved) + l.gapPt;
845
+ if (!last && kept.length > 0 && used + h > room) break;
846
+ kept.push(l);
847
+ used += h;
848
+ }
849
+ carried = all.slice(kept.length);
850
+ blocks[idx] = rebuildBoxLines(box, kept);
851
+ });
852
+ }
853
+ }
854
+ function boxLines(box) {
855
+ return box.textLines.map((line, i) => ({
856
+ line,
857
+ gapPt: box.textLineGaps?.get(i) ?? 0,
858
+ ...box.textCharts?.get(i) ? { chart: box.textCharts.get(i) } : {},
859
+ ...box.textShapes?.get(i) ? { shape: box.textShapes.get(i) } : {},
860
+ ...box.textTables?.get(i) ? { table: box.textTables.get(i) } : {}
861
+ }));
862
+ }
863
+ function rebuildBoxLines(box, lines) {
864
+ const charts = /* @__PURE__ */ new Map();
865
+ const shapes = /* @__PURE__ */ new Map();
866
+ const tables = /* @__PURE__ */ new Map();
867
+ const gaps = /* @__PURE__ */ new Map();
868
+ let height = 0;
869
+ lines.forEach((l, i) => {
870
+ if (l.chart) charts.set(i, l.chart);
871
+ if (l.shape) shapes.set(i, l.shape);
872
+ if (l.table) tables.set(i, l.table);
873
+ if (l.gapPt > 0) gaps.set(i, l.gapPt);
874
+ height += computeLineHeight(l.line, l.line.resolved) + l.gapPt;
875
+ });
876
+ const { textCharts: _c, textShapes: _s, textTables: _t, textLineGaps: _g, ...rest } = box;
877
+ return {
878
+ ...rest,
879
+ textLines: lines.map((l) => l.line),
880
+ ...charts.size > 0 ? { textCharts: charts } : {},
881
+ ...shapes.size > 0 ? { textShapes: shapes } : {},
882
+ ...tables.size > 0 ? { textTables: tables } : {},
883
+ ...gaps.size > 0 ? { textLineGaps: gaps } : {},
884
+ textHeightPt: height
885
+ };
886
+ }
887
+ /**
888
+ * §17.3.1.11 — whether the frame is placed DOWN THE PAGE by itself, rather than
889
+ * where the flow has reached. `w:vAnchor="text"` (the default) measures `w:y`
890
+ * from the paragraph's own position, so such frames stack as paragraphs do —
891
+ * and a float takes no flow space, so lifting them out puts every one of them
892
+ * on the same line: tdf105035_framePrB.docx writes its title and its author
893
+ * line as text-anchored frames and both landed on the cursor, one over the
894
+ * other. Only a frame anchored to the page or the margin, or aligned by
895
+ * keyword, truly stands somewhere of its own.
896
+ */
897
+ function framePositioned(frame) {
898
+ return frame.yAlign !== void 0 || frame.vAnchor !== void 0 && frame.vAnchor !== "text";
899
+ }
778
900
  function frameFloat(frame) {
779
- const wrap = frame.wrap === "none" ? "none" : frame.wrap === "notBeside" ? "topAndBottom" : "square";
901
+ const wrap = frame.wrap === "none" ? "none" : frame.wrap === "notBeside" ? framePositioned(frame) ? "notBeside" : "topAndBottom" : "square";
780
902
  const hRel = frame.hAnchor === "page" ? "page" : frame.hAnchor === "margin" ? "margin" : "column";
781
903
  const vRel = frame.vAnchor === "page" ? "page" : frame.vAnchor === "margin" ? "margin" : "paragraph";
782
904
  const align = frame.xAlign === "left" || frame.xAlign === "center" || frame.xAlign === "right" ? frame.xAlign : void 0;
@@ -825,7 +947,10 @@ function laidOutBlocksFor(elements, options, fontResources, contentWidth, imageR
825
947
  function layoutBodyElement(el, options, fontResources, imageResources, contentWidth, maxHeight, box) {
826
948
  if (el.kind === "paragraph") return layoutParagraphBlock(el.paragraph, options, fontResources, imageResources, contentWidth);
827
949
  if (el.kind === "table") return layoutTableBlock(el.table, options, fontResources, imageResources, contentWidth);
828
- if (el.kind === "image") return layoutImageBlock(el.image, imageResources, contentWidth, maxHeight, box);
950
+ if (el.kind === "image") {
951
+ const meta = el.image.resource ? imageResources?.get(el.image.resource)?.metafile : void 0;
952
+ return meta ? layoutMetafileBlock(el.image, meta, options, fontResources, contentWidth, maxHeight, box) : layoutImageBlock(el.image, imageResources, contentWidth, maxHeight, box);
953
+ }
829
954
  if (el.kind === "chart") return layoutChartBlock(el.chart, options, fontResources, contentWidth, maxHeight);
830
955
  return layoutShapeBlock(el.shape, options, fontResources, imageResources, contentWidth, maxHeight, box);
831
956
  }
@@ -873,6 +998,7 @@ function layoutImageBlock(image, imageResources, contentWidth, maxHeight, box) {
873
998
  resolvedAlignment,
874
999
  resourceName: res?.resourceName ?? "",
875
1000
  ...image.outline ? { outline: image.outline } : {},
1001
+ ...image.wash ? { wash: image.wash } : {},
876
1002
  ...image.crop ? { crop: image.crop } : {},
877
1003
  ...image.rotation60k ? { rotationDeg: image.rotation60k / 6e4 } : {},
878
1004
  ...image.flipH ? { flipH: true } : {},
@@ -898,7 +1024,12 @@ function layoutShapeBlock(shape, options, fontResources, imageResources, content
898
1024
  heightPt = maxHeight;
899
1025
  }
900
1026
  const fillGradient = shape.fill.kind === "gradient" ? shape.fill.gradient : void 0;
901
- const fillImageResourceName = shape.fill.kind === "picture" && shape.fill.imageResource !== void 0 ? imageResources?.get(shape.fill.imageResource)?.resourceName : void 0;
1027
+ const fillImage = shape.fill.kind === "picture" && shape.fill.imageResource !== void 0 ? imageResources?.get(shape.fill.imageResource) : void 0;
1028
+ const fillImageResourceName = fillImage?.resourceName;
1029
+ const fillImageTile = shape.fill.kind === "picture" && shape.fill.tiled === true && fillImage?.prepared ? {
1030
+ widthPt: fillImage.prepared.widthPx * 72 / 96,
1031
+ heightPt: fillImage.prepared.heightPx * 72 / 96
1032
+ } : void 0;
902
1033
  const fillColorHex = shape.fill.kind === "solid" ? shape.fill.colorHex : fillGradient ? gradientToSolid(fillGradient) : void 0;
903
1034
  const stroke = buildStroke(shape.line);
904
1035
  const t = shape.transform;
@@ -1052,9 +1183,11 @@ function layoutShapeBlock(shape, options, fontResources, imageResources, content
1052
1183
  paths,
1053
1184
  ...markerPaths.length > 0 ? { markerPaths } : {},
1054
1185
  ...fillImageResourceName ? { fillImageResourceName } : {},
1186
+ ...fillImageTile ? { fillImageTile } : {},
1055
1187
  ...fillImageResourceName && shape.fill.imageCrop ? { fillImageCrop: shape.fill.imageCrop } : {},
1056
1188
  ...fillColorHex ? { fillColorHex } : {},
1057
1189
  ...fillGradient ? { fillGradient } : {},
1190
+ ...shape.fill.alpha !== void 0 && shape.fill.alpha < 1 ? { fillAlpha: shape.fill.alpha } : {},
1058
1191
  ...stroke ? { stroke } : {},
1059
1192
  ...shape.shadow ? { shadow: shape.shadow } : {},
1060
1193
  rotation60k: t?.rotation60k ?? 0,
@@ -1069,6 +1202,7 @@ function layoutShapeBlock(shape, options, fontResources, imageResources, content
1069
1202
  ...textTables.size > 0 ? { textTables } : {},
1070
1203
  ...textLineGaps.size > 0 ? { textLineGaps } : {},
1071
1204
  textHeightPt,
1205
+ ...text?.chain ? { textChain: text.chain } : {},
1072
1206
  insetLeftPt,
1073
1207
  insetRightPt,
1074
1208
  insetTopPt,
@@ -1079,6 +1213,144 @@ function layoutShapeBlock(shape, options, fontResources, imageResources, content
1079
1213
  ...shape.float ? { float: shape.float } : {}
1080
1214
  };
1081
1215
  }
1216
+ /**
1217
+ * MS-EMF / MS-WMF — a metafile picture as the primitives a chart is made of,
1218
+ * scaled from its own logical box into the box the drawing reserves.
1219
+ *
1220
+ * A metafile IS a little drawing program, and what it draws is paths and words:
1221
+ * the same two things a chart's layout carries, placed in the same local y-up
1222
+ * frame. So one is turned into the other rather than growing a block kind, a
1223
+ * pagination arm and an emitter of its own.
1224
+ */
1225
+ function layoutMetafileBlock(image, pic, options, fontResources, contentWidth, maxHeight, box) {
1226
+ const placed = layoutImageBlock(image, void 0, contentWidth, maxHeight, box);
1227
+ const widthPt = placed.widthPt;
1228
+ const heightPt = placed.heightPt;
1229
+ const layout = rotateDrawing(metafileDrawing(pic, widthPt, heightPt, options, fontResources), (image.rotation60k ?? 0) / 6e4, widthPt / 2, heightPt / 2);
1230
+ const pp = image.paragraphProperties;
1231
+ return {
1232
+ kind: "chart",
1233
+ widthPt,
1234
+ heightPt,
1235
+ layout,
1236
+ resolvedAlignment: pp.alignment ?? "left",
1237
+ spacingBeforePt: pp.spacingBefore ?? 0,
1238
+ spacingAfterPt: pp.spacingAfter ?? 0,
1239
+ figureRole: "Image",
1240
+ ...image.altText ? { altText: image.altText } : {},
1241
+ ...image.float ? { float: image.float } : {}
1242
+ };
1243
+ }
1244
+ /** The same primitives, turned `deg` CLOCKWISE about a point of the local frame. */
1245
+ function rotateDrawing(layout, deg, cx, cy) {
1246
+ if (!deg) return layout;
1247
+ const a = -deg * Math.PI / 180;
1248
+ const cos = Math.cos(a);
1249
+ const sin = Math.sin(a);
1250
+ const at = (x, y) => ({
1251
+ x: cx + (x - cx) * cos - (y - cy) * sin,
1252
+ y: cy + (x - cx) * sin + (y - cy) * cos
1253
+ });
1254
+ return {
1255
+ shapes: layout.shapes.map((sh) => ({
1256
+ ...sh,
1257
+ paths: sh.paths.map((path) => ({
1258
+ ...path,
1259
+ segments: path.segments.map((seg) => {
1260
+ if (seg.op === "close") return seg;
1261
+ if (seg.op === "cubic") {
1262
+ const c1 = at(seg.x1, seg.y1);
1263
+ const c2 = at(seg.x2, seg.y2);
1264
+ const e = at(seg.x, seg.y);
1265
+ return {
1266
+ op: "cubic",
1267
+ x1: c1.x,
1268
+ y1: c1.y,
1269
+ x2: c2.x,
1270
+ y2: c2.y,
1271
+ x: e.x,
1272
+ y: e.y
1273
+ };
1274
+ }
1275
+ const p = at(seg.x, seg.y);
1276
+ return {
1277
+ op: seg.op,
1278
+ x: p.x,
1279
+ y: p.y
1280
+ };
1281
+ })
1282
+ }))
1283
+ })),
1284
+ texts: layout.texts.map((t) => {
1285
+ const p = at(t.x, t.y);
1286
+ return {
1287
+ ...t,
1288
+ x: p.x,
1289
+ y: p.y,
1290
+ rotationDeg: (t.rotationDeg ?? 0) - deg
1291
+ };
1292
+ })
1293
+ };
1294
+ }
1295
+ /**
1296
+ * A metafile's primitives, scaled from its own logical box into a `widthPt` ×
1297
+ * `heightPt` one and turned y-up about its bottom-left corner — the frame both
1298
+ * a chart block and an inline picture token are drawn in.
1299
+ */
1300
+ function metafileDrawing(pic, widthPt, heightPt, options, fontResources) {
1301
+ const sx = widthPt / pic.width;
1302
+ const sy = heightPt / pic.height;
1303
+ const mapX = (x) => (x - pic.left) * sx;
1304
+ const mapY = (y) => heightPt - (y - pic.top) * sy;
1305
+ const shapes = [];
1306
+ const texts = [];
1307
+ for (const prim of pic.prims) {
1308
+ if (prim.kind === "path") {
1309
+ shapes.push({
1310
+ paths: prim.paths.map((path) => ({
1311
+ ...path,
1312
+ segments: path.segments.map((seg) => seg.op === "close" ? seg : seg.op === "cubic" ? {
1313
+ op: "cubic",
1314
+ x1: mapX(seg.x1),
1315
+ y1: mapY(seg.y1),
1316
+ x2: mapX(seg.x2),
1317
+ y2: mapY(seg.y2),
1318
+ x: mapX(seg.x),
1319
+ y: mapY(seg.y)
1320
+ } : {
1321
+ op: seg.op,
1322
+ x: mapX(seg.x),
1323
+ y: mapY(seg.y)
1324
+ })
1325
+ })),
1326
+ ...prim.fillColorHex !== void 0 ? { fillColorHex: prim.fillColorHex } : {},
1327
+ ...prim.stroke ? { stroke: {
1328
+ ...prim.stroke,
1329
+ widthPt: Math.max(.25, prim.stroke.widthPt * sx)
1330
+ } } : {}
1331
+ });
1332
+ continue;
1333
+ }
1334
+ if (prim.kind !== "text") continue;
1335
+ const { variant } = options.registry.resolveByStyle(prim.bold === true, prim.italic === true);
1336
+ const font = fontResources.get(variant);
1337
+ if (!font) continue;
1338
+ const sizePt = Math.max(1, prim.sizeLu * sy);
1339
+ const line = makeChartLabelLine(prim.text, font, sizePt, prim.colorHex);
1340
+ const shift = prim.alignH === "center" ? -line.contentWidthPt / 2 : prim.alignH === "right" ? -line.contentWidthPt : 0;
1341
+ const baselineY = mapY(prim.y) - (prim.alignBaseline ? 0 : sizePt * .8);
1342
+ texts.push({
1343
+ line,
1344
+ x: mapX(prim.x) + shift,
1345
+ y: baselineY,
1346
+ ...prim.escapement ? { rotationDeg: prim.escapement / 10 } : {}
1347
+ });
1348
+ }
1349
+ return {
1350
+ shapes,
1351
+ texts
1352
+ };
1353
+ }
1082
1354
  function layoutChartBlock(block, options, fontResources, contentWidth, maxHeight) {
1083
1355
  let widthPt = block.width;
1084
1356
  let heightPt = block.height;
@@ -1346,6 +1618,21 @@ function makeChartLabelLine(text, font, sizePt, colorHex) {
1346
1618
  isLastInParagraph: true
1347
1619
  };
1348
1620
  }
1621
+ /**
1622
+ * §17.2.1 — the page background's picture, with the size a TILE of it is drawn
1623
+ * at: the picture's own pixels read at 96 dpi, which is the resolution Office
1624
+ * measures a picture in.
1625
+ */
1626
+ function backgroundImageOf(options, imageResources) {
1627
+ const id = options.pageBackgroundFill?.imageResource;
1628
+ const res = id === void 0 ? void 0 : imageResources.get(id);
1629
+ if (!res?.resourceName || !res.prepared) return void 0;
1630
+ return {
1631
+ name: res.resourceName,
1632
+ widthPt: res.prepared.widthPx * 72 / 96,
1633
+ heightPt: res.prepared.heightPx * 72 / 96
1634
+ };
1635
+ }
1349
1636
  function collectImageResources(body, headersFooters, options) {
1350
1637
  const out = /* @__PURE__ */ new Map();
1351
1638
  if (!options.resources || options.resources.size === 0) return out;
@@ -1376,6 +1663,15 @@ function collectImageResources(body, headersFooters, options) {
1376
1663
  for (const resourceId of seen) {
1377
1664
  const bytes = options.resources.get(resourceId);
1378
1665
  if (!bytes) continue;
1666
+ if (isEmf(bytes) || isWmf(bytes)) {
1667
+ try {
1668
+ out.set(resourceId, {
1669
+ resourceName: "",
1670
+ metafile: isEmf(bytes) ? readEmf(bytes) : readWmf(bytes)
1671
+ });
1672
+ } catch {}
1673
+ continue;
1674
+ }
1379
1675
  let prepared;
1380
1676
  try {
1381
1677
  prepared = prepareImage(bytes, { flattenAlpha });
@@ -1481,21 +1777,76 @@ function emitShapeText(sh, x, bottomYUp, sink, pageHeight, figId) {
1481
1777
  textY -= sh.textLineGaps?.get(i) ?? 0;
1482
1778
  });
1483
1779
  }
1780
+ /** The page a table is being drawn onto, as a cell's floats need to see it. */
1781
+ function cellAnchorCtx(asm) {
1782
+ return {
1783
+ sink: (behind, zOrder) => PageAssembler.zSink(behind ? asm.floatsBehind : asm.floatsFront, zOrder),
1784
+ frame: asm.floatFrame(),
1785
+ exclude: (f, x, topYUp, widthPt, heightPt) => {
1786
+ asm.excludeFloat(f, x, topYUp, widthPt, heightPt);
1787
+ },
1788
+ pageHeight: asm.ctx.pageHeight
1789
+ };
1790
+ }
1791
+ function emitCellFloat(cf, ctx, frame) {
1792
+ const float = cf.block.float;
1793
+ if (!float) return;
1794
+ const x = floatXIn(frame, float, cf.block.widthPt);
1795
+ const topYUp = floatTopYUpIn(frame, float, cf.block.heightPt);
1796
+ const sink = ctx.sink(float.behind === true, float.zOrder);
1797
+ if (cf.block.kind === "shape") emitShapeItems(cf.block, x, topYUp - cf.block.heightPt, sink, ctx.pageHeight, void 0);
1798
+ else {
1799
+ const ins = cf.block.drawInset;
1800
+ sink.push({
1801
+ type: "image",
1802
+ x: pt(x + (ins?.dxPt ?? 0)),
1803
+ y: pt(ctx.pageHeight - (topYUp - (ins?.dyTopPt ?? 0))),
1804
+ width: pt(ins?.widthPt ?? cf.block.widthPt),
1805
+ height: pt(ins?.heightPt ?? cf.block.heightPt),
1806
+ imageResourceName: cf.block.resourceName,
1807
+ ...cf.block.crop ? { crop: cf.block.crop } : {},
1808
+ ...cf.block.wash ? { wash: cf.block.wash } : {},
1809
+ ...cf.block.rotationDeg ? { rotationDeg: cf.block.rotationDeg } : {},
1810
+ ...cf.block.flipH ? { flipH: true } : {},
1811
+ ...cf.block.flipV ? { flipV: true } : {}
1812
+ });
1813
+ 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));
1814
+ }
1815
+ if (float.wrap !== "none") ctx.exclude(float, x, topYUp, cf.block.widthPt, cf.block.heightPt);
1816
+ }
1484
1817
  function emitShapeItems(sh, x, bottomYUp, sink, pageHeight, figId) {
1485
- if (sh.fillImageResourceName) sink.push({
1486
- type: "image",
1487
- x: pt(x),
1488
- y: pt(pageHeight - (bottomYUp + sh.heightPt)),
1489
- width: pt(sh.widthPt),
1490
- height: pt(sh.heightPt),
1491
- imageResourceName: sh.fillImageResourceName,
1492
- ...sh.fillImageCrop ? { crop: sh.fillImageCrop } : {},
1493
- clip: {
1818
+ if (sh.fillImageResourceName) {
1819
+ const clip = {
1494
1820
  paths: sh.paths,
1495
1821
  transform: flipTransform(buildShapeTransform(x, bottomYUp, sh.widthPt, sh.heightPt, sh.rotation60k, sh.flipH, sh.flipV), pageHeight)
1496
- },
1497
- ...figId !== void 0 ? { structId: figId } : {}
1498
- });
1822
+ };
1823
+ const fig = figId !== void 0 ? { structId: figId } : {};
1824
+ const tile = sh.fillImageTile;
1825
+ if (tile) for (const item of PageAssembler.tileItems(sh.fillImageResourceName, tile, {
1826
+ x,
1827
+ y: pageHeight - (bottomYUp + sh.heightPt),
1828
+ width: sh.widthPt,
1829
+ height: sh.heightPt
1830
+ })) {
1831
+ if (item.type !== "image") continue;
1832
+ sink.push({
1833
+ ...item,
1834
+ clip,
1835
+ ...fig
1836
+ });
1837
+ }
1838
+ else sink.push({
1839
+ type: "image",
1840
+ x: pt(x),
1841
+ y: pt(pageHeight - (bottomYUp + sh.heightPt)),
1842
+ width: pt(sh.widthPt),
1843
+ height: pt(sh.heightPt),
1844
+ imageResourceName: sh.fillImageResourceName,
1845
+ ...sh.fillImageCrop ? { crop: sh.fillImageCrop } : {},
1846
+ clip,
1847
+ ...fig
1848
+ });
1849
+ }
1499
1850
  if (sh.markerPaths && sh.stroke) sink.push({
1500
1851
  type: "shape",
1501
1852
  shape: {
@@ -1511,6 +1862,7 @@ function emitShapeItems(sh, x, bottomYUp, sink, pageHeight, figId) {
1511
1862
  paths: sh.paths,
1512
1863
  ...sh.fillColorHex ? { fillColorHex: sh.fillColorHex } : {},
1513
1864
  ...sh.fillGradient ? { fillGradient: sh.fillGradient } : {},
1865
+ ...sh.fillAlpha !== void 0 ? { fillAlpha: sh.fillAlpha } : {},
1514
1866
  ...sh.stroke ? { stroke: sh.stroke } : {},
1515
1867
  ...sh.shadow ? { shadow: sh.shadow } : {},
1516
1868
  transform: flipTransform(buildShapeTransform(x, bottomYUp, sh.widthPt, sh.heightPt, sh.rotation60k, sh.flipH, sh.flipV), pageHeight)
@@ -1593,6 +1945,15 @@ function drawBlocksSequentially(blocks, startX, startY, pageHeight, contentWidth
1593
1945
  const ordered = blocks.some(isBehind) ? [...blocks.filter(isBehind), ...blocks.filter((b) => !isBehind(b))] : blocks;
1594
1946
  for (const block of ordered) {
1595
1947
  if (block.kind === "table") {
1948
+ const anchored = isOutOfFlowFloat(block.float) ? bandAnchor(block.float, block.totalWidthPt, contentWidth, startX) : void 0;
1949
+ if (anchored !== void 0 && block.float) {
1950
+ let rowY = bandAnchorTop(block.float, cursorY, pageHeight, block.heightPt, bandMargins);
1951
+ for (const row of block.rows) {
1952
+ emitRowChunk(out, row, startX + anchored, rowY, pageHeight, block.colCount);
1953
+ rowY -= row.heightPt;
1954
+ }
1955
+ continue;
1956
+ }
1596
1957
  const tableX = startX + block.xOffsetPt;
1597
1958
  for (const row of block.rows) {
1598
1959
  emitRowChunk(out, row, tableX, cursorY, pageHeight, block.colCount);
@@ -1620,6 +1981,7 @@ function drawBlocksSequentially(blocks, startX, startY, pageHeight, contentWidth
1620
1981
  height: pt(block.heightPt),
1621
1982
  imageResourceName: block.resourceName,
1622
1983
  ...block.crop ? { crop: block.crop } : {},
1984
+ ...block.wash ? { wash: block.wash } : {},
1623
1985
  ...block.rotationDeg ? { rotationDeg: block.rotationDeg } : {},
1624
1986
  ...block.flipH ? { flipH: true } : {},
1625
1987
  ...block.flipV ? { flipV: true } : {}
@@ -1739,7 +2101,7 @@ function runFontKeyAndParsed(options, ascii, bold, italic) {
1739
2101
  function lookupFont(resources, fontKey) {
1740
2102
  return resources.get(fontKey) ?? resources.values().next().value;
1741
2103
  }
1742
- function collectFontResources(body, headersFooters, options) {
2104
+ function collectFontResources(body, headersFooters, options, imageResources) {
1743
2105
  const used = /* @__PURE__ */ new Map();
1744
2106
  const addRun = (run, para) => {
1745
2107
  const resolved = resolveRunProperties(run.properties, para.properties, options.styles);
@@ -1774,30 +2136,48 @@ function collectFontResources(body, headersFooters, options) {
1774
2136
  for (const ch of seg.text) bucket.gids.add(parsed.glyphForCodepoint(ch.codePointAt(0)));
1775
2137
  }
1776
2138
  };
1777
- const visit = (elements) => {
1778
- for (const el of elements) if (el.kind === "paragraph") for (const run of displayCapsRuns(el.paragraph, options)) {
1779
- if (run.math) {
1780
- addMath(run.math);
1781
- continue;
1782
- }
1783
- if (run.inlineImage && !run.text) continue;
1784
- if (run.field !== void 0) {
1785
- addRun({
1786
- text: `${run.text}0123456789`,
1787
- properties: run.properties
1788
- }, el.paragraph);
1789
- continue;
1790
- }
1791
- if (run.footnoteRef !== void 0 || run.endnoteRef !== void 0 || run.noteNumber) {
1792
- addRun({
1793
- text: `${run.text}0123456789 `,
1794
- properties: run.properties
1795
- }, el.paragraph);
1796
- continue;
2139
+ const addMetafile = (resource) => {
2140
+ const pic = resource ? imageResources.get(resource)?.metafile : void 0;
2141
+ for (const prim of pic?.prims ?? []) {
2142
+ if (prim.kind !== "text") continue;
2143
+ const reg = options.registry.resolveByStyle(prim.bold === true, prim.italic === true);
2144
+ let bucket = used.get(reg.variant);
2145
+ if (!bucket) {
2146
+ bucket = {
2147
+ parsed: reg.parsed,
2148
+ gids: /* @__PURE__ */ new Set()
2149
+ };
2150
+ used.set(reg.variant, bucket);
1797
2151
  }
1798
- addRun(run, el.paragraph);
2152
+ for (const ch of prim.text) bucket.gids.add(reg.parsed.glyphForCodepoint(ch.codePointAt(0)));
1799
2153
  }
1800
- else if (el.kind === "table") for (const row of el.table.rows) for (const cell of row.cells) {
2154
+ };
2155
+ const visit = (elements) => {
2156
+ for (const el of elements) if (el.kind === "paragraph") {
2157
+ for (const run of el.paragraph.runs) addMetafile(run.inlineImage?.resource);
2158
+ for (const run of displayCapsRuns(el.paragraph, options)) {
2159
+ if (run.math) {
2160
+ addMath(run.math);
2161
+ continue;
2162
+ }
2163
+ if (run.inlineImage && !run.text) continue;
2164
+ if (run.field !== void 0) {
2165
+ addRun({
2166
+ text: `${run.text}0123456789`,
2167
+ properties: run.properties
2168
+ }, el.paragraph);
2169
+ continue;
2170
+ }
2171
+ if (run.footnoteRef !== void 0 || run.endnoteRef !== void 0 || run.noteNumber) {
2172
+ addRun({
2173
+ text: `${run.text}0123456789 `,
2174
+ properties: run.properties
2175
+ }, el.paragraph);
2176
+ continue;
2177
+ }
2178
+ addRun(run, el.paragraph);
2179
+ }
2180
+ } else if (el.kind === "table") for (const row of el.table.rows) for (const cell of row.cells) {
1801
2181
  if (cell.properties.hashOnOverflow === true) for (const inner of cell.content) {
1802
2182
  if (inner.kind !== "paragraph") continue;
1803
2183
  for (const run of inner.paragraph.runs) addRun({
@@ -1813,7 +2193,8 @@ function collectFontResources(body, headersFooters, options) {
1813
2193
  for (const child of sh.children ?? []) shapeText(child.shape);
1814
2194
  };
1815
2195
  shapeText(el.shape);
1816
- } else if (el.kind === "chart") {
2196
+ } else if (el.kind === "image") addMetafile(el.image.resource);
2197
+ else {
1817
2198
  const chart = options.charts?.get(el.chart.chartRelId);
1818
2199
  if (chart) {
1819
2200
  const reg = options.registry.resolveByStyle(false, false);
@@ -2104,8 +2485,10 @@ function tokenizeParagraph(paragraph, options, fontResources, imageResources, co
2104
2485
  imageWidthPt: widthPt,
2105
2486
  imageHeightPt: heightPt,
2106
2487
  imageResourceName: res?.resourceName ?? "",
2488
+ ...res?.metafile ? { imageMetafile: metafileDrawing(res.metafile, drawBox?.widthPt ?? widthPt, drawBox?.heightPt ?? heightPt, options, fontResources) } : {},
2107
2489
  ...run.inlineImage.outline ? { imageOutline: run.inlineImage.outline } : {},
2108
2490
  ...run.inlineImage.shadow ? { imageShadow: run.inlineImage.shadow } : {},
2491
+ ...run.inlineImage.wash ? { imageWash: run.inlineImage.wash } : {},
2109
2492
  ...run.inlineImage.crop ? { imageCrop: run.inlineImage.crop } : {},
2110
2493
  ...run.inlineImage.rotation60k ? { imageRotationDeg: run.inlineImage.rotation60k / 6e4 } : {},
2111
2494
  ...run.inlineImage.flipH ? { imageFlipH: true } : {},
@@ -2189,6 +2572,8 @@ function tokenizePlansLtr(plans) {
2189
2572
  heightPt: plan.imageHeightPt,
2190
2573
  ...plan.imageOutline ? { outline: plan.imageOutline } : {},
2191
2574
  ...plan.imageShadow ? { shadow: plan.imageShadow } : {},
2575
+ ...plan.imageWash ? { wash: plan.imageWash } : {},
2576
+ ...plan.imageMetafile ? { metafile: plan.imageMetafile } : {},
2192
2577
  ...plan.imageCrop ? { crop: plan.imageCrop } : {},
2193
2578
  ...plan.imageRotationDeg ? { rotationDeg: plan.imageRotationDeg } : {},
2194
2579
  ...plan.imageFlipH ? { flipH: true } : {},
@@ -2260,6 +2645,8 @@ function tokenizePlansBidi(plans, realLevels) {
2260
2645
  heightPt: plan.imageHeightPt,
2261
2646
  ...plan.imageOutline ? { outline: plan.imageOutline } : {},
2262
2647
  ...plan.imageShadow ? { shadow: plan.imageShadow } : {},
2648
+ ...plan.imageWash ? { wash: plan.imageWash } : {},
2649
+ ...plan.imageMetafile ? { metafile: plan.imageMetafile } : {},
2263
2650
  ...plan.imageCrop ? { crop: plan.imageCrop } : {},
2264
2651
  ...plan.imageRotationDeg ? { rotationDeg: plan.imageRotationDeg } : {},
2265
2652
  ...plan.imageFlipH ? { flipH: true } : {},
@@ -2872,9 +3259,67 @@ function alignmentOffset(alignment, lineWidth, available) {
2872
3259
  if (alignment === "center") return Math.max(0, (available - lineWidth) / 2);
2873
3260
  return 0;
2874
3261
  }
3262
+ /**
3263
+ * §17.4.85 `w:vMerge` — a vertical merge is ONE cell, so its continuation rows
3264
+ * are painted with the START cell's shading. Word writes the fill on the cell
3265
+ * that opens the merge and nothing on the rest; painting only what is written,
3266
+ * tdf123104.docx's three-row banner came out a staircase of yellow with white
3267
+ * notches where every merged cell continued.
3268
+ */
3269
+ function withMergedShading(table, roles) {
3270
+ const columnOf = table.rows.map((row) => {
3271
+ const at = [];
3272
+ let col = row.properties.gridBefore ?? 0;
3273
+ for (const cell of row.cells) {
3274
+ at.push(col);
3275
+ col += Math.max(1, cell.properties.colSpan ?? 1);
3276
+ }
3277
+ return at;
3278
+ });
3279
+ let changed = false;
3280
+ const rows = table.rows.map((row, r) => {
3281
+ const cells = row.cells.map((cell, c) => {
3282
+ const role = roles[r]?.[c] ?? "standalone";
3283
+ if (role !== "middle" && role !== "end") return cell;
3284
+ if (cell.properties.shading?.colorHex) return cell;
3285
+ const col = columnOf[r]?.[c];
3286
+ if (col === void 0) return cell;
3287
+ for (let up = r - 1; up >= 0; up--) {
3288
+ const idx = columnOf[up]?.indexOf(col) ?? -1;
3289
+ if (idx < 0) break;
3290
+ const above = table.rows[up]?.cells[idx];
3291
+ const aboveRole = roles[up]?.[idx] ?? "standalone";
3292
+ if (!above) break;
3293
+ if (aboveRole === "start") {
3294
+ const shading = above.properties.shading;
3295
+ if (!shading?.colorHex) break;
3296
+ changed = true;
3297
+ return {
3298
+ ...cell,
3299
+ properties: {
3300
+ ...cell.properties,
3301
+ shading
3302
+ }
3303
+ };
3304
+ }
3305
+ if (aboveRole !== "middle") break;
3306
+ }
3307
+ return cell;
3308
+ });
3309
+ return {
3310
+ ...row,
3311
+ cells
3312
+ };
3313
+ });
3314
+ return changed ? {
3315
+ ...table,
3316
+ rows
3317
+ } : table;
3318
+ }
2875
3319
  function layoutTableBlock(table, options, fontResources, imageResources, contentWidth) {
2876
3320
  const columnWidthsPt = computeColumnWidths(table, options, fontResources, contentWidth);
2877
3321
  const mergeRoles = table.rows.map((r) => r.cells.map((c) => c.properties.merge ?? "standalone"));
3322
+ table = withMergedShading(table, mergeRoles);
2878
3323
  const rows = [];
2879
3324
  const colCount = columnWidthsPt.length;
2880
3325
  let aboveBordersByCol = [];
@@ -3158,7 +3603,8 @@ function layoutTableRow(row, rowIdx, rowCount, colCount, tableProps, columnWidth
3158
3603
  cells: cells.map((c) => clipCellToHeight(c, heightPt)),
3159
3604
  columnXOffsets,
3160
3605
  rowIdx,
3161
- rowCount
3606
+ rowCount,
3607
+ ...row.properties.cantSplit ? { cantSplit: true } : {}
3162
3608
  };
3163
3609
  } else if (row.properties.height && row.properties.heightRule === "atLeast") heightPt = Math.max(heightPt, row.properties.height);
3164
3610
  return {
@@ -3166,7 +3612,8 @@ function layoutTableRow(row, rowIdx, rowCount, colCount, tableProps, columnWidth
3166
3612
  cells,
3167
3613
  columnXOffsets,
3168
3614
  rowIdx,
3169
- rowCount
3615
+ rowCount,
3616
+ ...row.properties.cantSplit ? { cantSplit: true } : {}
3170
3617
  };
3171
3618
  }
3172
3619
  /**
@@ -3212,6 +3659,7 @@ function imageRun(image) {
3212
3659
  inlineImage: {
3213
3660
  ...image.resource ? { resource: image.resource } : {},
3214
3661
  ...image.outline ? { outline: image.outline } : {},
3662
+ ...image.wash ? { wash: image.wash } : {},
3215
3663
  width: image.width,
3216
3664
  height: image.height,
3217
3665
  ...image.crop ? { crop: image.crop } : {},
@@ -3235,6 +3683,7 @@ function layoutTableCell(cell, tableProps, widthPt, options, fontResources, imag
3235
3683
  const lines = [];
3236
3684
  const nestedTables = [];
3237
3685
  const cellShapes = [];
3686
+ const cellFloats = [];
3238
3687
  const lineGaps = /* @__PURE__ */ new Map();
3239
3688
  let contentHeightPt = 0;
3240
3689
  let clipped = false;
@@ -3288,7 +3737,14 @@ function layoutTableCell(cell, tableProps, widthPt, options, fontResources, imag
3288
3737
  });
3289
3738
  contentHeightPt += block.heightPt;
3290
3739
  pendingGapPt = block.spacingAfterPt;
3291
- }
3740
+ } else if (el.kind === "image" && isOutOfFlowFloat(el.image.float)) cellFloats.push({
3741
+ block: layoutImageBlock(el.image, imageResources, innerWidth),
3742
+ afterLine: lines.length
3743
+ });
3744
+ else if (el.kind === "shape" && isOutOfFlowFloat(el.shape.float)) cellFloats.push({
3745
+ block: layoutShapeBlock(el.shape, options, fontResources, imageResources, innerWidth),
3746
+ afterLine: lines.length
3747
+ });
3292
3748
  contentHeightPt += pendingGapPt;
3293
3749
  }
3294
3750
  const colEnd = colStart + colSpan - 1;
@@ -3309,6 +3765,7 @@ function layoutTableCell(cell, tableProps, widthPt, options, fontResources, imag
3309
3765
  ...lineGaps.size > 0 ? { lineGaps } : {},
3310
3766
  ...nestedTables.length > 0 ? { nestedTables } : {},
3311
3767
  ...cellShapes.length > 0 ? { shapes: cellShapes } : {},
3768
+ ...cellFloats.length > 0 ? { floats: cellFloats } : {},
3312
3769
  contentHeightPt,
3313
3770
  totalHeightPt,
3314
3771
  ...cell.properties.verticalAlign ? { verticalAlign: cell.properties.verticalAlign } : {},
@@ -3441,12 +3898,12 @@ var PageAssembler = class PageAssembler {
3441
3898
  * `undefined` when there are none.
3442
3899
  * @param bookmarkPositions Out-param: bookmark name → its GoTo destination.
3443
3900
  */
3444
- constructor(sectionCtxs, builder, notes, bookmarkPositions, backgroundImageName) {
3901
+ constructor(sectionCtxs, builder, notes, bookmarkPositions, backgroundImage) {
3445
3902
  this.sectionCtxs = sectionCtxs;
3446
3903
  this.builder = builder;
3447
3904
  this.notes = notes;
3448
3905
  this.bookmarkPositions = bookmarkPositions;
3449
- this.backgroundImageName = backgroundImageName;
3906
+ this.backgroundImage = backgroundImage;
3450
3907
  this.ctx = sectionCtxs[0];
3451
3908
  this.pageStartCtx = this.ctx;
3452
3909
  this.pageNumber = this.ctx.properties.pageNumberStart ?? 1;
@@ -3488,6 +3945,12 @@ var PageAssembler = class PageAssembler {
3488
3945
  /** The current column's width (the section content width when single-column). */
3489
3946
  colWidth = () => this.ctx.columns?.[this.colIdx]?.widthPt ?? this.ctx.contentWidth;
3490
3947
  /**
3948
+ * The measure the section's blocks were BROKEN at — the first column's, since
3949
+ * that is all the block pass can know. A column of another width has to
3950
+ * re-break what lands in it.
3951
+ */
3952
+ builtWidth = () => this.ctx.columns?.[0]?.widthPt ?? this.ctx.contentWidth;
3953
+ /**
3491
3954
  * Whether the current column is already in use.
3492
3955
  *
3493
3956
  * This gates every overflow break, so that a block too tall for an empty page
@@ -3639,44 +4102,36 @@ var PageAssembler = class PageAssembler {
3639
4102
  */
3640
4103
  excludeFloat = (f, x, topYUp, w, h) => {
3641
4104
  const d = f.wrapDist;
4105
+ const wide = f.wrap === "notBeside";
3642
4106
  this.exclusions.push({
3643
- x0: x - (d?.leftPt ?? 0),
3644
- x1: x + w + (d?.rightPt ?? 0),
4107
+ x0: wide ? this.colLeft() : x - (d?.leftPt ?? 0),
4108
+ x1: wide ? this.colLeft() + this.colWidth() : x + w + (d?.rightPt ?? 0),
3645
4109
  topYUp: topYUp + (d?.topPt ?? 0),
3646
- bottomYUp: topYUp - h - (d?.bottomPt ?? 0)
4110
+ bottomYUp: topYUp - h - (d?.bottomPt ?? 0),
4111
+ ...f.wrapSide === "bothSides" ? { wrapBothSides: true } : {}
3647
4112
  });
3648
4113
  };
4114
+ /** The frame a float anchored in the body flow is resolved against. */
4115
+ floatFrame = () => ({
4116
+ pageLeft: 0,
4117
+ pageWidth: this.ctx.pageWidth,
4118
+ pageTopYUp: this.ctx.pageHeight,
4119
+ pageBottomYUp: 0,
4120
+ marginLeft: this.ctx.marginLeft,
4121
+ contentWidth: this.ctx.contentWidth,
4122
+ marginTopYUp: this.ctx.pageHeight - this.ctx.marginTop,
4123
+ marginBottomYUp: this.ctx.marginBottom,
4124
+ columnLeft: this.colLeft(),
4125
+ columnWidth: this.colWidth(),
4126
+ anchorY: this.cursorY
4127
+ });
3649
4128
  /** The float's left edge on the page, resolved from its horizontal anchor. */
3650
- floatX = (f, widthPt) => {
3651
- const h = f.posH;
3652
- if (!h) return this.colLeft();
3653
- const textRight = this.ctx.marginLeft + this.ctx.contentWidth;
3654
- const base = h.relativeFrom === "page" || h.relativeFrom === "leftMargin" ? 0 : h.relativeFrom === "column" ? this.colLeft() : h.relativeFrom === "rightMargin" ? textRight : this.ctx.marginLeft;
3655
- const span = h.relativeFrom === "page" ? this.ctx.pageWidth : h.relativeFrom === "column" ? this.colWidth() : h.relativeFrom === "leftMargin" ? this.ctx.marginLeft : h.relativeFrom === "rightMargin" ? this.ctx.pageWidth - textRight : this.ctx.contentWidth;
3656
- if (h.align === "center") return base + (span - widthPt) / 2;
3657
- if (h.align === "right") return base + span - widthPt;
3658
- return base + (h.offsetPt ?? 0);
3659
- };
4129
+ floatX = (f, widthPt) => floatXIn(this.floatFrame(), f, widthPt);
3660
4130
  /**
3661
4131
  * The drawing's TOP in the y-up cursor frame. Paragraph/line-relative offsets
3662
4132
  * hang off the anchoring paragraph's current position.
3663
4133
  */
3664
- floatTopYUp = (f, heightPt = 0) => {
3665
- const v = f.posV;
3666
- if (!v) return this.cursorY;
3667
- if (v.align !== void 0) {
3668
- const page = v.relativeFrom === "page";
3669
- const topYUp = page ? this.ctx.pageHeight : this.ctx.pageHeight - this.ctx.marginTop;
3670
- const bottomYUp = page ? 0 : this.ctx.marginBottom;
3671
- if (v.align === "top") return topYUp;
3672
- if (v.align === "bottom") return bottomYUp + heightPt;
3673
- return bottomYUp + (topYUp - bottomYUp + heightPt) / 2;
3674
- }
3675
- if (v.relativeFrom === "page" || v.relativeFrom === "topMargin") return this.ctx.pageHeight - (v.offsetPt ?? 0);
3676
- if (v.relativeFrom === "bottomMargin") return this.ctx.marginBottom - (v.offsetPt ?? 0);
3677
- if (v.relativeFrom === "margin") return this.ctx.pageHeight - this.ctx.marginTop - (v.offsetPt ?? 0);
3678
- return this.cursorY - (v.offsetPt ?? 0);
3679
- };
4134
+ floatTopYUp = (f, heightPt = 0) => floatTopYUpIn(this.floatFrame(), f, heightPt);
3680
4135
  /**
3681
4136
  * Tagged PDF: the stack of open list levels (for L/LI/LBody nesting). Cleared
3682
4137
  * whenever a non-list-item block interrupts the run of list paragraphs.
@@ -3739,6 +4194,14 @@ var PageAssembler = class PageAssembler {
3739
4194
  xOffset: 0,
3740
4195
  blockedUntilYUp: e.bottomYUp
3741
4196
  };
4197
+ if (leftRoom >= MIN_WRAP_WIDTH && rightRoom >= MIN_WRAP_WIDTH && e.wrapBothSides) return {
4198
+ width: leftRoom,
4199
+ xOffset: 0,
4200
+ pair: {
4201
+ width: rightRoom,
4202
+ xOffset: full - rightRoom
4203
+ }
4204
+ };
3742
4205
  if (rightRoom >= leftRoom) return {
3743
4206
  width: rightRoom,
3744
4207
  xOffset: full - rightRoom
@@ -3770,6 +4233,7 @@ var PageAssembler = class PageAssembler {
3770
4233
  continue;
3771
4234
  }
3772
4235
  widths.push(g.width);
4236
+ if (g.pair) widths.push(g.pair.width);
3773
4237
  if (g.width < this.colWidth()) narrowed = true;
3774
4238
  else if (narrowed) break;
3775
4239
  y -= h0;
@@ -3947,19 +4411,49 @@ var PageAssembler = class PageAssembler {
3947
4411
  *
3948
4412
  * @returns The background items for this page (at most one).
3949
4413
  */
4414
+ /**
4415
+ * §14.1.2.5 `@type="tile"` — the picture repeated over a box at its own size,
4416
+ * as image items. A tile is a texture, so its natural size is what makes it
4417
+ * one: stretched over the page it is a blur, and its 128 pixels are 96 points
4418
+ * at the resolution Office measures a picture in.
4419
+ */
4420
+ static tileItems = (resourceName, natural, box) => {
4421
+ const tw = Math.max(1, natural.widthPt);
4422
+ const th = Math.max(1, natural.heightPt);
4423
+ const cols = Math.min(200, Math.ceil(box.width / tw));
4424
+ const rows = Math.min(200, Math.ceil(box.height / th));
4425
+ const out = [];
4426
+ for (let r = 0; r < rows; r++) for (let c = 0; c < cols; c++) out.push({
4427
+ type: "image",
4428
+ x: pt(box.x + c * tw),
4429
+ y: pt(box.y + r * th),
4430
+ width: pt(tw),
4431
+ height: pt(th),
4432
+ imageResourceName: resourceName
4433
+ });
4434
+ return out;
4435
+ };
3950
4436
  pageBackgroundItems = () => {
3951
4437
  const options = this.ctx.options;
3952
4438
  const w = this.ctx.pageWidth;
3953
4439
  const h = this.ctx.pageHeight;
3954
4440
  const fill = options?.pageBackgroundFill;
3955
- if (fill?.kind === "picture" && this.backgroundImageName !== void 0) return [{
3956
- type: "image",
3957
- x: pt(0),
3958
- y: pt(0),
3959
- width: pt(w),
3960
- height: pt(h),
3961
- imageResourceName: this.backgroundImageName
3962
- }];
4441
+ if (fill?.kind === "picture" && this.backgroundImage !== void 0) {
4442
+ const bg = this.backgroundImage;
4443
+ return fill.tiled ? PageAssembler.tileItems(bg.name, bg, {
4444
+ x: 0,
4445
+ y: 0,
4446
+ width: w,
4447
+ height: h
4448
+ }) : [{
4449
+ type: "image",
4450
+ x: pt(0),
4451
+ y: pt(0),
4452
+ width: pt(w),
4453
+ height: pt(h),
4454
+ imageResourceName: bg.name
4455
+ }];
4456
+ }
3963
4457
  if (fill?.kind === "gradient" && fill.gradient) return [{
3964
4458
  type: "shape",
3965
4459
  shape: {
@@ -4043,9 +4537,9 @@ var PageAssembler = class PageAssembler {
4043
4537
  this.balanceHeightPt = 0;
4044
4538
  };
4045
4539
  };
4046
- function paginateSections(blocks, sectionCtxs, builder, defaultLang = "en-US", notes, bookmarkPositions, reflowParagraph, backgroundImageName) {
4540
+ function paginateSections(blocks, sectionCtxs, builder, defaultLang = "en-US", notes, bookmarkPositions, reflowParagraph, backgroundImage) {
4047
4541
  if (sectionCtxs.length === 0) return [];
4048
- const asm = new PageAssembler(sectionCtxs, builder, notes, bookmarkPositions, backgroundImageName);
4542
+ const asm = new PageAssembler(sectionCtxs, builder, notes, bookmarkPositions, backgroundImage);
4049
4543
  const balanceIfEndsContinuous = (fromBlock) => {
4050
4544
  const here = asm.ctx;
4051
4545
  const next = sectionCtxs[asm.secIdx + 1];
@@ -4083,9 +4577,11 @@ function paginateSections(blocks, sectionCtxs, builder, defaultLang = "en-US", n
4083
4577
  if ((block.resolved.pageBreakBefore || block.pageBreakBefore) && asm.pageHasContent()) asm.flushPage();
4084
4578
  asm.cursorY -= block.spacingBeforePt;
4085
4579
  let pb = block;
4580
+ const colW = asm.colWidth();
4581
+ if (reflowParagraph && pb.source && Math.abs(colW - asm.builtWidth()) > .5) pb = reflowParagraph(pb.source, colW);
4086
4582
  if (reflowParagraph && pb.source && asm.exclusions.length > 0) {
4087
- const widths = asm.lineWidthsFor(block, asm.cursorY);
4088
- if (widths) pb = reflowParagraph(pb.source, asm.colWidth(), widths);
4583
+ const widths = asm.lineWidthsFor(pb, asm.cursorY);
4584
+ if (widths) pb = reflowParagraph(pb.source, colW, widths);
4089
4585
  }
4090
4586
  const borderTopY = asm.cursorY;
4091
4587
  const borderPage = asm.pages.length;
@@ -4103,6 +4599,7 @@ function paginateSections(blocks, sectionCtxs, builder, defaultLang = "en-US", n
4103
4599
  if (lang && lang !== defaultLang) builder.node(structId).lang = lang;
4104
4600
  }
4105
4601
  let firstLineOfBlock = true;
4602
+ let pairPending;
4106
4603
  for (const [lineIdx, line] of pb.lines.entries()) {
4107
4604
  if (pb.columnBreakLines?.has(lineIdx) && asm.colHasContent()) asm.advanceColumn();
4108
4605
  const h = computeLineHeight(line, pb.resolved);
@@ -4132,16 +4629,22 @@ function paginateSections(blocks, sectionCtxs, builder, defaultLang = "en-US", n
4132
4629
  });
4133
4630
  }
4134
4631
  }
4135
- for (let guard = 0; guard < 8 && asm.exclusions.length > 0; guard++) {
4136
- const blocked = asm.lineGeometryAt(asm.cursorY, h).blockedUntilYUp;
4137
- if (blocked === void 0 || blocked >= asm.cursorY) break;
4138
- asm.cursorY = blocked;
4632
+ const pairedNow = pairPending;
4633
+ pairPending = void 0;
4634
+ if (pairedNow === void 0) {
4635
+ for (let guard = 0; guard < 8 && asm.exclusions.length > 0; guard++) {
4636
+ const blocked = asm.lineGeometryAt(asm.cursorY, h).blockedUntilYUp;
4637
+ if (blocked === void 0 || blocked >= asm.cursorY) break;
4638
+ asm.cursorY = blocked;
4639
+ }
4640
+ asm.cursorY -= h;
4139
4641
  }
4140
- asm.cursorY -= h;
4141
4642
  const indentLeft = pb.resolved.indentLeft + (line.firstLine ? pb.resolved.indentFirstLine : 0);
4142
4643
  const offset = alignmentOffset(pb.resolved.alignment, line.contentWidthPt, line.availableWidthPt);
4143
4644
  const baselineY = pt(asm.ctx.pageHeight - (asm.cursorY + lineBaselineOffset(line, pb.resolved)));
4144
- const exclusionShift = asm.exclusions.length > 0 ? asm.lineGeometryAt(asm.cursorY + h, h).xOffset : 0;
4645
+ const geometry = asm.exclusions.length > 0 ? asm.lineGeometryAt(asm.cursorY + h, h) : void 0;
4646
+ const exclusionShift = pairedNow ?? geometry?.xOffset ?? 0;
4647
+ if (pairedNow === void 0 && geometry?.pair && Math.abs(line.availableWidthPt - geometry.width) < 1) pairPending = geometry.pair.xOffset;
4145
4648
  const originX = asm.colLeft() + indentLeft + offset + exclusionShift;
4146
4649
  if (markerLblId !== void 0 && line === firstLine) {
4147
4650
  let k = 0;
@@ -4197,6 +4700,7 @@ function paginateSections(blocks, sectionCtxs, builder, defaultLang = "en-US", n
4197
4700
  height: pt(ins?.heightPt ?? block.heightPt),
4198
4701
  imageResourceName: block.resourceName,
4199
4702
  ...block.crop ? { crop: block.crop } : {},
4703
+ ...block.wash ? { wash: block.wash } : {},
4200
4704
  ...block.rotationDeg ? { rotationDeg: block.rotationDeg } : {},
4201
4705
  ...block.flipH ? { flipH: true } : {},
4202
4706
  ...block.flipV ? { flipV: true } : {},
@@ -4236,7 +4740,7 @@ function paginateSections(blocks, sectionCtxs, builder, defaultLang = "en-US", n
4236
4740
  asm.cursorY -= block.spacingAfterPt;
4237
4741
  }
4238
4742
  } else if (block.kind === "chart") {
4239
- const figId = builder ? createFigure(builder, block.altText, "Chart") : void 0;
4743
+ const figId = builder ? createFigure(builder, block.altText, block.figureRole ?? "Chart") : void 0;
4240
4744
  const emitChartAt = (x, bottomYUp, sink) => {
4241
4745
  sink.push(...chartPageItems(block, x, bottomYUp, asm.ctx.pageHeight, figId));
4242
4746
  };
@@ -4262,7 +4766,7 @@ function paginateSections(blocks, sectionCtxs, builder, defaultLang = "en-US", n
4262
4766
  const sink = PageAssembler.zSink(block.float.behind ? asm.floatsBehind : asm.floatsFront, block.float.zOrder);
4263
4767
  let rowY = fy;
4264
4768
  for (const row of block.rows) {
4265
- emitRowChunk(sink, row, fx, rowY, asm.ctx.pageHeight, colCount);
4769
+ emitRowChunk(sink, row, fx, rowY, asm.ctx.pageHeight, colCount, void 0, void 0, cellAnchorCtx(asm));
4266
4770
  rowY -= row.heightPt;
4267
4771
  }
4268
4772
  if (block.float.wrap !== "none") asm.excludeFloat(block.float, fx, fy, block.totalWidthPt, block.heightPt);
@@ -4291,7 +4795,9 @@ function paginateSections(blocks, sectionCtxs, builder, defaultLang = "en-US", n
4291
4795
  return b.create("P", cellNode).id;
4292
4796
  });
4293
4797
  }
4294
- const chunks = row.heightPt > asm.ctx.pageContentHeight ? splitRowIntoChunks(row, asm.ctx.pageContentHeight) : [row];
4798
+ const room = asm.cursorY - asm.bottomLimit();
4799
+ const page = asm.ctx.pageContentHeight;
4800
+ const chunks = !row.cantSplit && row.heightPt > room && asm.colHasContent() && room > 0 ? splitRowIntoChunks(row, room, page) : row.heightPt > page ? splitRowIntoChunks(row, page) : [row];
4295
4801
  for (let ci = 0; ci < chunks.length; ci++) {
4296
4802
  const chunk = chunks[ci];
4297
4803
  const forcedBreak = ci === 0 && row.breakBefore && !isLeadingHeader && asm.current.length > 0;
@@ -4305,7 +4811,7 @@ function paginateSections(blocks, sectionCtxs, builder, defaultLang = "en-US", n
4305
4811
  }
4306
4812
  }
4307
4813
  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;
4308
- emitRowChunk(asm.current, chunk, tableX, asm.cursorY, asm.ctx.pageHeight, colCount, cellStructIds, mergedHeights);
4814
+ emitRowChunk(asm.current, chunk, tableX, asm.cursorY, asm.ctx.pageHeight, colCount, cellStructIds, mergedHeights, cellAnchorCtx(asm));
4309
4815
  asm.cursorY -= chunk.heightPt;
4310
4816
  if (ci === chunks.length - 1 && ri < block.rows.length - 1) {
4311
4817
  const next = block.rows[ri + 1];
@@ -4374,7 +4880,7 @@ function tableCellRowSpan(rows, startRowIdx, colStart) {
4374
4880
  }
4375
4881
  return span;
4376
4882
  }
4377
- function emitRowChunk(out, row, marginLeft, cursorY, pageHeight, colCount, cellStructIds, mergedHeights) {
4883
+ function emitRowChunk(out, row, marginLeft, cursorY, pageHeight, colCount, cellStructIds, mergedHeights, anchored) {
4378
4884
  const rowTop = cursorY;
4379
4885
  const rowBottom = cursorY - row.heightPt;
4380
4886
  {
@@ -4553,7 +5059,7 @@ function emitRowChunk(out, row, marginLeft, cursorY, pageHeight, colCount, cellS
4553
5059
  if (nt.afterLine !== n) continue;
4554
5060
  for (const nrow of nt.block.rows) {
4555
5061
  const nestedIds = structId !== void 0 ? nrow.cells.map(() => structId) : void 0;
4556
- emitRowChunk(out, nrow, nestedX, textY, pageHeight, nt.block.colCount, nestedIds);
5062
+ emitRowChunk(out, nrow, nestedX, textY, pageHeight, nt.block.colCount, nestedIds, void 0, anchored);
4557
5063
  textY -= nrow.heightPt;
4558
5064
  }
4559
5065
  }
@@ -4563,6 +5069,29 @@ function emitRowChunk(out, row, marginLeft, cursorY, pageHeight, colCount, cellS
4563
5069
  textY -= cs.block.heightPt;
4564
5070
  emitShapeItems(cs.block, nestedX + offset, textY, out, pageHeight, structId);
4565
5071
  }
5072
+ for (const cf of cell.floats ?? []) {
5073
+ if (cf.afterLine !== n || !anchored) continue;
5074
+ const inner = cell.widthPt - cell.padLeftPt - cell.padRightPt;
5075
+ const cellTop = rowTop - cell.padTopPt;
5076
+ const cellBottom = rowTop - (mergedHeights?.[i] ?? row.heightPt) + cell.padBottomPt;
5077
+ const own = cf.block.float?.inCell !== false;
5078
+ emitCellFloat(cf, anchored, {
5079
+ ...anchored.frame,
5080
+ ...own ? {
5081
+ pageLeft: nestedX,
5082
+ pageWidth: inner,
5083
+ pageTopYUp: cellTop,
5084
+ pageBottomYUp: cellBottom,
5085
+ marginLeft: nestedX,
5086
+ contentWidth: inner,
5087
+ marginTopYUp: cellTop,
5088
+ marginBottomYUp: cellBottom
5089
+ } : {},
5090
+ columnLeft: nestedX,
5091
+ columnWidth: inner,
5092
+ anchorY: textY
5093
+ });
5094
+ }
4566
5095
  };
4567
5096
  for (const [lineIdx, line] of cell.lines.entries()) {
4568
5097
  drawNestedBefore(lineIdx);
@@ -4588,10 +5117,13 @@ function emitRowChunk(out, row, marginLeft, cursorY, pageHeight, colCount, cellS
4588
5117
  drawNestedBefore(cell.lines.length);
4589
5118
  }
4590
5119
  }
4591
- function splitRowIntoChunks(row, capacity) {
5120
+ function splitRowIntoChunks(row, capacity, restCapacity = capacity) {
4592
5121
  if (row.cells.some((c) => c.nestedTables && c.nestedTables.length > 0)) return [row];
4593
5122
  const queues = row.cells.map((c) => ({
4594
- remaining: [...c.lines],
5123
+ remaining: c.lines.map((line, i) => ({
5124
+ line,
5125
+ gapPt: c.lineGaps?.get(i) ?? 0
5126
+ })),
4595
5127
  template: c
4596
5128
  }));
4597
5129
  const anyHasLines = () => queues.some((q) => q.template.mergeRole === "middle" || q.template.mergeRole === "end" ? false : q.remaining.length > 0);
@@ -4609,23 +5141,31 @@ function splitRowIntoChunks(row, capacity) {
4609
5141
  continue;
4610
5142
  }
4611
5143
  const padTop = isFirst ? tpl.padTopPt : 0;
4612
- const capacityForLines = Math.max(0, capacity - padTop);
5144
+ const capacityForLines = Math.max(0, (isFirst ? capacity : restCapacity) - padTop);
4613
5145
  const taken = [];
4614
5146
  let takenHeight = 0;
4615
5147
  while (q.remaining.length > 0) {
4616
5148
  const next = q.remaining[0];
4617
- const lh = computeLineHeight(next, next.resolved);
5149
+ const lh = computeLineHeight(next.line, next.line.resolved) + next.gapPt;
4618
5150
  if (taken.length > 0 && takenHeight + lh > capacityForLines) break;
4619
5151
  taken.push(next);
4620
5152
  takenHeight += lh;
4621
5153
  q.remaining.shift();
4622
5154
  }
4623
5155
  const cellHeight = padTop + takenHeight;
5156
+ const gaps = /* @__PURE__ */ new Map();
5157
+ taken.forEach((t, li) => {
5158
+ if (t.gapPt > 0) gaps.set(li, t.gapPt);
5159
+ });
5160
+ const { lineGaps: _g, shapes: _s, floats: _f, ...restOfCell } = tpl;
4624
5161
  const chunkCell = {
4625
- ...tpl,
5162
+ ...restOfCell,
5163
+ ...isFirst && tpl.shapes ? { shapes: tpl.shapes } : {},
5164
+ ...isFirst && tpl.floats ? { floats: tpl.floats } : {},
5165
+ ...gaps.size > 0 ? { lineGaps: gaps } : {},
4626
5166
  padTopPt: padTop,
4627
5167
  padBottomPt: 0,
4628
- lines: taken,
5168
+ lines: taken.map((t) => t.line),
4629
5169
  contentHeightPt: takenHeight,
4630
5170
  totalHeightPt: cellHeight
4631
5171
  };
@@ -4664,7 +5204,8 @@ function splitRowIntoChunks(row, capacity) {
4664
5204
  cells: chunkCells,
4665
5205
  columnXOffsets: row.columnXOffsets,
4666
5206
  rowIdx: row.rowIdx,
4667
- rowCount: row.rowCount
5207
+ rowCount: row.rowCount,
5208
+ ...row.isHeader ? { isHeader: true } : {}
4668
5209
  });
4669
5210
  isFirst = false;
4670
5211
  }