docxodus 9.7.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 (101) hide show
  1. package/LICENSE +22 -0
  2. package/README.md +0 -1
  3. package/dist/editor.bundle.js +454 -99
  4. package/dist/editor.d.ts +4 -4
  5. package/dist/editor.d.ts.map +1 -1
  6. package/dist/editor.js +55 -8
  7. package/dist/editor.js.map +1 -1
  8. package/dist/embed.bundle.js +454 -99
  9. package/dist/embed.iife.js +454 -99
  10. package/dist/page-geometry.d.ts +50 -3
  11. package/dist/page-geometry.d.ts.map +1 -1
  12. package/dist/page-geometry.js +63 -6
  13. package/dist/page-geometry.js.map +1 -1
  14. package/dist/pagination.bundle.js +416 -91
  15. package/dist/pagination.d.ts +61 -8
  16. package/dist/pagination.d.ts.map +1 -1
  17. package/dist/pagination.js +353 -105
  18. package/dist/pagination.js.map +1 -1
  19. package/dist/ribbon-chrome.d.ts +1 -1
  20. package/dist/ribbon-chrome.d.ts.map +1 -1
  21. package/dist/ribbon-chrome.js +1 -0
  22. package/dist/ribbon-chrome.js.map +1 -1
  23. package/dist/wasm/_framework/Docxodus.wasm +0 -0
  24. package/dist/wasm/_framework/Docxodus.wasm.br +0 -0
  25. package/dist/wasm/_framework/DocxodusWasm.wasm +0 -0
  26. package/dist/wasm/_framework/DocxodusWasm.wasm.br +0 -0
  27. package/dist/wasm/_framework/System.Collections.Concurrent.wasm +0 -0
  28. package/dist/wasm/_framework/System.Collections.Concurrent.wasm.br +0 -0
  29. package/dist/wasm/_framework/System.Collections.Immutable.wasm +0 -0
  30. package/dist/wasm/_framework/System.Collections.Immutable.wasm.br +0 -0
  31. package/dist/wasm/_framework/System.Collections.NonGeneric.wasm +0 -0
  32. package/dist/wasm/_framework/System.Collections.NonGeneric.wasm.br +0 -0
  33. package/dist/wasm/_framework/System.Collections.Specialized.wasm +0 -0
  34. package/dist/wasm/_framework/System.Collections.Specialized.wasm.br +0 -0
  35. package/dist/wasm/_framework/System.Collections.wasm +0 -0
  36. package/dist/wasm/_framework/System.Collections.wasm.br +0 -0
  37. package/dist/wasm/_framework/System.ComponentModel.Primitives.wasm +0 -0
  38. package/dist/wasm/_framework/System.ComponentModel.Primitives.wasm.br +0 -0
  39. package/dist/wasm/_framework/System.ComponentModel.TypeConverter.wasm +0 -0
  40. package/dist/wasm/_framework/System.ComponentModel.TypeConverter.wasm.br +0 -0
  41. package/dist/wasm/_framework/System.ComponentModel.wasm +0 -0
  42. package/dist/wasm/_framework/System.ComponentModel.wasm.br +0 -0
  43. package/dist/wasm/_framework/System.Console.wasm +0 -0
  44. package/dist/wasm/_framework/System.Console.wasm.br +0 -0
  45. package/dist/wasm/_framework/System.Diagnostics.Process.wasm +0 -0
  46. package/dist/wasm/_framework/System.Diagnostics.Process.wasm.br +0 -0
  47. package/dist/wasm/_framework/System.IO.Compression.wasm +0 -0
  48. package/dist/wasm/_framework/System.IO.Compression.wasm.br +0 -0
  49. package/dist/wasm/_framework/System.IO.Pipelines.wasm +0 -0
  50. package/dist/wasm/_framework/System.IO.Pipelines.wasm.br +0 -0
  51. package/dist/wasm/_framework/System.Linq.Expressions.wasm +0 -0
  52. package/dist/wasm/_framework/System.Linq.Expressions.wasm.br +0 -0
  53. package/dist/wasm/_framework/System.Linq.wasm +0 -0
  54. package/dist/wasm/_framework/System.Linq.wasm.br +0 -0
  55. package/dist/wasm/_framework/System.Memory.wasm +0 -0
  56. package/dist/wasm/_framework/System.Memory.wasm.br +0 -0
  57. package/dist/wasm/_framework/System.Net.Http.wasm +0 -0
  58. package/dist/wasm/_framework/System.Net.Http.wasm.br +0 -0
  59. package/dist/wasm/_framework/System.Net.Primitives.wasm +0 -0
  60. package/dist/wasm/_framework/System.Net.Primitives.wasm.br +0 -0
  61. package/dist/wasm/_framework/System.ObjectModel.wasm +0 -0
  62. package/dist/wasm/_framework/System.ObjectModel.wasm.br +0 -0
  63. package/dist/wasm/_framework/System.Private.CoreLib.wasm +0 -0
  64. package/dist/wasm/_framework/System.Private.CoreLib.wasm.br +0 -0
  65. package/dist/wasm/_framework/System.Private.Uri.wasm +0 -0
  66. package/dist/wasm/_framework/System.Private.Uri.wasm.br +0 -0
  67. package/dist/wasm/_framework/System.Private.Xml.Linq.wasm +0 -0
  68. package/dist/wasm/_framework/System.Private.Xml.Linq.wasm.br +0 -0
  69. package/dist/wasm/_framework/System.Private.Xml.wasm +0 -0
  70. package/dist/wasm/_framework/System.Private.Xml.wasm.br +0 -0
  71. package/dist/wasm/_framework/System.Runtime.InteropServices.JavaScript.wasm +0 -0
  72. package/dist/wasm/_framework/System.Runtime.InteropServices.JavaScript.wasm.br +0 -0
  73. package/dist/wasm/_framework/System.Runtime.wasm +0 -0
  74. package/dist/wasm/_framework/System.Runtime.wasm.br +0 -0
  75. package/dist/wasm/_framework/System.Security.Cryptography.wasm +0 -0
  76. package/dist/wasm/_framework/System.Security.Cryptography.wasm.br +0 -0
  77. package/dist/wasm/_framework/System.Text.Encodings.Web.wasm +0 -0
  78. package/dist/wasm/_framework/System.Text.Encodings.Web.wasm.br +0 -0
  79. package/dist/wasm/_framework/System.Text.Json.wasm +0 -0
  80. package/dist/wasm/_framework/System.Text.Json.wasm.br +0 -0
  81. package/dist/wasm/_framework/System.Text.RegularExpressions.wasm +0 -0
  82. package/dist/wasm/_framework/System.Text.RegularExpressions.wasm.br +0 -0
  83. package/dist/wasm/_framework/System.Xml.Linq.wasm +0 -0
  84. package/dist/wasm/_framework/System.Xml.Linq.wasm.br +0 -0
  85. package/dist/wasm/_framework/System.Xml.XDocument.wasm +0 -0
  86. package/dist/wasm/_framework/System.Xml.XDocument.wasm.br +0 -0
  87. package/dist/wasm/_framework/System.wasm +0 -0
  88. package/dist/wasm/_framework/System.wasm.br +0 -0
  89. package/dist/wasm/_framework/dotnet.boot.js +35 -35
  90. package/dist/wasm/_framework/dotnet.boot.js.br +0 -0
  91. package/dist/wasm/_framework/dotnet.js +1 -1
  92. package/dist/wasm/_framework/dotnet.js.br +0 -0
  93. package/dist/wasm/_framework/dotnet.native.js +3 -3
  94. package/dist/wasm/_framework/dotnet.native.js.br +0 -0
  95. package/dist/wasm/_framework/dotnet.native.wasm +0 -0
  96. package/dist/wasm/_framework/dotnet.native.wasm.br +0 -0
  97. package/dist/wasm/_framework/dotnet.runtime.js +1 -1
  98. package/dist/wasm/_framework/dotnet.runtime.js.br +0 -0
  99. package/package.json +14 -5
  100. package/dist/wasm/index.html +0 -238
  101. package/dist/wasm/main.js +0 -26
