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
@@ -1544,6 +1544,9 @@ var Docxodus = (() => {
1544
1544
  }
1545
1545
 
1546
1546
  // src/pagination.ts
1547
+ function samePageBox(a, b) {
1548
+ 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;
1549
+ }
1547
1550
  var MAX_FOOTNOTE_AREA_RATIO = 0.6;
1548
1551
  var MIN_BODY_CONTENT_HEIGHT = 72;
1549
1552
  var PaginationEngine = class {
@@ -1589,16 +1592,43 @@ var Docxodus = (() => {
1589
1592
  );
1590
1593
  this.pageNumbering = this.parsePageNumbering(sections);
1591
1594
  const sectionsToProcess = sections.length > 0 ? Array.from(sections) : [this.stagingElement];
1595
+ const runs = [];
1592
1596
  for (const section of sectionsToProcess) {
1593
- const sectionIndex = parseInt(section.dataset.sectionIndex || "0", 10);
1594
1597
  const dims = parseSectionDimensions(section);
1598
+ const previous = runs[runs.length - 1];
1599
+ if (previous && section.dataset.sectionType === "continuous" && samePageBox(previous.dims, dims)) {
1600
+ previous.sections.push(section);
1601
+ } else {
1602
+ runs.push({
1603
+ sections: [section],
1604
+ sectionIndex: parseInt(section.dataset.sectionIndex || "0", 10),
1605
+ dims
1606
+ });
1607
+ }
1608
+ }
1609
+ for (const run of runs) {
1595
1610
  this.stagingElement.style.visibility = "hidden";
1596
1611
  this.stagingElement.style.position = "absolute";
1597
1612
  this.stagingElement.style.left = "-9999px";
1598
1613
  this.stagingElement.style.display = "block";
1599
- section.style.width = `${dims.contentWidth}pt`;
1600
- const blocks = this.measureBlocks(section, dims);
1601
- const sectionPages = this.flowToPages(blocks, dims, pageNumber, sectionIndex);
1614
+ const blocks = [];
1615
+ for (const section of run.sections) {
1616
+ section.style.width = `${run.dims.contentWidth}pt`;
1617
+ const columnCount = parseInt(section.dataset.cols || "1", 10);
1618
+ if (columnCount > 1) {
1619
+ const gap = parseFloat(section.dataset.colGap || "");
1620
+ blocks.push(...this.buildColumnBlocks(
1621
+ section,
1622
+ run.dims,
1623
+ run.sectionIndex,
1624
+ columnCount,
1625
+ Number.isFinite(gap) ? gap : 36
1626
+ ));
1627
+ } else {
1628
+ blocks.push(...this.measureBlocks(section, run.dims));
1629
+ }
1630
+ }
1631
+ const sectionPages = this.flowToPages(blocks, run.dims, pageNumber, run.sectionIndex);
1602
1632
  pages.push(...sectionPages);
1603
1633
  pageNumber += sectionPages.length;
1604
1634
  }
@@ -1687,11 +1717,70 @@ var Docxodus = (() => {
1687
1717
  keepWithNext: child.dataset.keepWithNext === "true",
1688
1718
  keepLines: child.dataset.keepLines === "true",
1689
1719
  pageBreakBefore: child.dataset.pageBreakBefore === "true",
1690
- isPageBreak
1720
+ isPageBreak,
1721
+ isWordParagraph: this.isWordParagraphElement(child)
1691
1722
  });
1692
1723
  }
1693
1724
  return blocks;
1694
1725
  }
1726
+ /**
1727
+ * Flows a multi-column (`w:cols`) section's children into CSS-multicol container
1728
+ * blocks. Word lays such a section out as N columns inside the same body extent;
1729
+ * a balanced `column-count` container reproduces that geometry, and the paginator
1730
+ * then places each container as one ordinary measured block. A container grows
1731
+ * greedily until its balanced height would exceed the smallest page body available
1732
+ * to the section, so a long columned section still splits across pages at block
1733
+ * boundaries. Each child lands in exactly one container, so anchors never
1734
+ * duplicate. An explicit page break child passes through as its own block, which
1735
+ * ends the current container and lets the normal flow logic turn the page.
1736
+ */
1737
+ buildColumnBlocks(section, dims, sectionIndex, columnCount, columnGapPt) {
1738
+ const children = Array.from(section.children);
1739
+ const blocks = [];
1740
+ const maxFragmentHeight = this.smallestEffectiveContentHeight(dims, sectionIndex);
1741
+ const isBreak = (child) => child.dataset.pageBreak === "true" || child.classList.contains(`${this.cssPrefix}break`);
1742
+ const makeContainer = (slice) => {
1743
+ const container = document.createElement("div");
1744
+ container.style.columnCount = String(columnCount);
1745
+ container.style.columnGap = `${columnGapPt}pt`;
1746
+ for (const child of slice) {
1747
+ container.appendChild(child.cloneNode(true));
1748
+ }
1749
+ return container;
1750
+ };
1751
+ let start2 = 0;
1752
+ while (start2 < children.length) {
1753
+ if (isBreak(children[start2])) {
1754
+ blocks.push(this.measureElement(children[start2], dims));
1755
+ start2++;
1756
+ continue;
1757
+ }
1758
+ let end = start2 + 1;
1759
+ let container = makeContainer(children.slice(start2, end));
1760
+ let measured = this.measureElement(container, dims);
1761
+ while (end < children.length && !isBreak(children[end])) {
1762
+ const candidate = makeContainer(children.slice(start2, end + 1));
1763
+ const candidateMeasured = this.measureElement(candidate, dims);
1764
+ if (candidateMeasured.heightPt > maxFragmentHeight) break;
1765
+ container = candidate;
1766
+ measured = candidateMeasured;
1767
+ end++;
1768
+ }
1769
+ blocks.push({
1770
+ element: container,
1771
+ heightPt: measured.heightPt,
1772
+ marginTopPt: measured.marginTopPt,
1773
+ marginBottomPt: measured.marginBottomPt,
1774
+ keepWithNext: false,
1775
+ keepLines: false,
1776
+ pageBreakBefore: false,
1777
+ isPageBreak: false,
1778
+ isWordParagraph: false
1779
+ });
1780
+ start2 = end;
1781
+ }
1782
+ return blocks;
1783
+ }
1695
1784
  /**
1696
1785
  * Measures one element in the same hidden staging context used for the source blocks.
1697
1786
  * This is intentionally DOM-based: table row heights cannot be inferred from individual
@@ -1716,7 +1805,8 @@ var Docxodus = (() => {
1716
1805
  keepWithNext: element.dataset.keepWithNext === "true",
1717
1806
  keepLines: element.dataset.keepLines === "true",
1718
1807
  pageBreakBefore: element.dataset.pageBreakBefore === "true",
1719
- isPageBreak: element.dataset.pageBreak === "true" || element.classList.contains(`${this.cssPrefix}break`)
1808
+ isPageBreak: element.dataset.pageBreak === "true" || element.classList.contains(`${this.cssPrefix}break`),
1809
+ isWordParagraph: this.isWordParagraphElement(element)
1720
1810
  };
1721
1811
  this.stagingElement.removeChild(measurementHost);
1722
1812
  return measured;
@@ -1748,10 +1838,15 @@ var Docxodus = (() => {
1748
1838
  * collapsed-margin rules as normal block placement. The trailing margin is
1749
1839
  * intentionally excluded, matching the individual block fit check.
1750
1840
  */
1751
- measureKeepWithNextChainBodyHeight(chain, previousMarginBottomPt, isFirstOnPage) {
1841
+ measureKeepWithNextChainBodyHeight(chain, previousMarginBottomPt, isFirstOnPage, pageInSection) {
1752
1842
  const firstBlock = chain[0];
1753
1843
  if (!firstBlock) return 0;
1754
- const firstMarginTop = isFirstOnPage ? firstBlock.marginTopPt : Math.max(firstBlock.marginTopPt, previousMarginBottomPt) - previousMarginBottomPt;
1844
+ const firstMarginTop = this.effectiveBlockMarginTop(
1845
+ firstBlock,
1846
+ previousMarginBottomPt,
1847
+ isFirstOnPage,
1848
+ pageInSection
1849
+ );
1755
1850
  let bodyHeight = firstMarginTop + firstBlock.heightPt;
1756
1851
  for (let index = 1; index < chain.length; index++) {
1757
1852
  const previousBlock = chain[index - 1];
@@ -1760,6 +1855,35 @@ var Docxodus = (() => {
1760
1855
  }
1761
1856
  return bodyHeight;
1762
1857
  }
1858
+ /** Word paragraphs render as `p`, or as `h1`–`h6` when their style has an outline level. */
1859
+ isWordParagraphElement(element) {
1860
+ return element.tagName === "P" || /^H[1-6]$/.test(element.tagName);
1861
+ }
1862
+ shouldSuppressPageTopSpacing(block, isFirstOnPage, pageInSection) {
1863
+ return isFirstOnPage && pageInSection > 1 && block.isWordParagraph === true;
1864
+ }
1865
+ /**
1866
+ * Resolves the part of a block's top margin that consumes the current page.
1867
+ *
1868
+ * In Word's native DOCX layout, paragraph space-before is suppressed when a paragraph is the
1869
+ * first body block on a later page of the SAME section. The first page of a document/section is
1870
+ * the exception and keeps its spacing. Tables and other block margins are not paragraph spacing,
1871
+ * so they continue to use the ordinary CSS collapsing rule.
1872
+ */
1873
+ effectiveBlockMarginTop(block, previousMarginBottomPt, isFirstOnPage, pageInSection) {
1874
+ if (this.shouldSuppressPageTopSpacing(block, isFirstOnPage, pageInSection)) {
1875
+ return 0;
1876
+ }
1877
+ return isFirstOnPage ? block.marginTopPt : Math.max(block.marginTopPt, previousMarginBottomPt) - previousMarginBottomPt;
1878
+ }
1879
+ /** Clone a source block with the same page-top spacing decision used by the height budget. */
1880
+ cloneBlockForPage(block, isFirstOnPage, pageInSection) {
1881
+ const clone = block.element.cloneNode(true);
1882
+ if (this.shouldSuppressPageTopSpacing(block, isFirstOnPage, pageInSection)) {
1883
+ clone.style.setProperty("margin-top", "0", "important");
1884
+ }
1885
+ return clone;
1886
+ }
1763
1887
  /**
1764
1888
  * Finds footnote references introduced by a sequence of blocks, preserving
1765
1889
  * document order and excluding references already assigned to the page.
@@ -2565,7 +2689,8 @@ var Docxodus = (() => {
2565
2689
  const currentChainHeight = this.measureKeepWithNextChainBodyHeight(
2566
2690
  keepChain,
2567
2691
  prevMarginBottomPt,
2568
- currentContent.length === 0
2692
+ currentContent.length === 0,
2693
+ pageInSection
2569
2694
  ) + additionalChainFootnoteHeight;
2570
2695
  const currentAvailableHeight = remainingHeight - currentFootnoteHeight;
2571
2696
  if (currentChainHeight > currentAvailableHeight) {
@@ -2578,7 +2703,8 @@ var Docxodus = (() => {
2578
2703
  const freshChainBodyHeight = this.measureKeepWithNextChainBodyHeight(
2579
2704
  keepChain,
2580
2705
  0,
2581
- true
2706
+ true,
2707
+ pageInSection + 1
2582
2708
  );
2583
2709
  const freshChainFootnoteHeight = this.measureFootnotesHeight(
2584
2710
  newChainFootnoteIds,
@@ -2604,10 +2730,12 @@ var Docxodus = (() => {
2604
2730
  additionalFootnoteHeight = totalFootnoteHeight - currentFootnoteHeight;
2605
2731
  }
2606
2732
  const isFirstOnPage = currentContent.length === 0;
2607
- let effectiveMarginTop = block.marginTopPt;
2608
- if (!isFirstOnPage) {
2609
- effectiveMarginTop = Math.max(block.marginTopPt, prevMarginBottomPt) - prevMarginBottomPt;
2610
- }
2733
+ const effectiveMarginTop = this.effectiveBlockMarginTop(
2734
+ block,
2735
+ prevMarginBottomPt,
2736
+ isFirstOnPage,
2737
+ pageInSection
2738
+ );
2611
2739
  const blockSpace = effectiveMarginTop + block.heightPt + additionalFootnoteHeight;
2612
2740
  const effectiveRemainingHeight = remainingHeight - currentFootnoteHeight;
2613
2741
  const bodyContentUsed = effectiveContentHeight - remainingHeight;
@@ -2627,19 +2755,19 @@ var Docxodus = (() => {
2627
2755
  }
2628
2756
  }
2629
2757
  if (blockSpace <= effectiveRemainingHeight) {
2630
- currentContent.push(block.element.cloneNode(true));
2758
+ currentContent.push(this.cloneBlockForPage(block, isFirstOnPage, pageInSection));
2631
2759
  remainingHeight -= effectiveMarginTop + block.heightPt + block.marginBottomPt;
2632
2760
  prevMarginBottomPt = block.marginBottomPt;
2633
2761
  if (newFootnoteIds.length > 0) {
2634
2762
  currentFootnoteIds.push(...newFootnoteIds);
2635
2763
  currentFootnoteHeight += additionalFootnoteHeight;
2636
2764
  }
2637
- } else if (block.heightPt + block.marginTopPt <= effectiveContentHeight) {
2765
+ } else if (block.heightPt + this.effectiveBlockMarginTop(block, 0, true, pageInSection + 1) <= effectiveContentHeight) {
2638
2766
  const blockSpaceWithoutFootnotes = effectiveMarginTop + block.heightPt;
2639
2767
  const minBodySpaceNeeded = bodyContentUsed + blockSpaceWithoutFootnotes + MIN_BODY_CONTENT_HEIGHT;
2640
2768
  const canExpandFootnotes = minBodySpaceNeeded <= effectiveContentHeight;
2641
2769
  if (newFootnoteIds.length > 0 && blockSpaceWithoutFootnotes <= effectiveRemainingHeight) {
2642
- currentContent.push(block.element.cloneNode(true));
2770
+ currentContent.push(this.cloneBlockForPage(block, isFirstOnPage, pageInSection));
2643
2771
  remainingHeight -= effectiveMarginTop + block.heightPt + block.marginBottomPt;
2644
2772
  prevMarginBottomPt = block.marginBottomPt;
2645
2773
  const availableForFootnotes = Math.min(
@@ -2686,7 +2814,7 @@ var Docxodus = (() => {
2686
2814
  const expandedFootnoteSpace = Math.min(maxFootnoteArea, additionalFootnoteHeight + currentFootnoteHeight);
2687
2815
  const bodySpaceAfterExpansion = effectiveContentHeight - expandedFootnoteSpace;
2688
2816
  if (blockSpaceWithoutFootnotes <= bodySpaceAfterExpansion - bodyContentUsed) {
2689
- currentContent.push(block.element.cloneNode(true));
2817
+ currentContent.push(this.cloneBlockForPage(block, isFirstOnPage, pageInSection));
2690
2818
  remainingHeight = bodySpaceAfterExpansion - bodyContentUsed - blockSpaceWithoutFootnotes;
2691
2819
  prevMarginBottomPt = block.marginBottomPt;
2692
2820
  currentFootnoteIds.push(...newFootnoteIds);
@@ -2694,8 +2822,14 @@ var Docxodus = (() => {
2694
2822
  } else {
2695
2823
  finishPage();
2696
2824
  const newPageFootnoteHeight = allBlockFootnoteIds.length > 0 ? this.measureFootnotesHeight(allBlockFootnoteIds, dims.contentWidth, currentContinuation) : currentContinuation ? this.measureContinuationHeight(currentContinuation, dims.contentWidth) : 0;
2697
- const newPageSpace = block.marginTopPt + block.heightPt + block.marginBottomPt;
2698
- currentContent.push(block.element.cloneNode(true));
2825
+ const newPageMarginTop = this.effectiveBlockMarginTop(
2826
+ block,
2827
+ 0,
2828
+ true,
2829
+ pageInSection
2830
+ );
2831
+ const newPageSpace = newPageMarginTop + block.heightPt + block.marginBottomPt;
2832
+ currentContent.push(this.cloneBlockForPage(block, true, pageInSection));
2699
2833
  remainingHeight = effectiveContentHeight - newPageSpace;
2700
2834
  prevMarginBottomPt = block.marginBottomPt;
2701
2835
  currentFootnoteIds = [...currentFootnoteIds, ...allBlockFootnoteIds];
@@ -2704,8 +2838,14 @@ var Docxodus = (() => {
2704
2838
  } else {
2705
2839
  finishPage();
2706
2840
  const newPageFootnoteHeight = allBlockFootnoteIds.length > 0 ? this.measureFootnotesHeight(allBlockFootnoteIds, dims.contentWidth, currentContinuation) : currentContinuation ? this.measureContinuationHeight(currentContinuation, dims.contentWidth) : 0;
2707
- const newPageSpace = block.marginTopPt + block.heightPt + block.marginBottomPt;
2708
- currentContent.push(block.element.cloneNode(true));
2841
+ const newPageMarginTop = this.effectiveBlockMarginTop(
2842
+ block,
2843
+ 0,
2844
+ true,
2845
+ pageInSection
2846
+ );
2847
+ const newPageSpace = newPageMarginTop + block.heightPt + block.marginBottomPt;
2848
+ currentContent.push(this.cloneBlockForPage(block, true, pageInSection));
2709
2849
  remainingHeight = effectiveContentHeight - newPageSpace;
2710
2850
  prevMarginBottomPt = block.marginBottomPt;
2711
2851
  currentFootnoteIds = [...currentFootnoteIds, ...allBlockFootnoteIds];
@@ -2724,7 +2864,7 @@ var Docxodus = (() => {
2724
2864
  if (currentContent.length > 0) {
2725
2865
  finishPage();
2726
2866
  }
2727
- currentContent.push(block.element.cloneNode(true));
2867
+ currentContent.push(this.cloneBlockForPage(block, true, pageInSection));
2728
2868
  currentFootnoteIds = [...currentFootnoteIds, ...allBlockFootnoteIds];
2729
2869
  finishPage();
2730
2870
  }
@@ -9254,6 +9394,7 @@ var Docxodus = (() => {
9254
9394
  color: var(--dxr-ink);
9255
9395
  background: var(--dxr-desk);
9256
9396
  -webkit-text-size-adjust: 100%;
9397
+ text-size-adjust: 100%;
9257
9398
  }
9258
9399
  .dxr *, .dxr *::before, .dxr *::after { box-sizing: border-box; }
9259
9400
 
@@ -125,6 +125,9 @@ var DocxodusPagination = (() => {
125
125
  }
126
126
 
127
127
  // src/pagination.ts
128
+ function samePageBox(a, b) {
129
+ 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;
130
+ }
128
131
  var MAX_FOOTNOTE_AREA_RATIO = 0.6;
129
132
  var MIN_BODY_CONTENT_HEIGHT = 72;
130
133
  var PaginationEngine = class {
@@ -170,16 +173,43 @@ var DocxodusPagination = (() => {
170
173
  );
171
174
  this.pageNumbering = this.parsePageNumbering(sections);
172
175
  const sectionsToProcess = sections.length > 0 ? Array.from(sections) : [this.stagingElement];
176
+ const runs = [];
173
177
  for (const section of sectionsToProcess) {
174
- const sectionIndex = parseInt(section.dataset.sectionIndex || "0", 10);
175
178
  const dims = parseSectionDimensions(section);
179
+ const previous = runs[runs.length - 1];
180
+ if (previous && section.dataset.sectionType === "continuous" && samePageBox(previous.dims, dims)) {
181
+ previous.sections.push(section);
182
+ } else {
183
+ runs.push({
184
+ sections: [section],
185
+ sectionIndex: parseInt(section.dataset.sectionIndex || "0", 10),
186
+ dims
187
+ });
188
+ }
189
+ }
190
+ for (const run of runs) {
176
191
  this.stagingElement.style.visibility = "hidden";
177
192
  this.stagingElement.style.position = "absolute";
178
193
  this.stagingElement.style.left = "-9999px";
179
194
  this.stagingElement.style.display = "block";
180
- section.style.width = `${dims.contentWidth}pt`;
181
- const blocks = this.measureBlocks(section, dims);
182
- const sectionPages = this.flowToPages(blocks, dims, pageNumber, sectionIndex);
195
+ const blocks = [];
196
+ for (const section of run.sections) {
197
+ section.style.width = `${run.dims.contentWidth}pt`;
198
+ const columnCount = parseInt(section.dataset.cols || "1", 10);
199
+ if (columnCount > 1) {
200
+ const gap = parseFloat(section.dataset.colGap || "");
201
+ blocks.push(...this.buildColumnBlocks(
202
+ section,
203
+ run.dims,
204
+ run.sectionIndex,
205
+ columnCount,
206
+ Number.isFinite(gap) ? gap : 36
207
+ ));
208
+ } else {
209
+ blocks.push(...this.measureBlocks(section, run.dims));
210
+ }
211
+ }
212
+ const sectionPages = this.flowToPages(blocks, run.dims, pageNumber, run.sectionIndex);
183
213
  pages.push(...sectionPages);
184
214
  pageNumber += sectionPages.length;
185
215
  }
@@ -268,11 +298,70 @@ var DocxodusPagination = (() => {
268
298
  keepWithNext: child.dataset.keepWithNext === "true",
269
299
  keepLines: child.dataset.keepLines === "true",
270
300
  pageBreakBefore: child.dataset.pageBreakBefore === "true",
271
- isPageBreak
301
+ isPageBreak,
302
+ isWordParagraph: this.isWordParagraphElement(child)
272
303
  });
273
304
  }
274
305
  return blocks;
275
306
  }
307
+ /**
308
+ * Flows a multi-column (`w:cols`) section's children into CSS-multicol container
309
+ * blocks. Word lays such a section out as N columns inside the same body extent;
310
+ * a balanced `column-count` container reproduces that geometry, and the paginator
311
+ * then places each container as one ordinary measured block. A container grows
312
+ * greedily until its balanced height would exceed the smallest page body available
313
+ * to the section, so a long columned section still splits across pages at block
314
+ * boundaries. Each child lands in exactly one container, so anchors never
315
+ * duplicate. An explicit page break child passes through as its own block, which
316
+ * ends the current container and lets the normal flow logic turn the page.
317
+ */
318
+ buildColumnBlocks(section, dims, sectionIndex, columnCount, columnGapPt) {
319
+ const children = Array.from(section.children);
320
+ const blocks = [];
321
+ const maxFragmentHeight = this.smallestEffectiveContentHeight(dims, sectionIndex);
322
+ const isBreak = (child) => child.dataset.pageBreak === "true" || child.classList.contains(`${this.cssPrefix}break`);
323
+ const makeContainer = (slice) => {
324
+ const container = document.createElement("div");
325
+ container.style.columnCount = String(columnCount);
326
+ container.style.columnGap = `${columnGapPt}pt`;
327
+ for (const child of slice) {
328
+ container.appendChild(child.cloneNode(true));
329
+ }
330
+ return container;
331
+ };
332
+ let start = 0;
333
+ while (start < children.length) {
334
+ if (isBreak(children[start])) {
335
+ blocks.push(this.measureElement(children[start], dims));
336
+ start++;
337
+ continue;
338
+ }
339
+ let end = start + 1;
340
+ let container = makeContainer(children.slice(start, end));
341
+ let measured = this.measureElement(container, dims);
342
+ while (end < children.length && !isBreak(children[end])) {
343
+ const candidate = makeContainer(children.slice(start, end + 1));
344
+ const candidateMeasured = this.measureElement(candidate, dims);
345
+ if (candidateMeasured.heightPt > maxFragmentHeight) break;
346
+ container = candidate;
347
+ measured = candidateMeasured;
348
+ end++;
349
+ }
350
+ blocks.push({
351
+ element: container,
352
+ heightPt: measured.heightPt,
353
+ marginTopPt: measured.marginTopPt,
354
+ marginBottomPt: measured.marginBottomPt,
355
+ keepWithNext: false,
356
+ keepLines: false,
357
+ pageBreakBefore: false,
358
+ isPageBreak: false,
359
+ isWordParagraph: false
360
+ });
361
+ start = end;
362
+ }
363
+ return blocks;
364
+ }
276
365
  /**
277
366
  * Measures one element in the same hidden staging context used for the source blocks.
278
367
  * This is intentionally DOM-based: table row heights cannot be inferred from individual
@@ -297,7 +386,8 @@ var DocxodusPagination = (() => {
297
386
  keepWithNext: element.dataset.keepWithNext === "true",
298
387
  keepLines: element.dataset.keepLines === "true",
299
388
  pageBreakBefore: element.dataset.pageBreakBefore === "true",
300
- isPageBreak: element.dataset.pageBreak === "true" || element.classList.contains(`${this.cssPrefix}break`)
389
+ isPageBreak: element.dataset.pageBreak === "true" || element.classList.contains(`${this.cssPrefix}break`),
390
+ isWordParagraph: this.isWordParagraphElement(element)
301
391
  };
302
392
  this.stagingElement.removeChild(measurementHost);
303
393
  return measured;
@@ -329,10 +419,15 @@ var DocxodusPagination = (() => {
329
419
  * collapsed-margin rules as normal block placement. The trailing margin is
330
420
  * intentionally excluded, matching the individual block fit check.
331
421
  */
332
- measureKeepWithNextChainBodyHeight(chain, previousMarginBottomPt, isFirstOnPage) {
422
+ measureKeepWithNextChainBodyHeight(chain, previousMarginBottomPt, isFirstOnPage, pageInSection) {
333
423
  const firstBlock = chain[0];
334
424
  if (!firstBlock) return 0;
335
- const firstMarginTop = isFirstOnPage ? firstBlock.marginTopPt : Math.max(firstBlock.marginTopPt, previousMarginBottomPt) - previousMarginBottomPt;
425
+ const firstMarginTop = this.effectiveBlockMarginTop(
426
+ firstBlock,
427
+ previousMarginBottomPt,
428
+ isFirstOnPage,
429
+ pageInSection
430
+ );
336
431
  let bodyHeight = firstMarginTop + firstBlock.heightPt;
337
432
  for (let index = 1; index < chain.length; index++) {
338
433
  const previousBlock = chain[index - 1];
@@ -341,6 +436,35 @@ var DocxodusPagination = (() => {
341
436
  }
342
437
  return bodyHeight;
343
438
  }
439
+ /** Word paragraphs render as `p`, or as `h1`–`h6` when their style has an outline level. */
440
+ isWordParagraphElement(element) {
441
+ return element.tagName === "P" || /^H[1-6]$/.test(element.tagName);
442
+ }
443
+ shouldSuppressPageTopSpacing(block, isFirstOnPage, pageInSection) {
444
+ return isFirstOnPage && pageInSection > 1 && block.isWordParagraph === true;
445
+ }
446
+ /**
447
+ * Resolves the part of a block's top margin that consumes the current page.
448
+ *
449
+ * In Word's native DOCX layout, paragraph space-before is suppressed when a paragraph is the
450
+ * first body block on a later page of the SAME section. The first page of a document/section is
451
+ * the exception and keeps its spacing. Tables and other block margins are not paragraph spacing,
452
+ * so they continue to use the ordinary CSS collapsing rule.
453
+ */
454
+ effectiveBlockMarginTop(block, previousMarginBottomPt, isFirstOnPage, pageInSection) {
455
+ if (this.shouldSuppressPageTopSpacing(block, isFirstOnPage, pageInSection)) {
456
+ return 0;
457
+ }
458
+ return isFirstOnPage ? block.marginTopPt : Math.max(block.marginTopPt, previousMarginBottomPt) - previousMarginBottomPt;
459
+ }
460
+ /** Clone a source block with the same page-top spacing decision used by the height budget. */
461
+ cloneBlockForPage(block, isFirstOnPage, pageInSection) {
462
+ const clone = block.element.cloneNode(true);
463
+ if (this.shouldSuppressPageTopSpacing(block, isFirstOnPage, pageInSection)) {
464
+ clone.style.setProperty("margin-top", "0", "important");
465
+ }
466
+ return clone;
467
+ }
344
468
  /**
345
469
  * Finds footnote references introduced by a sequence of blocks, preserving
346
470
  * document order and excluding references already assigned to the page.
@@ -1146,7 +1270,8 @@ var DocxodusPagination = (() => {
1146
1270
  const currentChainHeight = this.measureKeepWithNextChainBodyHeight(
1147
1271
  keepChain,
1148
1272
  prevMarginBottomPt,
1149
- currentContent.length === 0
1273
+ currentContent.length === 0,
1274
+ pageInSection
1150
1275
  ) + additionalChainFootnoteHeight;
1151
1276
  const currentAvailableHeight = remainingHeight - currentFootnoteHeight;
1152
1277
  if (currentChainHeight > currentAvailableHeight) {
@@ -1159,7 +1284,8 @@ var DocxodusPagination = (() => {
1159
1284
  const freshChainBodyHeight = this.measureKeepWithNextChainBodyHeight(
1160
1285
  keepChain,
1161
1286
  0,
1162
- true
1287
+ true,
1288
+ pageInSection + 1
1163
1289
  );
1164
1290
  const freshChainFootnoteHeight = this.measureFootnotesHeight(
1165
1291
  newChainFootnoteIds,
@@ -1185,10 +1311,12 @@ var DocxodusPagination = (() => {
1185
1311
  additionalFootnoteHeight = totalFootnoteHeight - currentFootnoteHeight;
1186
1312
  }
1187
1313
  const isFirstOnPage = currentContent.length === 0;
1188
- let effectiveMarginTop = block.marginTopPt;
1189
- if (!isFirstOnPage) {
1190
- effectiveMarginTop = Math.max(block.marginTopPt, prevMarginBottomPt) - prevMarginBottomPt;
1191
- }
1314
+ const effectiveMarginTop = this.effectiveBlockMarginTop(
1315
+ block,
1316
+ prevMarginBottomPt,
1317
+ isFirstOnPage,
1318
+ pageInSection
1319
+ );
1192
1320
  const blockSpace = effectiveMarginTop + block.heightPt + additionalFootnoteHeight;
1193
1321
  const effectiveRemainingHeight = remainingHeight - currentFootnoteHeight;
1194
1322
  const bodyContentUsed = effectiveContentHeight - remainingHeight;
@@ -1208,19 +1336,19 @@ var DocxodusPagination = (() => {
1208
1336
  }
1209
1337
  }
1210
1338
  if (blockSpace <= effectiveRemainingHeight) {
1211
- currentContent.push(block.element.cloneNode(true));
1339
+ currentContent.push(this.cloneBlockForPage(block, isFirstOnPage, pageInSection));
1212
1340
  remainingHeight -= effectiveMarginTop + block.heightPt + block.marginBottomPt;
1213
1341
  prevMarginBottomPt = block.marginBottomPt;
1214
1342
  if (newFootnoteIds.length > 0) {
1215
1343
  currentFootnoteIds.push(...newFootnoteIds);
1216
1344
  currentFootnoteHeight += additionalFootnoteHeight;
1217
1345
  }
1218
- } else if (block.heightPt + block.marginTopPt <= effectiveContentHeight) {
1346
+ } else if (block.heightPt + this.effectiveBlockMarginTop(block, 0, true, pageInSection + 1) <= effectiveContentHeight) {
1219
1347
  const blockSpaceWithoutFootnotes = effectiveMarginTop + block.heightPt;
1220
1348
  const minBodySpaceNeeded = bodyContentUsed + blockSpaceWithoutFootnotes + MIN_BODY_CONTENT_HEIGHT;
1221
1349
  const canExpandFootnotes = minBodySpaceNeeded <= effectiveContentHeight;
1222
1350
  if (newFootnoteIds.length > 0 && blockSpaceWithoutFootnotes <= effectiveRemainingHeight) {
1223
- currentContent.push(block.element.cloneNode(true));
1351
+ currentContent.push(this.cloneBlockForPage(block, isFirstOnPage, pageInSection));
1224
1352
  remainingHeight -= effectiveMarginTop + block.heightPt + block.marginBottomPt;
1225
1353
  prevMarginBottomPt = block.marginBottomPt;
1226
1354
  const availableForFootnotes = Math.min(
@@ -1267,7 +1395,7 @@ var DocxodusPagination = (() => {
1267
1395
  const expandedFootnoteSpace = Math.min(maxFootnoteArea, additionalFootnoteHeight + currentFootnoteHeight);
1268
1396
  const bodySpaceAfterExpansion = effectiveContentHeight - expandedFootnoteSpace;
1269
1397
  if (blockSpaceWithoutFootnotes <= bodySpaceAfterExpansion - bodyContentUsed) {
1270
- currentContent.push(block.element.cloneNode(true));
1398
+ currentContent.push(this.cloneBlockForPage(block, isFirstOnPage, pageInSection));
1271
1399
  remainingHeight = bodySpaceAfterExpansion - bodyContentUsed - blockSpaceWithoutFootnotes;
1272
1400
  prevMarginBottomPt = block.marginBottomPt;
1273
1401
  currentFootnoteIds.push(...newFootnoteIds);
@@ -1275,8 +1403,14 @@ var DocxodusPagination = (() => {
1275
1403
  } else {
1276
1404
  finishPage();
1277
1405
  const newPageFootnoteHeight = allBlockFootnoteIds.length > 0 ? this.measureFootnotesHeight(allBlockFootnoteIds, dims.contentWidth, currentContinuation) : currentContinuation ? this.measureContinuationHeight(currentContinuation, dims.contentWidth) : 0;
1278
- const newPageSpace = block.marginTopPt + block.heightPt + block.marginBottomPt;
1279
- currentContent.push(block.element.cloneNode(true));
1406
+ const newPageMarginTop = this.effectiveBlockMarginTop(
1407
+ block,
1408
+ 0,
1409
+ true,
1410
+ pageInSection
1411
+ );
1412
+ const newPageSpace = newPageMarginTop + block.heightPt + block.marginBottomPt;
1413
+ currentContent.push(this.cloneBlockForPage(block, true, pageInSection));
1280
1414
  remainingHeight = effectiveContentHeight - newPageSpace;
1281
1415
  prevMarginBottomPt = block.marginBottomPt;
1282
1416
  currentFootnoteIds = [...currentFootnoteIds, ...allBlockFootnoteIds];
@@ -1285,8 +1419,14 @@ var DocxodusPagination = (() => {
1285
1419
  } else {
1286
1420
  finishPage();
1287
1421
  const newPageFootnoteHeight = allBlockFootnoteIds.length > 0 ? this.measureFootnotesHeight(allBlockFootnoteIds, dims.contentWidth, currentContinuation) : currentContinuation ? this.measureContinuationHeight(currentContinuation, dims.contentWidth) : 0;
1288
- const newPageSpace = block.marginTopPt + block.heightPt + block.marginBottomPt;
1289
- currentContent.push(block.element.cloneNode(true));
1422
+ const newPageMarginTop = this.effectiveBlockMarginTop(
1423
+ block,
1424
+ 0,
1425
+ true,
1426
+ pageInSection
1427
+ );
1428
+ const newPageSpace = newPageMarginTop + block.heightPt + block.marginBottomPt;
1429
+ currentContent.push(this.cloneBlockForPage(block, true, pageInSection));
1290
1430
  remainingHeight = effectiveContentHeight - newPageSpace;
1291
1431
  prevMarginBottomPt = block.marginBottomPt;
1292
1432
  currentFootnoteIds = [...currentFootnoteIds, ...allBlockFootnoteIds];
@@ -1305,7 +1445,7 @@ var DocxodusPagination = (() => {
1305
1445
  if (currentContent.length > 0) {
1306
1446
  finishPage();
1307
1447
  }
1308
- currentContent.push(block.element.cloneNode(true));
1448
+ currentContent.push(this.cloneBlockForPage(block, true, pageInSection));
1309
1449
  currentFootnoteIds = [...currentFootnoteIds, ...allBlockFootnoteIds];
1310
1450
  finishPage();
1311
1451
  }
@@ -59,6 +59,8 @@ export interface MeasuredBlock {
59
59
  pageBreakBefore: boolean;
60
60
  /** Whether this is a page break marker */
61
61
  isPageBreak: boolean;
62
+ /** Whether the block is a Word paragraph whose top margin represents paragraph space-before */
63
+ isWordParagraph?: boolean;
62
64
  }
63
65
  /**
64
66
  * Information about a rendered page.
@@ -162,6 +164,18 @@ export declare class PaginationEngine {
162
164
  * Measures all content blocks in a section.
163
165
  */
164
166
  private measureBlocks;
167
+ /**
168
+ * Flows a multi-column (`w:cols`) section's children into CSS-multicol container
169
+ * blocks. Word lays such a section out as N columns inside the same body extent;
170
+ * a balanced `column-count` container reproduces that geometry, and the paginator
171
+ * then places each container as one ordinary measured block. A container grows
172
+ * greedily until its balanced height would exceed the smallest page body available
173
+ * to the section, so a long columned section still splits across pages at block
174
+ * boundaries. Each child lands in exactly one container, so anchors never
175
+ * duplicate. An explicit page break child passes through as its own block, which
176
+ * ends the current container and lets the normal flow logic turn the page.
177
+ */
178
+ private buildColumnBlocks;
165
179
  /**
166
180
  * Measures one element in the same hidden staging context used for the source blocks.
167
181
  * This is intentionally DOM-based: table row heights cannot be inferred from individual
@@ -182,6 +196,20 @@ export declare class PaginationEngine {
182
196
  * intentionally excluded, matching the individual block fit check.
183
197
  */
184
198
  private measureKeepWithNextChainBodyHeight;
199
+ /** Word paragraphs render as `p`, or as `h1`–`h6` when their style has an outline level. */
200
+ private isWordParagraphElement;
201
+ private shouldSuppressPageTopSpacing;
202
+ /**
203
+ * Resolves the part of a block's top margin that consumes the current page.
204
+ *
205
+ * In Word's native DOCX layout, paragraph space-before is suppressed when a paragraph is the
206
+ * first body block on a later page of the SAME section. The first page of a document/section is
207
+ * the exception and keeps its spacing. Tables and other block margins are not paragraph spacing,
208
+ * so they continue to use the ordinary CSS collapsing rule.
209
+ */
210
+ private effectiveBlockMarginTop;
211
+ /** Clone a source block with the same page-top spacing decision used by the height budget. */
212
+ private cloneBlockForPage;
185
213
  /**
186
214
  * Finds footnote references introduced by a sequence of blocks, preserving
187
215
  * document order and excluding references already assigned to the page.