@harbour-enterprises/superdoc 1.5.0 → 1.6.0-next.1

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.
@@ -1,6 +1,6 @@
1
1
  import { B as Buffer$2 } from "./jszip-B1fkPkPJ.es.js";
2
2
  import { t as twipsToInches, i as inchesToTwips, p as ptToTwips, l as linesToTwips, a as twipsToLines, b as pixelsToTwips, h as halfPointToPoints, c as twipsToPixels$2, d as convertSizeToCSS, e as inchesToPixels } from "./helpers-C8e9wR5l.es.js";
3
- import { g as generateDocxRandomId, T as TextSelection$1, o as objectIncludes, w as wrapTextsInRuns, D as DOMParser$1, c as createDocFromMarkdown, a as createDocFromHTML, b as chainableEditorState, d as convertMarkdownToHTML, f as findParentNode, e as findParentNodeClosestToPos, h as generateRandom32BitHex, i as generateRandomSigned32BitIntStrId, P as PluginKey, j as Plugin, M as Mapping, N as NodeSelection, k as Selection, l as Slice, m as DOMSerializer, F as Fragment, n as Mark$1, p as dropPoint, A as AllSelection, q as Schema$1, s as canSplit, t as resolveRunProperties, u as encodeMarksFromRPr, v as liftTarget, x as canJoin, y as joinPoint, z as replaceStep$1, R as ReplaceAroundStep$1, B as htmlHandler, C as ReplaceStep, E as getResolvedParagraphProperties, G as changeListLevel, H as isList$1, I as updateNumberingProperties, L as ListHelpers, J as inputRulesPlugin, K as TrackDeleteMarkName$1, O as TrackInsertMarkName$1, Q as TrackFormatMarkName$1, U as AddMarkStep, V as RemoveMarkStep, W as CommandService, S as SuperConverter, X as EditorState, Y as unflattenListsInHtml, Z as SelectionRange, _ as Transform, $ as createOoxmlResolver, a0 as translator, a1 as translator$1, a2 as resolveDocxFontFamily, a3 as combineIndentProperties, a4 as _getReferencedTableStyles, a5 as decodeRPrFromMarks, a6 as calculateResolvedParagraphProperties, a7 as encodeCSSFromPPr, a8 as encodeCSSFromRPr, a9 as generateOrderedListIndex, aa as docxNumberingHelpers, ab as InputRule, ac as insertNewRelationship, ad as kebabCase$1, ae as getUnderlineCssString } from "./SuperConverter-Dfxp14RO.es.js";
3
+ import { g as generateDocxRandomId, T as TextSelection$1, o as objectIncludes, w as wrapTextsInRuns, D as DOMParser$1, c as createDocFromMarkdown, a as createDocFromHTML, b as chainableEditorState, d as convertMarkdownToHTML, f as findParentNode, e as findParentNodeClosestToPos, h as generateRandom32BitHex, i as generateRandomSigned32BitIntStrId, P as PluginKey, j as Plugin, M as Mapping, N as NodeSelection, k as Selection, l as Slice, m as DOMSerializer, F as Fragment, n as Mark$1, p as dropPoint, A as AllSelection, q as Schema$1, s as canSplit, t as resolveRunProperties, u as encodeMarksFromRPr, v as liftTarget, x as canJoin, y as joinPoint, z as replaceStep$1, R as ReplaceAroundStep$1, B as htmlHandler, C as ReplaceStep, E as getResolvedParagraphProperties, G as changeListLevel, H as isList$1, I as updateNumberingProperties, L as ListHelpers, J as inputRulesPlugin, K as TrackDeleteMarkName$1, O as TrackInsertMarkName$1, Q as TrackFormatMarkName$1, U as AddMarkStep, V as RemoveMarkStep, W as CommandService, S as SuperConverter, X as EditorState, Y as unflattenListsInHtml, Z as SelectionRange, _ as Transform, $ as createOoxmlResolver, a0 as translator, a1 as translator$1, a2 as resolveDocxFontFamily, a3 as combineIndentProperties, a4 as _getReferencedTableStyles, a5 as decodeRPrFromMarks, a6 as calculateResolvedParagraphProperties, a7 as encodeCSSFromPPr, a8 as encodeCSSFromRPr, a9 as generateOrderedListIndex, aa as docxNumberingHelpers, ab as InputRule, ac as insertNewRelationship, ad as kebabCase$1, ae as getUnderlineCssString } from "./SuperConverter-Cyn9peRO.es.js";
4
4
  import { p as process$1, r as ref, C as global$1, c as computed, E as createElementBlock, F as Fragment$1, S as renderList, O as withModifiers, G as openBlock, P as normalizeClass, M as createCommentVNode, H as toDisplayString, K as createBaseVNode, U as createApp, f as onMounted, X as onUnmounted, R as withDirectives, v as unref, Y as vModelText, y as nextTick, L as normalizeStyle, u as watch, Z as withKeys, _ as createTextVNode, I as createVNode, h as h$1, $ as readonly, s as getCurrentInstance, o as onBeforeUnmount, j as reactive, b as onBeforeMount, i as inject, a0 as onActivated, a1 as onDeactivated, a2 as Comment, d as defineComponent, a as provide, g as Teleport, t as toRef, a3 as renderSlot, a4 as isVNode, D as shallowRef, w as watchEffect, T as Transition, a5 as mergeProps, a6 as vShow, a7 as cloneVNode, a8 as Text$2, m as markRaw, N as createBlock, J as withCtx, a9 as useCssVars, V as resolveDynamicComponent, aa as normalizeProps, ab as guardReactiveProps } from "./vue-BnBKJwCW.es.js";
5
5
  import "./jszip.min-DCl8qkFO.es.js";
6
6
  import { E as EventEmitter$1 } from "./eventemitter3-CwrdEv8r.es.js";
@@ -15507,7 +15507,7 @@ const canUseDOM = () => {
15507
15507
  return false;
15508
15508
  }
15509
15509
  };
15510
- const summaryVersion = "1.5.0";
15510
+ const summaryVersion = "1.6.0-next.1";
15511
15511
  const nodeKeys = ["group", "content", "marks", "inline", "atom", "defining", "code", "tableRole", "summary"];
15512
15512
  const markKeys = ["group", "inclusive", "excludes", "spanning", "code"];