@@ -1450,7 +1450,7 @@ var Docxodus = (() => {
1450
1450
  var DEFAULT_PAGE_WIDTH = 612;
1451
1451
  var DEFAULT_PAGE_HEIGHT = 792;
1452
1452
  var DEFAULT_MARGIN = 72;
1453
- var DEFAULT_HEADER_FOOTER_HEIGHT = 36;
1453
+ var DEFAULT_HEADER_FOOTER_DISTANCE = 36;
1454
1454
  function pxToPt(px) {
1455
1455
  return px * 0.75;
1456
1456
  }
@@ -1466,8 +1466,8 @@ var Docxodus = (() => {
1466
1466
  const marginRight = parseFloat(section.dataset.marginRight || "") || DEFAULT_MARGIN;
1467
1467
  const marginBottom = parseFloat(section.dataset.marginBottom || "") || DEFAULT_MARGIN;
1468
1468
  const marginLeft = parseFloat(section.dataset.marginLeft || "") || DEFAULT_MARGIN;
1469
- const headerHeight = parseFloat(section.dataset.headerHeight || "") || DEFAULT_HEADER_FOOTER_HEIGHT;
1470
- const footerHeight = parseFloat(section.dataset.footerHeight || "") || DEFAULT_HEADER_FOOTER_HEIGHT;
1469
+ const headerDistance = parseFloat(section.dataset.headerHeight || "") || DEFAULT_HEADER_FOOTER_DISTANCE;
1470
+ const footerDistance = parseFloat(section.dataset.footerHeight || "") || DEFAULT_HEADER_FOOTER_DISTANCE;
1471
1471
  return {
1472
1472
  pageWidth,
1473
1473
  pageHeight,
@@ -1477,8 +1477,37 @@ var Docxodus = (() => {
1477
1477
  marginRight,
1478
1478
  marginBottom,
1479
1479
  marginLeft,
1480
- headerHeight,
1481
- footerHeight
1480
+ headerDistance,
1481
+ footerDistance,
1482
+ headerHeight: headerDistance,
1483
+ footerHeight: footerDistance
1484
+ };
1485
+ }
1486
+ var MIN_BODY_BAND_PT = 12;
1487
+ function resolvePageBands(dims, headerContentHeight, footerContentHeight) {
1488
+ const headerTop = dims.headerDistance;
1489
+ const footerBottom = dims.pageHeight - dims.footerDistance;
1490
+ const headerReach = headerContentHeight > 0 ? headerTop + headerContentHeight : 0;
1491
+ const footerReach = footerContentHeight > 0 ? footerBottom - footerContentHeight : dims.pageHeight;
1492
+ let bodyTop = Math.max(dims.marginTop, headerReach);
1493
+ let bodyBottom = Math.min(dims.pageHeight - dims.marginBottom, footerReach);
1494
+ const shortfall = MIN_BODY_BAND_PT - (bodyBottom - bodyTop);
1495
+ if (shortfall > 0) {
1496
+ const headerGrowth = bodyTop - dims.marginTop;
1497
+ const footerGrowth = dims.pageHeight - dims.marginBottom - bodyBottom;
1498
+ const growth = headerGrowth + footerGrowth;
1499
+ if (growth > 0) {
1500
+ bodyTop -= shortfall * headerGrowth / growth;
1501
+ bodyBottom += shortfall * footerGrowth / growth;
1502
+ }
1503
+ }
1504
+ return {
1505
+ headerTop,
1506
+ headerHeight: Math.max(0, Math.min(headerContentHeight, bodyTop - headerTop)),
1507
+ bodyTop,
1508
+ bodyHeight: Math.max(0, bodyBottom - bodyTop),
1509
+ footerTop: footerBottom - Math.max(0, Math.min(footerContentHeight, footerBottom - bodyBottom)),
1510
+ footerHeight: Math.max(0, Math.min(footerContentHeight, footerBottom - bodyBottom))
1482
1511
  };
1483
1512
  }
1484
1513
  function sectionWrappers(root) {
@@ -1515,6 +1544,9 @@ var Docxodus = (() => {
1515
1544
  }
1516
1545
 
1517
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
+ }
1518
1550
  var MAX_FOOTNOTE_AREA_RATIO = 0.6;
1519
1551
  var MIN_BODY_CONTENT_HEIGHT = 72;
1520
1552
  var PaginationEngine = class {
@@ -1560,16 +1592,43 @@ var Docxodus = (() => {
1560
1592
  );
1561
1593
  this.pageNumbering = this.parsePageNumbering(sections);
1562
1594
  const sectionsToProcess = sections.length > 0 ? Array.from(sections) : [this.stagingElement];
1595
+ const runs = [];
1563
1596
  for (const section of sectionsToProcess) {
1564
- const sectionIndex = parseInt(section.dataset.sectionIndex || "0", 10);
1565
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) {
1566
1610
  this.stagingElement.style.visibility = "hidden";
1567
1611
  this.stagingElement.style.position = "absolute";
1568
1612
  this.stagingElement.style.left = "-9999px";
1569
1613
  this.stagingElement.style.display = "block";
1570
- section.style.width = `${dims.contentWidth}pt`;
1571
- const blocks = this.measureBlocks(section, dims);
1572
- 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);
1573
1632
  pages.push(...sectionPages);
1574
1633
  pageNumber += sectionPages.length;
1575
1634
  }
@@ -1658,11 +1717,70 @@ var Docxodus = (() => {
1658
1717
  keepWithNext: child.dataset.keepWithNext === "true",
1659
1718
  keepLines: child.dataset.keepLines === "true",
1660
1719
  pageBreakBefore: child.dataset.pageBreakBefore === "true",
1661
- isPageBreak
1720
+ isPageBreak,
1721
+ isWordParagraph: this.isWordParagraphElement(child)
1662
1722
  });
1663
1723
  }
1664
1724
  return blocks;
1665
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
+ }
1666
1784
  /**
1667
1785
  * Measures one element in the same hidden staging context used for the source blocks.
1668
1786
  * This is intentionally DOM-based: table row heights cannot be inferred from individual
@@ -1687,7 +1805,8 @@ var Docxodus = (() => {
1687
1805
  keepWithNext: element.dataset.keepWithNext === "true",
1688
1806
  keepLines: element.dataset.keepLines === "true",
1689
1807
  pageBreakBefore: element.dataset.pageBreakBefore === "true",
1690
- 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)
1691
1810
  };
1692
1811
  this.stagingElement.removeChild(measurementHost);
1693
1812
  return measured;
@@ -1719,10 +1838,15 @@ var Docxodus = (() => {
1719
1838
  * collapsed-margin rules as normal block placement. The trailing margin is
1720
1839
  * intentionally excluded, matching the individual block fit check.
1721
1840
  */
1722
- measureKeepWithNextChainBodyHeight(chain, previousMarginBottomPt, isFirstOnPage) {
1841
+ measureKeepWithNextChainBodyHeight(chain, previousMarginBottomPt, isFirstOnPage, pageInSection) {
1723
1842
  const firstBlock = chain[0];
1724
1843
  if (!firstBlock) return 0;
1725
- 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
+ );
1726
1850
  let bodyHeight = firstMarginTop + firstBlock.heightPt;
1727
1851
  for (let index = 1; index < chain.length; index++) {
1728
1852
  const previousBlock = chain[index - 1];
@@ -1731,6 +1855,35 @@ var Docxodus = (() => {
1731
1855
  }
1732
1856
  return bodyHeight;
1733
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
+ }
1734
1887
  /**
1735
1888
  * Finds footnote references introduced by a sequence of blocks, preserving
1736
1889
  * document order and excluding references already assigned to the page.
@@ -1755,9 +1908,9 @@ var Docxodus = (() => {
1755
1908
  */
1756
1909
  smallestEffectiveContentHeight(dims, sectionIndex) {
1757
1910
  return Math.min(
1758
- this.getEffectiveHeights(dims, sectionIndex, 1, 1).contentHeight,
1759
- this.getEffectiveHeights(dims, sectionIndex, 2, 1).contentHeight,
1760
- this.getEffectiveHeights(dims, sectionIndex, 2, 2).contentHeight
1911
+ this.getPageBands(dims, sectionIndex, 1, 1).bodyHeight,
1912
+ this.getPageBands(dims, sectionIndex, 2, 1).bodyHeight,
1913
+ this.getPageBands(dims, sectionIndex, 2, 2).bodyHeight
1761
1914
  );
1762
1915
  }
1763
1916
  /**
@@ -2281,7 +2434,7 @@ var Docxodus = (() => {
2281
2434
  /**
2282
2435
  * Adds footnotes to a page container, including continuation content.
2283
2436
  */
2284
- addPageFootnotes(pageBox, footnoteIds, dims, footnoteHeight, continuation, partialFootnotes) {
2437
+ addPageFootnotes(pageBox, footnoteIds, dims, bands, footnoteHeight, continuation, partialFootnotes) {
2285
2438
  const hasContinuation = continuation && continuation.remainingElements.length > 0;
2286
2439
  if (footnoteIds.length === 0 && !hasContinuation) {
2287
2440
  return;
@@ -2292,12 +2445,12 @@ var Docxodus = (() => {
2292
2445
  const partialFootnoteIds = new Set(partialFootnotes?.map((p) => p.footnoteId) || []);
2293
2446
  const maxFootnoteHeight = Math.min(
2294
2447
  footnoteHeight,
2295
- dims.contentHeight * MAX_FOOTNOTE_AREA_RATIO
2448
+ bands.bodyHeight * MAX_FOOTNOTE_AREA_RATIO
2296
2449
  );
2297
2450
  const footnotesDiv = document.createElement("div");
2298
2451
  footnotesDiv.className = `${this.cssPrefix}footnotes`;
2299
2452
  footnotesDiv.style.position = "absolute";
2300
- footnotesDiv.style.bottom = `${dims.marginBottom}pt`;
2453
+ footnotesDiv.style.bottom = `${dims.pageHeight - (bands.bodyTop + bands.bodyHeight)}pt`;
2301
2454
  footnotesDiv.style.left = `${dims.marginLeft}pt`;
2302
2455
  footnotesDiv.style.width = `${dims.contentWidth}pt`;
2303
2456
  footnotesDiv.style.boxSizing = "border-box";
@@ -2371,50 +2524,51 @@ var Docxodus = (() => {
2371
2524
  return sectionHf.footerDefault;
2372
2525
  }
2373
2526
  /**
2374
- * Computes effective header, footer, and content heights for a specific page position.
2375
- * Uses pre-measured header/footer heights from the registry.
2376
- * This method is deterministic - same inputs always produce same outputs.
2377
- * This enables lazy loading compatibility since available height can be computed
2378
- * for any page position without knowing the page's content.
2527
+ * The header, body, and footer bands for one page position.
2528
+ *
2529
+ * The single owner of "where does anything sit vertically on this page" — placement in
2530
+ * {@link createPage}, the body budget the flow loop spends, and the note area's anchor all
2531
+ * read it, so those three cannot disagree about where the body ends.
2532
+ *
2533
+ * Deterministic: it depends only on the section's page setup and the registry's pre-measured
2534
+ * story heights, never on the page's content, which is what keeps it lazy-loading compatible.
2379
2535
  */
2380
- getEffectiveHeights(dims, sectionIndex, pageInSection, globalPageNumber) {
2536
+ getPageBands(dims, sectionIndex, pageInSection, globalPageNumber) {
2381
2537
  const sectionHf = this.hfRegistry.get(sectionIndex);
2382
- let headerHeight = dims.marginTop;
2383
- if (sectionHf) {
2384
- let measuredHeaderHeight;
2385
- if (pageInSection === 1 && sectionHf.headerFirstHeight != null) {
2386
- measuredHeaderHeight = sectionHf.headerFirstHeight;
2387
- } else if (globalPageNumber % 2 === 0 && sectionHf.headerEvenHeight != null) {
2388
- measuredHeaderHeight = sectionHf.headerEvenHeight;
2389
- } else if (sectionHf.headerDefaultHeight != null) {
2390
- measuredHeaderHeight = sectionHf.headerDefaultHeight;
2391
- }
2392
- if (measuredHeaderHeight != null) {
2393
- headerHeight = Math.max(dims.marginTop, measuredHeaderHeight);
2394
- }
2395
- }
2396
- let footerHeight = dims.marginBottom;
2397
- if (sectionHf) {
2398
- let measuredFooterHeight;
2399
- if (pageInSection === 1 && sectionHf.footerFirstHeight != null) {
2400
- measuredFooterHeight = sectionHf.footerFirstHeight;
2401
- } else if (globalPageNumber % 2 === 0 && sectionHf.footerEvenHeight != null) {
2402
- measuredFooterHeight = sectionHf.footerEvenHeight;
2403
- } else if (sectionHf.footerDefaultHeight != null) {
2404
- measuredFooterHeight = sectionHf.footerDefaultHeight;
2405
- }
2406
- if (measuredFooterHeight != null) {
2407
- footerHeight = Math.max(dims.marginBottom, measuredFooterHeight);
2408
- }
2409
- }
2410
- const headerExpansion = headerHeight - dims.marginTop;
2411
- const footerExpansion = footerHeight - dims.marginBottom;
2412
- const contentHeight = dims.contentHeight - headerExpansion - footerExpansion;
2413
- return { headerHeight, footerHeight, contentHeight };
2538
+ return resolvePageBands(
2539
+ dims,
2540
+ this.selectStoryHeight(
2541
+ sectionHf?.headerFirstHeight,
2542
+ sectionHf?.headerEvenHeight,
2543
+ sectionHf?.headerDefaultHeight,
2544
+ pageInSection,
2545
+ globalPageNumber
2546
+ ),
2547
+ this.selectStoryHeight(
2548
+ sectionHf?.footerFirstHeight,
2549
+ sectionHf?.footerEvenHeight,
2550
+ sectionHf?.footerDefaultHeight,
2551
+ pageInSection,
2552
+ globalPageNumber
2553
+ )
2554
+ );
2555
+ }
2556
+ /**
2557
+ * The measured height of the running story this page position selects, mirroring
2558
+ * {@link selectHeader}/{@link selectFooter}. Zero when the page has no such story.
2559
+ */
2560
+ selectStoryHeight(first, even, fallback, pageInSection, globalPageNumber) {
2561
+ if (pageInSection === 1 && first != null) return first;
2562
+ if (globalPageNumber % 2 === 0 && even != null) return even;
2563
+ return fallback ?? 0;
2414
2564
  }
2415
2565
  /**
2416
2566
  * Measures the content height of a header or footer element.
2417
- * This is needed because headers/footers can contain more content than fits in the margin area.
2567
+ *
2568
+ * This is what tells {@link resolvePageBands} whether the story stays inside its margin or
2569
+ * pushes the body, so it must measure the story ALONE — any padding added here would have to
2570
+ * be added to the rendered band too, and the two drifting apart is exactly how a header
2571
+ * silently starts overlapping body text.
2418
2572
  */
2419
2573
  measureHeaderFooterHeight(source, contentWidth) {
2420
2574
  const measureContainer = document.createElement("div");
@@ -2422,7 +2576,6 @@ var Docxodus = (() => {
2422
2576
  measureContainer.style.visibility = "hidden";
2423
2577
  measureContainer.style.width = `${contentWidth}pt`;
2424
2578
  measureContainer.style.left = "-9999px";
2425
- measureContainer.style.paddingBottom = "4pt";
2426
2579
  for (const child of Array.from(source.childNodes)) {
2427
2580
  measureContainer.appendChild(child.cloneNode(true));
2428
2581
  }
@@ -2442,7 +2595,7 @@ var Docxodus = (() => {
2442
2595
  let currentContent = [];
2443
2596
  let pageNumber = startPageNumber;
2444
2597
  let pageInSection = 1;
2445
- let { contentHeight: effectiveContentHeight } = this.getEffectiveHeights(
2598
+ let { bodyHeight: effectiveContentHeight } = this.getPageBands(
2446
2599
  dims,
2447
2600
  sectionIndex,
2448
2601
  pageInSection,
@@ -2477,8 +2630,8 @@ var Docxodus = (() => {
2477
2630
  pageNumber++;
2478
2631
  pageInSection++;
2479
2632
  currentContent = [];
2480
- const newHeights = this.getEffectiveHeights(dims, sectionIndex, pageInSection, pageNumber);
2481
- effectiveContentHeight = newHeights.contentHeight;
2633
+ const newBands = this.getPageBands(dims, sectionIndex, pageInSection, pageNumber);
2634
+ effectiveContentHeight = newBands.bodyHeight;
2482
2635
  remainingHeight = effectiveContentHeight;
2483
2636
  prevMarginBottomPt = 0;
2484
2637
  currentFootnoteIds = [];
@@ -2536,11 +2689,12 @@ var Docxodus = (() => {
2536
2689
  const currentChainHeight = this.measureKeepWithNextChainBodyHeight(
2537
2690
  keepChain,
2538
2691
  prevMarginBottomPt,
2539
- currentContent.length === 0
2692
+ currentContent.length === 0,
2693
+ pageInSection
2540
2694
  ) + additionalChainFootnoteHeight;
2541
2695
  const currentAvailableHeight = remainingHeight - currentFootnoteHeight;
2542
2696
  if (currentChainHeight > currentAvailableHeight) {
2543
- const nextPageHeights = this.getEffectiveHeights(
2697
+ const nextPageBands = this.getPageBands(
2544
2698
  dims,
2545
2699
  sectionIndex,
2546
2700
  pageInSection + 1,
@@ -2549,14 +2703,15 @@ var Docxodus = (() => {
2549
2703
  const freshChainBodyHeight = this.measureKeepWithNextChainBodyHeight(
2550
2704
  keepChain,
2551
2705
  0,
2552
- true
2706
+ true,
2707
+ pageInSection + 1
2553
2708
  );
2554
2709
  const freshChainFootnoteHeight = this.measureFootnotesHeight(
2555
2710
  newChainFootnoteIds,
2556
2711
  dims.contentWidth,
2557
2712
  nextPageContinuation
2558
2713
  );
2559
- if (freshChainBodyHeight + freshChainFootnoteHeight <= nextPageHeights.contentHeight) {
2714
+ if (freshChainBodyHeight + freshChainFootnoteHeight <= nextPageBands.bodyHeight) {
2560
2715
  finishPage();
2561
2716
  }
2562
2717
  }
@@ -2575,10 +2730,12 @@ var Docxodus = (() => {
2575
2730
  additionalFootnoteHeight = totalFootnoteHeight - currentFootnoteHeight;
2576
2731
  }
2577
2732
  const isFirstOnPage = currentContent.length === 0;
2578
- let effectiveMarginTop = block.marginTopPt;
2579
- if (!isFirstOnPage) {
2580
- effectiveMarginTop = Math.max(block.marginTopPt, prevMarginBottomPt) - prevMarginBottomPt;
2581
- }
2733
+ const effectiveMarginTop = this.effectiveBlockMarginTop(
2734
+ block,
2735
+ prevMarginBottomPt,
2736
+ isFirstOnPage,
2737
+ pageInSection
2738
+ );
2582
2739
  const blockSpace = effectiveMarginTop + block.heightPt + additionalFootnoteHeight;
2583
2740
  const effectiveRemainingHeight = remainingHeight - currentFootnoteHeight;
2584
2741
  const bodyContentUsed = effectiveContentHeight - remainingHeight;
@@ -2598,19 +2755,19 @@ var Docxodus = (() => {
2598
2755
  }
2599
2756
  }
2600
2757
  if (blockSpace <= effectiveRemainingHeight) {
2601
- currentContent.push(block.element.cloneNode(true));
2758
+ currentContent.push(this.cloneBlockForPage(block, isFirstOnPage, pageInSection));
2602
2759
  remainingHeight -= effectiveMarginTop + block.heightPt + block.marginBottomPt;
2603
2760
  prevMarginBottomPt = block.marginBottomPt;
2604
2761
  if (newFootnoteIds.length > 0) {
2605
2762
  currentFootnoteIds.push(...newFootnoteIds);
2606
2763
  currentFootnoteHeight += additionalFootnoteHeight;
2607
2764
  }
2608
- } else if (block.heightPt + block.marginTopPt <= effectiveContentHeight) {
2765
+ } else if (block.heightPt + this.effectiveBlockMarginTop(block, 0, true, pageInSection + 1) <= effectiveContentHeight) {
2609
2766
  const blockSpaceWithoutFootnotes = effectiveMarginTop + block.heightPt;
2610
2767
  const minBodySpaceNeeded = bodyContentUsed + blockSpaceWithoutFootnotes + MIN_BODY_CONTENT_HEIGHT;
2611
2768
  const canExpandFootnotes = minBodySpaceNeeded <= effectiveContentHeight;
2612
2769
  if (newFootnoteIds.length > 0 && blockSpaceWithoutFootnotes <= effectiveRemainingHeight) {
2613
- currentContent.push(block.element.cloneNode(true));
2770
+ currentContent.push(this.cloneBlockForPage(block, isFirstOnPage, pageInSection));
2614
2771
  remainingHeight -= effectiveMarginTop + block.heightPt + block.marginBottomPt;
2615
2772
  prevMarginBottomPt = block.marginBottomPt;
2616
2773
  const availableForFootnotes = Math.min(
@@ -2657,7 +2814,7 @@ var Docxodus = (() => {
2657
2814
  const expandedFootnoteSpace = Math.min(maxFootnoteArea, additionalFootnoteHeight + currentFootnoteHeight);
2658
2815
  const bodySpaceAfterExpansion = effectiveContentHeight - expandedFootnoteSpace;
2659
2816
  if (blockSpaceWithoutFootnotes <= bodySpaceAfterExpansion - bodyContentUsed) {
2660
- currentContent.push(block.element.cloneNode(true));
2817
+ currentContent.push(this.cloneBlockForPage(block, isFirstOnPage, pageInSection));
2661
2818
  remainingHeight = bodySpaceAfterExpansion - bodyContentUsed - blockSpaceWithoutFootnotes;
2662
2819
  prevMarginBottomPt = block.marginBottomPt;
2663
2820
  currentFootnoteIds.push(...newFootnoteIds);
@@ -2665,8 +2822,14 @@ var Docxodus = (() => {
2665
2822
  } else {
2666
2823
  finishPage();
2667
2824
  const newPageFootnoteHeight = allBlockFootnoteIds.length > 0 ? this.measureFootnotesHeight(allBlockFootnoteIds, dims.contentWidth, currentContinuation) : currentContinuation ? this.measureContinuationHeight(currentContinuation, dims.contentWidth) : 0;
2668
- const newPageSpace = block.marginTopPt + block.heightPt + block.marginBottomPt;
2669
- 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));
2670
2833
  remainingHeight = effectiveContentHeight - newPageSpace;
2671
2834
  prevMarginBottomPt = block.marginBottomPt;
2672
2835
  currentFootnoteIds = [...currentFootnoteIds, ...allBlockFootnoteIds];
@@ -2675,8 +2838,14 @@ var Docxodus = (() => {
2675
2838
  } else {
2676
2839
  finishPage();
2677
2840
  const newPageFootnoteHeight = allBlockFootnoteIds.length > 0 ? this.measureFootnotesHeight(allBlockFootnoteIds, dims.contentWidth, currentContinuation) : currentContinuation ? this.measureContinuationHeight(currentContinuation, dims.contentWidth) : 0;
2678
- const newPageSpace = block.marginTopPt + block.heightPt + block.marginBottomPt;
2679
- 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));
2680
2849
  remainingHeight = effectiveContentHeight - newPageSpace;
2681
2850
  prevMarginBottomPt = block.marginBottomPt;
2682
2851
  currentFootnoteIds = [...currentFootnoteIds, ...allBlockFootnoteIds];
@@ -2695,7 +2864,7 @@ var Docxodus = (() => {
2695
2864
  if (currentContent.length > 0) {
2696
2865
  finishPage();
2697
2866
  }
2698
- currentContent.push(block.element.cloneNode(true));
2867
+ currentContent.push(this.cloneBlockForPage(block, true, pageInSection));
2699
2868
  currentFootnoteIds = [...currentFootnoteIds, ...allBlockFootnoteIds];
2700
2869
  finishPage();
2701
2870
  }
@@ -2725,6 +2894,161 @@ var Docxodus = (() => {
2725
2894
  if (el.getAttribute("contenteditable") !== null) el.setAttribute("contenteditable", "false");
2726
2895
  }
2727
2896
  }
2897
+ /**
2898
+ * Resolves floating DrawingML objects after their anchor paragraphs have landed on a page.
2899
+ *
2900
+ * The converter deliberately carries the OOXML bases and offsets as data instead of flattening
2901
+ * them into CSS: page/margin/column bases belong to the page, while paragraph/line/character
2902
+ * bases belong to the laid-out anchor. Once both coordinate systems exist, promote the object
2903
+ * from the clipped text column into the page box and position it in one shared point space.
2904
+ */
2905
+ positionDrawingAnchors(pageBox, contentArea, dims, pageNumber) {
2906
+ const anchors = Array.from(
2907
+ contentArea.querySelectorAll('[data-docx-drawing-anchor="true"]')
2908
+ );
2909
+ if (anchors.length === 0) return;
2910
+ const pageRect = pageBox.getBoundingClientRect();
2911
+ const pixelsPerPoint = pageRect.width / dims.pageWidth;
2912
+ if (!(pixelsPerPoint > 0)) return;
2913
+ for (const anchor of anchors) {
2914
+ const staticRect = anchor.getBoundingClientRect();
2915
+ const paragraph = anchor.closest("p, h1, h2, h3, h4, h5, h6");
2916
+ const paragraphRect = paragraph?.getBoundingClientRect() ?? staticRect;
2917
+ const toPageX = (x) => (x - pageRect.left) / pixelsPerPoint;
2918
+ const toPageY = (y) => (y - pageRect.top) / pixelsPerPoint;
2919
+ const context = {
2920
+ staticLeft: toPageX(staticRect.left),
2921
+ staticTop: toPageY(staticRect.top),
2922
+ lineHeight: paragraph ? (parseFloat(getComputedStyle(paragraph).lineHeight) || staticRect.height) / pixelsPerPoint : staticRect.height / pixelsPerPoint,
2923
+ paragraphLeft: toPageX(paragraphRect.left),
2924
+ paragraphTop: toPageY(paragraphRect.top),
2925
+ paragraphWidth: paragraphRect.width / pixelsPerPoint,
2926
+ paragraphHeight: paragraphRect.height / pixelsPerPoint
2927
+ };
2928
+ const widthReference = this.horizontalAnchorReference(
2929
+ anchor.getAttribute("data-docx-anchor-width-relative") ?? "margin",
2930
+ dims,
2931
+ pageNumber,
2932
+ context
2933
+ );
2934
+ const widthPercent = this.anchorNumber(anchor, "width-percent");
2935
+ if (widthPercent !== void 0) {
2936
+ anchor.style.width = `${widthReference.size * widthPercent / 100}pt`;
2937
+ }
2938
+ const heightReference = this.verticalAnchorReference(
2939
+ anchor.getAttribute("data-docx-anchor-height-relative") ?? "margin",
2940
+ dims,
2941
+ pageNumber,
2942
+ context
2943
+ );
2944
+ const heightPercent = this.anchorNumber(anchor, "height-percent");
2945
+ if (heightPercent !== void 0 && anchor.dataset.docxAnchorAutofit !== "true") {
2946
+ anchor.style.height = `${heightReference.size * heightPercent / 100}pt`;
2947
+ }
2948
+ const sizedRect = anchor.getBoundingClientRect();
2949
+ const width = sizedRect.width / pixelsPerPoint;
2950
+ const height = sizedRect.height / pixelsPerPoint;
2951
+ const horizontal2 = this.horizontalAnchorReference(
2952
+ anchor.getAttribute("data-docx-anchor-h-relative") ?? "column",
2953
+ dims,
2954
+ pageNumber,
2955
+ context
2956
+ );
2957
+ const vertical2 = this.verticalAnchorReference(
2958
+ anchor.getAttribute("data-docx-anchor-v-relative") ?? "paragraph",
2959
+ dims,
2960
+ pageNumber,
2961
+ context
2962
+ );
2963
+ const left2 = this.resolveAnchorAxis(
2964
+ horizontal2,
2965
+ width,
2966
+ anchor.getAttribute("data-docx-anchor-h-align"),
2967
+ this.anchorNumber(anchor, "h-offset"),
2968
+ pageNumber
2969
+ );
2970
+ const top2 = this.resolveAnchorAxis(
2971
+ vertical2,
2972
+ height,
2973
+ anchor.getAttribute("data-docx-anchor-v-align"),
2974
+ this.anchorNumber(anchor, "v-offset"),
2975
+ pageNumber
2976
+ );
2977
+ pageBox.appendChild(anchor);
2978
+ anchor.style.position = "absolute";
2979
+ anchor.style.left = `${left2}pt`;
2980
+ anchor.style.top = `${top2}pt`;
2981
+ anchor.style.margin = "0";
2982
+ }
2983
+ }
2984
+ anchorNumber(anchor, suffix) {
2985
+ const raw = anchor.getAttribute(`data-docx-anchor-${suffix}`);
2986
+ if (raw === null) return void 0;
2987
+ const value = Number(raw);
2988
+ return Number.isFinite(value) ? value : void 0;
2989
+ }
2990
+ horizontalAnchorReference(relativeFrom, dims, pageNumber, context) {
2991
+ const leftMargin = { start: 0, size: dims.marginLeft };
2992
+ const rightMargin = {
2993
+ start: dims.marginLeft + dims.contentWidth,
2994
+ size: dims.marginRight
2995
+ };
2996
+ switch (relativeFrom) {
2997
+ case "page":
2998
+ return { start: 0, size: dims.pageWidth };
2999
+ case "character":
3000
+ return { start: context.staticLeft, size: 0 };
3001
+ case "leftMargin":
3002
+ return leftMargin;
3003
+ case "rightMargin":
3004
+ return rightMargin;
3005
+ case "insideMargin":
3006
+ return pageNumber % 2 === 1 ? leftMargin : rightMargin;
3007
+ case "outsideMargin":
3008
+ return pageNumber % 2 === 1 ? rightMargin : leftMargin;
3009
+ case "paragraph":
3010
+ return { start: context.paragraphLeft, size: context.paragraphWidth };
3011
+ case "column":
3012
+ case "margin":
3013
+ default:
3014
+ return { start: dims.marginLeft, size: dims.contentWidth };
3015
+ }
3016
+ }
3017
+ verticalAnchorReference(relativeFrom, dims, pageNumber, context) {
3018
+ const topMargin = { start: 0, size: dims.marginTop };
3019
+ const bottomMargin = {
3020
+ start: dims.marginTop + dims.contentHeight,
3021
+ size: dims.marginBottom
3022
+ };
3023
+ switch (relativeFrom) {
3024
+ case "page":
3025
+ return { start: 0, size: dims.pageHeight };
3026
+ case "paragraph":
3027
+ return { start: context.paragraphTop, size: context.paragraphHeight };
3028
+ case "line":
3029
+ return { start: context.staticTop, size: context.lineHeight };
3030
+ case "topMargin":
3031
+ return topMargin;
3032
+ case "bottomMargin":
3033
+ return bottomMargin;
3034
+ case "insideMargin":
3035
+ return pageNumber % 2 === 1 ? topMargin : bottomMargin;
3036
+ case "outsideMargin":
3037
+ return pageNumber % 2 === 1 ? bottomMargin : topMargin;
3038
+ case "margin":
3039
+ default:
3040
+ return { start: dims.marginTop, size: dims.contentHeight };
3041
+ }
3042
+ }
3043
+ resolveAnchorAxis(reference, objectSize, alignment, offset, pageNumber) {
3044
+ if (offset !== void 0) return reference.start + offset;
3045
+ let fraction = 0;
3046
+ if (alignment === "center") fraction = 0.5;
3047
+ else if (alignment === "right" || alignment === "bottom") fraction = 1;
3048
+ else if (alignment === "inside") fraction = pageNumber % 2 === 1 ? 0 : 1;
3049
+ else if (alignment === "outside") fraction = pageNumber % 2 === 1 ? 1 : 0;
3050
+ return reference.start + (reference.size - objectSize) * fraction;
3051
+ }
2728
3052
  /**
2729
3053
  * Creates a page container element.
2730
3054
  */
@@ -2751,22 +3075,22 @@ var Docxodus = (() => {
2751
3075
  pageBox.dataset.pageNumber = String(pageNumber);
2752
3076
  pageBox.dataset.sectionIndex = String(sectionIndex);
2753
3077
  pageBox.dataset.pageInSection = String(pageInSection);
2754
- const effectiveHeights = this.getEffectiveHeights(dims, sectionIndex, pageInSection, pageNumber);
3078
+ const bands = this.getPageBands(dims, sectionIndex, pageInSection, pageNumber);
2755
3079
  const headerSource = this.selectHeader(sectionIndex, pageInSection, pageNumber);
2756
3080
  if (headerSource) {
2757
3081
  const headerDiv = document.createElement("div");
2758
3082
  headerDiv.className = `${this.cssPrefix}header`;
2759
3083
  headerDiv.style.position = "absolute";
2760
- headerDiv.style.top = "0";
3084
+ headerDiv.style.top = `${bands.headerTop}pt`;
3085
+ headerDiv.style.bottom = "auto";
2761
3086
  headerDiv.style.left = `${dims.marginLeft}pt`;
2762
3087
  headerDiv.style.width = `${dims.contentWidth}pt`;
2763
- headerDiv.style.height = `${effectiveHeights.headerHeight}pt`;
3088
+ headerDiv.style.height = `${bands.headerHeight}pt`;
2764
3089
  headerDiv.style.overflow = "hidden";
2765
3090
  headerDiv.style.boxSizing = "border-box";
2766
3091
  headerDiv.style.display = "flex";
2767
3092
  headerDiv.style.flexDirection = "column";
2768
- headerDiv.style.justifyContent = "flex-end";
2769
- headerDiv.style.paddingBottom = "4pt";
3093
+ headerDiv.style.justifyContent = "flex-start";
2770
3094
  for (const child of Array.from(headerSource.childNodes)) {
2771
3095
  const clonedheaderDiv = child.cloneNode(true);
2772
3096
  if (clonedheaderDiv.nodeType === 1) this.makeClonedStoryInert(clonedheaderDiv);
@@ -2774,8 +3098,8 @@ var Docxodus = (() => {
2774
3098
  }
2775
3099
  pageBox.appendChild(headerDiv);
2776
3100
  }
2777
- const contentAreaTop = effectiveHeights.headerHeight;
2778
- const contentAreaHeight = effectiveHeights.contentHeight;
3101
+ const contentAreaTop = bands.bodyTop;
3102
+ const contentAreaHeight = bands.bodyHeight;
2779
3103
  const contentArea = document.createElement("div");
2780
3104
  contentArea.className = `${this.cssPrefix}content`;
2781
3105
  contentArea.style.position = "absolute";
@@ -2790,23 +3114,23 @@ var Docxodus = (() => {
2790
3114
  pageBox.appendChild(contentArea);
2791
3115
  const hasContinuation = continuation && continuation.remainingElements.length > 0;
2792
3116
  if (footnoteIds.length > 0 || hasContinuation) {
2793
- this.addPageFootnotes(pageBox, footnoteIds, dims, footnoteHeight, continuation, partialFootnotes);
3117
+ this.addPageFootnotes(pageBox, footnoteIds, dims, bands, footnoteHeight, continuation, partialFootnotes);
2794
3118
  }
2795
3119
  const footerSource = this.selectFooter(sectionIndex, pageInSection, pageNumber);
2796
3120
  if (footerSource) {
2797
3121
  const footerDiv = document.createElement("div");
2798
3122
  footerDiv.className = `${this.cssPrefix}footer`;
2799
3123
  footerDiv.style.position = "absolute";
2800
- footerDiv.style.bottom = "0";
3124
+ footerDiv.style.top = "auto";
3125
+ footerDiv.style.bottom = `${dims.footerDistance}pt`;
2801
3126
  footerDiv.style.left = `${dims.marginLeft}pt`;
2802
3127
  footerDiv.style.width = `${dims.contentWidth}pt`;
2803
- footerDiv.style.height = `${effectiveHeights.footerHeight}pt`;
3128
+ footerDiv.style.height = `${bands.footerHeight}pt`;
2804
3129
  footerDiv.style.overflow = "hidden";
2805
3130
  footerDiv.style.boxSizing = "border-box";
2806
3131
  footerDiv.style.display = "flex";
2807
3132
  footerDiv.style.flexDirection = "column";
2808
- footerDiv.style.justifyContent = "flex-start";
2809
- footerDiv.style.paddingTop = "4pt";
3133
+ footerDiv.style.justifyContent = "flex-end";
2810
3134
  for (const child of Array.from(footerSource.childNodes)) {
2811
3135
  const clonedfooterDiv = child.cloneNode(true);
2812
3136
  if (clonedfooterDiv.nodeType === 1) this.makeClonedStoryInert(clonedfooterDiv);
@@ -2821,6 +3145,7 @@ var Docxodus = (() => {
2821
3145
  pageBox.appendChild(pageNum);
2822
3146
  }
2823
3147
  this.containerElement.appendChild(pageBox);
3148
+ this.positionDrawingAnchors(pageBox, contentArea, dims, pageNumber);
2824
3149
  const notesEl = pageBox.querySelector(`.${this.cssPrefix}footnotes`);
2825
3150
  if (notesEl) {
2826
3151
  const notesHeightPt = pxToPt(notesEl.getBoundingClientRect().height);
@@ -6195,6 +6520,19 @@ var Docxodus = (() => {
6195
6520
  function isListBlock(block) {
6196
6521
  return !!block.querySelector(":scope > [data-list-marker]");
6197
6522
  }
6523
+ function ensureListMarkerSeparator(block) {
6524
+ const marker = block.querySelector(":scope > [data-list-marker]");
6525
+ if (!marker) return;
6526
+ const next = marker.nextElementSibling;
6527
+ if (next?.hasAttribute("data-editor-list-separator")) return;
6528
+ const separator = block.ownerDocument.createElement("span");
6529
+ separator.setAttribute("data-editor-list-separator", "");
6530
+ separator.setAttribute("data-list-marker", "true");
6531
+ separator.setAttribute("aria-hidden", "true");
6532
+ separator.style.display = "none";
6533
+ separator.textContent = " ";
6534
+ marker.after(separator);
6535
+ }
6198
6536
  var GENERATED_CHROME_SELECTOR = '[data-list-marker], a.footnote-ref, a.endnote-ref, a[class$="-backref"]';
6199
6537
  function isGeneratedChrome(node) {
6200
6538
  return node?.nodeType === 1 && !!node.matches?.(GENERATED_CHROME_SELECTOR);
@@ -7485,6 +7823,7 @@ var Docxodus = (() => {
7485
7823
  const unid = el.getAttribute("data-anchor");
7486
7824
  if (!unid || !this.anchorIdOf(el)) return;
7487
7825
  el.setAttribute("contenteditable", "true");
7826
+ ensureListMarkerSeparator(el);
7488
7827
  el.querySelectorAll(GENERATED_CHROME_SELECTOR).forEach((m) => m.setAttribute("contenteditable", "false"));
7489
7828
  el.dataset.committedText = blockContentText(el);
7490
7829
  el.addEventListener("focus", () => {
@@ -8347,8 +8686,8 @@ var Docxodus = (() => {
8347
8686
  * editable `data-anchor` blocks in the notes section, so editing it afterwards needs no new op.
8348
8687
  *
8349
8688
  * Body blocks only: Word disallows a note reference inside a header/footer story or inside
8350
- * another note, and the session rejects those with `AnchorWrongKind`. Remounts, because a new
8351
- * note renumbers the citations after it and can add a whole part.
8689
+ * another note, and the session rejects those with `AnchorWrongKind`. Reconciliation creates
8690
+ * the first notes section when needed and renumbers every affected citation in place.
8352
8691
  */
8353
8692
  insertFootnote(markdown = "New footnote.") {
8354
8693
  this.insertNote("footnote", markdown);
@@ -8740,7 +9079,7 @@ var Docxodus = (() => {
8740
9079
  * the current node — never a section div (multi-child) or the edit root. */
8741
9080
  unitWrapperOf(el) {
8742
9081
  let n = el;
8743
- while (n.parentElement && n.parentElement !== this.editRoot && n.parentElement.tagName === "DIV" && !n.parentElement.hasAttribute("data-anchor") && n.parentElement.childElementCount === 1) {
9082
+ while (n.parentElement && n.parentElement !== this.editRoot && n.parentElement.tagName === "DIV" && !n.parentElement.hasAttribute("data-section-index") && !n.parentElement.hasAttribute("data-anchor") && n.parentElement.childElementCount === 1) {
8744
9083
  n = n.parentElement;
8745
9084
  }
8746
9085
  return n;
@@ -8776,6 +9115,7 @@ var Docxodus = (() => {
8776
9115
  const pureRemoved = diff.removed.filter(
8777
9116
  (i) => !diff.substituted.some((s) => s.oldIndex === i) && !movedOld.has(i)
8778
9117
  );
9118
+ const appliedFresh = /* @__PURE__ */ new Set();
8779
9119
  const nodeAt = (j) => {
8780
9120
  const oi = diff.keep.get(j);
8781
9121
  if (oi !== void 0) return oldNodes[oi];
@@ -8783,7 +9123,7 @@ var Docxodus = (() => {
8783
9123
  if (movedOi !== void 0) return oldNodes[movedOi];
8784
9124
  if (subOldByNew.has(j)) return fresh.get(j);
8785
9125
  const f = fresh.get(j);
8786
- return f && f.isConnected ? f : null;
9126
+ return f && appliedFresh.has(j) ? f : null;
8787
9127
  };
8788
9128
  for (const j of pureAdded) {
8789
9129
  const el = fresh.get(j);
@@ -8798,6 +9138,7 @@ var Docxodus = (() => {
8798
9138
  if (prevW) prevW.after(el);
8799
9139
  else if (nextW) nextW.before(el);
8800
9140
  else return "insert with no anchored neighbor";
9141
+ appliedFresh.add(j);
8801
9142
  this.wireUnit(el, units[j]);
8802
9143
  }
8803
9144
  for (const i of pureRemoved) {
@@ -8836,13 +9177,13 @@ var Docxodus = (() => {
8836
9177
  if (anchorEl) this.wireBlock(anchorEl);
8837
9178
  root.querySelectorAll("[data-anchor]").forEach((b) => this.wireBlock(b));
8838
9179
  }
8839
- /** Old-sequence diff state for one notes section. `null` requests remount (DOM not
8840
- * stampable/consistent). */
9180
+ /** Old-sequence diff state for one notes section. `null` requests remount when an
9181
+ * existing list is not stampable; an absent list is the valid first-note case. */
8841
9182
  notesDiff(sectionClass, units) {
8842
9183
  const ol = this.editRoot.querySelector(`section.${sectionClass} > ol`);
8843
9184
  const lis = ol ? Array.from(ol.children).filter((c) => c.tagName === "LI") : [];
8844
9185
  if (units.length === 0 && lis.length === 0) return { lis, diff: diffUnits([], []) };
8845
- if (!ol) return null;
9186
+ if (!ol) return { lis, diff: diffUnits([], units) };
8846
9187
  const tokens = [];
8847
9188
  for (const li of lis) {
8848
9189
  const unid = li.getAttribute("data-note-anchor");
@@ -8859,7 +9200,20 @@ var Docxodus = (() => {
8859
9200
  this.editRoot.querySelector(`section.${sectionClass}`)?.remove();
8860
9201
  return;
8861
9202
  }
8862
- const ol = this.editRoot.querySelector(`section.${sectionClass} > ol`);
9203
+ let section = this.editRoot.querySelector(`:scope > section.${sectionClass}`);
9204
+ let ol = section?.querySelector(`:scope > ol`) ?? null;
9205
+ if (!ol) {
9206
+ if (!section) {
9207
+ section = document.createElement("section");
9208
+ section.className = sectionClass;
9209
+ const following = sectionClass === "footnotes" ? this.editRoot.querySelector(`:scope > section.endnotes`) : null;
9210
+ if (following) following.before(section);
9211
+ else this.editRoot.append(section);
9212
+ }
9213
+ if (!section.querySelector(":scope > hr")) section.prepend(document.createElement("hr"));
9214
+ ol = document.createElement("ol");
9215
+ section.append(ol);
9216
+ }
8863
9217
  const prefix = sectionClass === "footnotes" ? "fn" : "en";
8864
9218
  const nodes = [];
8865
9219
  for (let j = 0; j < units.length; j++) {
@@ -9040,6 +9394,7 @@ var Docxodus = (() => {
9040
9394
  color: var(--dxr-ink);
9041
9395
  background: var(--dxr-desk);
9042
9396
  -webkit-text-size-adjust: 100%;
9397
+ text-size-adjust: 100%;
9043
9398
  }
9044
9399
  .dxr *, .dxr *::before, .dxr *::after { box-sizing: border-box; }
9045
9400