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
@@ -1332,7 +1332,7 @@ function formatPageNumber(value, format) {
1332
1332
  var DEFAULT_PAGE_WIDTH = 612;
1333
1333
  var DEFAULT_PAGE_HEIGHT = 792;
1334
1334
  var DEFAULT_MARGIN = 72;
1335
- var DEFAULT_HEADER_FOOTER_HEIGHT = 36;
1335
+ var DEFAULT_HEADER_FOOTER_DISTANCE = 36;
1336
1336
  function pxToPt(px) {
1337
1337
  return px * 0.75;
1338
1338
  }
@@ -1348,8 +1348,8 @@ function parseSectionDimensions(section) {
1348
1348
  const marginRight = parseFloat(section.dataset.marginRight || "") || DEFAULT_MARGIN;
1349
1349
  const marginBottom = parseFloat(section.dataset.marginBottom || "") || DEFAULT_MARGIN;
1350
1350
  const marginLeft = parseFloat(section.dataset.marginLeft || "") || DEFAULT_MARGIN;
1351
- const headerHeight = parseFloat(section.dataset.headerHeight || "") || DEFAULT_HEADER_FOOTER_HEIGHT;
1352
- const footerHeight = parseFloat(section.dataset.footerHeight || "") || DEFAULT_HEADER_FOOTER_HEIGHT;
1351
+ const headerDistance = parseFloat(section.dataset.headerHeight || "") || DEFAULT_HEADER_FOOTER_DISTANCE;
1352
+ const footerDistance = parseFloat(section.dataset.footerHeight || "") || DEFAULT_HEADER_FOOTER_DISTANCE;
1353
1353
  return {
1354
1354
  pageWidth,
1355
1355
  pageHeight,
@@ -1359,8 +1359,37 @@ function parseSectionDimensions(section) {
1359
1359
  marginRight,
1360
1360
  marginBottom,
1361
1361
  marginLeft,
1362
- headerHeight,
1363
- footerHeight
1362
+ headerDistance,
1363
+ footerDistance,
1364
+ headerHeight: headerDistance,
1365
+ footerHeight: footerDistance
1366
+ };
1367
+ }
1368
+ var MIN_BODY_BAND_PT = 12;
1369
+ function resolvePageBands(dims, headerContentHeight, footerContentHeight) {
1370
+ const headerTop = dims.headerDistance;
1371
+ const footerBottom = dims.pageHeight - dims.footerDistance;
1372
+ const headerReach = headerContentHeight > 0 ? headerTop + headerContentHeight : 0;
1373
+ const footerReach = footerContentHeight > 0 ? footerBottom - footerContentHeight : dims.pageHeight;
1374
+ let bodyTop = Math.max(dims.marginTop, headerReach);
1375
+ let bodyBottom = Math.min(dims.pageHeight - dims.marginBottom, footerReach);
1376
+ const shortfall = MIN_BODY_BAND_PT - (bodyBottom - bodyTop);
1377
+ if (shortfall > 0) {
1378
+ const headerGrowth = bodyTop - dims.marginTop;
1379
+ const footerGrowth = dims.pageHeight - dims.marginBottom - bodyBottom;
1380
+ const growth = headerGrowth + footerGrowth;
1381
+ if (growth > 0) {
1382
+ bodyTop -= shortfall * headerGrowth / growth;
1383
+ bodyBottom += shortfall * footerGrowth / growth;
1384
+ }
1385
+ }
1386
+ return {
1387
+ headerTop,
1388
+ headerHeight: Math.max(0, Math.min(headerContentHeight, bodyTop - headerTop)),
1389
+ bodyTop,
1390
+ bodyHeight: Math.max(0, bodyBottom - bodyTop),
1391
+ footerTop: footerBottom - Math.max(0, Math.min(footerContentHeight, footerBottom - bodyBottom)),
1392
+ footerHeight: Math.max(0, Math.min(footerContentHeight, footerBottom - bodyBottom))
1364
1393
  };
1365
1394
  }
1366
1395
  function sectionWrappers(root) {
@@ -1397,6 +1426,9 @@ function applyZoom(el, scale, naturalPt) {
1397
1426
  }
1398
1427
 
1399
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
+ }
1400
1432
  var MAX_FOOTNOTE_AREA_RATIO = 0.6;
1401
1433
  var MIN_BODY_CONTENT_HEIGHT = 72;
1402
1434
  var PaginationEngine = class {
@@ -1442,16 +1474,43 @@ var PaginationEngine = class {
1442
1474
  );
1443
1475
  this.pageNumbering = this.parsePageNumbering(sections);
1444
1476
  const sectionsToProcess = sections.length > 0 ? Array.from(sections) : [this.stagingElement];
1477
+ const runs = [];
1445
1478
  for (const section of sectionsToProcess) {
1446
- const sectionIndex = parseInt(section.dataset.sectionIndex || "0", 10);
1447
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) {
1448
1492
  this.stagingElement.style.visibility = "hidden";
1449
1493
  this.stagingElement.style.position = "absolute";
1450
1494
  this.stagingElement.style.left = "-9999px";
1451
1495
  this.stagingElement.style.display = "block";
1452
- section.style.width = `${dims.contentWidth}pt`;
1453
- const blocks = this.measureBlocks(section, dims);
1454
- 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);
1455
1514
  pages.push(...sectionPages);
1456
1515
  pageNumber += sectionPages.length;
1457
1516
  }
@@ -1540,11 +1599,70 @@ var PaginationEngine = class {
1540
1599
  keepWithNext: child.dataset.keepWithNext === "true",
1541
1600
  keepLines: child.dataset.keepLines === "true",
1542
1601
  pageBreakBefore: child.dataset.pageBreakBefore === "true",
1543
- isPageBreak
1602
+ isPageBreak,
1603
+ isWordParagraph: this.isWordParagraphElement(child)
1544
1604
  });
1545
1605
  }
