docxodus 9.8.0 → 9.9.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 (89) hide show
  1. package/dist/editor.bundle.js +164 -23
  2. package/dist/embed.bundle.js +164 -23
  3. package/dist/embed.iife.js +164 -23
  4. package/dist/pagination.bundle.js +163 -23
  5. package/dist/pagination.d.ts +28 -0
  6. package/dist/pagination.d.ts.map +1 -1
  7. package/dist/pagination.js +155 -27
  8. package/dist/pagination.js.map +1 -1
  9. package/dist/ribbon-chrome.d.ts +1 -1
  10. package/dist/ribbon-chrome.d.ts.map +1 -1
  11. package/dist/ribbon-chrome.js +1 -0
  12. package/dist/ribbon-chrome.js.map +1 -1
  13. package/dist/wasm/_framework/Docxodus.wasm +0 -0
  14. package/dist/wasm/_framework/Docxodus.wasm.br +0 -0
  15. package/dist/wasm/_framework/DocxodusWasm.wasm +0 -0
  16. package/dist/wasm/_framework/DocxodusWasm.wasm.br +0 -0
  17. package/dist/wasm/_framework/System.Collections.Concurrent.wasm +0 -0
  18. package/dist/wasm/_framework/System.Collections.Concurrent.wasm.br +0 -0
  19. package/dist/wasm/_framework/System.Collections.Immutable.wasm +0 -0
  20. package/dist/wasm/_framework/System.Collections.Immutable.wasm.br +0 -0
  21. package/dist/wasm/_framework/System.Collections.NonGeneric.wasm +0 -0
  22. package/dist/wasm/_framework/System.Collections.NonGeneric.wasm.br +0 -0
  23. package/dist/wasm/_framework/System.Collections.Specialized.wasm +0 -0
  24. package/dist/wasm/_framework/System.Collections.Specialized.wasm.br +0 -0
  25. package/dist/wasm/_framework/System.Collections.wasm +0 -0
  26. package/dist/wasm/_framework/System.Collections.wasm.br +0 -0
  27. package/dist/wasm/_framework/System.ComponentModel.Primitives.wasm +0 -0
  28. package/dist/wasm/_framework/System.ComponentModel.Primitives.wasm.br +0 -0
  29. package/dist/wasm/_framework/System.ComponentModel.TypeConverter.wasm +0 -0
  30. package/dist/wasm/_framework/System.ComponentModel.TypeConverter.wasm.br +0 -0
  31. package/dist/wasm/_framework/System.ComponentModel.wasm +0 -0
  32. package/dist/wasm/_framework/System.ComponentModel.wasm.br +0 -0
  33. package/dist/wasm/_framework/System.Console.wasm +0 -0
  34. package/dist/wasm/_framework/System.Console.wasm.br +0 -0
  35. package/dist/wasm/_framework/System.Diagnostics.Process.wasm +0 -0
  36. package/dist/wasm/_framework/System.Diagnostics.Process.wasm.br +0 -0
  37. package/dist/wasm/_framework/System.IO.Compression.wasm +0 -0
  38. package/dist/wasm/_framework/System.IO.Compression.wasm.br +0 -0
  39. package/dist/wasm/_framework/System.IO.Pipelines.wasm +0 -0
  40. package/dist/wasm/_framework/System.IO.Pipelines.wasm.br +0 -0
  41. package/dist/wasm/_framework/System.Linq.Expressions.wasm +0 -0
  42. package/dist/wasm/_framework/System.Linq.Expressions.wasm.br +0 -0
  43. package/dist/wasm/_framework/System.Linq.wasm +0 -0
  44. package/dist/wasm/_framework/System.Linq.wasm.br +0 -0
  45. package/dist/wasm/_framework/System.Memory.wasm +0 -0
  46. package/dist/wasm/_framework/System.Memory.wasm.br +0 -0
  47. package/dist/wasm/_framework/System.Net.Http.wasm +0 -0
  48. package/dist/wasm/_framework/System.Net.Http.wasm.br +0 -0
  49. package/dist/wasm/_framework/System.Net.Primitives.wasm +0 -0
  50. package/dist/wasm/_framework/System.Net.Primitives.wasm.br +0 -0
  51. package/dist/wasm/_framework/System.ObjectModel.wasm +0 -0
  52. package/dist/wasm/_framework/System.ObjectModel.wasm.br +0 -0
  53. package/dist/wasm/_framework/System.Private.CoreLib.wasm +0 -0
  54. package/dist/wasm/_framework/System.Private.CoreLib.wasm.br +0 -0
  55. package/dist/wasm/_framework/System.Private.Uri.wasm +0 -0
  56. package/dist/wasm/_framework/System.Private.Uri.wasm.br +0 -0
  57. package/dist/wasm/_framework/System.Private.Xml.Linq.wasm +0 -0
  58. package/dist/wasm/_framework/System.Private.Xml.Linq.wasm.br +0 -0
  59. package/dist/wasm/_framework/System.Private.Xml.wasm +0 -0
  60. package/dist/wasm/_framework/System.Private.Xml.wasm.br +0 -0
  61. package/dist/wasm/_framework/System.Runtime.InteropServices.JavaScript.wasm +0 -0
  62. package/dist/wasm/_framework/System.Runtime.InteropServices.JavaScript.wasm.br +0 -0
  63. package/dist/wasm/_framework/System.Runtime.wasm +0 -0
  64. package/dist/wasm/_framework/System.Runtime.wasm.br +0 -0
  65. package/dist/wasm/_framework/System.Security.Cryptography.wasm +0 -0
  66. package/dist/wasm/_framework/System.Security.Cryptography.wasm.br +0 -0
  67. package/dist/wasm/_framework/System.Text.Encodings.Web.wasm +0 -0
  68. package/dist/wasm/_framework/System.Text.Encodings.Web.wasm.br +0 -0
  69. package/dist/wasm/_framework/System.Text.Json.wasm +0 -0
  70. package/dist/wasm/_framework/System.Text.Json.wasm.br +0 -0
  71. package/dist/wasm/_framework/System.Text.RegularExpressions.wasm +0 -0
  72. package/dist/wasm/_framework/System.Text.RegularExpressions.wasm.br +0 -0
  73. package/dist/wasm/_framework/System.Xml.Linq.wasm +0 -0
  74. package/dist/wasm/_framework/System.Xml.Linq.wasm.br +0 -0
  75. package/dist/wasm/_framework/System.Xml.XDocument.wasm +0 -0
  76. package/dist/wasm/_framework/System.Xml.XDocument.wasm.br +0 -0
  77. package/dist/wasm/_framework/System.wasm +0 -0
  78. package/dist/wasm/_framework/System.wasm.br +0 -0
  79. package/dist/wasm/_framework/dotnet.boot.js +35 -35
  80. package/dist/wasm/_framework/dotnet.boot.js.br +0 -0
  81. package/dist/wasm/_framework/dotnet.js +1 -1
  82. package/dist/wasm/_framework/dotnet.js.br +0 -0
  83. package/dist/wasm/_framework/dotnet.native.js +3 -3
  84. package/dist/wasm/_framework/dotnet.native.js.br +0 -0
  85. package/dist/wasm/_framework/dotnet.native.wasm +0 -0
  86. package/dist/wasm/_framework/dotnet.native.wasm.br +0 -0
  87. package/dist/wasm/_framework/dotnet.runtime.js +1 -1
  88. package/dist/wasm/_framework/dotnet.runtime.js.br +0 -0
  89. package/package.json +5 -4