15513
15513
  function mapAttributes(attrs) {
@@ -17764,6 +17764,10 @@ class Editor extends EventEmitter {
17764
17764
  const hasCustomSettings = !!this.converter.convertedXml["word/settings.xml"]?.elements?.length;
17765
17765
  const customSettings = hasCustomSettings ? this.converter.schemaToXml(this.converter.convertedXml["word/settings.xml"]?.elements?.[0]) : null;
17766
17766
  const rels = this.converter.schemaToXml(this.converter.convertedXml["word/_rels/document.xml.rels"].elements[0]);
17767
+ const footnotesData = this.converter.convertedXml["word/footnotes.xml"];
17768
+ const footnotesXml = footnotesData?.elements?.[0] ? this.converter.schemaToXml(footnotesData.elements[0]) : null;
17769
+ const footnotesRelsData = this.converter.convertedXml["word/_rels/footnotes.xml.rels"];
17770
+ const footnotesRelsXml = footnotesRelsData?.elements?.[0] ? this.converter.schemaToXml(footnotesRelsData.elements[0]) : null;
17767
17771
  const media = this.converter.addedMedia;
17768
17772
  const updatedHeadersFooters = {};
17769
17773
  Object.entries(this.converter.convertedXml).forEach(([name, json2]) => {
@@ -17788,6 +17792,12 @@ class Editor extends EventEmitter {
17788
17792
  if (hasCustomSettings) {
17789
17793
  updatedDocs["word/settings.xml"] = String(customSettings);
17790
17794
  }
17795
+ if (footnotesXml) {
17796
+ updatedDocs["word/footnotes.xml"] = String(footnotesXml);
17797
+ }
17798
+ if (footnotesRelsXml) {
17799
+ updatedDocs["word/_rels/footnotes.xml.rels"] = String(footnotesRelsXml);
17800
+ }
17791
17801
  if (comments.length) {
17792
17802
  const commentsXml = this.converter.schemaToXml(this.converter.convertedXml["word/comments.xml"].elements[0]);
17793
17803
  const commentsExtendedXml = this.converter.schemaToXml(
@@ -18164,7 +18174,7 @@ class Editor extends EventEmitter {
18164
18174
  * Process collaboration migrations
18165
18175
  */
18166
18176
  processCollaborationMigrations() {
18167
- console.debug("[checkVersionMigrations] Current editor version", "1.5.0");
18177
+ console.debug("[checkVersionMigrations] Current editor version", "1.6.0-next.1");
18168
18178
  if (!this.options.ydoc) return;
18169
18179
  const metaMap = this.options.ydoc.getMap("meta");
18170
18180
  let docVersion = metaMap.get("version");
@@ -34045,7 +34055,7 @@ function getParagraphSpacingBefore(block) {
34045
34055
  const value = spacing?.before ?? spacing?.lineSpaceBefore;
34046
34056
  return typeof value === "number" && Number.isFinite(value) && value > 0 ? value : 0;
34047
34057
  }
34048
- function getParagraphSpacingAfter(block) {
34058
+ function getParagraphSpacingAfter$1(block) {
34049
34059
  const spacing = block.attrs?.spacing;
34050
34060
  const value = spacing?.after ?? spacing?.lineSpaceAfter;
34051
34061
  return typeof value === "number" && Number.isFinite(value) && value > 0 ? value : 0;
@@ -34072,7 +34082,7 @@ function getMeasureHeight(block, measure) {
34072
34082
  }
34073
34083
  const DEFAULT_PAGE_SIZE$2 = { w: 612, h: 792 };
34074
34084
  const DEFAULT_MARGINS$2 = { top: 72, right: 72, bottom: 72, left: 72 };
34075
- const COLUMN_EPSILON = 1e-4;
34085
+ const COLUMN_EPSILON$1 = 1e-4;
34076
34086
  const layoutDebugEnabled$1 = typeof process$1 !== "undefined" && typeof process$1.env !== "undefined" && Boolean(process$1.env.SD_DEBUG_LAYOUT);
34077
34087
  const layoutLog = (...args) => {
34078
34088
  if (!layoutDebugEnabled$1) return;
@@ -34383,7 +34393,13 @@ function layoutDocument(blocks, measures, options = {}) {
34383
34393
  const paginator = createPaginator({
34384
34394
  margins: paginatorMargins,
34385
34395
  getActiveTopMargin: () => activeTopMargin,
34386
- getActiveBottomMargin: () => activeBottomMargin,
34396
+ getActiveBottomMargin: () => {
34397
+ const reserves = options.footnoteReservedByPageIndex;
34398
+ const pageIndex = Math.max(0, pageCount - 1);
34399
+ const reserve = Array.isArray(reserves) ? reserves[pageIndex] : 0;
34400
+ const reservePx = typeof reserve === "number" && Number.isFinite(reserve) && reserve > 0 ? reserve : 0;
34401
+ return activeBottomMargin + reservePx;
34402
+ },
34387
34403
  getActiveHeaderDistance: () => activeHeaderDistance,
34388
34404
  getActiveFooterDistance: () => activeFooterDistance,
34389
34405
  getActivePageSize: () => activePageSize,
@@ -34867,7 +34883,7 @@ function layoutDocument(blocks, measures, options = {}) {
34867
34883
  if (!shouldSkipAnchoredTable) {
34868
34884
  let state = paginator.ensurePage();
34869
34885
  const availableHeight = state.contentBottom - state.cursorY;
34870
- const spacingAfter = getParagraphSpacingAfter(paraBlock);
34886
+ const spacingAfter = getParagraphSpacingAfter$1(paraBlock);
34871
34887
  const currentHeight = getMeasureHeight(paraBlock, measure);
34872
34888
  const nextHeight = getMeasureHeight(nextBlock, nextMeasure);
34873
34889
  const nextIsParagraph = nextBlock.kind === "paragraph" && nextMeasure.kind === "paragraph";
@@ -35180,7 +35196,7 @@ function normalizeColumns(input, contentWidth) {
35180
35196
  const gap = Math.max(0, input?.gap ?? 0);
35181
35197
  const totalGap = gap * (count - 1);
35182
35198
  const width = (contentWidth - totalGap) / count;
35183
- if (width <= COLUMN_EPSILON) {
35199
+ if (width <= COLUMN_EPSILON$1) {
35184
35200
  return {
35185
35201
  count: 1,
35186
35202
  gap: 0,
@@ -36943,6 +36959,417 @@ const perfLog = (...args) => {
36943
36959
  if (!layoutDebugEnabled) return;
36944
36960
  console.log(...args);
36945
36961
  };
36962
+ const isFootnotesLayoutInput = (value) => {
36963
+ if (!value || typeof value !== "object") return false;
36964
+ const v = value;
36965
+ if (!Array.isArray(v.refs)) return false;
36966
+ if (!(v.blocksById instanceof Map)) return false;
36967
+ return true;
36968
+ };
36969
+ const findPageIndexForPos = (layout, pos) => {
36970
+ if (!Number.isFinite(pos)) return null;
36971
+ const fallbackRanges = [];
36972
+ for (let pageIndex = 0; pageIndex < layout.pages.length; pageIndex++) {
36973
+ const page = layout.pages[pageIndex];
36974
+ let minStart = null;
36975
+ let maxEnd = null;
36976
+ for (const fragment of page.fragments) {
36977
+ const pmStart = fragment.pmStart;
36978
+ const pmEnd = fragment.pmEnd;
36979
+ if (pmStart == null || pmEnd == null) continue;
36980
+ if (minStart == null || pmStart < minStart) minStart = pmStart;
36981
+ if (maxEnd == null || pmEnd > maxEnd) maxEnd = pmEnd;
36982
+ if (pos >= pmStart && pos <= pmEnd) {
36983
+ return pageIndex;
36984
+ }
36985
+ }
36986
+ fallbackRanges[pageIndex] = minStart != null && maxEnd != null ? { pageIndex, minStart, maxEnd } : null;
36987
+ }
36988
+ let best = null;
36989
+ for (const entry of fallbackRanges) {
36990
+ if (!entry) continue;
36991
+ const distance = pos < entry.minStart ? entry.minStart - pos : pos > entry.maxEnd ? pos - entry.maxEnd : 0;
36992
+ if (!best || distance < best.distance) {
36993
+ best = { pageIndex: entry.pageIndex, distance };
36994
+ }
36995
+ }
36996
+ if (best) return best.pageIndex;
36997
+ if (layout.pages.length > 0) return layout.pages.length - 1;
36998
+ return null;
36999
+ };
37000
+ const footnoteColumnKey = (pageIndex, columnIndex) => `${pageIndex}:${columnIndex}`;
37001
+ const COLUMN_EPSILON = 0.01;
37002
+ const normalizeColumnsForFootnotes = (input, contentWidth) => {
37003
+ const rawCount = Number.isFinite(input?.count) ? Math.floor(input.count) : 1;
37004
+ const count = Math.max(1, rawCount || 1);
37005
+ const gap = Math.max(0, input?.gap ?? 0);
37006
+ const totalGap = gap * (count - 1);
37007
+ const width = (contentWidth - totalGap) / count;
37008
+ if (!Number.isFinite(width) || width <= COLUMN_EPSILON) {
37009
+ return {
37010
+ count: 1,
37011
+ gap: 0,
37012
+ width: Math.max(0, contentWidth)
37013
+ };
37014
+ }
37015
+ return { count, gap, width };
37016
+ };
37017
+ const resolveSectionColumnsByIndex = (options, blocks) => {
37018
+ const result = /* @__PURE__ */ new Map();
37019
+ let activeColumns = options.columns ?? { count: 1, gap: 0 };
37020
+ if (blocks && blocks.length > 0) {
37021
+ for (const block of blocks) {
37022
+ if (block.kind !== "sectionBreak") continue;
37023
+ const sectionIndexRaw = block.attrs?.sectionIndex;
37024
+ const sectionIndex = typeof sectionIndexRaw === "number" && Number.isFinite(sectionIndexRaw) ? sectionIndexRaw : result.size;
37025
+ if (block.columns) {
37026
+ activeColumns = { count: block.columns.count, gap: block.columns.gap };
37027
+ }
37028
+ result.set(sectionIndex, { ...activeColumns });
37029
+ }
37030
+ }
37031
+ if (result.size === 0) {
37032
+ result.set(0, { ...activeColumns });
37033
+ }
37034
+ return result;
37035
+ };
37036
+ const resolvePageColumns = (layout, options, blocks) => {
37037
+ const sectionColumns = resolveSectionColumnsByIndex(options, blocks);
37038
+ const result = /* @__PURE__ */ new Map();
37039
+ for (let pageIndex = 0; pageIndex < layout.pages.length; pageIndex += 1) {
37040
+ const page = layout.pages[pageIndex];
37041
+ const pageSize = page.size ?? layout.pageSize ?? DEFAULT_PAGE_SIZE$1;
37042
+ const marginLeft = normalizeMargin(
37043
+ page.margins?.left,
37044
+ normalizeMargin(options.margins?.left, DEFAULT_MARGINS$1.left)
37045
+ );
37046
+ const marginRight = normalizeMargin(
37047
+ page.margins?.right,
37048
+ normalizeMargin(options.margins?.right, DEFAULT_MARGINS$1.right)
37049
+ );
37050
+ const contentWidth = pageSize.w - (marginLeft + marginRight);
37051
+ const sectionIndex = page.sectionIndex ?? 0;
37052
+ const columnsConfig = sectionColumns.get(sectionIndex) ?? options.columns ?? { count: 1, gap: 0 };
37053
+ const normalized = normalizeColumnsForFootnotes(columnsConfig, contentWidth);
37054
+ result.set(pageIndex, { ...normalized, left: marginLeft, contentWidth });
37055
+ }
37056
+ return result;
37057
+ };
37058
+ const findFragmentForPos = (page, pos) => {
37059
+ for (const fragment of page.fragments) {
37060
+ const pmStart = fragment.pmStart;
37061
+ const pmEnd = fragment.pmEnd;
37062
+ if (pmStart == null || pmEnd == null) continue;
37063
+ if (pos >= pmStart && pos <= pmEnd) {
37064
+ return fragment;
37065
+ }
37066
+ }
37067
+ return null;
37068
+ };
37069
+ const assignFootnotesToColumns = (layout, refs, pageColumns) => {
37070
+ const result = /* @__PURE__ */ new Map();
37071
+ const seenByColumn = /* @__PURE__ */ new Map();
37072
+ for (const ref2 of refs) {
37073
+ const pageIndex = findPageIndexForPos(layout, ref2.pos);
37074
+ if (pageIndex == null) continue;
37075
+ const columns = pageColumns.get(pageIndex);
37076
+ const page = layout.pages[pageIndex];
37077
+ let columnIndex = 0;
37078
+ if (columns && columns.count > 1 && page) {
37079
+ const fragment = findFragmentForPos(page, ref2.pos);
37080
+ if (fragment && typeof fragment.x === "number") {
37081
+ const columnStride = columns.width + columns.gap;
37082
+ const rawIndex = columnStride > 0 ? Math.floor((fragment.x - columns.left) / columnStride) : 0;
37083
+ columnIndex = Math.max(0, Math.min(columns.count - 1, rawIndex));
37084
+ }
37085
+ }
37086
+ const key2 = footnoteColumnKey(pageIndex, columnIndex);
37087
+ let seen = seenByColumn.get(key2);
37088
+ if (!seen) {
37089
+ seen = /* @__PURE__ */ new Set();
37090
+ seenByColumn.set(key2, seen);
37091
+ }
37092
+ if (seen.has(ref2.id)) continue;
37093
+ seen.add(ref2.id);
37094
+ const pageMap = result.get(pageIndex) ?? /* @__PURE__ */ new Map();
37095
+ const list = pageMap.get(columnIndex) ?? [];
37096
+ list.push(ref2.id);
37097
+ pageMap.set(columnIndex, list);
37098
+ result.set(pageIndex, pageMap);
37099
+ }
37100
+ return result;
37101
+ };
37102
+ const resolveFootnoteMeasurementWidth = (options, blocks) => {
37103
+ const pageSize = options.pageSize ?? DEFAULT_PAGE_SIZE$1;
37104
+ const margins = {
37105
+ right: normalizeMargin(options.margins?.right, DEFAULT_MARGINS$1.right),
37106
+ left: normalizeMargin(options.margins?.left, DEFAULT_MARGINS$1.left)
37107
+ };
37108
+ let width = pageSize.w - (margins.left + margins.right);
37109
+ let activeColumns = options.columns ?? { count: 1, gap: 0 };
37110
+ let activePageSize = pageSize;
37111
+ let activeMargins = { ...margins };
37112
+ const resolveColumnWidth = () => {
37113
+ const contentWidth = activePageSize.w - (activeMargins.left + activeMargins.right);
37114
+ const normalized = normalizeColumnsForFootnotes(activeColumns, contentWidth);
37115
+ return normalized.width;
37116
+ };
37117
+ width = resolveColumnWidth();
37118
+ if (blocks && blocks.length > 0) {
37119
+ for (const block of blocks) {
37120
+ if (block.kind !== "sectionBreak") continue;
37121
+ activePageSize = block.pageSize ?? activePageSize;
37122
+ activeMargins = {
37123
+ right: normalizeMargin(block.margins?.right, activeMargins.right),
37124
+ left: normalizeMargin(block.margins?.left, activeMargins.left)
37125
+ };
37126
+ if (block.columns) {
37127
+ activeColumns = { count: block.columns.count, gap: block.columns.gap };
37128
+ }
37129
+ const w = resolveColumnWidth();
37130
+ if (w > 0 && w < width) width = w;
37131
+ }
37132
+ }
37133
+ if (!Number.isFinite(width) || width <= 0) return 0;
37134
+ return width;
37135
+ };
37136
+ const MIN_FOOTNOTE_BODY_HEIGHT = 1;
37137
+ const DEFAULT_FOOTNOTE_SEPARATOR_SPACING_BEFORE = 12;
37138
+ const computeMaxFootnoteReserve = (layoutForPages, pageIndex, baseReserve = 0) => {
37139
+ const page = layoutForPages.pages?.[pageIndex];
37140
+ if (!page) return 0;
37141
+ const pageSize = page.size ?? layoutForPages.pageSize ?? DEFAULT_PAGE_SIZE$1;
37142
+ const topMargin = normalizeMargin(page.margins?.top, DEFAULT_MARGINS$1.top);
37143
+ const bottomWithReserve = normalizeMargin(page.margins?.bottom, DEFAULT_MARGINS$1.bottom);
37144
+ const baseReserveSafe = Number.isFinite(baseReserve) ? Math.max(0, baseReserve) : 0;
37145
+ const bottomMargin = Math.max(0, bottomWithReserve - baseReserveSafe);
37146
+ const availableForBody = pageSize.h - topMargin - bottomMargin;
37147
+ if (!Number.isFinite(availableForBody)) return 0;
37148
+ return Math.max(0, availableForBody - MIN_FOOTNOTE_BODY_HEIGHT);
37149
+ };
37150
+ const sumLineHeights$1 = (lines, fromLine, toLine) => {
37151
+ if (!lines || fromLine >= toLine) return 0;
37152
+ let total = 0;
37153
+ for (let i = fromLine; i < toLine; i += 1) {
37154
+ total += lines[i]?.lineHeight ?? 0;
37155
+ }
37156
+ return total;
37157
+ };
37158
+ const getParagraphSpacingAfter = (block) => {
37159
+ const spacing = block.attrs?.spacing;
37160
+ const value = spacing?.after ?? spacing?.lineSpaceAfter;
37161
+ return typeof value === "number" && Number.isFinite(value) && value > 0 ? value : 0;
37162
+ };
37163
+ const resolveSeparatorSpacingBefore = (rangesByFootnoteId, measuresById, explicitValue, fallbackValue) => {
37164
+ if (typeof explicitValue === "number" && Number.isFinite(explicitValue)) {
37165
+ return Math.max(0, explicitValue);
37166
+ }
37167
+ for (const ranges of rangesByFootnoteId.values()) {
37168
+ for (const range of ranges) {
37169
+ if (range.kind === "paragraph") {
37170
+ const measure = measuresById.get(range.blockId);
37171
+ if (measure?.kind !== "paragraph") continue;
37172
+ const lineHeight2 = measure.lines?.[range.fromLine]?.lineHeight ?? measure.lines?.[0]?.lineHeight;
37173
+ if (typeof lineHeight2 === "number" && Number.isFinite(lineHeight2) && lineHeight2 > 0) {
37174
+ return lineHeight2;
37175
+ }
37176
+ }
37177
+ if (range.kind === "list-item") {
37178
+ const measure = measuresById.get(range.blockId);
37179
+ if (measure?.kind !== "list") continue;
37180
+ const itemMeasure = measure.items.find((item) => item.itemId === range.itemId);
37181
+ const lineHeight2 = itemMeasure?.paragraph?.lines?.[range.fromLine]?.lineHeight ?? itemMeasure?.paragraph?.lines?.[0]?.lineHeight;
37182
+ if (typeof lineHeight2 === "number" && Number.isFinite(lineHeight2) && lineHeight2 > 0) {
37183
+ return lineHeight2;
37184
+ }
37185
+ }
37186
+ }
37187
+ }
37188
+ return Math.max(0, fallbackValue);
37189
+ };
37190
+ const getRangeRenderHeight = (range) => {
37191
+ if (range.kind === "paragraph" || range.kind === "list-item") {
37192
+ const spacing = range.toLine >= range.totalLines ? range.spacingAfter : 0;
37193
+ return range.height + spacing;
37194
+ }
37195
+ return range.height;
37196
+ };
37197
+ const buildFootnoteRanges = (blocks, measuresById) => {
37198
+ const ranges = [];
37199
+ blocks.forEach((block) => {
37200
+ const measure = measuresById.get(block.id);
37201
+ if (!measure) return;
37202
+ if (block.kind === "paragraph") {
37203
+ if (measure.kind !== "paragraph") return;
37204
+ const lineCount = measure.lines?.length ?? 0;
37205
+ if (lineCount === 0) return;
37206
+ ranges.push({
37207
+ kind: "paragraph",
37208
+ blockId: block.id,
37209
+ fromLine: 0,
37210
+ toLine: lineCount,
37211
+ totalLines: lineCount,
37212
+ height: sumLineHeights$1(measure.lines, 0, lineCount),
37213
+ spacingAfter: getParagraphSpacingAfter(block)
37214
+ });
37215
+ return;
37216
+ }
37217
+ if (block.kind === "list") {
37218
+ if (measure.kind !== "list") return;
37219
+ block.items.forEach((item) => {
37220
+ const itemMeasure = measure.items.find((entry) => entry.itemId === item.id);
37221
+ if (!itemMeasure) return;
37222
+ const lineCount = itemMeasure.paragraph.lines?.length ?? 0;
37223
+ if (lineCount === 0) return;
37224
+ ranges.push({
37225
+ kind: "list-item",
37226
+ blockId: block.id,
37227
+ itemId: item.id,
37228
+ fromLine: 0,
37229
+ toLine: lineCount,
37230
+ totalLines: lineCount,
37231
+ height: sumLineHeights$1(itemMeasure.paragraph.lines, 0, lineCount),
37232
+ spacingAfter: getParagraphSpacingAfter(item.paragraph)
37233
+ });
37234
+ });
37235
+ return;
37236
+ }
37237
+ if (block.kind === "table" && measure.kind === "table") {
37238
+ const height = Math.max(0, measure.totalHeight ?? 0);
37239
+ if (height > 0) {
37240
+ ranges.push({ kind: "table", blockId: block.id, height });
37241
+ }
37242
+ return;
37243
+ }
37244
+ if (block.kind === "image" && measure.kind === "image") {
37245
+ const height = Math.max(0, measure.height ?? 0);
37246
+ if (height > 0) {
37247
+ ranges.push({ kind: "image", blockId: block.id, height });
37248
+ }
37249
+ return;
37250
+ }
37251
+ if (block.kind === "drawing" && measure.kind === "drawing") {
37252
+ const height = Math.max(0, measure.height ?? 0);
37253
+ if (height > 0) {
37254
+ ranges.push({ kind: "drawing", blockId: block.id, height });
37255
+ }
37256
+ }
37257
+ });
37258
+ return ranges;
37259
+ };
37260
+ const splitRangeAtHeight = (range, availableHeight, measuresById) => {
37261
+ if (availableHeight <= 0) return { fitted: null, remaining: range };
37262
+ if (range.kind !== "paragraph") {
37263
+ return getRangeRenderHeight(range) <= availableHeight ? { fitted: range, remaining: null } : { fitted: null, remaining: range };
37264
+ }
37265
+ const measure = measuresById.get(range.blockId);
37266
+ if (!measure || measure.kind !== "paragraph" || !measure.lines) {
37267
+ return getRangeRenderHeight(range) <= availableHeight ? { fitted: range, remaining: null } : { fitted: null, remaining: range };
37268
+ }
37269
+ let accumulatedHeight = 0;
37270
+ let splitLine = range.fromLine;
37271
+ for (let i = range.fromLine; i < range.toLine; i += 1) {
37272
+ const lineHeight2 = measure.lines[i]?.lineHeight ?? 0;
37273
+ if (accumulatedHeight + lineHeight2 > availableHeight) break;
37274
+ accumulatedHeight += lineHeight2;
37275
+ splitLine = i + 1;
37276
+ }
37277
+ if (splitLine === range.fromLine) {
37278
+ return { fitted: null, remaining: range };
37279
+ }
37280
+ const fitted = {
37281
+ ...range,
37282
+ toLine: splitLine,
37283
+ height: sumLineHeights$1(measure.lines, range.fromLine, splitLine)
37284
+ };
37285
+ if (splitLine >= range.toLine) {
37286
+ return getRangeRenderHeight(fitted) <= availableHeight ? { fitted, remaining: null } : { fitted: null, remaining: range };
37287
+ }
37288
+ const remaining = {
37289
+ ...range,
37290
+ fromLine: splitLine,
37291
+ height: sumLineHeights$1(measure.lines, splitLine, range.toLine)
37292
+ };
37293
+ return { fitted, remaining };
37294
+ };
37295
+ const forceFitFirstRange = (range, measuresById) => {
37296
+ if (range.kind !== "paragraph") {
37297
+ return { fitted: range, remaining: null };
37298
+ }
37299
+ const measure = measuresById.get(range.blockId);
37300
+ if (!measure || measure.kind !== "paragraph" || !measure.lines?.length) {
37301
+ return { fitted: range, remaining: null };
37302
+ }
37303
+ const nextLine = Math.min(range.fromLine + 1, range.toLine);
37304
+ const fitted = {
37305
+ ...range,
37306
+ toLine: nextLine,
37307
+ height: sumLineHeights$1(measure.lines, range.fromLine, nextLine)
37308
+ };
37309
+ if (nextLine >= range.toLine) {
37310
+ return { fitted, remaining: null };
37311
+ }
37312
+ const remaining = {
37313
+ ...range,
37314
+ fromLine: nextLine,
37315
+ height: sumLineHeights$1(measure.lines, nextLine, range.toLine)
37316
+ };
37317
+ return { fitted, remaining };
37318
+ };
37319
+ const fitFootnoteContent = (id, inputRanges, availableHeight, pageIndex, columnIndex, isContinuation, measuresById, forceFirstRange) => {
37320
+ const fittedRanges = [];
37321
+ let remainingRanges = [];
37322
+ let usedHeight = 0;
37323
+ const maxHeight = Math.max(0, availableHeight);
37324
+ for (let index2 = 0; index2 < inputRanges.length; index2 += 1) {
37325
+ const range = inputRanges[index2];
37326
+ const remainingSpace = maxHeight - usedHeight;
37327
+ const rangeHeight = getRangeRenderHeight(range);
37328
+ if (rangeHeight <= remainingSpace) {
37329
+ fittedRanges.push(range);
37330
+ usedHeight += rangeHeight;
37331
+ continue;
37332
+ }
37333
+ if (range.kind === "paragraph") {
37334
+ const split = splitRangeAtHeight(range, remainingSpace, measuresById);
37335
+ if (split.fitted && getRangeRenderHeight(split.fitted) <= remainingSpace) {
37336
+ fittedRanges.push(split.fitted);
37337
+ usedHeight += getRangeRenderHeight(split.fitted);
37338
+ }
37339
+ if (split.remaining) {
37340
+ remainingRanges = [split.remaining, ...inputRanges.slice(index2 + 1)];
37341
+ } else {
37342
+ remainingRanges = inputRanges.slice(index2 + 1);
37343
+ }
37344
+ break;
37345
+ }
37346
+ remainingRanges = [range, ...inputRanges.slice(index2 + 1)];
37347
+ break;
37348
+ }
37349
+ if (fittedRanges.length === 0 && forceFirstRange && inputRanges.length > 0) {
37350
+ const forced = forceFitFirstRange(inputRanges[0], measuresById);
37351
+ if (forced.fitted) {
37352
+ fittedRanges.push(forced.fitted);
37353
+ usedHeight = getRangeRenderHeight(forced.fitted);
37354
+ remainingRanges = [];
37355
+ if (forced.remaining) {
37356
+ remainingRanges.push(forced.remaining);
37357
+ }
37358
+ remainingRanges.push(...inputRanges.slice(1));
37359
+ }
37360
+ }
37361
+ return {
37362
+ slice: {
37363
+ id,
37364
+ pageIndex,
37365
+ columnIndex,
37366
+ isContinuation,
37367
+ ranges: fittedRanges,
37368
+ totalHeight: usedHeight
37369
+ },
37370
+ remainingRanges
37371
+ };
37372
+ };
36946
37373
  async function incrementalLayout(previousBlocks, _previousLayout, nextBlocks, options, measureBlock2, headerFooter) {
36947
37374
  performance.now();
36948
37375
  const dirty = computeDirtyRegions(previousBlocks, nextBlocks);
@@ -37206,6 +37633,497 @@ async function incrementalLayout(previousBlocks, _previousLayout, nextBlocks, op
37206
37633
  converged
37207
37634
  });
37208
37635
  }
37636
+ let extraBlocks;
37637
+ let extraMeasures;
37638
+ const footnotesInput = isFootnotesLayoutInput(options.footnotes) ? options.footnotes : null;
37639
+ if (footnotesInput && footnotesInput.refs.length > 0 && footnotesInput.blocksById.size > 0) {
37640
+ const gap = typeof footnotesInput.gap === "number" && Number.isFinite(footnotesInput.gap) ? footnotesInput.gap : 2;
37641
+ const topPadding = typeof footnotesInput.topPadding === "number" && Number.isFinite(footnotesInput.topPadding) ? footnotesInput.topPadding : 6;
37642
+ const dividerHeight = typeof footnotesInput.dividerHeight === "number" && Number.isFinite(footnotesInput.dividerHeight) ? footnotesInput.dividerHeight : 6;
37643
+ const safeGap = Math.max(0, gap);
37644
+ const safeTopPadding = Math.max(0, topPadding);
37645
+ const safeDividerHeight = Math.max(0, dividerHeight);
37646
+ const continuationDividerHeight = safeDividerHeight;
37647
+ const continuationDividerWidthFactor = 0.3;
37648
+ const footnoteWidth = resolveFootnoteMeasurementWidth(options, currentBlocks);
37649
+ if (footnoteWidth > 0) {
37650
+ const footnoteConstraints = { maxWidth: footnoteWidth, maxHeight: measurementHeight };
37651
+ const collectFootnoteIdsByColumn = (idsByColumn2) => {
37652
+ const ids = /* @__PURE__ */ new Set();
37653
+ idsByColumn2.forEach((columns) => {
37654
+ columns.forEach((list) => {
37655
+ list.forEach((id) => ids.add(id));
37656
+ });
37657
+ });
37658
+ return ids;
37659
+ };
37660
+ const measureFootnoteBlocks = async (ids) => {
37661
+ const needed = /* @__PURE__ */ new Map();
37662
+ ids.forEach((id) => {
37663
+ const blocks2 = footnotesInput.blocksById.get(id) ?? [];
37664
+ blocks2.forEach((block) => {
37665
+ if (block?.id && !needed.has(block.id)) {
37666
+ needed.set(block.id, block);
37667
+ }
37668
+ });
37669
+ });
37670
+ const blocks = Array.from(needed.values());
37671
+ const measuresById2 = /* @__PURE__ */ new Map();
37672
+ await Promise.all(
37673
+ blocks.map(async (block) => {
37674
+ const cached = measureCache.get(block, footnoteConstraints.maxWidth, footnoteConstraints.maxHeight);
37675
+ if (cached) {
37676
+ measuresById2.set(block.id, cached);
37677
+ return;
37678
+ }
37679
+ const measurement = await measureBlock2(block, footnoteConstraints);
37680
+ measureCache.set(block, footnoteConstraints.maxWidth, footnoteConstraints.maxHeight, measurement);
37681
+ measuresById2.set(block.id, measurement);
37682
+ })
37683
+ );
37684
+ return { blocks, measuresById: measuresById2 };
37685
+ };
37686
+ const computeFootnoteLayoutPlan = (layoutForPages, idsByColumn2, measuresById2, baseReserves = [], pageColumns2) => {
37687
+ const pageCount = layoutForPages.pages.length;
37688
+ const slicesByPage = /* @__PURE__ */ new Map();
37689
+ const reserves2 = new Array(pageCount).fill(0);
37690
+ const hasContinuationByColumn = /* @__PURE__ */ new Map();
37691
+ const rangesByFootnoteId = /* @__PURE__ */ new Map();
37692
+ const cappedPages = /* @__PURE__ */ new Set();
37693
+ const allIds = collectFootnoteIdsByColumn(idsByColumn2);
37694
+ allIds.forEach((id) => {
37695
+ const blocks = footnotesInput.blocksById.get(id) ?? [];
37696
+ rangesByFootnoteId.set(id, buildFootnoteRanges(blocks, measuresById2));
37697
+ });
37698
+ const separatorSpacingBefore = resolveSeparatorSpacingBefore(
37699
+ rangesByFootnoteId,
37700
+ measuresById2,
37701
+ footnotesInput.separatorSpacingBefore,
37702
+ DEFAULT_FOOTNOTE_SEPARATOR_SPACING_BEFORE
37703
+ );
37704
+ const safeSeparatorSpacingBefore = Math.max(0, separatorSpacingBefore);
37705
+ let pendingByColumn = /* @__PURE__ */ new Map();
37706
+ for (let pageIndex = 0; pageIndex < pageCount; pageIndex += 1) {
37707
+ const baseReserve = Number.isFinite(baseReserves?.[pageIndex]) ? Math.max(0, baseReserves[pageIndex]) : 0;
37708
+ const maxReserve = computeMaxFootnoteReserve(layoutForPages, pageIndex, baseReserve);
37709
+ const columns = pageColumns2.get(pageIndex);
37710
+ const columnCount = Math.max(1, Math.floor(columns?.count ?? 1));
37711
+ const pendingForPage = /* @__PURE__ */ new Map();
37712
+ pendingByColumn.forEach((entries, columnIndex) => {
37713
+ const targetIndex = columnIndex < columnCount ? columnIndex : Math.max(0, columnCount - 1);
37714
+ const list = pendingForPage.get(targetIndex) ?? [];
37715
+ list.push(...entries);
37716
+ pendingForPage.set(targetIndex, list);
37717
+ });
37718
+ pendingByColumn = /* @__PURE__ */ new Map();
37719
+ const pageSlices = [];
37720
+ let pageReserve = 0;
37721
+ for (let columnIndex = 0; columnIndex < columnCount; columnIndex += 1) {
37722
+ let usedHeight = 0;
37723
+ const columnSlices = [];
37724
+ const nextPending = [];
37725
+ let stopPlacement = false;
37726
+ const columnKey = footnoteColumnKey(pageIndex, columnIndex);
37727
+ const placeFootnote = (id, ranges, isContinuation) => {
37728
+ if (!ranges || ranges.length === 0) {
37729
+ return { placed: false, remaining: [] };
37730
+ }
37731
+ const isFirstSlice = columnSlices.length === 0;
37732
+ const separatorBefore = isFirstSlice ? safeSeparatorSpacingBefore : 0;
37733
+ const separatorHeight = isFirstSlice ? isContinuation ? continuationDividerHeight : safeDividerHeight : 0;
37734
+ const overhead = isFirstSlice ? separatorBefore + separatorHeight + safeTopPadding : 0;
37735
+ const gapBefore = !isFirstSlice ? safeGap : 0;
37736
+ const availableHeight = Math.max(0, maxReserve - usedHeight - overhead - gapBefore);
37737
+ const { slice: slice2, remainingRanges } = fitFootnoteContent(
37738
+ id,
37739
+ ranges,
37740
+ availableHeight,
37741
+ pageIndex,
37742
+ columnIndex,
37743
+ isContinuation,
37744
+ measuresById2,
37745
+ isFirstSlice && maxReserve > 0
37746
+ );
37747
+ if (slice2.ranges.length === 0) {
37748
+ return { placed: false, remaining: ranges };
37749
+ }
37750
+ if (isFirstSlice) {
37751
+ usedHeight += overhead;
37752
+ if (isContinuation) {
37753
+ hasContinuationByColumn.set(columnKey, true);
37754
+ }
37755
+ }
37756
+ if (gapBefore > 0) {
37757
+ usedHeight += gapBefore;
37758
+ }
37759
+ usedHeight += slice2.totalHeight;
37760
+ columnSlices.push(slice2);
37761
+ return { placed: true, remaining: remainingRanges };
37762
+ };
37763
+ const pending = pendingForPage.get(columnIndex) ?? [];
37764
+ for (const entry of pending) {
37765
+ if (stopPlacement) {
37766
+ nextPending.push(entry);
37767
+ continue;
37768
+ }
37769
+ if (!entry.ranges || entry.ranges.length === 0) continue;
37770
+ const result = placeFootnote(entry.id, entry.ranges, true);
37771
+ if (!result.placed) {
37772
+ nextPending.push(entry);
37773
+ stopPlacement = true;
37774
+ continue;
37775
+ }
37776
+ if (result.remaining.length > 0) {
37777
+ nextPending.push({ id: entry.id, ranges: result.remaining });
37778
+ }
37779
+ }
37780
+ if (!stopPlacement) {
37781
+ const ids = idsByColumn2.get(pageIndex)?.get(columnIndex) ?? [];
37782
+ for (let idIndex = 0; idIndex < ids.length; idIndex += 1) {
37783
+ const id = ids[idIndex];
37784
+ const ranges = rangesByFootnoteId.get(id) ?? [];
37785
+ if (ranges.length === 0) continue;
37786
+ const result = placeFootnote(id, ranges, false);
37787
+ if (!result.placed) {
37788
+ nextPending.push({ id, ranges });
37789
+ for (let remainingIndex = idIndex + 1; remainingIndex < ids.length; remainingIndex += 1) {
37790
+ const remainingId = ids[remainingIndex];
37791
+ const remainingRanges = rangesByFootnoteId.get(remainingId) ?? [];
37792
+ nextPending.push({ id: remainingId, ranges: remainingRanges });
37793
+ }
37794
+ stopPlacement = true;
37795
+ break;
37796
+ }
37797
+ if (result.remaining.length > 0) {
37798
+ nextPending.push({ id, ranges: result.remaining });
37799
+ }
37800
+ }
37801
+ }
37802
+ if (columnSlices.length > 0) {
37803
+ const rawReserve = Math.max(0, Math.ceil(usedHeight));
37804
+ const cappedReserve = Math.min(rawReserve, maxReserve);
37805
+ if (cappedReserve < rawReserve) {
37806
+ cappedPages.add(pageIndex);
37807
+ }
37808
+ pageReserve = Math.max(pageReserve, cappedReserve);
37809
+ pageSlices.push(...columnSlices);
37810
+ }
37811
+ if (nextPending.length > 0) {
37812
+ pendingByColumn.set(columnIndex, nextPending);
37813
+ }
37814
+ }
37815
+ if (pageSlices.length > 0) {
37816
+ slicesByPage.set(pageIndex, pageSlices);
37817
+ }
37818
+ reserves2[pageIndex] = pageReserve;
37819
+ }
37820
+ if (cappedPages.size > 0) {
37821
+ console.warn("[layout] Footnote reserve capped to preserve body area", {
37822
+ pages: Array.from(cappedPages)
37823
+ });
37824
+ }
37825
+ if (pendingByColumn.size > 0) {
37826
+ const pendingIds = /* @__PURE__ */ new Set();
37827
+ pendingByColumn.forEach((entries) => entries.forEach((entry) => pendingIds.add(entry.id)));
37828
+ console.warn("[layout] Footnote content truncated: extends beyond document pages", {
37829
+ ids: Array.from(pendingIds)
37830
+ });
37831
+ }
37832
+ return { slicesByPage, reserves: reserves2, hasContinuationByColumn, separatorSpacingBefore: safeSeparatorSpacingBefore };
37833
+ };
37834
+ const injectFragments = (layoutForPages, plan2, measuresById2, reservesByPageIndex, blockById, pageColumns2) => {
37835
+ const decorativeBlocks = [];
37836
+ const decorativeMeasures = [];
37837
+ for (let pageIndex = 0; pageIndex < layoutForPages.pages.length; pageIndex++) {
37838
+ const page = layoutForPages.pages[pageIndex];
37839
+ page.footnoteReserved = Math.max(0, reservesByPageIndex[pageIndex] ?? plan2.reserves[pageIndex] ?? 0);
37840
+ const slices = plan2.slicesByPage.get(pageIndex) ?? [];
37841
+ if (slices.length === 0) continue;
37842
+ if (!page.margins) continue;
37843
+ const pageSize = page.size ?? layoutForPages.pageSize;
37844
+ const marginLeft = normalizeMargin(
37845
+ page.margins.left,
37846
+ normalizeMargin(options.margins?.left, DEFAULT_MARGINS$1.left)
37847
+ );
37848
+ const marginRight = normalizeMargin(
37849
+ page.margins.right,
37850
+ normalizeMargin(options.margins?.right, DEFAULT_MARGINS$1.right)
37851
+ );
37852
+ const pageContentWidth = pageSize.w - (marginLeft + marginRight);
37853
+ const fallbackColumns = normalizeColumnsForFootnotes(
37854
+ options.columns ?? { count: 1, gap: 0 },
37855
+ pageContentWidth
37856
+ );
37857
+ const columns = pageColumns2.get(pageIndex) ?? {
37858
+ ...fallbackColumns,
37859
+ left: marginLeft
37860
+ };
37861
+ const bandTopY = pageSize.h - (page.margins.bottom ?? 0);
37862
+ const slicesByColumn = /* @__PURE__ */ new Map();
37863
+ slices.forEach((slice2) => {
37864
+ const columnIndex = Number.isFinite(slice2.columnIndex) ? slice2.columnIndex : 0;
37865
+ const list = slicesByColumn.get(columnIndex) ?? [];
37866
+ list.push(slice2);
37867
+ slicesByColumn.set(columnIndex, list);
37868
+ });
37869
+ slicesByColumn.forEach((columnSlices, rawColumnIndex) => {
37870
+ if (columnSlices.length === 0) return;
37871
+ const columnIndex = Math.max(0, Math.min(columns.count - 1, rawColumnIndex));
37872
+ const columnStride = columns.width + columns.gap;
37873
+ const columnX = columns.left + columnIndex * columnStride;
37874
+ const contentWidth = Math.min(columns.width, footnoteWidth);
37875
+ if (!Number.isFinite(contentWidth) || contentWidth <= 0) return;
37876
+ const columnKey = footnoteColumnKey(pageIndex, columnIndex);
37877
+ const isContinuation = plan2.hasContinuationByColumn.get(columnKey) ?? false;
37878
+ let cursorY = bandTopY + Math.max(0, plan2.separatorSpacingBefore);
37879
+ const separatorHeight = isContinuation ? continuationDividerHeight : safeDividerHeight;
37880
+ const separatorWidth = isContinuation ? Math.max(0, contentWidth * continuationDividerWidthFactor) : contentWidth;
37881
+ if (separatorHeight > 0 && separatorWidth > 0) {
37882
+ const separatorId = isContinuation ? `footnote-continuation-separator-page-${page.number}-col-${columnIndex}` : `footnote-separator-page-${page.number}-col-${columnIndex}`;
37883
+ decorativeBlocks.push({
37884
+ kind: "drawing",
37885
+ id: separatorId,
37886
+ drawingKind: "vectorShape",
37887
+ geometry: { width: separatorWidth, height: separatorHeight },
37888
+ shapeKind: "rect",
37889
+ fillColor: "#000000",
37890
+ strokeColor: null,
37891
+ strokeWidth: 0
37892
+ });
37893
+ decorativeMeasures.push({
37894
+ kind: "drawing",
37895
+ drawingKind: "vectorShape",
37896
+ width: separatorWidth,
37897
+ height: separatorHeight,
37898
+ scale: 1,
37899
+ naturalWidth: separatorWidth,
37900
+ naturalHeight: separatorHeight,
37901
+ geometry: { width: separatorWidth, height: separatorHeight }
37902
+ });
37903
+ page.fragments.push({
37904
+ kind: "drawing",
37905
+ blockId: separatorId,
37906
+ drawingKind: "vectorShape",
37907
+ x: columnX,
37908
+ y: cursorY,
37909
+ width: separatorWidth,
37910
+ height: separatorHeight,
37911
+ geometry: { width: separatorWidth, height: separatorHeight },
37912
+ scale: 1
37913
+ });
37914
+ cursorY += separatorHeight;
37915
+ }
37916
+ cursorY += safeTopPadding;
37917
+ columnSlices.forEach((slice2, sliceIndex) => {
37918
+ slice2.ranges.forEach((range) => {
37919
+ if (range.kind === "paragraph") {
37920
+ const measure = measuresById2.get(range.blockId);
37921
+ if (!measure || measure.kind !== "paragraph") return;
37922
+ const marker = measure.marker;
37923
+ page.fragments.push({
37924
+ kind: "para",
37925
+ blockId: range.blockId,
37926
+ fromLine: range.fromLine,
37927
+ toLine: range.toLine,
37928
+ x: columnX,
37929
+ y: cursorY,
37930
+ width: contentWidth,
37931
+ continuesFromPrev: range.fromLine > 0,
37932
+ continuesOnNext: range.toLine < range.totalLines,
37933
+ ...marker?.markerWidth != null ? { markerWidth: marker.markerWidth } : {},
37934
+ ...marker?.markerTextWidth != null ? { markerTextWidth: marker.markerTextWidth } : {},
37935
+ ...marker?.gutterWidth != null ? { markerGutter: marker.gutterWidth } : {}
37936
+ });
37937
+ cursorY += getRangeRenderHeight(range);
37938
+ return;
37939
+ }
37940
+ if (range.kind === "list-item") {
37941
+ const measure = measuresById2.get(range.blockId);
37942
+ const block = blockById.get(range.blockId);
37943
+ if (!measure || measure.kind !== "list") return;
37944
+ if (!block || block.kind !== "list") return;
37945
+ const itemMeasure = measure.items.find((entry) => entry.itemId === range.itemId);
37946
+ if (!itemMeasure) return;
37947
+ const indentLeft = Number.isFinite(itemMeasure.indentLeft) ? itemMeasure.indentLeft : 0;
37948
+ const markerWidth = Number.isFinite(itemMeasure.markerWidth) ? itemMeasure.markerWidth : 0;
37949
+ const itemWidth = Math.max(0, contentWidth - indentLeft - markerWidth);
37950
+ page.fragments.push({
37951
+ kind: "list-item",
37952
+ blockId: range.blockId,
37953
+ itemId: range.itemId,
37954
+ fromLine: range.fromLine,
37955
+ toLine: range.toLine,
37956
+ x: columnX + indentLeft + markerWidth,
37957
+ y: cursorY,
37958
+ width: itemWidth,
37959
+ markerWidth,
37960
+ continuesFromPrev: range.fromLine > 0,
37961
+ continuesOnNext: range.toLine < range.totalLines
37962
+ });
37963
+ cursorY += getRangeRenderHeight(range);
37964
+ return;
37965
+ }
37966
+ if (range.kind === "table") {
37967
+ const measure = measuresById2.get(range.blockId);
37968
+ const block = blockById.get(range.blockId);
37969
+ if (!measure || measure.kind !== "table") return;
37970
+ if (!block || block.kind !== "table") return;
37971
+ const tableWidthRaw = Math.max(0, measure.totalWidth ?? 0);
37972
+ let tableWidth = Math.min(contentWidth, tableWidthRaw);
37973
+ let tableX = columnX;
37974
+ const justification = typeof block.attrs?.justification === "string" ? block.attrs.justification : void 0;
37975
+ if (justification === "center") {
37976
+ tableX = columnX + Math.max(0, (contentWidth - tableWidth) / 2);
37977
+ } else if (justification === "right" || justification === "end") {
37978
+ tableX = columnX + Math.max(0, contentWidth - tableWidth);
37979
+ } else {
37980
+ const indentValue = block.attrs?.tableIndent?.width;
37981
+ const indent = typeof indentValue === "number" && Number.isFinite(indentValue) ? indentValue : 0;
37982
+ tableX += indent;
37983
+ tableWidth = Math.max(0, tableWidth - indent);
37984
+ }
37985
+ page.fragments.push({
37986
+ kind: "table",
37987
+ blockId: range.blockId,
37988
+ fromRow: 0,
37989
+ toRow: block.rows.length,
37990
+ x: tableX,
37991
+ y: cursorY,
37992
+ width: tableWidth,
37993
+ height: Math.max(0, measure.totalHeight ?? 0)
37994
+ });
37995
+ cursorY += getRangeRenderHeight(range);
37996
+ return;
37997
+ }
37998
+ if (range.kind === "image") {
37999
+ const measure = measuresById2.get(range.blockId);
38000
+ if (!measure || measure.kind !== "image") return;
38001
+ page.fragments.push({
38002
+ kind: "image",
38003
+ blockId: range.blockId,
38004
+ x: columnX,
38005
+ y: cursorY,
38006
+ width: Math.min(contentWidth, Math.max(0, measure.width ?? 0)),
38007
+ height: Math.max(0, measure.height ?? 0)
38008
+ });
38009
+ cursorY += getRangeRenderHeight(range);
38010
+ return;
38011
+ }
38012
+ if (range.kind === "drawing") {
38013
+ const measure = measuresById2.get(range.blockId);
38014
+ const block = blockById.get(range.blockId);
38015
+ if (!measure || measure.kind !== "drawing") return;
38016
+ if (!block || block.kind !== "drawing") return;
38017
+ page.fragments.push({
38018
+ kind: "drawing",
38019
+ blockId: range.blockId,
38020
+ drawingKind: block.drawingKind,
38021
+ x: columnX,
38022
+ y: cursorY,
38023
+ width: Math.min(contentWidth, Math.max(0, measure.width ?? 0)),
38024
+ height: Math.max(0, measure.height ?? 0),
38025
+ geometry: measure.geometry,
38026
+ scale: measure.scale
38027
+ });
38028
+ cursorY += getRangeRenderHeight(range);
38029
+ }
38030
+ });
38031
+ if (sliceIndex < columnSlices.length - 1) {
38032
+ cursorY += safeGap;
38033
+ }
38034
+ });
38035
+ });
38036
+ }
38037
+ return { decorativeBlocks, decorativeMeasures };
38038
+ };
38039
+ const resolveFootnoteAssignments = (layoutForPages) => {
38040
+ const columns = resolvePageColumns(layoutForPages, options, currentBlocks);
38041
+ const idsByColumn2 = assignFootnotesToColumns(layoutForPages, footnotesInput.refs, columns);
38042
+ return { columns, idsByColumn: idsByColumn2 };
38043
+ };
38044
+ let { columns: pageColumns, idsByColumn } = resolveFootnoteAssignments(layout);
38045
+ let { measuresById } = await measureFootnoteBlocks(collectFootnoteIdsByColumn(idsByColumn));
38046
+ let plan = computeFootnoteLayoutPlan(layout, idsByColumn, measuresById, [], pageColumns);
38047
+ let reserves = plan.reserves;
38048
+ if (reserves.some((h2) => h2 > 0)) {
38049
+ layout = layoutDocument(currentBlocks, currentMeasures, {
38050
+ ...options,
38051
+ footnoteReservedByPageIndex: reserves,
38052
+ headerContentHeights,
38053
+ footerContentHeights,
38054
+ remeasureParagraph: (block, maxWidth, firstLineIndent) => remeasureParagraph(block, maxWidth, firstLineIndent)
38055
+ });
38056
+ ({ columns: pageColumns, idsByColumn } = resolveFootnoteAssignments(layout));
38057
+ ({ measuresById } = await measureFootnoteBlocks(collectFootnoteIdsByColumn(idsByColumn)));
38058
+ plan = computeFootnoteLayoutPlan(layout, idsByColumn, measuresById, reserves, pageColumns);
38059
+ reserves = plan.reserves;
38060
+ layout = layoutDocument(currentBlocks, currentMeasures, {
38061
+ ...options,
38062
+ footnoteReservedByPageIndex: reserves,
38063
+ headerContentHeights,
38064
+ footerContentHeights,
38065
+ remeasureParagraph: (block, maxWidth, firstLineIndent) => remeasureParagraph(block, maxWidth, firstLineIndent)
38066
+ });
38067
+ let { columns: finalPageColumns, idsByColumn: finalIdsByColumn } = resolveFootnoteAssignments(layout);
38068
+ let { blocks: finalBlocks, measuresById: finalMeasuresById } = await measureFootnoteBlocks(
38069
+ collectFootnoteIdsByColumn(finalIdsByColumn)
38070
+ );
38071
+ let finalPlan = computeFootnoteLayoutPlan(
38072
+ layout,
38073
+ finalIdsByColumn,
38074
+ finalMeasuresById,
38075
+ reserves,
38076
+ finalPageColumns
38077
+ );
38078
+ const finalReserves = finalPlan.reserves;
38079
+ let reservesAppliedToLayout = reserves;
38080
+ const reservesDiffer = finalReserves.length !== reserves.length || finalReserves.some((h2, i) => (reserves[i] ?? 0) !== h2) || reserves.some((h2, i) => (finalReserves[i] ?? 0) !== h2);
38081
+ if (reservesDiffer) {
38082
+ layout = layoutDocument(currentBlocks, currentMeasures, {
38083
+ ...options,
38084
+ footnoteReservedByPageIndex: finalReserves,
38085
+ headerContentHeights,
38086
+ footerContentHeights,
38087
+ remeasureParagraph: (block, maxWidth, firstLineIndent) => remeasureParagraph(block, maxWidth, firstLineIndent)
38088
+ });
38089
+ reservesAppliedToLayout = finalReserves;
38090
+ ({ columns: finalPageColumns, idsByColumn: finalIdsByColumn } = resolveFootnoteAssignments(layout));
38091
+ ({ blocks: finalBlocks, measuresById: finalMeasuresById } = await measureFootnoteBlocks(
38092
+ collectFootnoteIdsByColumn(finalIdsByColumn)
38093
+ ));
38094
+ finalPlan = computeFootnoteLayoutPlan(
38095
+ layout,
38096
+ finalIdsByColumn,
38097
+ finalMeasuresById,
38098
+ reservesAppliedToLayout,
38099
+ finalPageColumns
38100
+ );
38101
+ }
38102
+ const blockById = /* @__PURE__ */ new Map();
38103
+ finalBlocks.forEach((block) => {
38104
+ blockById.set(block.id, block);
38105
+ });
38106
+ const injected = injectFragments(
38107
+ layout,
38108
+ finalPlan,
38109
+ finalMeasuresById,
38110
+ reservesAppliedToLayout,
38111
+ blockById,
38112
+ finalPageColumns
38113
+ );
38114
+ const alignedBlocks = [];
38115
+ const alignedMeasures = [];
38116
+ finalBlocks.forEach((block) => {
38117
+ const measure = finalMeasuresById.get(block.id);
38118
+ if (!measure) return;
38119
+ alignedBlocks.push(block);
38120
+ alignedMeasures.push(measure);
38121
+ });
38122
+ extraBlocks = injected ? alignedBlocks.concat(injected.decorativeBlocks) : alignedBlocks;
38123
+ extraMeasures = injected ? alignedMeasures.concat(injected.decorativeMeasures) : alignedMeasures;
38124
+ }
38125
+ }
38126
+ }
37209
38127
  let headers;
37210
38128
  let footers;
37211
38129
  if (headerFooter?.constraints && (headerFooter.headerBlocks || headerFooter.footerBlocks)) {
@@ -37266,7 +38184,9 @@ async function incrementalLayout(previousBlocks, _previousLayout, nextBlocks, op
37266
38184
  measures: currentMeasures,
37267
38185
  dirty,
37268
38186
  headers,
37269
- footers
38187
+ footers,
38188
+ extraBlocks,
38189
+ extraMeasures
37270
38190
  };
37271
38191
  }
37272
38192
  const DEFAULT_PAGE_SIZE$1 = { w: 612, h: 792 };
@@ -41305,6 +42225,7 @@ const ATOMIC_INLINE_TYPES = /* @__PURE__ */ new Set([
41305
42225
  "lineBreak",
41306
42226
  "page-number",
41307
42227
  "total-page-number",
42228
+ "footnoteReference",
41308
42229
  "passthroughInline",
41309
42230
  "bookmarkEnd"
41310
42231
  ]);
@@ -46329,6 +47250,28 @@ function paragraphToFlowBlocks$1(para, nextBlockId, positions, defaultFont, defa
46329
47250
  let partIndex = 0;
46330
47251
  let tabOrdinal = 0;
46331
47252
  let suppressedByVanish = false;
47253
+ const toSuperscriptDigits2 = (value) => {
47254
+ const map3 = {
47255
+ "0": "⁰",
47256
+ "1": "¹",
47257
+ "2": "²",
47258
+ "3": "³",
47259
+ "4": "⁴",
47260
+ "5": "⁵",
47261
+ "6": "⁶",
47262
+ "7": "⁷",
47263
+ "8": "⁸",
47264
+ "9": "⁹"
47265
+ };
47266
+ return String(value ?? "").split("").map((ch) => map3[ch] ?? ch).join("");
47267
+ };
47268
+ const resolveFootnoteDisplayNumber2 = (id) => {
47269
+ const key2 = id == null ? null : String(id);
47270
+ if (!key2) return null;
47271
+ const mapping = converterContext?.footnoteNumberById;
47272
+ const mapped = mapping && typeof mapping === "object" ? mapping[key2] : void 0;
47273
+ return typeof mapped === "number" && Number.isFinite(mapped) && mapped > 0 ? mapped : null;
47274
+ };
46332
47275
  const nextId = () => partIndex === 0 ? baseBlockId : `${baseBlockId}-${partIndex}`;
46333
47276
  const attachAnchorParagraphId = (block, anchorParagraphId) => {
46334
47277
  const applicableKinds = /* @__PURE__ */ new Set(["drawing", "image", "table"]);
@@ -46374,6 +47317,34 @@ function paragraphToFlowBlocks$1(para, nextBlockId, positions, defaultFont, defa
46374
47317
  });
46375
47318
  };
46376
47319
  const visitNode = (node, inheritedMarks = [], activeSdt, activeRunStyleId = null, activeRunProperties, activeHidden = false) => {
47320
+ if (node.type === "footnoteReference") {
47321
+ const mergedMarks = [...node.marks ?? [], ...inheritedMarks ?? []];
47322
+ const refPos = positions.get(node);
47323
+ const id = node.attrs?.id;
47324
+ const displayId = resolveFootnoteDisplayNumber2(id) ?? id ?? "*";
47325
+ const displayText = toSuperscriptDigits2(displayId);
47326
+ const run = textNodeToRun(
47327
+ { type: "text", text: displayText },
47328
+ positions,
47329
+ defaultFont,
47330
+ defaultSize,
47331
+ [],
47332
+ // marks applied after linked styles/base defaults
47333
+ activeSdt,
47334
+ hyperlinkConfig,
47335
+ themeColors
47336
+ );
47337
+ const inlineStyleId = getInlineStyleId(mergedMarks);
47338
+ applyRunStyles2(run, inlineStyleId, activeRunStyleId);
47339
+ applyBaseRunDefaults(run, baseRunDefaults, defaultFont, defaultSize);
47340
+ applyMarksToRun(run, mergedMarks, hyperlinkConfig, themeColors);
47341
+ if (refPos) {
47342
+ run.pmStart = refPos.start;
47343
+ run.pmEnd = refPos.end;
47344
+ }
47345
+ currentRuns.push(run);
47346
+ return;
47347
+ }
46377
47348
  if (activeHidden && node.type !== "run") {
46378
47349
  suppressedByVanish = true;
46379
47350
  return;
@@ -54774,12 +55745,36 @@ class PresentationEditor extends EventEmitter {
54774
55745
  const sectionMetadata = [];
54775
55746
  let blocks;
54776
55747
  let bookmarks = /* @__PURE__ */ new Map();
55748
+ let converterContext = void 0;
54777
55749
  try {
54778
55750
  const converter2 = this.#editor.converter;
54779
- const converterContext = converter2 ? {
55751
+ const footnoteNumberById = {};
55752
+ try {
55753
+ const seen = /* @__PURE__ */ new Set();
55754
+ let counter = 1;
55755
+ this.#editor?.state?.doc?.descendants?.((node) => {
55756
+ if (node?.type?.name !== "footnoteReference") return;
55757
+ const rawId = node?.attrs?.id;
55758
+ if (rawId == null) return;
55759
+ const key2 = String(rawId);
55760
+ if (!key2 || seen.has(key2)) return;
55761
+ seen.add(key2);
55762
+ footnoteNumberById[key2] = counter;
55763
+ counter += 1;
55764
+ });
55765
+ } catch {
55766
+ }
55767
+ try {
55768
+ if (converter2 && typeof converter2 === "object") {
55769
+ converter2["footnoteNumberById"] = footnoteNumberById;
55770
+ }
55771
+ } catch {
55772
+ }
55773
+ converterContext = converter2 ? {
54780
55774
  docx: converter2.convertedXml,
54781
55775
  numbering: converter2.numbering,
54782
- linkedStyles: converter2.linkedStyles
55776
+ linkedStyles: converter2.linkedStyles,
55777
+ ...Object.keys(footnoteNumberById).length ? { footnoteNumberById } : {}
54783
55778
  } : void 0;
54784
55779
  const atomNodeTypes = getAtomNodeTypes(this.#editor?.schema ?? null);
54785
55780
  const positionMap = this.#editor?.state?.doc && docJson ? buildPositionMapFromPmDoc(this.#editor.state.doc, docJson) : null;
@@ -54807,13 +55802,20 @@ class PresentationEditor extends EventEmitter {
54807
55802
  this.#handleLayoutError("render", new Error("toFlowBlocks returned undefined blocks"));
54808
55803
  return;
54809
55804
  }
54810
- const layoutOptions = this.#resolveLayoutOptions(blocks, sectionMetadata);
55805
+ const baseLayoutOptions = this.#resolveLayoutOptions(blocks, sectionMetadata);
55806
+ const footnotesLayoutInput = this.#buildFootnotesLayoutInput({
55807
+ converterContext,
55808
+ themeColors: this.#editor?.converter?.themeColors ?? void 0
55809
+ });
55810
+ const layoutOptions = footnotesLayoutInput ? { ...baseLayoutOptions, footnotes: footnotesLayoutInput } : baseLayoutOptions;
54811
55811
  const previousBlocks = this.#layoutState.blocks;
54812
55812
  const previousLayout = this.#layoutState.layout;
54813
55813
  let layout;
54814
55814
  let measures;
54815
55815
  let headerLayouts;
54816
55816
  let footerLayouts;
55817
+ let extraBlocks;
55818
+ let extraMeasures;
54817
55819
  const headerFooterInput = this.#buildHeaderFooterInput();
54818
55820
  try {
54819
55821
  const result = await incrementalLayout(
@@ -54837,6 +55839,8 @@ class PresentationEditor extends EventEmitter {
54837
55839
  return;
54838
55840
  }
54839
55841
  ({ layout, measures } = result);
55842
+ extraBlocks = Array.isArray(result.extraBlocks) ? result.extraBlocks : void 0;
55843
+ extraMeasures = Array.isArray(result.extraMeasures) ? result.extraMeasures : void 0;
54840
55844
  layout.pageGap = this.#getEffectivePageGap();
54841
55845
  layout.layoutEpoch = layoutEpoch;
54842
55846
  headerLayouts = result.headers;
@@ -54896,6 +55900,10 @@ class PresentationEditor extends EventEmitter {
54896
55900
  footerBlocks.push(...rIdResult.blocks);
54897
55901
  footerMeasures.push(...rIdResult.measures);
54898
55902
  }
55903
+ if (extraBlocks && extraMeasures && extraBlocks.length === extraMeasures.length && extraBlocks.length > 0) {
55904
+ footerBlocks.push(...extraBlocks);
55905
+ footerMeasures.push(...extraMeasures);
55906
+ }
54899
55907
  painter.setData?.(
54900
55908
  blocks,
54901
55909
  measures,
@@ -55182,6 +56190,123 @@ class PresentationEditor extends EventEmitter {
55182
56190
  sectionMetadata
55183
56191
  };
55184
56192
  }
56193
+ #buildFootnotesLayoutInput({
56194
+ converterContext,
56195
+ themeColors
56196
+ }) {
56197
+ const footnoteNumberById = converterContext?.footnoteNumberById;
56198
+ const toSuperscriptDigits2 = (value) => {
56199
+ const map3 = {
56200
+ "0": "⁰",
56201
+ "1": "¹",
56202
+ "2": "²",
56203
+ "3": "³",
56204
+ "4": "⁴",
56205
+ "5": "⁵",
56206
+ "6": "⁶",
56207
+ "7": "⁷",
56208
+ "8": "⁸",
56209
+ "9": "⁹"
56210
+ };
56211
+ const str = String(value ?? "");
56212
+ return str.split("").map((ch) => map3[ch] ?? ch).join("");
56213
+ };
56214
+ const ensureFootnoteMarker = (blocks, id) => {
56215
+ const displayNumberRaw = footnoteNumberById && typeof footnoteNumberById === "object" ? footnoteNumberById[id] : void 0;
56216
+ const displayNumber = typeof displayNumberRaw === "number" && Number.isFinite(displayNumberRaw) && displayNumberRaw > 0 ? displayNumberRaw : 1;
56217
+ const firstParagraph = blocks.find((b2) => b2?.kind === "paragraph");
56218
+ if (!firstParagraph) return;
56219
+ const runs = Array.isArray(firstParagraph.runs) ? firstParagraph.runs : [];
56220
+ const markerText = toSuperscriptDigits2(displayNumber);
56221
+ const baseRun = runs.find((r2) => {
56222
+ const dataAttrs = r2.dataAttrs;
56223
+ if (dataAttrs?.["data-sd-footnote-number"]) return false;
56224
+ const pmStart = r2.pmStart;
56225
+ const pmEnd = r2.pmEnd;
56226
+ return typeof pmStart === "number" && Number.isFinite(pmStart) && typeof pmEnd === "number" && Number.isFinite(pmEnd);
56227
+ });
56228
+ const markerPmStart = baseRun?.pmStart ?? null;
56229
+ const markerPmEnd = markerPmStart != null ? baseRun?.pmEnd != null ? Math.max(markerPmStart, Math.min(baseRun.pmEnd, markerPmStart + markerText.length)) : markerPmStart + markerText.length : null;
56230
+ const alreadyHasMarker = runs.some((r2) => {
56231
+ const dataAttrs = r2.dataAttrs;
56232
+ return Boolean(dataAttrs?.["data-sd-footnote-number"]);
56233
+ });
56234
+ if (alreadyHasMarker) {
56235
+ if (markerPmStart != null && markerPmEnd != null) {
56236
+ const markerRun2 = runs.find((r2) => {
56237
+ const dataAttrs = r2.dataAttrs;
56238
+ return Boolean(dataAttrs?.["data-sd-footnote-number"]);
56239
+ });
56240
+ if (markerRun2) {
56241
+ if (markerRun2.pmStart == null) markerRun2.pmStart = markerPmStart;
56242
+ if (markerRun2.pmEnd == null) markerRun2.pmEnd = markerPmEnd;
56243
+ }
56244
+ }
56245
+ return;
56246
+ }
56247
+ const firstTextRun = runs.find((r2) => typeof r2.text === "string");
56248
+ const markerRun = {
56249
+ kind: "text",
56250
+ text: markerText,
56251
+ dataAttrs: {
56252
+ "data-sd-footnote-number": "true"
56253
+ },
56254
+ ...markerPmStart != null ? { pmStart: markerPmStart } : {},
56255
+ ...markerPmEnd != null ? { pmEnd: markerPmEnd } : {}
56256
+ };
56257
+ markerRun.fontFamily = typeof firstTextRun?.fontFamily === "string" ? firstTextRun.fontFamily : "Arial";
56258
+ markerRun.fontSize = typeof firstTextRun?.fontSize === "number" && Number.isFinite(firstTextRun.fontSize) ? firstTextRun.fontSize : 12;
56259
+ if (firstTextRun?.color != null) markerRun.color = firstTextRun.color;
56260
+ runs.unshift(markerRun);
56261
+ firstParagraph.runs = runs;
56262
+ };
56263
+ const state = this.#editor?.state;
56264
+ if (!state) return null;
56265
+ const converter = this.#editor?.converter;
56266
+ const importedFootnotes = Array.isArray(converter?.footnotes) ? converter.footnotes : [];
56267
+ if (importedFootnotes.length === 0) return null;
56268
+ const refs = [];
56269
+ const idsInUse = /* @__PURE__ */ new Set();
56270
+ state.doc.descendants((node, pos) => {
56271
+ if (node.type?.name !== "footnoteReference") return;
56272
+ const id = node.attrs?.id;
56273
+ if (id == null) return;
56274
+ const key2 = String(id);
56275
+ const insidePos = Math.min(pos + 1, state.doc.content.size);
56276
+ refs.push({ id: key2, pos: insidePos });
56277
+ idsInUse.add(key2);
56278
+ });
56279
+ if (refs.length === 0) return null;
56280
+ const blocksById = /* @__PURE__ */ new Map();
56281
+ idsInUse.forEach((id) => {
56282
+ const entry = importedFootnotes.find((f) => String(f?.id) === id);
56283
+ const content = entry?.content;
56284
+ if (!Array.isArray(content) || content.length === 0) return;
56285
+ try {
56286
+ const clonedContent = JSON.parse(JSON.stringify(content));
56287
+ const footnoteDoc = { type: "doc", content: clonedContent };
56288
+ const result = toFlowBlocks(footnoteDoc, {
56289
+ blockIdPrefix: `footnote-${id}-`,
56290
+ enableRichHyperlinks: true,
56291
+ themeColors,
56292
+ converterContext
56293
+ });
56294
+ if (result?.blocks?.length) {
56295
+ ensureFootnoteMarker(result.blocks, id);
56296
+ blocksById.set(id, result.blocks);
56297
+ }
56298
+ } catch {
56299
+ }
56300
+ });
56301
+ if (blocksById.size === 0) return null;
56302
+ return {
56303
+ refs,
56304
+ blocksById,
56305
+ gap: 2,
56306
+ topPadding: 4,
56307
+ dividerHeight: 1
56308
+ };
56309
+ }
55185
56310
  #buildHeaderFooterInput() {
55186
56311
  if (!this.#headerFooterAdapter) {
55187
56312
  return null;
@@ -55371,7 +56496,8 @@ class PresentationEditor extends EventEmitter {
55371
56496
  const fragments2 = slotPage2.fragments ?? [];
55372
56497
  const pageHeight2 = page?.size?.h ?? layout.pageSize?.h ?? this.#layoutOptions.pageSize?.h ?? DEFAULT_PAGE_SIZE.h;
55373
56498
  const margins2 = pageMargins ?? layout.pages[0]?.margins ?? this.#layoutOptions.margins ?? DEFAULT_MARGINS;
55374
- const box2 = this.#computeDecorationBox(kind, margins2, pageHeight2);
56499
+ const decorationMargins2 = kind === "footer" ? this.#stripFootnoteReserveFromBottomMargin(margins2, page ?? null) : margins2;
56500
+ const box2 = this.#computeDecorationBox(kind, decorationMargins2, pageHeight2);
55375
56501
  const rawLayoutHeight2 = rIdLayout.layout.height ?? 0;
55376
56502
  const metrics2 = this.#computeHeaderFooterMetrics(
55377
56503
  kind,
@@ -55422,7 +56548,8 @@ class PresentationEditor extends EventEmitter {
55422
56548
  const fragments = slotPage.fragments ?? [];
55423
56549
  const pageHeight = page?.size?.h ?? layout.pageSize?.h ?? this.#layoutOptions.pageSize?.h ?? DEFAULT_PAGE_SIZE.h;
55424
56550
  const margins = pageMargins ?? layout.pages[0]?.margins ?? this.#layoutOptions.margins ?? DEFAULT_MARGINS;
55425
- const box = this.#computeDecorationBox(kind, margins, pageHeight);
56551
+ const decorationMargins = kind === "footer" ? this.#stripFootnoteReserveFromBottomMargin(margins, page ?? null) : margins;
56552
+ const box = this.#computeDecorationBox(kind, decorationMargins, pageHeight);
55426
56553
  const rawLayoutHeight = variant.layout.height ?? 0;
55427
56554
  const metrics = this.#computeHeaderFooterMetrics(kind, rawLayoutHeight, box, pageHeight, margins.footer ?? 0);
55428
56555
  const fallbackId = this.#headerFooterManager?.getVariantId(kind, headerFooterType);
@@ -55509,6 +56636,16 @@ class PresentationEditor extends EventEmitter {
55509
56636
  return { x: left2, width, height, offset: offset2 };
55510
56637
  }
55511
56638
  }
56639
+ #stripFootnoteReserveFromBottomMargin(pageMargins, page) {
56640
+ const reserveRaw = page?.footnoteReserved;
56641
+ const reserve = typeof reserveRaw === "number" && Number.isFinite(reserveRaw) && reserveRaw > 0 ? reserveRaw : 0;
56642
+ if (!reserve) return pageMargins;
56643
+ const bottomRaw = pageMargins.bottom;
56644
+ const bottom2 = typeof bottomRaw === "number" && Number.isFinite(bottomRaw) ? bottomRaw : 0;
56645
+ const nextBottom = Math.max(0, bottom2 - reserve);
56646
+ if (nextBottom === bottom2) return pageMargins;
56647
+ return { ...pageMargins, bottom: nextBottom };
56648
+ }
55512
56649
  /**
55513
56650
  * Computes the expected header/footer section type for a page based on document configuration.
55514
56651
  *
@@ -55573,7 +56710,8 @@ class PresentationEditor extends EventEmitter {
55573
56710
  height: headerPayload?.hitRegion?.height ?? headerBox.height
55574
56711
  });
55575
56712
  const footerPayload = this.#footerDecorationProvider?.(page.number, margins, page);
55576
- const footerBox = this.#computeDecorationBox("footer", margins, actualPageHeight);
56713
+ const footerBoxMargins = this.#stripFootnoteReserveFromBottomMargin(margins, page);
56714
+ const footerBox = this.#computeDecorationBox("footer", footerBoxMargins, actualPageHeight);
55577
56715
  this.#footerRegions.set(pageIndex, {
55578
56716
  kind: "footer",
55579
56717
  headerId: footerPayload?.headerId,
@@ -61474,6 +62612,109 @@ const CommentsMark = Mark.create({
61474
62612
  return [CommentMarkName$1, Attribute.mergeAttributes(this.options.htmlAttributes, htmlAttributes)];
61475
62613
  }
61476
62614
  });
62615
+ const toSuperscriptDigits = (value) => {
62616
+ const map3 = {
62617
+ 0: "⁰",
62618
+ 1: "¹",
62619
+ 2: "²",
62620
+ 3: "³",
62621
+ 4: "⁴",
62622
+ 5: "⁵",
62623
+ 6: "⁶",
62624
+ 7: "⁷",
62625
+ 8: "⁸",
62626
+ 9: "⁹"
62627
+ };
62628
+ return String(value ?? "").split("").map((ch) => map3[ch] ?? ch).join("");
62629
+ };
62630
+ const resolveFootnoteDisplayNumber = (editor, id) => {
62631
+ const key2 = id == null ? null : String(id);
62632
+ if (!key2) return null;
62633
+ const map3 = editor?.converter?.footnoteNumberById;
62634
+ const mapped = map3 && typeof map3 === "object" ? map3[key2] : void 0;
62635
+ return typeof mapped === "number" && Number.isFinite(mapped) && mapped > 0 ? mapped : null;
62636
+ };
62637
+ class FootnoteReferenceNodeView {
62638
+ constructor(node, getPos, decorations, editor, htmlAttributes = {}) {
62639
+ this.node = node;
62640
+ this.getPos = getPos;
62641
+ this.editor = editor;
62642
+ this.dom = this.#renderDom(node, htmlAttributes);
62643
+ }
62644
+ #renderDom(node, htmlAttributes) {
62645
+ const el = document.createElement("sup");
62646
+ el.className = "sd-footnote-ref";
62647
+ el.setAttribute("contenteditable", "false");
62648
+ el.setAttribute("aria-label", "Footnote reference");
62649
+ Object.entries(htmlAttributes).forEach(([key2, value]) => {
62650
+ if (value != null && value !== false) {
62651
+ el.setAttribute(key2, String(value));
62652
+ }
62653
+ });
62654
+ const id = node?.attrs?.id;
62655
+ if (id != null) {
62656
+ el.setAttribute("data-footnote-id", String(id));
62657
+ const display = resolveFootnoteDisplayNumber(this.editor, id) ?? id;
62658
+ el.textContent = toSuperscriptDigits(display);
62659
+ } else {
62660
+ el.textContent = "*";
62661
+ }
62662
+ return el;
62663
+ }
62664
+ update(node) {
62665
+ const incomingType = node?.type?.name;
62666
+ const currentType = this.node?.type?.name;
62667
+ if (!incomingType || incomingType !== currentType) return false;
62668
+ this.node = node;
62669
+ const id = node?.attrs?.id;
62670
+ if (id != null) {
62671
+ this.dom.setAttribute("data-footnote-id", String(id));
62672
+ const display = resolveFootnoteDisplayNumber(this.editor, id) ?? id;
62673
+ this.dom.textContent = toSuperscriptDigits(display);
62674
+ } else {
62675
+ this.dom.removeAttribute("data-footnote-id");
62676
+ this.dom.textContent = "*";
62677
+ }
62678
+ return true;
62679
+ }
62680
+ }
62681
+ const FootnoteReference = Node$1.create({
62682
+ name: "footnoteReference",
62683
+ group: "inline",
62684
+ inline: true,
62685
+ atom: true,
62686
+ selectable: false,
62687
+ draggable: false,
62688
+ addOptions() {
62689
+ return {
62690
+ htmlAttributes: {
62691
+ "data-footnote-ref": "true"
62692
+ }
62693
+ };
62694
+ },
62695
+ addAttributes() {
62696
+ return {
62697
+ id: {
62698
+ default: null
62699
+ },
62700
+ customMarkFollows: {
62701
+ default: null
62702
+ }
62703
+ };
62704
+ },
62705
+ addNodeView() {
62706
+ return ({ node, editor, getPos, decorations }) => {
62707
+ const htmlAttributes = this.options.htmlAttributes;
62708
+ return new FootnoteReferenceNodeView(node, getPos, decorations, editor, htmlAttributes);
62709
+ };
62710
+ },
62711
+ parseDOM() {
62712
+ return [{ tag: "sup[data-footnote-id]" }];
62713
+ },
62714
+ renderDOM({ htmlAttributes }) {
62715
+ return ["sup", Attribute.mergeAttributes(this.options.htmlAttributes, htmlAttributes)];
62716
+ }
62717
+ });
61477
62718
  let cache$1 = /* @__PURE__ */ new WeakMap();
61478
62719
  function getParagraphContext(paragraph, startPos, helpers2, revision, compute) {
61479
62720
  const cached = cache$1.get(paragraph);
@@ -75085,6 +76326,7 @@ const getStarterExtensions = () => {
75085
76326
  CommentRangeStart,
75086
76327
  CommentRangeEnd,
75087
76328
  CommentReference,
76329
+ FootnoteReference,
75088
76330
  Document,
75089
76331
  FontFamily,
75090
76332
  FontSize,