1546
1606
  return blocks;
1547
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
+ }
1548
1666
  /**
1549
1667
  * Measures one element in the same hidden staging context used for the source blocks.
1550
1668
  * This is intentionally DOM-based: table row heights cannot be inferred from individual
@@ -1569,7 +1687,8 @@ var PaginationEngine = class {
1569
1687
  keepWithNext: element.dataset.keepWithNext === "true",
1570
1688
  keepLines: element.dataset.keepLines === "true",
1571
1689
  pageBreakBefore: element.dataset.pageBreakBefore === "true",
1572
- 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)
1573
1692
  };
1574
1693
  this.stagingElement.removeChild(measurementHost);
1575
1694
  return measured;
@@ -1601,10 +1720,15 @@ var PaginationEngine = class {
1601
1720
  * collapsed-margin rules as normal block placement. The trailing margin is
1602
1721
  * intentionally excluded, matching the individual block fit check.
1603
1722
  */
1604
- measureKeepWithNextChainBodyHeight(chain, previousMarginBottomPt, isFirstOnPage) {
1723
+ measureKeepWithNextChainBodyHeight(chain, previousMarginBottomPt, isFirstOnPage, pageInSection) {
1605
1724
  const firstBlock = chain[0];
1606
1725
  if (!firstBlock) return 0;
1607
- 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
+ );
1608
1732
  let bodyHeight = firstMarginTop + firstBlock.heightPt;
1609
1733
  for (let index = 1; index < chain.length; index++) {
1610
1734
  const previousBlock = chain[index - 1];
@@ -1613,6 +1737,35 @@ var PaginationEngine = class {
1613
1737
  }
1614
1738
  return bodyHeight;
1615
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
+ }
1616
1769
  /**
1617
1770
  * Finds footnote references introduced by a sequence of blocks, preserving
1618
1771
  * document order and excluding references already assigned to the page.
@@ -1637,9 +1790,9 @@ var PaginationEngine = class {
1637
1790
  */
1638
1791
  smallestEffectiveContentHeight(dims, sectionIndex) {
1639
1792
  return Math.min(
1640
- this.getEffectiveHeights(dims, sectionIndex, 1, 1).contentHeight,
1641
- this.getEffectiveHeights(dims, sectionIndex, 2, 1).contentHeight,
1642
- this.getEffectiveHeights(dims, sectionIndex, 2, 2).contentHeight
1793
+ this.getPageBands(dims, sectionIndex, 1, 1).bodyHeight,
1794
+ this.getPageBands(dims, sectionIndex, 2, 1).bodyHeight,
1795
+ this.getPageBands(dims, sectionIndex, 2, 2).bodyHeight
1643
1796
  );
1644
1797
  }
1645
1798
  /**
@@ -2163,7 +2316,7 @@ var PaginationEngine = class {
2163
2316
  /**
2164
2317
  * Adds footnotes to a page container, including continuation content.
2165
2318
  */
2166
- addPageFootnotes(pageBox, footnoteIds, dims, footnoteHeight, continuation, partialFootnotes) {
2319
+ addPageFootnotes(pageBox, footnoteIds, dims, bands, footnoteHeight, continuation, partialFootnotes) {
2167
2320
  const hasContinuation = continuation && continuation.remainingElements.length > 0;
2168
2321
  if (footnoteIds.length === 0 && !hasContinuation) {
2169
2322
  return;
@@ -2174,12 +2327,12 @@ var PaginationEngine = class {
2174
2327
  const partialFootnoteIds = new Set(partialFootnotes?.map((p) => p.footnoteId) || []);
2175
2328
  const maxFootnoteHeight = Math.min(
2176
2329
  footnoteHeight,
2177
- dims.contentHeight * MAX_FOOTNOTE_AREA_RATIO
2330
+ bands.bodyHeight * MAX_FOOTNOTE_AREA_RATIO
2178
2331
  );
2179
2332
  const footnotesDiv = document.createElement("div");
2180
2333
  footnotesDiv.className = `${this.cssPrefix}footnotes`;
2181
2334
  footnotesDiv.style.position = "absolute";
2182
- footnotesDiv.style.bottom = `${dims.marginBottom}pt`;
2335
+ footnotesDiv.style.bottom = `${dims.pageHeight - (bands.bodyTop + bands.bodyHeight)}pt`;
2183
2336
  footnotesDiv.style.left = `${dims.marginLeft}pt`;
2184
2337
  footnotesDiv.style.width = `${dims.contentWidth}pt`;
2185
2338
  footnotesDiv.style.boxSizing = "border-box";
@@ -2253,50 +2406,51 @@ var PaginationEngine = class {
2253
2406
  return sectionHf.footerDefault;
2254
2407
  }
2255
2408
  /**
2256
- * Computes effective header, footer, and content heights for a specific page position.
2257
- * Uses pre-measured header/footer heights from the registry.
2258
- * This method is deterministic - same inputs always produce same outputs.
2259
- * This enables lazy loading compatibility since available height can be computed
2260
- * for any page position without knowing the page's content.
2409
+ * The header, body, and footer bands for one page position.
2410
+ *
2411
+ * The single owner of "where does anything sit vertically on this page" — placement in
2412
+ * {@link createPage}, the body budget the flow loop spends, and the note area's anchor all
2413
+ * read it, so those three cannot disagree about where the body ends.
2414
+ *
2415
+ * Deterministic: it depends only on the section's page setup and the registry's pre-measured
2416
+ * story heights, never on the page's content, which is what keeps it lazy-loading compatible.
2261
2417
  */
2262
- getEffectiveHeights(dims, sectionIndex, pageInSection, globalPageNumber) {
2418
+ getPageBands(dims, sectionIndex, pageInSection, globalPageNumber) {
2263
2419
  const sectionHf = this.hfRegistry.get(sectionIndex);
2264
- let headerHeight = dims.marginTop;
2265
- if (sectionHf) {
2266
- let measuredHeaderHeight;
2267
- if (pageInSection === 1 && sectionHf.headerFirstHeight != null) {
2268
- measuredHeaderHeight = sectionHf.headerFirstHeight;
2269
- } else if (globalPageNumber % 2 === 0 && sectionHf.headerEvenHeight != null) {
2270
- measuredHeaderHeight = sectionHf.headerEvenHeight;
2271
- } else if (sectionHf.headerDefaultHeight != null) {
2272
- measuredHeaderHeight = sectionHf.headerDefaultHeight;
2273
- }
2274
- if (measuredHeaderHeight != null) {
2275
- headerHeight = Math.max(dims.marginTop, measuredHeaderHeight);
2276
- }
2277
- }
2278
- let footerHeight = dims.marginBottom;
2279
- if (sectionHf) {
2280
- let measuredFooterHeight;
2281
- if (pageInSection === 1 && sectionHf.footerFirstHeight != null) {
2282
- measuredFooterHeight = sectionHf.footerFirstHeight;
2283
- } else if (globalPageNumber % 2 === 0 && sectionHf.footerEvenHeight != null) {
2284
- measuredFooterHeight = sectionHf.footerEvenHeight;
2285
- } else if (sectionHf.footerDefaultHeight != null) {
2286
- measuredFooterHeight = sectionHf.footerDefaultHeight;
2287
- }
2288
- if (measuredFooterHeight != null) {
2289
- footerHeight = Math.max(dims.marginBottom, measuredFooterHeight);
2290
- }
2291
- }
2292
- const headerExpansion = headerHeight - dims.marginTop;
2293
- const footerExpansion = footerHeight - dims.marginBottom;
2294
- const contentHeight = dims.contentHeight - headerExpansion - footerExpansion;
2295
- return { headerHeight, footerHeight, contentHeight };
2420
+ return resolvePageBands(
2421
+ dims,
2422
+ this.selectStoryHeight(
2423
+ sectionHf?.headerFirstHeight,
2424
+ sectionHf?.headerEvenHeight,
2425
+ sectionHf?.headerDefaultHeight,
2426
+ pageInSection,
2427
+ globalPageNumber
2428
+ ),
2429
+ this.selectStoryHeight(
2430
+ sectionHf?.footerFirstHeight,
2431
+ sectionHf?.footerEvenHeight,
2432
+ sectionHf?.footerDefaultHeight,
2433
+ pageInSection,
2434
+ globalPageNumber
2435
+ )
2436
+ );
2437
+ }
2438
+ /**
2439
+ * The measured height of the running story this page position selects, mirroring
2440
+ * {@link selectHeader}/{@link selectFooter}. Zero when the page has no such story.
2441
+ */
2442
+ selectStoryHeight(first, even, fallback, pageInSection, globalPageNumber) {
2443
+ if (pageInSection === 1 && first != null) return first;
2444
+ if (globalPageNumber % 2 === 0 && even != null) return even;
2445
+ return fallback ?? 0;
2296
2446
  }
2297
2447
  /**
2298
2448
  * Measures the content height of a header or footer element.
2299
- * This is needed because headers/footers can contain more content than fits in the margin area.
2449
+ *
2450
+ * This is what tells {@link resolvePageBands} whether the story stays inside its margin or
2451
+ * pushes the body, so it must measure the story ALONE — any padding added here would have to
2452
+ * be added to the rendered band too, and the two drifting apart is exactly how a header
2453
+ * silently starts overlapping body text.
2300
2454
  */
2301
2455
  measureHeaderFooterHeight(source, contentWidth) {
2302
2456
  const measureContainer = document.createElement("div");
@@ -2304,7 +2458,6 @@ var PaginationEngine = class {
2304
2458
  measureContainer.style.visibility = "hidden";
2305
2459
  measureContainer.style.width = `${contentWidth}pt`;
2306
2460
  measureContainer.style.left = "-9999px";
2307
- measureContainer.style.paddingBottom = "4pt";
2308
2461
  for (const child of Array.from(source.childNodes)) {
2309
2462
  measureContainer.appendChild(child.cloneNode(true));
2310
2463
  }
@@ -2324,7 +2477,7 @@ var PaginationEngine = class {
2324
2477
  let currentContent = [];
2325
2478
  let pageNumber = startPageNumber;
2326
2479
  let pageInSection = 1;
2327
- let { contentHeight: effectiveContentHeight } = this.getEffectiveHeights(
2480
+ let { bodyHeight: effectiveContentHeight } = this.getPageBands(
2328
2481
  dims,
2329
2482
  sectionIndex,
2330
2483
  pageInSection,
@@ -2359,8 +2512,8 @@ var PaginationEngine = class {
2359
2512
  pageNumber++;
2360
2513
  pageInSection++;
2361
2514
  currentContent = [];
2362
- const newHeights = this.getEffectiveHeights(dims, sectionIndex, pageInSection, pageNumber);
2363
- effectiveContentHeight = newHeights.contentHeight;
2515
+ const newBands = this.getPageBands(dims, sectionIndex, pageInSection, pageNumber);
2516
+ effectiveContentHeight = newBands.bodyHeight;
2364
2517
  remainingHeight = effectiveContentHeight;
2365
2518
  prevMarginBottomPt = 0;
2366
2519
  currentFootnoteIds = [];
@@ -2418,11 +2571,12 @@ var PaginationEngine = class {
2418
2571
  const currentChainHeight = this.measureKeepWithNextChainBodyHeight(
2419
2572
  keepChain,
2420
2573
  prevMarginBottomPt,
2421
- currentContent.length === 0
2574
+ currentContent.length === 0,
2575
+ pageInSection
2422
2576
  ) + additionalChainFootnoteHeight;
2423
2577
  const currentAvailableHeight = remainingHeight - currentFootnoteHeight;
2424
2578
  if (currentChainHeight > currentAvailableHeight) {
2425
- const nextPageHeights = this.getEffectiveHeights(
2579
+ const nextPageBands = this.getPageBands(
2426
2580
  dims,
2427
2581
  sectionIndex,
2428
2582
  pageInSection + 1,
@@ -2431,14 +2585,15 @@ var PaginationEngine = class {
2431
2585
  const freshChainBodyHeight = this.measureKeepWithNextChainBodyHeight(
2432
2586
  keepChain,
2433
2587
  0,
2434
- true
2588
+ true,
2589
+ pageInSection + 1
2435
2590
  );
2436
2591
  const freshChainFootnoteHeight = this.measureFootnotesHeight(
2437
2592
  newChainFootnoteIds,
2438
2593
  dims.contentWidth,
2439
2594
  nextPageContinuation
2440
2595
  );
2441
- if (freshChainBodyHeight + freshChainFootnoteHeight <= nextPageHeights.contentHeight) {
2596
+ if (freshChainBodyHeight + freshChainFootnoteHeight <= nextPageBands.bodyHeight) {
2442
2597
  finishPage();
2443
2598
  }
2444
2599
  }
@@ -2457,10 +2612,12 @@ var PaginationEngine = class {
2457
2612
  additionalFootnoteHeight = totalFootnoteHeight - currentFootnoteHeight;
2458
2613
  }
2459
2614
  const isFirstOnPage = currentContent.length === 0;
2460
- let effectiveMarginTop = block.marginTopPt;
2461
- if (!isFirstOnPage) {
2462
- effectiveMarginTop = Math.max(block.marginTopPt, prevMarginBottomPt) - prevMarginBottomPt;
2463
- }
2615
+ const effectiveMarginTop = this.effectiveBlockMarginTop(
2616
+ block,
2617
+ prevMarginBottomPt,
2618
+ isFirstOnPage,
2619
+ pageInSection
2620
+ );
2464
2621
  const blockSpace = effectiveMarginTop + block.heightPt + additionalFootnoteHeight;
2465
2622
  const effectiveRemainingHeight = remainingHeight - currentFootnoteHeight;
2466
2623
  const bodyContentUsed = effectiveContentHeight - remainingHeight;
@@ -2480,19 +2637,19 @@ var PaginationEngine = class {
2480
2637
  }
2481
2638
  }
2482
2639
  if (blockSpace <= effectiveRemainingHeight) {
2483
- currentContent.push(block.element.cloneNode(true));
2640
+ currentContent.push(this.cloneBlockForPage(block, isFirstOnPage, pageInSection));
2484
2641
  remainingHeight -= effectiveMarginTop + block.heightPt + block.marginBottomPt;
2485
2642
  prevMarginBottomPt = block.marginBottomPt;
2486
2643
  if (newFootnoteIds.length > 0) {
2487
2644
  currentFootnoteIds.push(...newFootnoteIds);
2488
2645
  currentFootnoteHeight += additionalFootnoteHeight;
2489
2646
  }
2490
- } else if (block.heightPt + block.marginTopPt <= effectiveContentHeight) {
2647
+ } else if (block.heightPt + this.effectiveBlockMarginTop(block, 0, true, pageInSection + 1) <= effectiveContentHeight) {
2491
2648
  const blockSpaceWithoutFootnotes = effectiveMarginTop + block.heightPt;
2492
2649
  const minBodySpaceNeeded = bodyContentUsed + blockSpaceWithoutFootnotes + MIN_BODY_CONTENT_HEIGHT;
2493
2650
  const canExpandFootnotes = minBodySpaceNeeded <= effectiveContentHeight;
2494
2651
  if (newFootnoteIds.length > 0 && blockSpaceWithoutFootnotes <= effectiveRemainingHeight) {
2495
- currentContent.push(block.element.cloneNode(true));
2652
+ currentContent.push(this.cloneBlockForPage(block, isFirstOnPage, pageInSection));
2496
2653
  remainingHeight -= effectiveMarginTop + block.heightPt + block.marginBottomPt;
2497
2654
  prevMarginBottomPt = block.marginBottomPt;
2498
2655
  const availableForFootnotes = Math.min(
@@ -2539,7 +2696,7 @@ var PaginationEngine = class {
2539
2696
  const expandedFootnoteSpace = Math.min(maxFootnoteArea, additionalFootnoteHeight + currentFootnoteHeight);
2540
2697
  const bodySpaceAfterExpansion = effectiveContentHeight - expandedFootnoteSpace;
2541
2698
  if (blockSpaceWithoutFootnotes <= bodySpaceAfterExpansion - bodyContentUsed) {
2542
- currentContent.push(block.element.cloneNode(true));
2699
+ currentContent.push(this.cloneBlockForPage(block, isFirstOnPage, pageInSection));
2543
2700
  remainingHeight = bodySpaceAfterExpansion - bodyContentUsed - blockSpaceWithoutFootnotes;
2544
2701
  prevMarginBottomPt = block.marginBottomPt;
2545
2702
  currentFootnoteIds.push(...newFootnoteIds);
@@ -2547,8 +2704,14 @@ var PaginationEngine = class {
2547
2704
  } else {
2548
2705
  finishPage();
2549
2706
  const newPageFootnoteHeight = allBlockFootnoteIds.length > 0 ? this.measureFootnotesHeight(allBlockFootnoteIds, dims.contentWidth, currentContinuation) : currentContinuation ? this.measureContinuationHeight(currentContinuation, dims.contentWidth) : 0;
2550
- const newPageSpace = block.marginTopPt + block.heightPt + block.marginBottomPt;
2551
- 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));
2552
2715
  remainingHeight = effectiveContentHeight - newPageSpace;
2553
2716
  prevMarginBottomPt = block.marginBottomPt;
2554
2717
  currentFootnoteIds = [...currentFootnoteIds, ...allBlockFootnoteIds];
@@ -2557,8 +2720,14 @@ var PaginationEngine = class {
2557
2720
  } else {
2558
2721
  finishPage();
2559
2722
  const newPageFootnoteHeight = allBlockFootnoteIds.length > 0 ? this.measureFootnotesHeight(allBlockFootnoteIds, dims.contentWidth, currentContinuation) : currentContinuation ? this.measureContinuationHeight(currentContinuation, dims.contentWidth) : 0;
2560
- const newPageSpace = block.marginTopPt + block.heightPt + block.marginBottomPt;
2561
- 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));
2562
2731
  remainingHeight = effectiveContentHeight - newPageSpace;
2563
2732
  prevMarginBottomPt = block.marginBottomPt;
2564
2733
  currentFootnoteIds = [...currentFootnoteIds, ...allBlockFootnoteIds];
@@ -2577,7 +2746,7 @@ var PaginationEngine = class {
2577
2746
  if (currentContent.length > 0) {
2578
2747
  finishPage();
2579
2748
  }
2580
- currentContent.push(block.element.cloneNode(true));
2749
+ currentContent.push(this.cloneBlockForPage(block, true, pageInSection));
2581
2750
  currentFootnoteIds = [...currentFootnoteIds, ...allBlockFootnoteIds];
2582
2751
  finishPage();
2583
2752
  }
@@ -2607,6 +2776,161 @@ var PaginationEngine = class {
2607
2776
  if (el.getAttribute("contenteditable") !== null) el.setAttribute("contenteditable", "false");
2608
2777
  }
2609
2778
  }
2779
+ /**
2780
+ * Resolves floating DrawingML objects after their anchor paragraphs have landed on a page.
2781
+ *
2782
+ * The converter deliberately carries the OOXML bases and offsets as data instead of flattening
2783
+ * them into CSS: page/margin/column bases belong to the page, while paragraph/line/character
2784
+ * bases belong to the laid-out anchor. Once both coordinate systems exist, promote the object
2785
+ * from the clipped text column into the page box and position it in one shared point space.
2786
+ */
2787
+ positionDrawingAnchors(pageBox, contentArea, dims, pageNumber) {
2788
+ const anchors = Array.from(
2789
+ contentArea.querySelectorAll('[data-docx-drawing-anchor="true"]')
2790
+ );
2791
+ if (anchors.length === 0) return;
2792
+ const pageRect = pageBox.getBoundingClientRect();
2793
+ const pixelsPerPoint = pageRect.width / dims.pageWidth;
2794
+ if (!(pixelsPerPoint > 0)) return;
2795
+ for (const anchor of anchors) {
2796
+ const staticRect = anchor.getBoundingClientRect();
2797
+ const paragraph = anchor.closest("p, h1, h2, h3, h4, h5, h6");
2798
+ const paragraphRect = paragraph?.getBoundingClientRect() ?? staticRect;
2799
+ const toPageX = (x) => (x - pageRect.left) / pixelsPerPoint;
2800
+ const toPageY = (y) => (y - pageRect.top) / pixelsPerPoint;
2801
+ const context = {
2802
+ staticLeft: toPageX(staticRect.left),
2803
+ staticTop: toPageY(staticRect.top),
2804
+ lineHeight: paragraph ? (parseFloat(getComputedStyle(paragraph).lineHeight) || staticRect.height) / pixelsPerPoint : staticRect.height / pixelsPerPoint,
2805
+ paragraphLeft: toPageX(paragraphRect.left),
2806
+ paragraphTop: toPageY(paragraphRect.top),
2807
+ paragraphWidth: paragraphRect.width / pixelsPerPoint,
2808
+ paragraphHeight: paragraphRect.height / pixelsPerPoint
2809
+ };
2810
+ const widthReference = this.horizontalAnchorReference(
2811
+ anchor.getAttribute("data-docx-anchor-width-relative") ?? "margin",
2812
+ dims,
2813
+ pageNumber,
2814
+ context
2815
+ );
2816
+ const widthPercent = this.anchorNumber(anchor, "width-percent");
2817
+ if (widthPercent !== void 0) {
2818
+ anchor.style.width = `${widthReference.size * widthPercent / 100}pt`;
2819
+ }
2820
+ const heightReference = this.verticalAnchorReference(
2821
+ anchor.getAttribute("data-docx-anchor-height-relative") ?? "margin",
2822
+ dims,
2823
+ pageNumber,
2824
+ context
2825
+ );
2826
+ const heightPercent = this.anchorNumber(anchor, "height-percent");
2827
+ if (heightPercent !== void 0 && anchor.dataset.docxAnchorAutofit !== "true") {
2828
+ anchor.style.height = `${heightReference.size * heightPercent / 100}pt`;
2829
+ }
2830
+ const sizedRect = anchor.getBoundingClientRect();
2831
+ const width = sizedRect.width / pixelsPerPoint;
2832
+ const height = sizedRect.height / pixelsPerPoint;
2833
+ const horizontal2 = this.horizontalAnchorReference(
2834
+ anchor.getAttribute("data-docx-anchor-h-relative") ?? "column",
2835
+ dims,
2836
+ pageNumber,
2837
+ context
2838
+ );
2839
+ const vertical2 = this.verticalAnchorReference(
2840
+ anchor.getAttribute("data-docx-anchor-v-relative") ?? "paragraph",
2841
+ dims,
2842
+ pageNumber,
2843
+ context
2844
+ );
2845
+ const left2 = this.resolveAnchorAxis(
2846
+ horizontal2,
2847
+ width,
2848
+ anchor.getAttribute("data-docx-anchor-h-align"),
2849
+ this.anchorNumber(anchor, "h-offset"),
2850
+ pageNumber
2851
+ );
2852
+ const top2 = this.resolveAnchorAxis(
2853
+ vertical2,
2854
+ height,
2855
+ anchor.getAttribute("data-docx-anchor-v-align"),
2856
+ this.anchorNumber(anchor, "v-offset"),
2857
+ pageNumber
2858
+ );
2859
+ pageBox.appendChild(anchor);
2860
+ anchor.style.position = "absolute";
2861
+ anchor.style.left = `${left2}pt`;
2862
+ anchor.style.top = `${top2}pt`;
2863
+ anchor.style.margin = "0";
2864
+ }
2865
+ }
2866
+ anchorNumber(anchor, suffix) {
2867
+ const raw = anchor.getAttribute(`data-docx-anchor-${suffix}`);
2868
+ if (raw === null) return void 0;
2869
+ const value = Number(raw);
2870
+ return Number.isFinite(value) ? value : void 0;
2871
+ }
2872
+ horizontalAnchorReference(relativeFrom, dims, pageNumber, context) {
2873
+ const leftMargin = { start: 0, size: dims.marginLeft };
2874
+ const rightMargin = {
2875
+ start: dims.marginLeft + dims.contentWidth,
2876
+ size: dims.marginRight
2877
+ };
2878
+ switch (relativeFrom) {
2879
+ case "page":
2880
+ return { start: 0, size: dims.pageWidth };
2881
+ case "character":
2882
+ return { start: context.staticLeft, size: 0 };
2883
+ case "leftMargin":
2884
+ return leftMargin;
2885
+ case "rightMargin":
2886
+ return rightMargin;
2887
+ case "insideMargin":
2888
+ return pageNumber % 2 === 1 ? leftMargin : rightMargin;
2889
+ case "outsideMargin":
2890
+ return pageNumber % 2 === 1 ? rightMargin : leftMargin;
2891
+ case "paragraph":
2892
+ return { start: context.paragraphLeft, size: context.paragraphWidth };
2893
+ case "column":
2894
+ case "margin":
2895
+ default:
2896
+ return { start: dims.marginLeft, size: dims.contentWidth };
2897
+ }
2898
+ }
2899
+ verticalAnchorReference(relativeFrom, dims, pageNumber, context) {
2900
+ const topMargin = { start: 0, size: dims.marginTop };
2901
+ const bottomMargin = {
2902
+ start: dims.marginTop + dims.contentHeight,
2903
+ size: dims.marginBottom
2904
+ };
2905
+ switch (relativeFrom) {
2906
+ case "page":
2907
+ return { start: 0, size: dims.pageHeight };
2908
+ case "paragraph":
2909
+ return { start: context.paragraphTop, size: context.paragraphHeight };
2910
+ case "line":
2911
+ return { start: context.staticTop, size: context.lineHeight };
2912
+ case "topMargin":
2913
+ return topMargin;
2914
+ case "bottomMargin":
2915
+ return bottomMargin;
2916
+ case "insideMargin":
2917
+ return pageNumber % 2 === 1 ? topMargin : bottomMargin;
2918
+ case "outsideMargin":
2919
+ return pageNumber % 2 === 1 ? bottomMargin : topMargin;
2920
+ case "margin":
2921
+ default:
2922
+ return { start: dims.marginTop, size: dims.contentHeight };
2923
+ }
2924
+ }
2925
+ resolveAnchorAxis(reference, objectSize, alignment, offset, pageNumber) {
2926
+ if (offset !== void 0) return reference.start + offset;
2927
+ let fraction = 0;
2928
+ if (alignment === "center") fraction = 0.5;
2929
+ else if (alignment === "right" || alignment === "bottom") fraction = 1;
2930
+ else if (alignment === "inside") fraction = pageNumber % 2 === 1 ? 0 : 1;
2931
+ else if (alignment === "outside") fraction = pageNumber % 2 === 1 ? 1 : 0;
2932
+ return reference.start + (reference.size - objectSize) * fraction;
2933
+ }
2610
2934
  /**
2611
2935
  * Creates a page container element.
2612
2936
  */
@@ -2633,22 +2957,22 @@ var PaginationEngine = class {
2633
2957
  pageBox.dataset.pageNumber = String(pageNumber);
2634
2958
  pageBox.dataset.sectionIndex = String(sectionIndex);
2635
2959
  pageBox.dataset.pageInSection = String(pageInSection);
2636
- const effectiveHeights = this.getEffectiveHeights(dims, sectionIndex, pageInSection, pageNumber);
2960
+ const bands = this.getPageBands(dims, sectionIndex, pageInSection, pageNumber);
2637
2961
  const headerSource = this.selectHeader(sectionIndex, pageInSection, pageNumber);
2638
2962
  if (headerSource) {
2639
2963
  const headerDiv = document.createElement("div");
2640
2964
  headerDiv.className = `${this.cssPrefix}header`;
2641
2965
  headerDiv.style.position = "absolute";
2642
- headerDiv.style.top = "0";
2966
+ headerDiv.style.top = `${bands.headerTop}pt`;
2967
+ headerDiv.style.bottom = "auto";
2643
2968
  headerDiv.style.left = `${dims.marginLeft}pt`;
2644
2969
  headerDiv.style.width = `${dims.contentWidth}pt`;
2645
- headerDiv.style.height = `${effectiveHeights.headerHeight}pt`;
2970
+ headerDiv.style.height = `${bands.headerHeight}pt`;
2646
2971
  headerDiv.style.overflow = "hidden";
2647
2972
  headerDiv.style.boxSizing = "border-box";
2648
2973
  headerDiv.style.display = "flex";
2649
2974
  headerDiv.style.flexDirection = "column";
2650
- headerDiv.style.justifyContent = "flex-end";
2651
- headerDiv.style.paddingBottom = "4pt";
2975
+ headerDiv.style.justifyContent = "flex-start";
2652
2976
  for (const child of Array.from(headerSource.childNodes)) {
2653
2977
  const clonedheaderDiv = child.cloneNode(true);
2654
2978
  if (clonedheaderDiv.nodeType === 1) this.makeClonedStoryInert(clonedheaderDiv);
@@ -2656,8 +2980,8 @@ var PaginationEngine = class {
2656
2980
  }
2657
2981
  pageBox.appendChild(headerDiv);
2658
2982
  }
2659
- const contentAreaTop = effectiveHeights.headerHeight;
2660
- const contentAreaHeight = effectiveHeights.contentHeight;
2983
+ const contentAreaTop = bands.bodyTop;
2984
+ const contentAreaHeight = bands.bodyHeight;
2661
2985
  const contentArea = document.createElement("div");
2662
2986
  contentArea.className = `${this.cssPrefix}content`;
2663
2987
  contentArea.style.position = "absolute";
@@ -2672,23 +2996,23 @@ var PaginationEngine = class {
2672
2996
  pageBox.appendChild(contentArea);
2673
2997
  const hasContinuation = continuation && continuation.remainingElements.length > 0;
2674
2998
  if (footnoteIds.length > 0 || hasContinuation) {
2675
- this.addPageFootnotes(pageBox, footnoteIds, dims, footnoteHeight, continuation, partialFootnotes);
2999
+ this.addPageFootnotes(pageBox, footnoteIds, dims, bands, footnoteHeight, continuation, partialFootnotes);
2676
3000
  }
2677
3001
  const footerSource = this.selectFooter(sectionIndex, pageInSection, pageNumber);
2678
3002
  if (footerSource) {
2679
3003
  const footerDiv = document.createElement("div");
2680
3004
  footerDiv.className = `${this.cssPrefix}footer`;
2681
3005
  footerDiv.style.position = "absolute";
2682
- footerDiv.style.bottom = "0";
3006
+ footerDiv.style.top = "auto";
3007
+ footerDiv.style.bottom = `${dims.footerDistance}pt`;
2683
3008
  footerDiv.style.left = `${dims.marginLeft}pt`;
2684
3009
  footerDiv.style.width = `${dims.contentWidth}pt`;
2685
- footerDiv.style.height = `${effectiveHeights.footerHeight}pt`;
3010
+ footerDiv.style.height = `${bands.footerHeight}pt`;
2686
3011
  footerDiv.style.overflow = "hidden";
2687
3012
  footerDiv.style.boxSizing = "border-box";
2688
3013
  footerDiv.style.display = "flex";
2689
3014
  footerDiv.style.flexDirection = "column";
2690
- footerDiv.style.justifyContent = "flex-start";
2691
- footerDiv.style.paddingTop = "4pt";
3015
+ footerDiv.style.justifyContent = "flex-end";
2692
3016
  for (const child of Array.from(footerSource.childNodes)) {
2693
3017
  const clonedfooterDiv = child.cloneNode(true);
2694
3018
  if (clonedfooterDiv.nodeType === 1) this.makeClonedStoryInert(clonedfooterDiv);
@@ -2703,6 +3027,7 @@ var PaginationEngine = class {
2703
3027
  pageBox.appendChild(pageNum);
2704
3028
  }
2705
3029
  this.containerElement.appendChild(pageBox);
3030
+ this.positionDrawingAnchors(pageBox, contentArea, dims, pageNumber);
2706
3031
  const notesEl = pageBox.querySelector(`.${this.cssPrefix}footnotes`);
2707
3032
  if (notesEl) {
2708
3033
  const notesHeightPt = pxToPt(notesEl.getBoundingClientRect().height);
@@ -6077,6 +6402,19 @@ function serializeInlineMarkdown(block) {
6077
6402
  function isListBlock(block) {
6078
6403
  return !!block.querySelector(":scope > [data-list-marker]");
6079
6404
  }
6405
+ function ensureListMarkerSeparator(block) {
6406
+ const marker = block.querySelector(":scope > [data-list-marker]");
6407
+ if (!marker) return;
6408
+ const next = marker.nextElementSibling;
6409
+ if (next?.hasAttribute("data-editor-list-separator")) return;
6410
+ const separator = block.ownerDocument.createElement("span");
6411
+ separator.setAttribute("data-editor-list-separator", "");
6412
+ separator.setAttribute("data-list-marker", "true");
6413
+ separator.setAttribute("aria-hidden", "true");
6414
+ separator.style.display = "none";
6415
+ separator.textContent = " ";
6416
+ marker.after(separator);
6417
+ }
6080
6418
  var GENERATED_CHROME_SELECTOR = '[data-list-marker], a.footnote-ref, a.endnote-ref, a[class$="-backref"]';
6081
6419
  function isGeneratedChrome(node) {
6082
6420
  return node?.nodeType === 1 && !!node.matches?.(GENERATED_CHROME_SELECTOR);
@@ -7367,6 +7705,7 @@ var DocxEditor = class _DocxEditor {
7367
7705
  const unid = el.getAttribute("data-anchor");
7368
7706
  if (!unid || !this.anchorIdOf(el)) return;
7369
7707
  el.setAttribute("contenteditable", "true");
7708
+ ensureListMarkerSeparator(el);
7370
7709
  el.querySelectorAll(GENERATED_CHROME_SELECTOR).forEach((m) => m.setAttribute("contenteditable", "false"));
7371
7710
  el.dataset.committedText = blockContentText(el);
7372
7711
  el.addEventListener("focus", () => {
@@ -8229,8 +8568,8 @@ var DocxEditor = class _DocxEditor {
8229
8568
  * editable `data-anchor` blocks in the notes section, so editing it afterwards needs no new op.
8230
8569
  *
8231
8570
  * Body blocks only: Word disallows a note reference inside a header/footer story or inside
8232
- * another note, and the session rejects those with `AnchorWrongKind`. Remounts, because a new
8233
- * note renumbers the citations after it and can add a whole part.
8571
+ * another note, and the session rejects those with `AnchorWrongKind`. Reconciliation creates
8572
+ * the first notes section when needed and renumbers every affected citation in place.
8234
8573
  */
8235
8574
  insertFootnote(markdown = "New footnote.") {
8236
8575
  this.insertNote("footnote", markdown);
@@ -8622,7 +8961,7 @@ var DocxEditor = class _DocxEditor {
8622
8961
  * the current node — never a section div (multi-child) or the edit root. */
8623
8962
  unitWrapperOf(el) {
8624
8963
  let n = el;
8625
- while (n.parentElement && n.parentElement !== this.editRoot && n.parentElement.tagName === "DIV" && !n.parentElement.hasAttribute("data-anchor") && n.parentElement.childElementCount === 1) {
8964
+ 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) {
8626
8965
  n = n.parentElement;
8627
8966
  }
8628
8967
  return n;
@@ -8658,6 +8997,7 @@ var DocxEditor = class _DocxEditor {
8658
8997
  const pureRemoved = diff.removed.filter(
8659
8998
  (i) => !diff.substituted.some((s) => s.oldIndex === i) && !movedOld.has(i)
8660
8999
  );
9000
+ const appliedFresh = /* @__PURE__ */ new Set();
8661
9001
  const nodeAt = (j) => {
8662
9002
  const oi = diff.keep.get(j);
8663
9003
  if (oi !== void 0) return oldNodes[oi];
@@ -8665,7 +9005,7 @@ var DocxEditor = class _DocxEditor {
8665
9005
  if (movedOi !== void 0) return oldNodes[movedOi];
8666
9006
  if (subOldByNew.has(j)) return fresh.get(j);
8667
9007
  const f = fresh.get(j);
8668
- return f && f.isConnected ? f : null;
9008
+ return f && appliedFresh.has(j) ? f : null;
8669
9009
  };
8670
9010
  for (const j of pureAdded) {
8671
9011
  const el = fresh.get(j);
@@ -8680,6 +9020,7 @@ var DocxEditor = class _DocxEditor {
8680
9020
  if (prevW) prevW.after(el);
8681
9021
  else if (nextW) nextW.before(el);
8682
9022
  else return "insert with no anchored neighbor";
9023
+ appliedFresh.add(j);
8683
9024
  this.wireUnit(el, units[j]);
8684
9025
  }
8685
9026
  for (const i of pureRemoved) {
@@ -8718,13 +9059,13 @@ var DocxEditor = class _DocxEditor {
8718
9059
  if (anchorEl) this.wireBlock(anchorEl);
8719
9060
  root.querySelectorAll("[data-anchor]").forEach((b) => this.wireBlock(b));
8720
9061
  }
8721
- /** Old-sequence diff state for one notes section. `null` requests remount (DOM not
8722
- * stampable/consistent). */
9062
+ /** Old-sequence diff state for one notes section. `null` requests remount when an
9063
+ * existing list is not stampable; an absent list is the valid first-note case. */
8723
9064
  notesDiff(sectionClass, units) {
8724
9065
  const ol = this.editRoot.querySelector(`section.${sectionClass} > ol`);
8725
9066
  const lis = ol ? Array.from(ol.children).filter((c) => c.tagName === "LI") : [];
8726
9067
  if (units.length === 0 && lis.length === 0) return { lis, diff: diffUnits([], []) };
8727
- if (!ol) return null;
9068
+ if (!ol) return { lis, diff: diffUnits([], units) };
8728
9069
  const tokens = [];
8729
9070
  for (const li of lis) {
8730
9071
  const unid = li.getAttribute("data-note-anchor");
@@ -8741,7 +9082,20 @@ var DocxEditor = class _DocxEditor {
8741
9082
  this.editRoot.querySelector(`section.${sectionClass}`)?.remove();
8742
9083
  return;
8743
9084
  }
8744
- const ol = this.editRoot.querySelector(`section.${sectionClass} > ol`);
9085
+ let section = this.editRoot.querySelector(`:scope > section.${sectionClass}`);
9086
+ let ol = section?.querySelector(`:scope > ol`) ?? null;
9087
+ if (!ol) {
9088
+ if (!section) {
9089
+ section = document.createElement("section");
9090
+ section.className = sectionClass;
9091
+ const following = sectionClass === "footnotes" ? this.editRoot.querySelector(`:scope > section.endnotes`) : null;
9092
+ if (following) following.before(section);
9093
+ else this.editRoot.append(section);
9094
+ }
9095
+ if (!section.querySelector(":scope > hr")) section.prepend(document.createElement("hr"));
9096
+ ol = document.createElement("ol");
9097
+ section.append(ol);
9098
+ }
8745
9099
  const prefix = sectionClass === "footnotes" ? "fn" : "en";
8746
9100
  const nodes = [];
8747
9101
  for (let j = 0; j < units.length; j++) {
@@ -8922,6 +9276,7 @@ var RIBBON_CSS = `
8922
9276
  color: var(--dxr-ink);
8923
9277
  background: var(--dxr-desk);
8924
9278
  -webkit-text-size-adjust: 100%;
9279
+ text-size-adjust: 100%;
8925
9280
  }
8926
9281
  .dxr *, .dxr *::before, .dxr *::after { box-sizing: border-box; }
8927
9282