@@ -259,6 +259,9 @@ var DocxodusEditor = (() => {
259
259
  }
260
260
 
261
261
  // src/pagination.ts
262
+ function samePageBox(a, b) {
263
+ return a.pageWidth === b.pageWidth && a.pageHeight === b.pageHeight && a.marginTop === b.marginTop && a.marginRight === b.marginRight && a.marginBottom === b.marginBottom && a.marginLeft === b.marginLeft;
264
+ }
262
265
  var MAX_FOOTNOTE_AREA_RATIO = 0.6;
263
266
  var MIN_BODY_CONTENT_HEIGHT = 72;
264
267
  var PaginationEngine = class {
@@ -304,16 +307,43 @@ var DocxodusEditor = (() => {
304
307
  );
305
308
  this.pageNumbering = this.parsePageNumbering(sections);
306
309
  const sectionsToProcess = sections.length > 0 ? Array.from(sections) : [this.stagingElement];
310
+ const runs = [];
307
311
  for (const section of sectionsToProcess) {
308
- const sectionIndex = parseInt(section.dataset.sectionIndex || "0", 10);
309
312
  const dims = parseSectionDimensions(section);
313
+ const previous = runs[runs.length - 1];
314
+ if (previous && section.dataset.sectionType === "continuous" && samePageBox(previous.dims, dims)) {
315
+ previous.sections.push(section);
316
+ } else {
317
+ runs.push({
318
+ sections: [section],
319
+ sectionIndex: parseInt(section.dataset.sectionIndex || "0", 10),
320
+ dims
321
+ });
322
+ }
323
+ }
324
+ for (const run of runs) {
310
325
  this.stagingElement.style.visibility = "hidden";
311
326
  this.stagingElement.style.position = "absolute";
312
327
  this.stagingElement.style.left = "-9999px";
313
328
  this.stagingElement.style.display = "block";
314
- section.style.width = `${dims.contentWidth}pt`;
315
- const blocks = this.measureBlocks(section, dims);
316
- const sectionPages = this.flowToPages(blocks, dims, pageNumber, sectionIndex);
329
+ const blocks = [];
330
+ for (const section of run.sections) {
331
+ section.style.width = `${run.dims.contentWidth}pt`;
332
+ const columnCount = parseInt(section.dataset.cols || "1", 10);
333
+ if (columnCount > 1) {
334
+ const gap = parseFloat(section.dataset.colGap || "");
335
+ blocks.push(...this.buildColumnBlocks(
336
+ section,
337
+ run.dims,
338
+ run.sectionIndex,
339
+ columnCount,
340
+ Number.isFinite(gap) ? gap : 36
341
+ ));
342
+ } else {
343
+ blocks.push(...this.measureBlocks(section, run.dims));
344
+ }
345
+ }
346
+ const sectionPages = this.flowToPages(blocks, run.dims, pageNumber, run.sectionIndex);
317
347
  pages.push(...sectionPages);
318
348
  pageNumber += sectionPages.length;
319
349
  }
@@ -402,11 +432,70 @@ var DocxodusEditor = (() => {
402
432
  keepWithNext: child.dataset.keepWithNext === "true",
403
433
  keepLines: child.dataset.keepLines === "true",
404
434
  pageBreakBefore: child.dataset.pageBreakBefore === "true",
405
- isPageBreak
435
+ isPageBreak,
436
+ isWordParagraph: this.isWordParagraphElement(child)
406
437
  });
407
438
  }
408
439
  return blocks;
409
440
  }
441
+ /**
442
+ * Flows a multi-column (`w:cols`) section's children into CSS-multicol container
443
+ * blocks. Word lays such a section out as N columns inside the same body extent;
444
+ * a balanced `column-count` container reproduces that geometry, and the paginator
445
+ * then places each container as one ordinary measured block. A container grows
446
+ * greedily until its balanced height would exceed the smallest page body available
447
+ * to the section, so a long columned section still splits across pages at block
448
+ * boundaries. Each child lands in exactly one container, so anchors never
449
+ * duplicate. An explicit page break child passes through as its own block, which
450
+ * ends the current container and lets the normal flow logic turn the page.
451
+ */
452
+ buildColumnBlocks(section, dims, sectionIndex, columnCount, columnGapPt) {
453
+ const children = Array.from(section.children);
454
+ const blocks = [];
455
+ const maxFragmentHeight = this.smallestEffectiveContentHeight(dims, sectionIndex);
456
+ const isBreak = (child) => child.dataset.pageBreak === "true" || child.classList.contains(`${this.cssPrefix}break`);
457
+ const makeContainer = (slice) => {
458
+ const container = document.createElement("div");
459
+ container.style.columnCount = String(columnCount);
460
+ container.style.columnGap = `${columnGapPt}pt`;
461
+ for (const child of slice) {
462
+ container.appendChild(child.cloneNode(true));
463
+ }
464
+ return container;
465
+ };
466
+ let start2 = 0;
467
+ while (start2 < children.length) {
468
+ if (isBreak(children[start2])) {
469
+ blocks.push(this.measureElement(children[start2], dims));
470
+ start2++;
471
+ continue;
472
+ }
473
+ let end = start2 + 1;
474
+ let container = makeContainer(children.slice(start2, end));
475
+ let measured = this.measureElement(container, dims);
476
+ while (end < children.length && !isBreak(children[end])) {
477
+ const candidate = makeContainer(children.slice(start2, end + 1));
478
+ const candidateMeasured = this.measureElement(candidate, dims);
479
+ if (candidateMeasured.heightPt > maxFragmentHeight) break;
480
+ container = candidate;
481
+ measured = candidateMeasured;
482
+ end++;
483
+ }
484
+ blocks.push({
485
+ element: container,
486
+ heightPt: measured.heightPt,
487
+ marginTopPt: measured.marginTopPt,
488
+ marginBottomPt: measured.marginBottomPt,
489
+ keepWithNext: false,
490
+ keepLines: false,
491
+ pageBreakBefore: false,
492
+ isPageBreak: false,
493
+ isWordParagraph: false
494
+ });
495
+ start2 = end;
496
+ }
497
+ return blocks;
498
+ }
410
499
  /**
411
500
  * Measures one element in the same hidden staging context used for the source blocks.
412
501
  * This is intentionally DOM-based: table row heights cannot be inferred from individual
@@ -431,7 +520,8 @@ var DocxodusEditor = (() => {
431
520
  keepWithNext: element.dataset.keepWithNext === "true",
432
521
  keepLines: element.dataset.keepLines === "true",
433
522
  pageBreakBefore: element.dataset.pageBreakBefore === "true",
434
- isPageBreak: element.dataset.pageBreak === "true" || element.classList.contains(`${this.cssPrefix}break`)
523
+ isPageBreak: element.dataset.pageBreak === "true" || element.classList.contains(`${this.cssPrefix}break`),
524
+ isWordParagraph: this.isWordParagraphElement(element)
435
525
  };
436
526
  this.stagingElement.removeChild(measurementHost);
437
527
  return measured;
@@ -463,10 +553,15 @@ var DocxodusEditor = (() => {
463
553
  * collapsed-margin rules as normal block placement. The trailing margin is
464
554
  * intentionally excluded, matching the individual block fit check.
465
555
  */
466
- measureKeepWithNextChainBodyHeight(chain, previousMarginBottomPt, isFirstOnPage) {
556
+ measureKeepWithNextChainBodyHeight(chain, previousMarginBottomPt, isFirstOnPage, pageInSection) {
467
557
  const firstBlock = chain[0];
468
558
  if (!firstBlock) return 0;
469
- const firstMarginTop = isFirstOnPage ? firstBlock.marginTopPt : Math.max(firstBlock.marginTopPt, previousMarginBottomPt) - previousMarginBottomPt;
559
+ const firstMarginTop = this.effectiveBlockMarginTop(
560
+ firstBlock,
561
+ previousMarginBottomPt,
562
+ isFirstOnPage,
563
+ pageInSection
564
+ );
470
565
  let bodyHeight = firstMarginTop + firstBlock.heightPt;
471
566
  for (let index = 1; index < chain.length; index++) {
472
567
  const previousBlock = chain[index - 1];
@@ -475,6 +570,35 @@ var DocxodusEditor = (() => {
475
570
  }
476
571
  return bodyHeight;
477
572
  }
573
+ /** Word paragraphs render as `p`, or as `h1`–`h6` when their style has an outline level. */
574
+ isWordParagraphElement(element) {
575
+ return element.tagName === "P" || /^H[1-6]$/.test(element.tagName);
576
+ }
577
+ shouldSuppressPageTopSpacing(block, isFirstOnPage, pageInSection) {
578
+ return isFirstOnPage && pageInSection > 1 && block.isWordParagraph === true;
579
+ }
580
+ /**
581
+ * Resolves the part of a block's top margin that consumes the current page.
582
+ *
583
+ * In Word's native DOCX layout, paragraph space-before is suppressed when a paragraph is the
584
+ * first body block on a later page of the SAME section. The first page of a document/section is
585
+ * the exception and keeps its spacing. Tables and other block margins are not paragraph spacing,
586
+ * so they continue to use the ordinary CSS collapsing rule.
587
+ */
588
+ effectiveBlockMarginTop(block, previousMarginBottomPt, isFirstOnPage, pageInSection) {
589
+ if (this.shouldSuppressPageTopSpacing(block, isFirstOnPage, pageInSection)) {
590
+ return 0;
591
+ }
592
+ return isFirstOnPage ? block.marginTopPt : Math.max(block.marginTopPt, previousMarginBottomPt) - previousMarginBottomPt;
593
+ }
594
+ /** Clone a source block with the same page-top spacing decision used by the height budget. */
595
+ cloneBlockForPage(block, isFirstOnPage, pageInSection) {
596
+ const clone = block.element.cloneNode(true);
597
+ if (this.shouldSuppressPageTopSpacing(block, isFirstOnPage, pageInSection)) {
598
+ clone.style.setProperty("margin-top", "0", "important");
599
+ }
600
+ return clone;
601
+ }
478
602
  /**
479
603
  * Finds footnote references introduced by a sequence of blocks, preserving
480
604
  * document order and excluding references already assigned to the page.
@@ -1280,7 +1404,8 @@ var DocxodusEditor = (() => {
1280
1404
  const currentChainHeight = this.measureKeepWithNextChainBodyHeight(
1281
1405
  keepChain,
1282
1406
  prevMarginBottomPt,
1283
- currentContent.length === 0
1407
+ currentContent.length === 0,
1408
+ pageInSection
1284
1409
  ) + additionalChainFootnoteHeight;
1285
1410
  const currentAvailableHeight = remainingHeight - currentFootnoteHeight;
1286
1411
  if (currentChainHeight > currentAvailableHeight) {
@@ -1293,7 +1418,8 @@ var DocxodusEditor = (() => {
1293
1418
  const freshChainBodyHeight = this.measureKeepWithNextChainBodyHeight(
1294
1419
  keepChain,
1295
1420
  0,
1296
- true
1421
+ true,
1422
+ pageInSection + 1
1297
1423
  );
1298
1424
  const freshChainFootnoteHeight = this.measureFootnotesHeight(
1299
1425
  newChainFootnoteIds,
@@ -1319,10 +1445,12 @@ var DocxodusEditor = (() => {
1319
1445
  additionalFootnoteHeight = totalFootnoteHeight - currentFootnoteHeight;
1320
1446
  }
1321
1447
  const isFirstOnPage = currentContent.length === 0;
1322
- let effectiveMarginTop = block.marginTopPt;
1323
- if (!isFirstOnPage) {
1324
- effectiveMarginTop = Math.max(block.marginTopPt, prevMarginBottomPt) - prevMarginBottomPt;
1325
- }
1448
+ const effectiveMarginTop = this.effectiveBlockMarginTop(
1449
+ block,
1450
+ prevMarginBottomPt,
1451
+ isFirstOnPage,
1452
+ pageInSection
1453
+ );
1326
1454
  const blockSpace = effectiveMarginTop + block.heightPt + additionalFootnoteHeight;
1327
1455
  const effectiveRemainingHeight = remainingHeight - currentFootnoteHeight;
1328
1456
  const bodyContentUsed = effectiveContentHeight - remainingHeight;
@@ -1342,19 +1470,19 @@ var DocxodusEditor = (() => {
1342
1470
  }
1343
1471
  }
1344
1472
  if (blockSpace <= effectiveRemainingHeight) {
1345
- currentContent.push(block.element.cloneNode(true));
1473
+ currentContent.push(this.cloneBlockForPage(block, isFirstOnPage, pageInSection));
1346
1474
  remainingHeight -= effectiveMarginTop + block.heightPt + block.marginBottomPt;
1347
1475
  prevMarginBottomPt = block.marginBottomPt;
1348
1476
  if (newFootnoteIds.length > 0) {
1349
1477
  currentFootnoteIds.push(...newFootnoteIds);
1350
1478
  currentFootnoteHeight += additionalFootnoteHeight;
1351
1479
  }
1352
- } else if (block.heightPt + block.marginTopPt <= effectiveContentHeight) {
1480
+ } else if (block.heightPt + this.effectiveBlockMarginTop(block, 0, true, pageInSection + 1) <= effectiveContentHeight) {
1353
1481
  const blockSpaceWithoutFootnotes = effectiveMarginTop + block.heightPt;
1354
1482
  const minBodySpaceNeeded = bodyContentUsed + blockSpaceWithoutFootnotes + MIN_BODY_CONTENT_HEIGHT;
1355
1483
  const canExpandFootnotes = minBodySpaceNeeded <= effectiveContentHeight;
1356
1484
  if (newFootnoteIds.length > 0 && blockSpaceWithoutFootnotes <= effectiveRemainingHeight) {
1357
- currentContent.push(block.element.cloneNode(true));
1485
+ currentContent.push(this.cloneBlockForPage(block, isFirstOnPage, pageInSection));
1358
1486
  remainingHeight -= effectiveMarginTop + block.heightPt + block.marginBottomPt;
1359
1487
  prevMarginBottomPt = block.marginBottomPt;
1360
1488
  const availableForFootnotes = Math.min(
@@ -1401,7 +1529,7 @@ var DocxodusEditor = (() => {
1401
1529
  const expandedFootnoteSpace = Math.min(maxFootnoteArea, additionalFootnoteHeight + currentFootnoteHeight);
1402
1530
  const bodySpaceAfterExpansion = effectiveContentHeight - expandedFootnoteSpace;
1403
1531
  if (blockSpaceWithoutFootnotes <= bodySpaceAfterExpansion - bodyContentUsed) {
1404
- currentContent.push(block.element.cloneNode(true));
1532
+ currentContent.push(this.cloneBlockForPage(block, isFirstOnPage, pageInSection));
1405
1533
  remainingHeight = bodySpaceAfterExpansion - bodyContentUsed - blockSpaceWithoutFootnotes;
1406
1534
  prevMarginBottomPt = block.marginBottomPt;
1407
1535
  currentFootnoteIds.push(...newFootnoteIds);
@@ -1409,8 +1537,14 @@ var DocxodusEditor = (() => {
1409
1537
  } else {
1410
1538
  finishPage();
1411
1539
  const newPageFootnoteHeight = allBlockFootnoteIds.length > 0 ? this.measureFootnotesHeight(allBlockFootnoteIds, dims.contentWidth, currentContinuation) : currentContinuation ? this.measureContinuationHeight(currentContinuation, dims.contentWidth) : 0;
1412
- const newPageSpace = block.marginTopPt + block.heightPt + block.marginBottomPt;
1413
- currentContent.push(block.element.cloneNode(true));
1540
+ const newPageMarginTop = this.effectiveBlockMarginTop(
1541
+ block,
1542
+ 0,
1543
+ true,
1544
+ pageInSection
1545
+ );
1546
+ const newPageSpace = newPageMarginTop + block.heightPt + block.marginBottomPt;
1547
+ currentContent.push(this.cloneBlockForPage(block, true, pageInSection));
1414
1548
  remainingHeight = effectiveContentHeight - newPageSpace;
1415
1549
  prevMarginBottomPt = block.marginBottomPt;
1416
1550
  currentFootnoteIds = [...currentFootnoteIds, ...allBlockFootnoteIds];
@@ -1419,8 +1553,14 @@ var DocxodusEditor = (() => {
1419
1553
  } else {
1420
1554
  finishPage();
1421
1555
  const newPageFootnoteHeight = allBlockFootnoteIds.length > 0 ? this.measureFootnotesHeight(allBlockFootnoteIds, dims.contentWidth, currentContinuation) : currentContinuation ? this.measureContinuationHeight(currentContinuation, dims.contentWidth) : 0;
1422
- const newPageSpace = block.marginTopPt + block.heightPt + block.marginBottomPt;
1423
- currentContent.push(block.element.cloneNode(true));
1556
+ const newPageMarginTop = this.effectiveBlockMarginTop(
1557
+ block,
1558
+ 0,
1559
+ true,
1560
+ pageInSection
1561
+ );
1562
+ const newPageSpace = newPageMarginTop + block.heightPt + block.marginBottomPt;
1563
+ currentContent.push(this.cloneBlockForPage(block, true, pageInSection));
1424
1564
  remainingHeight = effectiveContentHeight - newPageSpace;
1425
1565
  prevMarginBottomPt = block.marginBottomPt;
1426
1566
  currentFootnoteIds = [...currentFootnoteIds, ...allBlockFootnoteIds];
@@ -1439,7 +1579,7 @@ var DocxodusEditor = (() => {
1439
1579
  if (currentContent.length > 0) {
1440
1580
  finishPage();
1441
1581
  }
1442
- currentContent.push(block.element.cloneNode(true));
1582
+ currentContent.push(this.cloneBlockForPage(block, true, pageInSection));
1443
1583
  currentFootnoteIds = [...currentFootnoteIds, ...allBlockFootnoteIds];
1444
1584
  finishPage();
1445
1585
  }
@@ -7969,6 +8109,7 @@ var DocxodusEditor = (() => {
7969
8109
  color: var(--dxr-ink);
7970
8110
  background: var(--dxr-desk);
7971
8111
  -webkit-text-size-adjust: 100%;
8112
+ text-size-adjust: 100%;
7972
8113
  }
7973
8114
  .dxr *, .dxr *::before, .dxr *::after { box-sizing: border-box; }
7974
8115
 
@@ -1426,6 +1426,9 @@ function applyZoom(el, scale, naturalPt) {
1426
1426
  }
1427
1427
 
1428
1428
  // src/pagination.ts
1429
+ function samePageBox(a, b) {
1430
+ return a.pageWidth === b.pageWidth && a.pageHeight === b.pageHeight && a.marginTop === b.marginTop && a.marginRight === b.marginRight && a.marginBottom === b.marginBottom && a.marginLeft === b.marginLeft;
1431
+ }
1429
1432
  var MAX_FOOTNOTE_AREA_RATIO = 0.6;
1430
1433
  var MIN_BODY_CONTENT_HEIGHT = 72;
1431
1434
  var PaginationEngine = class {
@@ -1471,16 +1474,43 @@ var PaginationEngine = class {
1471
1474
  );
1472
1475
  this.pageNumbering = this.parsePageNumbering(sections);
1473
1476
  const sectionsToProcess = sections.length > 0 ? Array.from(sections) : [this.stagingElement];
1477
+ const runs = [];
1474
1478
  for (const section of sectionsToProcess) {
1475
- const sectionIndex = parseInt(section.dataset.sectionIndex || "0", 10);
1476
1479
  const dims = parseSectionDimensions(section);
1480
+ const previous = runs[runs.length - 1];
1481
+ if (previous && section.dataset.sectionType === "continuous" && samePageBox(previous.dims, dims)) {
1482
+ previous.sections.push(section);
1483
+ } else {
1484
+ runs.push({
1485
+ sections: [section],
1486
+ sectionIndex: parseInt(section.dataset.sectionIndex || "0", 10),
1487
+ dims
1488
+ });
1489
+ }
1490
+ }
1491
+ for (const run of runs) {
1477
1492
  this.stagingElement.style.visibility = "hidden";
1478
1493
  this.stagingElement.style.position = "absolute";
1479
1494
  this.stagingElement.style.left = "-9999px";
1480
1495
  this.stagingElement.style.display = "block";
1481
- section.style.width = `${dims.contentWidth}pt`;
1482
- const blocks = this.measureBlocks(section, dims);
1483
- const sectionPages = this.flowToPages(blocks, dims, pageNumber, sectionIndex);
1496
+ const blocks = [];
1497
+ for (const section of run.sections) {
1498
+ section.style.width = `${run.dims.contentWidth}pt`;
1499
+ const columnCount = parseInt(section.dataset.cols || "1", 10);
1500
+ if (columnCount > 1) {
1501
+ const gap = parseFloat(section.dataset.colGap || "");
1502
+ blocks.push(...this.buildColumnBlocks(
1503
+ section,
1504
+ run.dims,
1505
+ run.sectionIndex,
1506
+ columnCount,
1507
+ Number.isFinite(gap) ? gap : 36
1508
+ ));
1509
+ } else {
1510
+ blocks.push(...this.measureBlocks(section, run.dims));
1511
+ }
1512
+ }
1513
+ const sectionPages = this.flowToPages(blocks, run.dims, pageNumber, run.sectionIndex);
1484
1514
  pages.push(...sectionPages);
1485
1515
  pageNumber += sectionPages.length;
1486
1516
  }
@@ -1569,11 +1599,70 @@ var PaginationEngine = class {
1569
1599
  keepWithNext: child.dataset.keepWithNext === "true",
1570
1600
  keepLines: child.dataset.keepLines === "true",
1571
1601
  pageBreakBefore: child.dataset.pageBreakBefore === "true",
1572
- isPageBreak
1602
+ isPageBreak,
1603
+ isWordParagraph: this.isWordParagraphElement(child)
1573
1604
  });
1574
1605
  }
1575
1606
  return blocks;
1576
1607
  }
1608
+ /**
1609
+ * Flows a multi-column (`w:cols`) section's children into CSS-multicol container
1610
+ * blocks. Word lays such a section out as N columns inside the same body extent;
1611
+ * a balanced `column-count` container reproduces that geometry, and the paginator
1612
+ * then places each container as one ordinary measured block. A container grows
1613
+ * greedily until its balanced height would exceed the smallest page body available
1614
+ * to the section, so a long columned section still splits across pages at block
1615
+ * boundaries. Each child lands in exactly one container, so anchors never
1616
+ * duplicate. An explicit page break child passes through as its own block, which
1617
+ * ends the current container and lets the normal flow logic turn the page.
1618
+ */
1619
+ buildColumnBlocks(section, dims, sectionIndex, columnCount, columnGapPt) {
1620
+ const children = Array.from(section.children);
1621
+ const blocks = [];
1622
+ const maxFragmentHeight = this.smallestEffectiveContentHeight(dims, sectionIndex);
1623
+ const isBreak = (child) => child.dataset.pageBreak === "true" || child.classList.contains(`${this.cssPrefix}break`);
1624
+ const makeContainer = (slice) => {
1625
+ const container = document.createElement("div");
1626
+ container.style.columnCount = String(columnCount);
1627
+ container.style.columnGap = `${columnGapPt}pt`;
1628
+ for (const child of slice) {
1629
+ container.appendChild(child.cloneNode(true));
1630
+ }
1631
+ return container;
1632
+ };
1633
+ let start2 = 0;
1634
+ while (start2 < children.length) {
1635
+ if (isBreak(children[start2])) {
1636
+ blocks.push(this.measureElement(children[start2], dims));
1637
+ start2++;
1638
+ continue;
1639
+ }
1640
+ let end = start2 + 1;
1641
+ let container = makeContainer(children.slice(start2, end));
1642
+ let measured = this.measureElement(container, dims);
1643
+ while (end < children.length && !isBreak(children[end])) {
1644
+ const candidate = makeContainer(children.slice(start2, end + 1));
1645
+ const candidateMeasured = this.measureElement(candidate, dims);
1646
+ if (candidateMeasured.heightPt > maxFragmentHeight) break;
1647
+ container = candidate;
1648
+ measured = candidateMeasured;
1649
+ end++;
1650
+ }
1651
+ blocks.push({
1652
+ element: container,
1653
+ heightPt: measured.heightPt,
1654
+ marginTopPt: measured.marginTopPt,
1655
+ marginBottomPt: measured.marginBottomPt,
1656
+ keepWithNext: false,
1657
+ keepLines: false,
1658
+ pageBreakBefore: false,
1659
+ isPageBreak: false,
1660
+ isWordParagraph: false
1661
+ });
1662
+ start2 = end;
1663
+ }
1664
+ return blocks;
1665
+ }
1577
1666
  /**
1578
1667
  * Measures one element in the same hidden staging context used for the source blocks.
1579
1668
  * This is intentionally DOM-based: table row heights cannot be inferred from individual
@@ -1598,7 +1687,8 @@ var PaginationEngine = class {
1598
1687
  keepWithNext: element.dataset.keepWithNext === "true",
1599
1688
  keepLines: element.dataset.keepLines === "true",
1600
1689
  pageBreakBefore: element.dataset.pageBreakBefore === "true",
1601
- isPageBreak: element.dataset.pageBreak === "true" || element.classList.contains(`${this.cssPrefix}break`)
1690
+ isPageBreak: element.dataset.pageBreak === "true" || element.classList.contains(`${this.cssPrefix}break`),
1691
+ isWordParagraph: this.isWordParagraphElement(element)
1602
1692
  };
1603
1693
  this.stagingElement.removeChild(measurementHost);
1604
1694
  return measured;
@@ -1630,10 +1720,15 @@ var PaginationEngine = class {
1630
1720
  * collapsed-margin rules as normal block placement. The trailing margin is
1631
1721
  * intentionally excluded, matching the individual block fit check.
1632
1722
  */
1633
- measureKeepWithNextChainBodyHeight(chain, previousMarginBottomPt, isFirstOnPage) {
1723
+ measureKeepWithNextChainBodyHeight(chain, previousMarginBottomPt, isFirstOnPage, pageInSection) {
1634
1724
  const firstBlock = chain[0];
1635
1725
  if (!firstBlock) return 0;
1636
- const firstMarginTop = isFirstOnPage ? firstBlock.marginTopPt : Math.max(firstBlock.marginTopPt, previousMarginBottomPt) - previousMarginBottomPt;
1726
+ const firstMarginTop = this.effectiveBlockMarginTop(
1727
+ firstBlock,
1728
+ previousMarginBottomPt,
1729
+ isFirstOnPage,
1730
+ pageInSection
1731
+ );
1637
1732
  let bodyHeight = firstMarginTop + firstBlock.heightPt;
1638
1733
  for (let index = 1; index < chain.length; index++) {
1639
1734
  const previousBlock = chain[index - 1];
@@ -1642,6 +1737,35 @@ var PaginationEngine = class {
1642
1737
  }
1643
1738
  return bodyHeight;
1644
1739
  }
1740
+ /** Word paragraphs render as `p`, or as `h1`–`h6` when their style has an outline level. */
1741
+ isWordParagraphElement(element) {
1742
+ return element.tagName === "P" || /^H[1-6]$/.test(element.tagName);
1743
+ }
1744
+ shouldSuppressPageTopSpacing(block, isFirstOnPage, pageInSection) {
1745
+ return isFirstOnPage && pageInSection > 1 && block.isWordParagraph === true;
1746
+ }
1747
+ /**
1748
+ * Resolves the part of a block's top margin that consumes the current page.
1749
+ *
1750
+ * In Word's native DOCX layout, paragraph space-before is suppressed when a paragraph is the
1751
+ * first body block on a later page of the SAME section. The first page of a document/section is
1752
+ * the exception and keeps its spacing. Tables and other block margins are not paragraph spacing,
1753
+ * so they continue to use the ordinary CSS collapsing rule.
1754
+ */
1755
+ effectiveBlockMarginTop(block, previousMarginBottomPt, isFirstOnPage, pageInSection) {
1756
+ if (this.shouldSuppressPageTopSpacing(block, isFirstOnPage, pageInSection)) {
1757
+ return 0;
1758
+ }
1759
+ return isFirstOnPage ? block.marginTopPt : Math.max(block.marginTopPt, previousMarginBottomPt) - previousMarginBottomPt;
1760
+ }
1761
+ /** Clone a source block with the same page-top spacing decision used by the height budget. */
1762
+ cloneBlockForPage(block, isFirstOnPage, pageInSection) {
1763
+ const clone = block.element.cloneNode(true);
1764
+ if (this.shouldSuppressPageTopSpacing(block, isFirstOnPage, pageInSection)) {
1765
+ clone.style.setProperty("margin-top", "0", "important");
1766
+ }
1767
+ return clone;
1768
+ }
1645
1769
  /**
1646
1770
  * Finds footnote references introduced by a sequence of blocks, preserving
1647
1771
  * document order and excluding references already assigned to the page.
@@ -2447,7 +2571,8 @@ var PaginationEngine = class {
2447
2571
  const currentChainHeight = this.measureKeepWithNextChainBodyHeight(
2448
2572
  keepChain,
2449
2573
  prevMarginBottomPt,
2450
- currentContent.length === 0
2574
+ currentContent.length === 0,
2575
+ pageInSection
2451
2576
  ) + additionalChainFootnoteHeight;
2452
2577
  const currentAvailableHeight = remainingHeight - currentFootnoteHeight;
2453
2578
  if (currentChainHeight > currentAvailableHeight) {
@@ -2460,7 +2585,8 @@ var PaginationEngine = class {
2460
2585
  const freshChainBodyHeight = this.measureKeepWithNextChainBodyHeight(
2461
2586
  keepChain,
2462
2587
  0,
2463
- true
2588
+ true,
2589
+ pageInSection + 1
2464
2590
  );
2465
2591
  const freshChainFootnoteHeight = this.measureFootnotesHeight(
2466
2592
  newChainFootnoteIds,
@@ -2486,10 +2612,12 @@ var PaginationEngine = class {
2486
2612
  additionalFootnoteHeight = totalFootnoteHeight - currentFootnoteHeight;
2487
2613
  }
2488
2614
  const isFirstOnPage = currentContent.length === 0;
2489
- let effectiveMarginTop = block.marginTopPt;
2490
- if (!isFirstOnPage) {
2491
- effectiveMarginTop = Math.max(block.marginTopPt, prevMarginBottomPt) - prevMarginBottomPt;
2492
- }
2615
+ const effectiveMarginTop = this.effectiveBlockMarginTop(
2616
+ block,
2617
+ prevMarginBottomPt,
2618
+ isFirstOnPage,
2619
+ pageInSection
2620
+ );
2493
2621
  const blockSpace = effectiveMarginTop + block.heightPt + additionalFootnoteHeight;
2494
2622
  const effectiveRemainingHeight = remainingHeight - currentFootnoteHeight;
2495
2623
  const bodyContentUsed = effectiveContentHeight - remainingHeight;
@@ -2509,19 +2637,19 @@ var PaginationEngine = class {
2509
2637
  }
2510
2638
  }
2511
2639
  if (blockSpace <= effectiveRemainingHeight) {
2512
- currentContent.push(block.element.cloneNode(true));
2640
+ currentContent.push(this.cloneBlockForPage(block, isFirstOnPage, pageInSection));
2513
2641
  remainingHeight -= effectiveMarginTop + block.heightPt + block.marginBottomPt;
2514
2642
  prevMarginBottomPt = block.marginBottomPt;
2515
2643
  if (newFootnoteIds.length > 0) {
2516
2644
  currentFootnoteIds.push(...newFootnoteIds);
2517
2645
  currentFootnoteHeight += additionalFootnoteHeight;
2518
2646
  }
2519
- } else if (block.heightPt + block.marginTopPt <= effectiveContentHeight) {
2647
+ } else if (block.heightPt + this.effectiveBlockMarginTop(block, 0, true, pageInSection + 1) <= effectiveContentHeight) {
2520
2648
  const blockSpaceWithoutFootnotes = effectiveMarginTop + block.heightPt;
2521
2649
  const minBodySpaceNeeded = bodyContentUsed + blockSpaceWithoutFootnotes + MIN_BODY_CONTENT_HEIGHT;
2522
2650
  const canExpandFootnotes = minBodySpaceNeeded <= effectiveContentHeight;
2523
2651
  if (newFootnoteIds.length > 0 && blockSpaceWithoutFootnotes <= effectiveRemainingHeight) {
2524
- currentContent.push(block.element.cloneNode(true));
2652
+ currentContent.push(this.cloneBlockForPage(block, isFirstOnPage, pageInSection));
2525
2653
  remainingHeight -= effectiveMarginTop + block.heightPt + block.marginBottomPt;
2526
2654
  prevMarginBottomPt = block.marginBottomPt;
2527
2655
  const availableForFootnotes = Math.min(
@@ -2568,7 +2696,7 @@ var PaginationEngine = class {
2568
2696
  const expandedFootnoteSpace = Math.min(maxFootnoteArea, additionalFootnoteHeight + currentFootnoteHeight);
2569
2697
  const bodySpaceAfterExpansion = effectiveContentHeight - expandedFootnoteSpace;
2570
2698
  if (blockSpaceWithoutFootnotes <= bodySpaceAfterExpansion - bodyContentUsed) {
2571
- currentContent.push(block.element.cloneNode(true));
2699
+ currentContent.push(this.cloneBlockForPage(block, isFirstOnPage, pageInSection));
2572
2700
  remainingHeight = bodySpaceAfterExpansion - bodyContentUsed - blockSpaceWithoutFootnotes;
2573
2701
  prevMarginBottomPt = block.marginBottomPt;
2574
2702
  currentFootnoteIds.push(...newFootnoteIds);
@@ -2576,8 +2704,14 @@ var PaginationEngine = class {
2576
2704
  } else {
2577
2705
  finishPage();
2578
2706
  const newPageFootnoteHeight = allBlockFootnoteIds.length > 0 ? this.measureFootnotesHeight(allBlockFootnoteIds, dims.contentWidth, currentContinuation) : currentContinuation ? this.measureContinuationHeight(currentContinuation, dims.contentWidth) : 0;
2579
- const newPageSpace = block.marginTopPt + block.heightPt + block.marginBottomPt;
2580
- currentContent.push(block.element.cloneNode(true));
2707
+ const newPageMarginTop = this.effectiveBlockMarginTop(
2708
+ block,
2709
+ 0,
2710
+ true,
2711
+ pageInSection
2712
+ );
2713
+ const newPageSpace = newPageMarginTop + block.heightPt + block.marginBottomPt;
2714
+ currentContent.push(this.cloneBlockForPage(block, true, pageInSection));
2581
2715
  remainingHeight = effectiveContentHeight - newPageSpace;
2582
2716
  prevMarginBottomPt = block.marginBottomPt;
2583
2717
  currentFootnoteIds = [...currentFootnoteIds, ...allBlockFootnoteIds];
@@ -2586,8 +2720,14 @@ var PaginationEngine = class {
2586
2720
  } else {
2587
2721
  finishPage();
2588
2722
  const newPageFootnoteHeight = allBlockFootnoteIds.length > 0 ? this.measureFootnotesHeight(allBlockFootnoteIds, dims.contentWidth, currentContinuation) : currentContinuation ? this.measureContinuationHeight(currentContinuation, dims.contentWidth) : 0;
2589
- const newPageSpace = block.marginTopPt + block.heightPt + block.marginBottomPt;
2590
- currentContent.push(block.element.cloneNode(true));
2723
+ const newPageMarginTop = this.effectiveBlockMarginTop(
2724
+ block,
2725
+ 0,
2726
+ true,
2727
+ pageInSection
2728
+ );
2729
+ const newPageSpace = newPageMarginTop + block.heightPt + block.marginBottomPt;
2730
+ currentContent.push(this.cloneBlockForPage(block, true, pageInSection));
2591
2731
  remainingHeight = effectiveContentHeight - newPageSpace;
2592
2732
  prevMarginBottomPt = block.marginBottomPt;
2593
2733
  currentFootnoteIds = [...currentFootnoteIds, ...allBlockFootnoteIds];
@@ -2606,7 +2746,7 @@ var PaginationEngine = class {
2606
2746
  if (currentContent.length > 0) {
2607
2747
  finishPage();
2608
2748
  }
2609
- currentContent.push(block.element.cloneNode(true));
2749
+ currentContent.push(this.cloneBlockForPage(block, true, pageInSection));
2610
2750
  currentFootnoteIds = [...currentFootnoteIds, ...allBlockFootnoteIds];
2611
2751
  finishPage();
2612
2752
  }
@@ -9136,6 +9276,7 @@ var RIBBON_CSS = `
9136
9276
  color: var(--dxr-ink);
9137
9277
  background: var(--dxr-desk);
9138
9278
  -webkit-text-size-adjust: 100%;
9279
+ text-size-adjust: 100%;
9139
9280
  }
9140
9281
  .dxr *, .dxr *::before, .dxr *::after { box-sizing: border-box; }
9141
9282