docxodus 9.7.0 → 9.8.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.
@@ -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) {
@@ -1637,9 +1666,9 @@ var PaginationEngine = class {
1637
1666
  */
1638
1667
  smallestEffectiveContentHeight(dims, sectionIndex) {
1639
1668
  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
1669
+ this.getPageBands(dims, sectionIndex, 1, 1).bodyHeight,
1670
+ this.getPageBands(dims, sectionIndex, 2, 1).bodyHeight,
1671
+ this.getPageBands(dims, sectionIndex, 2, 2).bodyHeight
1643
1672
  );
1644
1673
  }
1645
1674
  /**
@@ -2163,7 +2192,7 @@ var PaginationEngine = class {
2163
2192
  /**
2164
2193
  * Adds footnotes to a page container, including continuation content.
2165
2194
  */
2166
- addPageFootnotes(pageBox, footnoteIds, dims, footnoteHeight, continuation, partialFootnotes) {
2195
+ addPageFootnotes(pageBox, footnoteIds, dims, bands, footnoteHeight, continuation, partialFootnotes) {
2167
2196
  const hasContinuation = continuation && continuation.remainingElements.length > 0;
2168
2197
  if (footnoteIds.length === 0 && !hasContinuation) {
2169
2198
  return;
@@ -2174,12 +2203,12 @@ var PaginationEngine = class {
2174
2203
  const partialFootnoteIds = new Set(partialFootnotes?.map((p) => p.footnoteId) || []);
2175
2204
  const maxFootnoteHeight = Math.min(
2176
2205
  footnoteHeight,
2177
- dims.contentHeight * MAX_FOOTNOTE_AREA_RATIO
2206
+ bands.bodyHeight * MAX_FOOTNOTE_AREA_RATIO
2178
2207
  );
2179
2208
  const footnotesDiv = document.createElement("div");
2180
2209
  footnotesDiv.className = `${this.cssPrefix}footnotes`;
2181
2210
  footnotesDiv.style.position = "absolute";
2182
- footnotesDiv.style.bottom = `${dims.marginBottom}pt`;
2211
+ footnotesDiv.style.bottom = `${dims.pageHeight - (bands.bodyTop + bands.bodyHeight)}pt`;
2183
2212
  footnotesDiv.style.left = `${dims.marginLeft}pt`;
2184
2213
  footnotesDiv.style.width = `${dims.contentWidth}pt`;
2185
2214
  footnotesDiv.style.boxSizing = "border-box";
@@ -2253,50 +2282,51 @@ var PaginationEngine = class {
2253
2282
  return sectionHf.footerDefault;
2254
2283
  }
2255
2284
  /**
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.
2285
+ * The header, body, and footer bands for one page position.
2286
+ *
2287
+ * The single owner of "where does anything sit vertically on this page" — placement in
2288
+ * {@link createPage}, the body budget the flow loop spends, and the note area's anchor all
2289
+ * read it, so those three cannot disagree about where the body ends.
2290
+ *
2291
+ * Deterministic: it depends only on the section's page setup and the registry's pre-measured
2292
+ * story heights, never on the page's content, which is what keeps it lazy-loading compatible.
2261
2293
  */
2262
- getEffectiveHeights(dims, sectionIndex, pageInSection, globalPageNumber) {
2294
+ getPageBands(dims, sectionIndex, pageInSection, globalPageNumber) {
2263
2295
  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 };
2296
+ return resolvePageBands(
2297
+ dims,
2298
+ this.selectStoryHeight(
2299
+ sectionHf?.headerFirstHeight,
2300
+ sectionHf?.headerEvenHeight,
2301
+ sectionHf?.headerDefaultHeight,
2302
+ pageInSection,
2303
+ globalPageNumber
2304
+ ),
2305
+ this.selectStoryHeight(
2306
+ sectionHf?.footerFirstHeight,
2307
+ sectionHf?.footerEvenHeight,
2308
+ sectionHf?.footerDefaultHeight,
2309
+ pageInSection,
2310
+ globalPageNumber
2311
+ )
2312
+ );
2313
+ }
2314
+ /**
2315
+ * The measured height of the running story this page position selects, mirroring
2316
+ * {@link selectHeader}/{@link selectFooter}. Zero when the page has no such story.
2317
+ */
2318
+ selectStoryHeight(first, even, fallback, pageInSection, globalPageNumber) {
2319
+ if (pageInSection === 1 && first != null) return first;
2320
+ if (globalPageNumber % 2 === 0 && even != null) return even;
2321
+ return fallback ?? 0;
2296
2322
  }
2297
2323
  /**
2298
2324
  * 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.
2325
+ *
2326
+ * This is what tells {@link resolvePageBands} whether the story stays inside its margin or
2327
+ * pushes the body, so it must measure the story ALONE — any padding added here would have to
2328
+ * be added to the rendered band too, and the two drifting apart is exactly how a header
2329
+ * silently starts overlapping body text.
2300
2330
  */
2301
2331
  measureHeaderFooterHeight(source, contentWidth) {
2302
2332
  const measureContainer = document.createElement("div");
@@ -2304,7 +2334,6 @@ var PaginationEngine = class {
2304
2334
  measureContainer.style.visibility = "hidden";
2305
2335
  measureContainer.style.width = `${contentWidth}pt`;
2306
2336
  measureContainer.style.left = "-9999px";
2307
- measureContainer.style.paddingBottom = "4pt";
2308
2337
  for (const child of Array.from(source.childNodes)) {
2309
2338
  measureContainer.appendChild(child.cloneNode(true));
2310
2339
  }
@@ -2324,7 +2353,7 @@ var PaginationEngine = class {
2324
2353
  let currentContent = [];
2325
2354
  let pageNumber = startPageNumber;
2326
2355
  let pageInSection = 1;
2327
- let { contentHeight: effectiveContentHeight } = this.getEffectiveHeights(
2356
+ let { bodyHeight: effectiveContentHeight } = this.getPageBands(
2328
2357
  dims,
2329
2358
  sectionIndex,
2330
2359
  pageInSection,
@@ -2359,8 +2388,8 @@ var PaginationEngine = class {
2359
2388
  pageNumber++;
2360
2389
  pageInSection++;
2361
2390
  currentContent = [];
2362
- const newHeights = this.getEffectiveHeights(dims, sectionIndex, pageInSection, pageNumber);
2363
- effectiveContentHeight = newHeights.contentHeight;
2391
+ const newBands = this.getPageBands(dims, sectionIndex, pageInSection, pageNumber);
2392
+ effectiveContentHeight = newBands.bodyHeight;
2364
2393
  remainingHeight = effectiveContentHeight;
2365
2394
  prevMarginBottomPt = 0;
2366
2395
  currentFootnoteIds = [];
@@ -2422,7 +2451,7 @@ var PaginationEngine = class {
2422
2451
  ) + additionalChainFootnoteHeight;
2423
2452
  const currentAvailableHeight = remainingHeight - currentFootnoteHeight;
2424
2453
  if (currentChainHeight > currentAvailableHeight) {
2425
- const nextPageHeights = this.getEffectiveHeights(
2454
+ const nextPageBands = this.getPageBands(
2426
2455
  dims,
2427
2456
  sectionIndex,
2428
2457
  pageInSection + 1,
@@ -2438,7 +2467,7 @@ var PaginationEngine = class {
2438
2467
  dims.contentWidth,
2439
2468
  nextPageContinuation
2440
2469
  );
2441
- if (freshChainBodyHeight + freshChainFootnoteHeight <= nextPageHeights.contentHeight) {
2470
+ if (freshChainBodyHeight + freshChainFootnoteHeight <= nextPageBands.bodyHeight) {
2442
2471
  finishPage();
2443
2472
  }
2444
2473
  }
@@ -2607,6 +2636,161 @@ var PaginationEngine = class {
2607
2636
  if (el.getAttribute("contenteditable") !== null) el.setAttribute("contenteditable", "false");
2608
2637
  }
2609
2638
  }
2639
+ /**
2640
+ * Resolves floating DrawingML objects after their anchor paragraphs have landed on a page.
2641
+ *
2642
+ * The converter deliberately carries the OOXML bases and offsets as data instead of flattening
2643
+ * them into CSS: page/margin/column bases belong to the page, while paragraph/line/character
2644
+ * bases belong to the laid-out anchor. Once both coordinate systems exist, promote the object
2645
+ * from the clipped text column into the page box and position it in one shared point space.
2646
+ */
2647
+ positionDrawingAnchors(pageBox, contentArea, dims, pageNumber) {
2648
+ const anchors = Array.from(
2649
+ contentArea.querySelectorAll('[data-docx-drawing-anchor="true"]')
2650
+ );
2651
+ if (anchors.length === 0) return;
2652
+ const pageRect = pageBox.getBoundingClientRect();
2653
+ const pixelsPerPoint = pageRect.width / dims.pageWidth;
2654
+ if (!(pixelsPerPoint > 0)) return;
2655
+ for (const anchor of anchors) {
2656
+ const staticRect = anchor.getBoundingClientRect();
2657
+ const paragraph = anchor.closest("p, h1, h2, h3, h4, h5, h6");
2658
+ const paragraphRect = paragraph?.getBoundingClientRect() ?? staticRect;
2659
+ const toPageX = (x) => (x - pageRect.left) / pixelsPerPoint;
2660
+ const toPageY = (y) => (y - pageRect.top) / pixelsPerPoint;
2661
+ const context = {
2662
+ staticLeft: toPageX(staticRect.left),
2663
+ staticTop: toPageY(staticRect.top),
2664
+ lineHeight: paragraph ? (parseFloat(getComputedStyle(paragraph).lineHeight) || staticRect.height) / pixelsPerPoint : staticRect.height / pixelsPerPoint,
2665
+ paragraphLeft: toPageX(paragraphRect.left),
2666
+ paragraphTop: toPageY(paragraphRect.top),
2667
+ paragraphWidth: paragraphRect.width / pixelsPerPoint,
2668
+ paragraphHeight: paragraphRect.height / pixelsPerPoint
2669
+ };
2670
+ const widthReference = this.horizontalAnchorReference(
2671
+ anchor.getAttribute("data-docx-anchor-width-relative") ?? "margin",
2672
+ dims,
2673
+ pageNumber,
2674
+ context
2675
+ );
2676
+ const widthPercent = this.anchorNumber(anchor, "width-percent");
2677
+ if (widthPercent !== void 0) {
2678
+ anchor.style.width = `${widthReference.size * widthPercent / 100}pt`;
2679
+ }
2680
+ const heightReference = this.verticalAnchorReference(
2681
+ anchor.getAttribute("data-docx-anchor-height-relative") ?? "margin",
2682
+ dims,
2683
+ pageNumber,
2684
+ context
2685
+ );
2686
+ const heightPercent = this.anchorNumber(anchor, "height-percent");
2687
+ if (heightPercent !== void 0 && anchor.dataset.docxAnchorAutofit !== "true") {
2688
+ anchor.style.height = `${heightReference.size * heightPercent / 100}pt`;
2689
+ }
2690
+ const sizedRect = anchor.getBoundingClientRect();
2691
+ const width = sizedRect.width / pixelsPerPoint;
2692
+ const height = sizedRect.height / pixelsPerPoint;
2693
+ const horizontal2 = this.horizontalAnchorReference(
2694
+ anchor.getAttribute("data-docx-anchor-h-relative") ?? "column",
2695
+ dims,
2696
+ pageNumber,
2697
+ context
2698
+ );
2699
+ const vertical2 = this.verticalAnchorReference(
2700
+ anchor.getAttribute("data-docx-anchor-v-relative") ?? "paragraph",
2701
+ dims,
2702
+ pageNumber,
2703
+ context
2704
+ );
2705
+ const left2 = this.resolveAnchorAxis(
2706
+ horizontal2,
2707
+ width,
2708
+ anchor.getAttribute("data-docx-anchor-h-align"),
2709
+ this.anchorNumber(anchor, "h-offset"),
2710
+ pageNumber
2711
+ );
2712
+ const top2 = this.resolveAnchorAxis(
2713
+ vertical2,
2714
+ height,
2715
+ anchor.getAttribute("data-docx-anchor-v-align"),
2716
+ this.anchorNumber(anchor, "v-offset"),
2717
+ pageNumber
2718
+ );
2719
+ pageBox.appendChild(anchor);
2720
+ anchor.style.position = "absolute";
2721
+ anchor.style.left = `${left2}pt`;
2722
+ anchor.style.top = `${top2}pt`;
2723
+ anchor.style.margin = "0";
2724
+ }
2725
+ }
2726
+ anchorNumber(anchor, suffix) {
2727
+ const raw = anchor.getAttribute(`data-docx-anchor-${suffix}`);
2728
+ if (raw === null) return void 0;
2729
+ const value = Number(raw);
2730
+ return Number.isFinite(value) ? value : void 0;
2731
+ }
2732
+ horizontalAnchorReference(relativeFrom, dims, pageNumber, context) {
2733
+ const leftMargin = { start: 0, size: dims.marginLeft };
2734
+ const rightMargin = {
2735
+ start: dims.marginLeft + dims.contentWidth,
2736
+ size: dims.marginRight
2737
+ };
2738
+ switch (relativeFrom) {
2739
+ case "page":
2740
+ return { start: 0, size: dims.pageWidth };
2741
+ case "character":
2742
+ return { start: context.staticLeft, size: 0 };
2743
+ case "leftMargin":
2744
+ return leftMargin;
2745
+ case "rightMargin":
2746
+ return rightMargin;
2747
+ case "insideMargin":
2748
+ return pageNumber % 2 === 1 ? leftMargin : rightMargin;
2749
+ case "outsideMargin":
2750
+ return pageNumber % 2 === 1 ? rightMargin : leftMargin;
2751
+ case "paragraph":
2752
+ return { start: context.paragraphLeft, size: context.paragraphWidth };
2753
+ case "column":
2754
+ case "margin":
2755
+ default:
2756
+ return { start: dims.marginLeft, size: dims.contentWidth };
2757
+ }
2758
+ }
2759
+ verticalAnchorReference(relativeFrom, dims, pageNumber, context) {
2760
+ const topMargin = { start: 0, size: dims.marginTop };
2761
+ const bottomMargin = {
2762
+ start: dims.marginTop + dims.contentHeight,
2763
+ size: dims.marginBottom
2764
+ };
2765
+ switch (relativeFrom) {
2766
+ case "page":
2767
+ return { start: 0, size: dims.pageHeight };
2768
+ case "paragraph":
2769
+ return { start: context.paragraphTop, size: context.paragraphHeight };
2770
+ case "line":
2771
+ return { start: context.staticTop, size: context.lineHeight };
2772
+ case "topMargin":
2773
+ return topMargin;
2774
+ case "bottomMargin":
2775
+ return bottomMargin;
2776
+ case "insideMargin":
2777
+ return pageNumber % 2 === 1 ? topMargin : bottomMargin;
2778
+ case "outsideMargin":
2779
+ return pageNumber % 2 === 1 ? bottomMargin : topMargin;
2780
+ case "margin":
2781
+ default:
2782
+ return { start: dims.marginTop, size: dims.contentHeight };
2783
+ }
2784
+ }
2785
+ resolveAnchorAxis(reference, objectSize, alignment, offset, pageNumber) {
2786
+ if (offset !== void 0) return reference.start + offset;
2787
+ let fraction = 0;
2788
+ if (alignment === "center") fraction = 0.5;
2789
+ else if (alignment === "right" || alignment === "bottom") fraction = 1;
2790
+ else if (alignment === "inside") fraction = pageNumber % 2 === 1 ? 0 : 1;
2791
+ else if (alignment === "outside") fraction = pageNumber % 2 === 1 ? 1 : 0;
2792
+ return reference.start + (reference.size - objectSize) * fraction;
2793
+ }
2610
2794
  /**
2611
2795
  * Creates a page container element.
2612
2796
  */
@@ -2633,22 +2817,22 @@ var PaginationEngine = class {
2633
2817
  pageBox.dataset.pageNumber = String(pageNumber);
2634
2818
  pageBox.dataset.sectionIndex = String(sectionIndex);
2635
2819
  pageBox.dataset.pageInSection = String(pageInSection);
2636
- const effectiveHeights = this.getEffectiveHeights(dims, sectionIndex, pageInSection, pageNumber);
2820
+ const bands = this.getPageBands(dims, sectionIndex, pageInSection, pageNumber);
2637
2821
  const headerSource = this.selectHeader(sectionIndex, pageInSection, pageNumber);
2638
2822
  if (headerSource) {
2639
2823
  const headerDiv = document.createElement("div");
2640
2824
  headerDiv.className = `${this.cssPrefix}header`;
2641
2825
  headerDiv.style.position = "absolute";
2642
- headerDiv.style.top = "0";
2826
+ headerDiv.style.top = `${bands.headerTop}pt`;
2827
+ headerDiv.style.bottom = "auto";
2643
2828
  headerDiv.style.left = `${dims.marginLeft}pt`;
2644
2829
  headerDiv.style.width = `${dims.contentWidth}pt`;
2645
- headerDiv.style.height = `${effectiveHeights.headerHeight}pt`;
2830
+ headerDiv.style.height = `${bands.headerHeight}pt`;
2646
2831
  headerDiv.style.overflow = "hidden";
2647
2832
  headerDiv.style.boxSizing = "border-box";
2648
2833
  headerDiv.style.display = "flex";
2649
2834
  headerDiv.style.flexDirection = "column";
2650
- headerDiv.style.justifyContent = "flex-end";
2651
- headerDiv.style.paddingBottom = "4pt";
2835
+ headerDiv.style.justifyContent = "flex-start";
2652
2836
  for (const child of Array.from(headerSource.childNodes)) {
2653
2837
  const clonedheaderDiv = child.cloneNode(true);
2654
2838
  if (clonedheaderDiv.nodeType === 1) this.makeClonedStoryInert(clonedheaderDiv);
@@ -2656,8 +2840,8 @@ var PaginationEngine = class {
2656
2840
  }
2657
2841
  pageBox.appendChild(headerDiv);
2658
2842
  }
2659
- const contentAreaTop = effectiveHeights.headerHeight;
2660
- const contentAreaHeight = effectiveHeights.contentHeight;
2843
+ const contentAreaTop = bands.bodyTop;
2844
+ const contentAreaHeight = bands.bodyHeight;
2661
2845
  const contentArea = document.createElement("div");
2662
2846
  contentArea.className = `${this.cssPrefix}content`;
2663
2847
  contentArea.style.position = "absolute";
@@ -2672,23 +2856,23 @@ var PaginationEngine = class {
2672
2856
  pageBox.appendChild(contentArea);
2673
2857
  const hasContinuation = continuation && continuation.remainingElements.length > 0;
2674
2858
  if (footnoteIds.length > 0 || hasContinuation) {
2675
- this.addPageFootnotes(pageBox, footnoteIds, dims, footnoteHeight, continuation, partialFootnotes);
2859
+ this.addPageFootnotes(pageBox, footnoteIds, dims, bands, footnoteHeight, continuation, partialFootnotes);
2676
2860
  }
2677
2861
  const footerSource = this.selectFooter(sectionIndex, pageInSection, pageNumber);
2678
2862
  if (footerSource) {
2679
2863
  const footerDiv = document.createElement("div");
2680
2864
  footerDiv.className = `${this.cssPrefix}footer`;
2681
2865
  footerDiv.style.position = "absolute";
2682
- footerDiv.style.bottom = "0";
2866
+ footerDiv.style.top = "auto";
2867
+ footerDiv.style.bottom = `${dims.footerDistance}pt`;
2683
2868
  footerDiv.style.left = `${dims.marginLeft}pt`;
2684
2869
  footerDiv.style.width = `${dims.contentWidth}pt`;
2685
- footerDiv.style.height = `${effectiveHeights.footerHeight}pt`;
2870
+ footerDiv.style.height = `${bands.footerHeight}pt`;
2686
2871
  footerDiv.style.overflow = "hidden";
2687
2872
  footerDiv.style.boxSizing = "border-box";
2688
2873
  footerDiv.style.display = "flex";
2689
2874
  footerDiv.style.flexDirection = "column";
2690
- footerDiv.style.justifyContent = "flex-start";
2691
- footerDiv.style.paddingTop = "4pt";
2875
+ footerDiv.style.justifyContent = "flex-end";
2692
2876
  for (const child of Array.from(footerSource.childNodes)) {
2693
2877
  const clonedfooterDiv = child.cloneNode(true);
2694
2878
  if (clonedfooterDiv.nodeType === 1) this.makeClonedStoryInert(clonedfooterDiv);
@@ -2703,6 +2887,7 @@ var PaginationEngine = class {
2703
2887
  pageBox.appendChild(pageNum);
2704
2888
  }
2705
2889
  this.containerElement.appendChild(pageBox);
2890
+ this.positionDrawingAnchors(pageBox, contentArea, dims, pageNumber);
2706
2891
  const notesEl = pageBox.querySelector(`.${this.cssPrefix}footnotes`);
2707
2892
  if (notesEl) {
2708
2893
  const notesHeightPt = pxToPt(notesEl.getBoundingClientRect().height);
@@ -6077,6 +6262,19 @@ function serializeInlineMarkdown(block) {
6077
6262
  function isListBlock(block) {
6078
6263
  return !!block.querySelector(":scope > [data-list-marker]");
6079
6264
  }
6265
+ function ensureListMarkerSeparator(block) {
6266
+ const marker = block.querySelector(":scope > [data-list-marker]");
6267
+ if (!marker) return;
6268
+ const next = marker.nextElementSibling;
6269
+ if (next?.hasAttribute("data-editor-list-separator")) return;
6270
+ const separator = block.ownerDocument.createElement("span");
6271
+ separator.setAttribute("data-editor-list-separator", "");
6272
+ separator.setAttribute("data-list-marker", "true");
6273
+ separator.setAttribute("aria-hidden", "true");
6274
+ separator.style.display = "none";
6275
+ separator.textContent = " ";
6276
+ marker.after(separator);
6277
+ }
6080
6278
  var GENERATED_CHROME_SELECTOR = '[data-list-marker], a.footnote-ref, a.endnote-ref, a[class$="-backref"]';
6081
6279
  function isGeneratedChrome(node) {
6082
6280
  return node?.nodeType === 1 && !!node.matches?.(GENERATED_CHROME_SELECTOR);
@@ -7367,6 +7565,7 @@ var DocxEditor = class _DocxEditor {
7367
7565
  const unid = el.getAttribute("data-anchor");
7368
7566
  if (!unid || !this.anchorIdOf(el)) return;
7369
7567
  el.setAttribute("contenteditable", "true");
7568
+ ensureListMarkerSeparator(el);
7370
7569
  el.querySelectorAll(GENERATED_CHROME_SELECTOR).forEach((m) => m.setAttribute("contenteditable", "false"));
7371
7570
  el.dataset.committedText = blockContentText(el);
7372
7571
  el.addEventListener("focus", () => {
@@ -8229,8 +8428,8 @@ var DocxEditor = class _DocxEditor {
8229
8428
  * editable `data-anchor` blocks in the notes section, so editing it afterwards needs no new op.
8230
8429
  *
8231
8430
  * 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.
8431
+ * another note, and the session rejects those with `AnchorWrongKind`. Reconciliation creates
8432
+ * the first notes section when needed and renumbers every affected citation in place.
8234
8433
  */
8235
8434
  insertFootnote(markdown = "New footnote.") {
8236
8435
  this.insertNote("footnote", markdown);
@@ -8622,7 +8821,7 @@ var DocxEditor = class _DocxEditor {
8622
8821
  * the current node — never a section div (multi-child) or the edit root. */
8623
8822
  unitWrapperOf(el) {
8624
8823
  let n = el;
8625
- while (n.parentElement && n.parentElement !== this.editRoot && n.parentElement.tagName === "DIV" && !n.parentElement.hasAttribute("data-anchor") && n.parentElement.childElementCount === 1) {
8824
+ 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
8825
  n = n.parentElement;
8627
8826
  }
8628
8827
  return n;
@@ -8658,6 +8857,7 @@ var DocxEditor = class _DocxEditor {
8658
8857
  const pureRemoved = diff.removed.filter(
8659
8858
  (i) => !diff.substituted.some((s) => s.oldIndex === i) && !movedOld.has(i)
8660
8859
  );
8860
+ const appliedFresh = /* @__PURE__ */ new Set();
8661
8861
  const nodeAt = (j) => {
8662
8862
  const oi = diff.keep.get(j);
8663
8863
  if (oi !== void 0) return oldNodes[oi];
@@ -8665,7 +8865,7 @@ var DocxEditor = class _DocxEditor {
8665
8865
  if (movedOi !== void 0) return oldNodes[movedOi];
8666
8866
  if (subOldByNew.has(j)) return fresh.get(j);
8667
8867
  const f = fresh.get(j);
8668
- return f && f.isConnected ? f : null;
8868
+ return f && appliedFresh.has(j) ? f : null;
8669
8869
  };
8670
8870
  for (const j of pureAdded) {
8671
8871
  const el = fresh.get(j);
@@ -8680,6 +8880,7 @@ var DocxEditor = class _DocxEditor {
8680
8880
  if (prevW) prevW.after(el);
8681
8881
  else if (nextW) nextW.before(el);
8682
8882
  else return "insert with no anchored neighbor";
8883
+ appliedFresh.add(j);
8683
8884
  this.wireUnit(el, units[j]);
8684
8885
  }
8685
8886
  for (const i of pureRemoved) {
@@ -8718,13 +8919,13 @@ var DocxEditor = class _DocxEditor {
8718
8919
  if (anchorEl) this.wireBlock(anchorEl);
8719
8920
  root.querySelectorAll("[data-anchor]").forEach((b) => this.wireBlock(b));
8720
8921
  }
8721
- /** Old-sequence diff state for one notes section. `null` requests remount (DOM not
8722
- * stampable/consistent). */
8922
+ /** Old-sequence diff state for one notes section. `null` requests remount when an
8923
+ * existing list is not stampable; an absent list is the valid first-note case. */
8723
8924
  notesDiff(sectionClass, units) {
8724
8925
  const ol = this.editRoot.querySelector(`section.${sectionClass} > ol`);
8725
8926
  const lis = ol ? Array.from(ol.children).filter((c) => c.tagName === "LI") : [];
8726
8927
  if (units.length === 0 && lis.length === 0) return { lis, diff: diffUnits([], []) };
8727
- if (!ol) return null;
8928
+ if (!ol) return { lis, diff: diffUnits([], units) };
8728
8929
  const tokens = [];
8729
8930
  for (const li of lis) {
8730
8931
  const unid = li.getAttribute("data-note-anchor");
@@ -8741,7 +8942,20 @@ var DocxEditor = class _DocxEditor {
8741
8942
  this.editRoot.querySelector(`section.${sectionClass}`)?.remove();
8742
8943
  return;
8743
8944
  }
8744
- const ol = this.editRoot.querySelector(`section.${sectionClass} > ol`);
8945
+ let section = this.editRoot.querySelector(`:scope > section.${sectionClass}`);
8946
+ let ol = section?.querySelector(`:scope > ol`) ?? null;
8947
+ if (!ol) {
8948
+ if (!section) {
8949
+ section = document.createElement("section");
8950
+ section.className = sectionClass;
8951
+ const following = sectionClass === "footnotes" ? this.editRoot.querySelector(`:scope > section.endnotes`) : null;
8952
+ if (following) following.before(section);
8953
+ else this.editRoot.append(section);
8954
+ }
8955
+ if (!section.querySelector(":scope > hr")) section.prepend(document.createElement("hr"));
8956
+ ol = document.createElement("ol");
8957
+ section.append(ol);
8958
+ }
8745
8959
  const prefix = sectionClass === "footnotes" ? "fn" : "en";
8746
8960
  const nodes = [];
8747
8961
  for (let j = 0; j < units.length; j++) {