docxodus 9.9.0 → 10.0.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.
- package/README.md +178 -0
- package/dist/canonical.d.ts +7 -0
- package/dist/canonical.d.ts.map +1 -0
- package/dist/canonical.js +56 -0
- package/dist/canonical.js.map +1 -0
- package/dist/docxodus.worker.d.ts +2 -1
- package/dist/docxodus.worker.d.ts.map +1 -1
- package/dist/docxodus.worker.js +244 -3
- package/dist/docxodus.worker.js.map +1 -1
- package/dist/editor.bundle.js +1914 -419
- package/dist/editor.d.ts +22 -2
- package/dist/editor.d.ts.map +1 -1
- package/dist/editor.js +94 -9
- package/dist/editor.js.map +1 -1
- package/dist/embed.bundle.js +2803 -469
- package/dist/embed.iife.js +2801 -467
- package/dist/export-assets.json +283 -0
- package/dist/export-browser.bundle.js +7990 -0
- package/dist/export-browser.d.ts +266 -0
- package/dist/export-browser.d.ts.map +1 -0
- package/dist/export-browser.js +2915 -0
- package/dist/export-browser.js.map +1 -0
- package/dist/export-resource-limits-v1.json +57 -0
- package/dist/font-contract.d.ts +150 -0
- package/dist/font-contract.d.ts.map +1 -0
- package/dist/font-contract.js +59 -0
- package/dist/font-contract.js.map +1 -0
- package/dist/font-runtime.d.ts +33 -0
- package/dist/font-runtime.d.ts.map +1 -0
- package/dist/font-runtime.js +1100 -0
- package/dist/font-runtime.js.map +1 -0
- package/dist/index.d.ts +62 -5
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +130 -16
- package/dist/index.js.map +1 -1
- package/dist/page-geometry.d.ts +8 -1
- package/dist/page-geometry.d.ts.map +1 -1
- package/dist/page-geometry.js +37 -11
- package/dist/page-geometry.js.map +1 -1
- package/dist/pagination.bundle.js +1827 -410
- package/dist/pagination.d.ts +249 -15
- package/dist/pagination.d.ts.map +1 -1
- package/dist/pagination.js +1942 -453
- package/dist/pagination.js.map +1 -1
- package/dist/react.d.ts +11 -4
- package/dist/react.d.ts.map +1 -1
- package/dist/react.js +38 -5
- package/dist/react.js.map +1 -1
- package/dist/render-report-v2.schema.json +1581 -0
- package/dist/ribbon-chrome.d.ts +1 -1
- package/dist/ribbon-chrome.d.ts.map +1 -1
- package/dist/ribbon-chrome.js +45 -0
- package/dist/ribbon-chrome.js.map +1 -1
- package/dist/ribbon.js +70 -0
- package/dist/ribbon.js.map +1 -1
- package/dist/session.bundle.js +695 -29
- package/dist/session.d.ts +134 -19
- package/dist/session.d.ts.map +1 -1
- package/dist/session.js +562 -25
- package/dist/session.js.map +1 -1
- package/dist/types.d.ts +1207 -16
- package/dist/types.d.ts.map +1 -1
- package/dist/types.js.map +1 -1
- package/dist/wasm/_framework/DocumentFormat.OpenXml.Framework.wasm +0 -0
- package/dist/wasm/_framework/DocumentFormat.OpenXml.Framework.wasm.br +0 -0
- package/dist/wasm/_framework/DocumentFormat.OpenXml.wasm +0 -0
- package/dist/wasm/_framework/DocumentFormat.OpenXml.wasm.br +0 -0
- package/dist/wasm/_framework/Docxodus.wasm +0 -0
- package/dist/wasm/_framework/Docxodus.wasm.br +0 -0
- package/dist/wasm/_framework/DocxodusWasm.wasm +0 -0
- package/dist/wasm/_framework/DocxodusWasm.wasm.br +0 -0
- package/dist/wasm/_framework/System.Collections.Concurrent.wasm +0 -0
- package/dist/wasm/_framework/System.Collections.Concurrent.wasm.br +0 -0
- package/dist/wasm/_framework/System.Collections.Immutable.wasm +0 -0
- package/dist/wasm/_framework/System.Collections.Immutable.wasm.br +0 -0
- package/dist/wasm/_framework/System.Collections.wasm +0 -0
- package/dist/wasm/_framework/System.Collections.wasm.br +0 -0
- package/dist/wasm/_framework/System.IO.Compression.wasm +0 -0
- package/dist/wasm/_framework/System.IO.Compression.wasm.br +0 -0
- package/dist/wasm/_framework/System.IO.Packaging.wasm +0 -0
- package/dist/wasm/_framework/System.IO.Packaging.wasm.br +0 -0
- package/dist/wasm/_framework/System.Linq.wasm +0 -0
- package/dist/wasm/_framework/System.Linq.wasm.br +0 -0
- package/dist/wasm/_framework/System.Private.CoreLib.wasm +0 -0
- package/dist/wasm/_framework/System.Private.CoreLib.wasm.br +0 -0
- package/dist/wasm/_framework/System.Private.Uri.wasm +0 -0
- package/dist/wasm/_framework/System.Private.Uri.wasm.br +0 -0
- package/dist/wasm/_framework/System.Private.Xml.Linq.wasm +0 -0
- package/dist/wasm/_framework/System.Private.Xml.Linq.wasm.br +0 -0
- package/dist/wasm/_framework/System.Private.Xml.wasm +0 -0
- package/dist/wasm/_framework/System.Private.Xml.wasm.br +0 -0
- package/dist/wasm/_framework/System.Runtime.InteropServices.JavaScript.wasm +0 -0
- package/dist/wasm/_framework/System.Runtime.InteropServices.JavaScript.wasm.br +0 -0
- package/dist/wasm/_framework/System.Runtime.wasm +0 -0
- package/dist/wasm/_framework/System.Runtime.wasm.br +0 -0
- package/dist/wasm/_framework/System.Security.Cryptography.wasm +0 -0
- package/dist/wasm/_framework/System.Security.Cryptography.wasm.br +0 -0
- package/dist/wasm/_framework/System.Text.Json.wasm +0 -0
- package/dist/wasm/_framework/System.Text.Json.wasm.br +0 -0
- package/dist/wasm/_framework/System.Text.RegularExpressions.wasm +0 -0
- package/dist/wasm/_framework/System.Text.RegularExpressions.wasm.br +0 -0
- package/dist/wasm/_framework/dotnet.boot.js +21 -21
- package/dist/wasm/_framework/dotnet.boot.js.br +0 -0
- package/dist/wasm/_framework/dotnet.native.wasm +0 -0
- package/dist/wasm/_framework/dotnet.native.wasm.br +0 -0
- package/dist/worker-proxy.bundle.js +196 -29
- package/dist/worker-proxy.d.ts +36 -3
- package/dist/worker-proxy.d.ts.map +1 -1
- package/dist/worker-proxy.js +186 -33
- package/dist/worker-proxy.js.map +1 -1
- package/package.json +22 -6
|
@@ -22,6 +22,9 @@ var DocxodusPagination = (() => {
|
|
|
22
22
|
var pagination_exports = {};
|
|
23
23
|
__export(pagination_exports, {
|
|
24
24
|
PaginationEngine: () => PaginationEngine,
|
|
25
|
+
clearPageCitationHighlight: () => clearPageCitationHighlight,
|
|
26
|
+
createUnavailablePageMap: () => createUnavailablePageMap,
|
|
27
|
+
navigateToPageCitation: () => navigateToPageCitation,
|
|
25
28
|
paginateHtml: () => paginateHtml
|
|
26
29
|
});
|
|
27
30
|
|
|
@@ -70,17 +73,41 @@ var DocxodusPagination = (() => {
|
|
|
70
73
|
function ptToPx(pt) {
|
|
71
74
|
return pt / 0.75;
|
|
72
75
|
}
|
|
76
|
+
function finiteDataNumber(value, fallback) {
|
|
77
|
+
if (value === void 0 || value.trim() === "") return fallback;
|
|
78
|
+
const parsed = Number(value);
|
|
79
|
+
return Number.isFinite(parsed) ? parsed : fallback;
|
|
80
|
+
}
|
|
81
|
+
function positiveDataNumber(value, fallback) {
|
|
82
|
+
const parsed = finiteDataNumber(value, fallback);
|
|
83
|
+
return parsed > 0 ? parsed : fallback;
|
|
84
|
+
}
|
|
85
|
+
function nonNegativeDataNumber(value, fallback) {
|
|
86
|
+
const parsed = finiteDataNumber(value, fallback);
|
|
87
|
+
return parsed >= 0 ? parsed : fallback;
|
|
88
|
+
}
|
|
89
|
+
function positiveFinite(value, fallback) {
|
|
90
|
+
return Number.isFinite(value) && value > 0 ? value : fallback;
|
|
91
|
+
}
|
|
73
92
|
function parseSectionDimensions(section) {
|
|
74
|
-
const pageWidth =
|
|
75
|
-
const pageHeight =
|
|
76
|
-
const
|
|
77
|
-
const
|
|
78
|
-
const
|
|
79
|
-
const
|
|
80
|
-
const
|
|
81
|
-
const
|
|
82
|
-
const
|
|
83
|
-
const
|
|
93
|
+
const pageWidth = positiveDataNumber(section.dataset.pageWidth, DEFAULT_PAGE_WIDTH);
|
|
94
|
+
const pageHeight = positiveDataNumber(section.dataset.pageHeight, DEFAULT_PAGE_HEIGHT);
|
|
95
|
+
const marginTop = finiteDataNumber(section.dataset.marginTop, DEFAULT_MARGIN);
|
|
96
|
+
const marginRight = finiteDataNumber(section.dataset.marginRight, DEFAULT_MARGIN);
|
|
97
|
+
const marginBottom = finiteDataNumber(section.dataset.marginBottom, DEFAULT_MARGIN);
|
|
98
|
+
const marginLeft = finiteDataNumber(section.dataset.marginLeft, DEFAULT_MARGIN);
|
|
99
|
+
const derivedContentWidth = positiveFinite(pageWidth - marginLeft - marginRight, pageWidth);
|
|
100
|
+
const derivedContentHeight = positiveFinite(pageHeight - marginTop - marginBottom, pageHeight);
|
|
101
|
+
const contentWidth = positiveDataNumber(section.dataset.contentWidth, derivedContentWidth);
|
|
102
|
+
const contentHeight = positiveDataNumber(section.dataset.contentHeight, derivedContentHeight);
|
|
103
|
+
const headerDistance = nonNegativeDataNumber(
|
|
104
|
+
section.dataset.headerHeight,
|
|
105
|
+
DEFAULT_HEADER_FOOTER_DISTANCE
|
|
106
|
+
);
|
|
107
|
+
const footerDistance = nonNegativeDataNumber(
|
|
108
|
+
section.dataset.footerHeight,
|
|
109
|
+
DEFAULT_HEADER_FOOTER_DISTANCE
|
|
110
|
+
);
|
|
84
111
|
return {
|
|
85
112
|
pageWidth,
|
|
86
113
|
pageHeight,
|
|
@@ -128,8 +155,93 @@ var DocxodusPagination = (() => {
|
|
|
128
155
|
function samePageBox(a, b) {
|
|
129
156
|
return a.pageWidth === b.pageWidth && a.pageHeight === b.pageHeight && a.marginTop === b.marginTop && a.marginRight === b.marginRight && a.marginBottom === b.marginBottom && a.marginLeft === b.marginLeft;
|
|
130
157
|
}
|
|
158
|
+
function createUnavailablePageMap(documentVersion, rendererFingerprint, mode = "continuous") {
|
|
159
|
+
if (!rendererFingerprint) throw new Error("rendererFingerprint must be non-empty");
|
|
160
|
+
return {
|
|
161
|
+
schemaVersion: 1,
|
|
162
|
+
mode,
|
|
163
|
+
availability: "unavailable",
|
|
164
|
+
documentVersion,
|
|
165
|
+
rendererFingerprint,
|
|
166
|
+
pages: [],
|
|
167
|
+
fragments: []
|
|
168
|
+
};
|
|
169
|
+
}
|
|
170
|
+
var activePageCitationHighlights = /* @__PURE__ */ new WeakMap();
|
|
171
|
+
function clearPageCitationHighlight(root) {
|
|
172
|
+
const active = activePageCitationHighlights.get(root);
|
|
173
|
+
if (!active) return;
|
|
174
|
+
if (active.highlightClass && active.addedClass) {
|
|
175
|
+
active.target.classList.remove(active.highlightClass);
|
|
176
|
+
}
|
|
177
|
+
for (const style of active.inlineStyles ?? []) {
|
|
178
|
+
if (style.value) {
|
|
179
|
+
active.target.style.setProperty(style.name, style.value, style.priority);
|
|
180
|
+
} else {
|
|
181
|
+
active.target.style.removeProperty(style.name);
|
|
182
|
+
}
|
|
183
|
+
}
|
|
184
|
+
activePageCitationHighlights.delete(root);
|
|
185
|
+
}
|
|
186
|
+
function navigateToPageCitation(root, citation, options = {}) {
|
|
187
|
+
clearPageCitationHighlight(root);
|
|
188
|
+
if (citation.availability !== "available" || citation.fragments.length === 0) {
|
|
189
|
+
return { navigated: false, unavailableReason: "citation_unavailable" };
|
|
190
|
+
}
|
|
191
|
+
const byAttribute = (name, value, within = root) => {
|
|
192
|
+
for (const node of Array.from(within.querySelectorAll(`[${name}]`))) {
|
|
193
|
+
if (node.getAttribute(name) === value) return node;
|
|
194
|
+
}
|
|
195
|
+
return null;
|
|
196
|
+
};
|
|
197
|
+
const fragment = citation.fragments[0];
|
|
198
|
+
let target = byAttribute("data-page-fragment-id", fragment.fragmentId);
|
|
199
|
+
if (!target) {
|
|
200
|
+
const page = byAttribute("data-page-number", String(fragment.pageNumber));
|
|
201
|
+
if (page) target = byAttribute("data-source-anchor-id", citation.anchorId, page) ?? page;
|
|
202
|
+
}
|
|
203
|
+
if (!target) {
|
|
204
|
+
return {
|
|
205
|
+
navigated: false,
|
|
206
|
+
pageNumber: fragment.pageNumber,
|
|
207
|
+
fragmentId: fragment.fragmentId,
|
|
208
|
+
unavailableReason: "fragment_not_found"
|
|
209
|
+
};
|
|
210
|
+
}
|
|
211
|
+
if (options.highlightClass) {
|
|
212
|
+
const addedClass = !target.classList.contains(options.highlightClass);
|
|
213
|
+
target.classList.add(options.highlightClass);
|
|
214
|
+
activePageCitationHighlights.set(root, {
|
|
215
|
+
target,
|
|
216
|
+
highlightClass: options.highlightClass,
|
|
217
|
+
addedClass
|
|
218
|
+
});
|
|
219
|
+
} else if (options.highlight !== false) {
|
|
220
|
+
const names = ["outline", "outline-offset", "background-color"];
|
|
221
|
+
const inlineStyles = names.map((name) => ({
|
|
222
|
+
name,
|
|
223
|
+
value: target.style.getPropertyValue(name),
|
|
224
|
+
priority: target.style.getPropertyPriority(name)
|
|
225
|
+
}));
|
|
226
|
+
target.style.setProperty("outline", "3px solid #f4b400", "important");
|
|
227
|
+
target.style.setProperty("outline-offset", "2px", "important");
|
|
228
|
+
target.style.setProperty("background-color", "rgba(255, 235, 59, .18)", "important");
|
|
229
|
+
activePageCitationHighlights.set(root, { target, inlineStyles });
|
|
230
|
+
}
|
|
231
|
+
target.scrollIntoView({
|
|
232
|
+
behavior: options.behavior ?? "smooth",
|
|
233
|
+
block: options.block ?? "center"
|
|
234
|
+
});
|
|
235
|
+
return {
|
|
236
|
+
navigated: true,
|
|
237
|
+
target,
|
|
238
|
+
pageNumber: fragment.pageNumber,
|
|
239
|
+
fragmentId: fragment.fragmentId
|
|
240
|
+
};
|
|
241
|
+
}
|
|
131
242
|
var MAX_FOOTNOTE_AREA_RATIO = 0.6;
|
|
132
|
-
var
|
|
243
|
+
var FOOTNOTE_MEASUREMENT_GUARD_PT = 2;
|
|
244
|
+
var FOOTNOTE_SOURCE_POSITION_ATTR = "data-pagination-footnote-source-position";
|
|
133
245
|
var PaginationEngine = class {
|
|
134
246
|
/**
|
|
135
247
|
* Creates a new pagination engine.
|
|
@@ -139,24 +251,46 @@ var DocxodusPagination = (() => {
|
|
|
139
251
|
* @param options - Pagination options
|
|
140
252
|
*/
|
|
141
253
|
constructor(staging, container, options = {}) {
|
|
254
|
+
this.createdPageCount = 0;
|
|
255
|
+
this.footnoteSeparator = null;
|
|
256
|
+
this.footnoteContinuationSeparator = null;
|
|
257
|
+
this.footnoteLayoutLikeWord8 = false;
|
|
142
258
|
this.pendingFootnoteContinuation = null;
|
|
143
259
|
/** Per-section `w:pgNumType` (start / format), read off the section wrappers. */
|
|
144
260
|
this.pageNumbering = /* @__PURE__ */ new Map();
|
|
145
|
-
this.
|
|
146
|
-
this.
|
|
261
|
+
this.lastPages = [];
|
|
262
|
+
this.expectedPageMapAnchorIds = /* @__PURE__ */ new Set();
|
|
263
|
+
this.state = "ready";
|
|
264
|
+
const ownerDocument = typeof staging !== "string" ? staging.ownerDocument : typeof container !== "string" ? container.ownerDocument : globalThis.document;
|
|
265
|
+
this.stagingElement = typeof staging === "string" ? ownerDocument.getElementById(staging) : staging;
|
|
266
|
+
this.containerElement = typeof container === "string" ? ownerDocument.getElementById(container) : container;
|
|
147
267
|
if (!this.stagingElement) {
|
|
148
268
|
throw new Error("Staging element not found");
|
|
149
269
|
}
|
|
150
270
|
if (!this.containerElement) {
|
|
151
271
|
throw new Error("Container element not found");
|
|
152
272
|
}
|
|
273
|
+
if (this.stagingElement.ownerDocument !== this.containerElement.ownerDocument) {
|
|
274
|
+
throw new Error("Staging and container elements must belong to the same document");
|
|
275
|
+
}
|
|
276
|
+
const view = ownerDocument.defaultView;
|
|
277
|
+
if (!view) {
|
|
278
|
+
throw new Error("Pagination requires an attached document with a defaultView");
|
|
279
|
+
}
|
|
280
|
+
this.document = ownerDocument;
|
|
281
|
+
this.view = view;
|
|
153
282
|
this.scale = options.scale ?? 1;
|
|
154
283
|
this.cssPrefix = options.cssPrefix ?? "page-";
|
|
155
284
|
this.showPageNumbers = options.showPageNumbers ?? true;
|
|
156
285
|
this.pageGap = options.pageGap ?? 20;
|
|
157
286
|
this.fragmentParagraphs = options.fragmentParagraphs ?? false;
|
|
287
|
+
this.deferFragmentIdentities = options.deferFragmentIdentities ?? false;
|
|
288
|
+
this.cancellationCheckpoint = options.checkCancellation;
|
|
289
|
+
this.pageCountCheckpoint = options.checkPageCount;
|
|
290
|
+
this.layoutToken = options.layoutToken;
|
|
158
291
|
this.hfRegistry = /* @__PURE__ */ new Map();
|
|
159
292
|
this.footnoteRegistry = /* @__PURE__ */ new Map();
|
|
293
|
+
this.commentMarginRegistry = /* @__PURE__ */ new Map();
|
|
160
294
|
}
|
|
161
295
|
/**
|
|
162
296
|
* Runs the pagination process.
|
|
@@ -164,58 +298,469 @@ var DocxodusPagination = (() => {
|
|
|
164
298
|
* @returns PaginationResult with page information
|
|
165
299
|
*/
|
|
166
300
|
paginate() {
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
"
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
const
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
301
|
+
this.checkpoint();
|
|
302
|
+
if (this.state !== "ready") {
|
|
303
|
+
throw new Error(`PaginationEngine is one-shot and is already ${this.state}`);
|
|
304
|
+
}
|
|
305
|
+
if (!this.stagingElement.isConnected || !this.containerElement.isConnected) {
|
|
306
|
+
throw new Error("Pagination requires staging and container elements attached to their document");
|
|
307
|
+
}
|
|
308
|
+
if (this.document.documentElement.getBoundingClientRect().width <= 0) {
|
|
309
|
+
throw new Error("Pagination requires a browsing context with non-zero layout");
|
|
310
|
+
}
|
|
311
|
+
this.state = "running";
|
|
312
|
+
try {
|
|
313
|
+
const pages = [];
|
|
314
|
+
let pageNumber = 1;
|
|
315
|
+
this.hfRegistry = this.parseHeaderFooterRegistry();
|
|
316
|
+
this.footnoteRegistry = this.parseFootnoteRegistry();
|
|
317
|
+
({
|
|
318
|
+
normal: this.footnoteSeparator,
|
|
319
|
+
continuation: this.footnoteContinuationSeparator
|
|
320
|
+
} = this.parseFootnoteSeparators());
|
|
321
|
+
this.commentMarginRegistry = this.parseCommentMarginRegistry();
|
|
322
|
+
this.footnoteLayoutLikeWord8 = this.stagingElement.dataset.footnoteLayoutLikeWord8 === "true";
|
|
323
|
+
const sections = this.stagingElement.querySelectorAll(
|
|
324
|
+
"[data-section-index]"
|
|
325
|
+
);
|
|
326
|
+
this.pageNumbering = this.parsePageNumbering(sections);
|
|
327
|
+
const sectionsToProcess = sections.length > 0 ? Array.from(sections) : [this.stagingElement];
|
|
328
|
+
this.expectedPageMapAnchorIds = /* @__PURE__ */ new Set();
|
|
329
|
+
const referencedFootnoteIds = /* @__PURE__ */ new Set();
|
|
330
|
+
const referencedCommentIds = /* @__PURE__ */ new Set();
|
|
331
|
+
for (const section of sectionsToProcess) {
|
|
332
|
+
this.checkpoint();
|
|
333
|
+
this.collectExpectedSourceAnchors(section, this.expectedPageMapAnchorIds, true);
|
|
334
|
+
for (const reference of Array.from(section.querySelectorAll("[data-footnote-id]"))) {
|
|
335
|
+
this.checkpoint();
|
|
336
|
+
if (reference.closest("#pagination-footnote-registry, #pagination-hf-registry")) continue;
|
|
337
|
+
const id = reference.dataset.footnoteId;
|
|
338
|
+
if (id) referencedFootnoteIds.add(id);
|
|
339
|
+
}
|
|
340
|
+
for (const reference of Array.from(section.querySelectorAll("[data-comment-id]"))) {
|
|
341
|
+
this.checkpoint();
|
|
342
|
+
if (reference.closest(
|
|
343
|
+
"#pagination-comment-margin-registry, #pagination-footnote-registry, #pagination-hf-registry"
|
|
344
|
+
)) continue;
|
|
345
|
+
const id = reference.dataset.commentId;
|
|
346
|
+
if (id) referencedCommentIds.add(id);
|
|
347
|
+
}
|
|
348
|
+
}
|
|
349
|
+
for (const id of referencedFootnoteIds) {
|
|
350
|
+
this.checkpoint();
|
|
351
|
+
const source = this.footnoteRegistry.get(id);
|
|
352
|
+
if (!source) continue;
|
|
353
|
+
this.collectExpectedSourceAnchors(source, this.expectedPageMapAnchorIds);
|
|
354
|
+
for (const reference of Array.from(
|
|
355
|
+
source.querySelectorAll("[data-comment-id]")
|
|
356
|
+
)) {
|
|
357
|
+
this.checkpoint();
|
|
358
|
+
const commentId = reference.dataset.commentId;
|
|
359
|
+
if (commentId) referencedCommentIds.add(commentId);
|
|
360
|
+
}
|
|
361
|
+
}
|
|
362
|
+
for (const id of referencedCommentIds) {
|
|
363
|
+
this.checkpoint();
|
|
364
|
+
const source = this.commentMarginRegistry.get(id);
|
|
365
|
+
if (source) this.collectExpectedSourceAnchors(source, this.expectedPageMapAnchorIds);
|
|
366
|
+
}
|
|
367
|
+
const runs = [];
|
|
368
|
+
for (const section of sectionsToProcess) {
|
|
369
|
+
this.checkpoint();
|
|
370
|
+
const dims = parseSectionDimensions(section);
|
|
371
|
+
const previous = runs[runs.length - 1];
|
|
372
|
+
if (previous && section.dataset.sectionType === "continuous" && samePageBox(previous.dims, dims)) {
|
|
373
|
+
previous.sections.push(section);
|
|
374
|
+
previous.sectionDimensions.set(
|
|
375
|
+
parseInt(section.dataset.sectionIndex || "0", 10),
|
|
376
|
+
dims
|
|
377
|
+
);
|
|
378
|
+
} else {
|
|
379
|
+
const sectionIndex = parseInt(section.dataset.sectionIndex || "0", 10);
|
|
380
|
+
runs.push({
|
|
381
|
+
sections: [section],
|
|
382
|
+
sectionIndex,
|
|
383
|
+
sectionType: section.dataset.sectionType ?? "nextPage",
|
|
384
|
+
dims,
|
|
385
|
+
sectionDimensions: /* @__PURE__ */ new Map([[sectionIndex, dims]])
|
|
386
|
+
});
|
|
387
|
+
}
|
|
388
|
+
}
|
|
389
|
+
for (const run of runs) {
|
|
390
|
+
this.checkpoint();
|
|
391
|
+
const needsParityFiller = pages.length > 0 && (run.sectionType === "oddPage" && pageNumber % 2 === 0 || run.sectionType === "evenPage" && pageNumber % 2 === 1);
|
|
392
|
+
if (needsParityFiller) {
|
|
393
|
+
const precedingPage = pages[pages.length - 1];
|
|
394
|
+
const precedingPageInSection = parseInt(
|
|
395
|
+
precedingPage.element.dataset.pageInSection ?? "1",
|
|
396
|
+
10
|
|
397
|
+
);
|
|
398
|
+
const precedingDisplayedPageNumber = parseInt(
|
|
399
|
+
precedingPage.element.dataset.displayedPageNumber ?? String(precedingPage.pageNumber),
|
|
400
|
+
10
|
|
401
|
+
);
|
|
402
|
+
const filler = this.createPage(
|
|
403
|
+
precedingPage.dimensions,
|
|
404
|
+
pageNumber,
|
|
405
|
+
precedingPage.sectionIndex,
|
|
406
|
+
precedingDisplayedPageNumber + 1,
|
|
407
|
+
[],
|
|
408
|
+
precedingPageInSection + 1,
|
|
409
|
+
[],
|
|
410
|
+
0,
|
|
411
|
+
null,
|
|
412
|
+
void 0,
|
|
413
|
+
true
|
|
414
|
+
);
|
|
415
|
+
pages.push(filler);
|
|
416
|
+
pageNumber++;
|
|
417
|
+
}
|
|
418
|
+
this.stagingElement.style.visibility = "hidden";
|
|
419
|
+
this.stagingElement.style.position = "absolute";
|
|
420
|
+
this.stagingElement.style.left = "-9999px";
|
|
421
|
+
this.stagingElement.style.display = "block";
|
|
422
|
+
const blocks = [];
|
|
423
|
+
for (const section of run.sections) {
|
|
424
|
+
this.checkpoint();
|
|
425
|
+
const sectionIndex = parseInt(section.dataset.sectionIndex || "0", 10);
|
|
426
|
+
const sectionDims = run.sectionDimensions.get(sectionIndex) ?? run.dims;
|
|
427
|
+
section.style.width = `${sectionDims.contentWidth}pt`;
|
|
428
|
+
const columnCount = parseInt(section.dataset.cols || "1", 10);
|
|
429
|
+
if (columnCount > 1) {
|
|
430
|
+
const gap = parseFloat(section.dataset.colGap || "");
|
|
431
|
+
blocks.push(...this.buildColumnBlocks(
|
|
432
|
+
section,
|
|
433
|
+
sectionDims,
|
|
434
|
+
sectionIndex,
|
|
435
|
+
columnCount,
|
|
436
|
+
Number.isFinite(gap) ? gap : 36
|
|
437
|
+
));
|
|
438
|
+
} else {
|
|
439
|
+
blocks.push(...this.measureBlocks(section, sectionDims, sectionIndex));
|
|
440
|
+
}
|
|
441
|
+
}
|
|
442
|
+
const sectionPages = this.flowToPages(
|
|
443
|
+
blocks,
|
|
444
|
+
run.dims,
|
|
445
|
+
pageNumber,
|
|
446
|
+
run.sectionIndex,
|
|
447
|
+
run.sectionDimensions
|
|
448
|
+
);
|
|
449
|
+
this.checkpoint();
|
|
450
|
+
pages.push(...sectionPages);
|
|
451
|
+
pageNumber += sectionPages.length;
|
|
452
|
+
}
|
|
453
|
+
this.stagingElement.style.display = "none";
|
|
454
|
+
this.substitutePageNumberFields(pages.length);
|
|
455
|
+
for (const page of pages) {
|
|
456
|
+
this.checkpoint();
|
|
457
|
+
if (page.element.dataset.sectionFiller === "true") continue;
|
|
458
|
+
const pageInSection = parseInt(page.element.dataset.pageInSection || "1", 10);
|
|
459
|
+
const displayedPageNumber = parseInt(
|
|
460
|
+
page.element.dataset.displayedPageNumber || String(page.pageNumber),
|
|
461
|
+
10
|
|
462
|
+
);
|
|
463
|
+
const header = this.selectHeader(page.sectionIndex, pageInSection, displayedPageNumber);
|
|
464
|
+
const footer = this.selectFooter(page.sectionIndex, pageInSection, displayedPageNumber);
|
|
465
|
+
if (header) this.collectExpectedSourceAnchors(header, this.expectedPageMapAnchorIds);
|
|
466
|
+
if (footer) this.collectExpectedSourceAnchors(footer, this.expectedPageMapAnchorIds);
|
|
467
|
+
}
|
|
468
|
+
this.qualifyPageFragments(pages);
|
|
469
|
+
this.transferVisibleFragmentTargets();
|
|
470
|
+
if (!this.deferFragmentIdentities) this.normalizeVisiblePageFragments(pages);
|
|
471
|
+
this.lastPages = pages;
|
|
472
|
+
const result = {
|
|
473
|
+
totalPages: pages.length,
|
|
474
|
+
pages,
|
|
475
|
+
pageMap: this.layoutToken ? this.materializePageMap(
|
|
476
|
+
this.layoutToken.documentVersion,
|
|
477
|
+
this.layoutToken.rendererFingerprint
|
|
478
|
+
) : void 0
|
|
479
|
+
};
|
|
480
|
+
this.state = "complete";
|
|
481
|
+
return result;
|
|
482
|
+
} catch (error) {
|
|
483
|
+
this.state = "failed";
|
|
484
|
+
throw error;
|
|
485
|
+
}
|
|
486
|
+
}
|
|
487
|
+
checkpoint() {
|
|
488
|
+
this.cancellationCheckpoint?.();
|
|
489
|
+
}
|
|
490
|
+
/**
|
|
491
|
+
* Normalize visible fragment identities after a caller applies final standalone styles. This
|
|
492
|
+
* deliberately runs before the stability barrier; materializePageMap is read-only so PageMap
|
|
493
|
+
* measurement cannot mutate a tree after it was declared stable.
|
|
494
|
+
*/
|
|
495
|
+
normalizePageMapFragmentIdentities() {
|
|
496
|
+
if (this.lastPages.length === 0) {
|
|
497
|
+
throw new Error("paginate() must complete before fragment identities can be normalized");
|
|
498
|
+
}
|
|
499
|
+
this.normalizeVisiblePageFragments(this.lastPages);
|
|
500
|
+
}
|
|
501
|
+
/**
|
|
502
|
+
* Materialize the last completed browser layout as portable page-relative point geometry.
|
|
503
|
+
* The caller supplies both invalidation tokens; this engine never guesses a document version
|
|
504
|
+
* or renderer fingerprint.
|
|
505
|
+
*/
|
|
506
|
+
materializePageMap(documentVersion, rendererFingerprint) {
|
|
507
|
+
if (!Number.isSafeInteger(documentVersion) || documentVersion < 0) {
|
|
508
|
+
throw new Error("documentVersion must be a non-negative safe integer");
|
|
509
|
+
}
|
|
510
|
+
if (!rendererFingerprint) throw new Error("rendererFingerprint must be non-empty");
|
|
511
|
+
if (this.lastPages.length === 0) throw new Error("paginate() must complete before materializePageMap()");
|
|
512
|
+
const pages = this.lastPages.map((page) => ({
|
|
513
|
+
pageNumber: page.pageNumber,
|
|
514
|
+
pageInSection: parseInt(page.element.dataset.pageInSection || "1", 10),
|
|
515
|
+
width: page.dimensions.pageWidth,
|
|
516
|
+
height: page.dimensions.pageHeight,
|
|
517
|
+
sectionIndex: page.sectionIndex,
|
|
518
|
+
pageName: `docxodus-section-${page.sectionIndex}`
|
|
519
|
+
}));
|
|
520
|
+
const fragments = [];
|
|
521
|
+
const requiredAnchorIds = new Set(this.expectedPageMapAnchorIds);
|
|
522
|
+
if (requiredAnchorIds.size === 0) {
|
|
523
|
+
throw new Error("cannot publish an available PageMap without canonical source inventory");
|
|
524
|
+
}
|
|
525
|
+
const measuredAnchorIds = /* @__PURE__ */ new Set();
|
|
526
|
+
const emittedFragmentCounts = /* @__PURE__ */ new Map();
|
|
527
|
+
for (const page of this.lastPages) {
|
|
528
|
+
this.checkpoint();
|
|
529
|
+
const pageRect = page.element.getBoundingClientRect();
|
|
530
|
+
if (pageRect.width <= 0 || pageRect.height <= 0) {
|
|
531
|
+
throw new Error(`page ${page.pageNumber} has no measurable geometry`);
|
|
532
|
+
}
|
|
533
|
+
const pointPerRenderedX = page.dimensions.pageWidth / pageRect.width;
|
|
534
|
+
const pointPerRenderedY = page.dimensions.pageHeight / pageRect.height;
|
|
535
|
+
const nodes = page.element.querySelectorAll("[data-source-anchor-id]");
|
|
536
|
+
for (const element of Array.from(nodes)) {
|
|
537
|
+
this.checkpoint();
|
|
538
|
+
if (this.isDeliberatelyUnrenderedSource(element, page.element)) continue;
|
|
539
|
+
const anchorId = element.dataset.sourceAnchorId;
|
|
540
|
+
if (!anchorId || !element.dataset.pageFragmentId || !Number.isInteger(parseInt(element.dataset.fragmentIndex || "", 10))) {
|
|
541
|
+
throw new Error(`page ${page.pageNumber} contains an unqualified source anchor`);
|
|
542
|
+
}
|
|
543
|
+
const rect = element.getBoundingClientRect();
|
|
544
|
+
const style = this.view.getComputedStyle(element);
|
|
545
|
+
const deliberatelyHidden = style.display === "none" || style.visibility === "hidden";
|
|
546
|
+
if (deliberatelyHidden) continue;
|
|
547
|
+
requiredAnchorIds.add(anchorId);
|
|
548
|
+
const visibleRect = this.intersectWithClippingAncestors(element, page.element, pageRect, rect);
|
|
549
|
+
const left = visibleRect.left;
|
|
550
|
+
const top = visibleRect.top;
|
|
551
|
+
const right = visibleRect.right;
|
|
552
|
+
const bottom = visibleRect.bottom;
|
|
553
|
+
if (rect.width <= 0 || rect.height <= 0 || right <= left || bottom <= top) {
|
|
554
|
+
continue;
|
|
555
|
+
}
|
|
556
|
+
measuredAnchorIds.add(anchorId);
|
|
557
|
+
const fragmentIndex = emittedFragmentCounts.get(anchorId) ?? 0;
|
|
558
|
+
emittedFragmentCounts.set(anchorId, fragmentIndex + 1);
|
|
559
|
+
const fragmentId = `p${page.pageNumber}-f${fragmentIndex}-${anchorId}`;
|
|
560
|
+
if (element.dataset.fragmentIndex !== String(fragmentIndex) || element.dataset.pageFragmentId !== fragmentId || element.dataset.pageNumber !== String(page.pageNumber)) {
|
|
561
|
+
throw new Error(
|
|
562
|
+
`page ${page.pageNumber} fragment identity changed after final-tree normalization`
|
|
563
|
+
);
|
|
564
|
+
}
|
|
565
|
+
fragments.push({
|
|
566
|
+
fragmentId,
|
|
567
|
+
anchorId,
|
|
568
|
+
fragmentIndex,
|
|
569
|
+
pageNumber: page.pageNumber,
|
|
570
|
+
geometry: {
|
|
571
|
+
x: (left - pageRect.left) * pointPerRenderedX,
|
|
572
|
+
y: (top - pageRect.top) * pointPerRenderedY,
|
|
573
|
+
width: (right - left) * pointPerRenderedX,
|
|
574
|
+
height: (bottom - top) * pointPerRenderedY
|
|
575
|
+
},
|
|
576
|
+
story: this.storyForCanonicalAnchor(anchorId),
|
|
577
|
+
inTableCell: element.matches("td,th") || element.closest("td,th") !== null
|
|
187
578
|
});
|
|
188
579
|
}
|
|
189
580
|
}
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
581
|
+
const missingAnchor = Array.from(requiredAnchorIds).find((id) => !measuredAnchorIds.has(id));
|
|
582
|
+
if (missingAnchor) {
|
|
583
|
+
throw new Error(`source anchor ${missingAnchor} has no measurable fragment in the paginated layout`);
|
|
584
|
+
}
|
|
585
|
+
return {
|
|
586
|
+
schemaVersion: 1,
|
|
587
|
+
mode: "paginated",
|
|
588
|
+
availability: "available",
|
|
589
|
+
documentVersion,
|
|
590
|
+
rendererFingerprint,
|
|
591
|
+
pages,
|
|
592
|
+
fragments
|
|
593
|
+
};
|
|
594
|
+
}
|
|
595
|
+
normalizeVisiblePageFragments(pages) {
|
|
596
|
+
const emittedFragmentCounts = /* @__PURE__ */ new Map();
|
|
597
|
+
for (const page of pages) {
|
|
598
|
+
this.checkpoint();
|
|
599
|
+
const pageRect = page.element.getBoundingClientRect();
|
|
600
|
+
for (const element of Array.from(
|
|
601
|
+
page.element.querySelectorAll("[data-source-anchor-id]")
|
|
602
|
+
)) {
|
|
603
|
+
this.checkpoint();
|
|
604
|
+
if (this.isDeliberatelyUnrenderedSource(element, page.element)) continue;
|
|
605
|
+
const anchorId = element.dataset.sourceAnchorId;
|
|
606
|
+
if (!anchorId) continue;
|
|
607
|
+
const style = this.view.getComputedStyle(element);
|
|
608
|
+
if (style.display === "none" || style.visibility === "hidden") continue;
|
|
609
|
+
const rect = element.getBoundingClientRect();
|
|
610
|
+
const visible = this.intersectWithClippingAncestors(element, page.element, pageRect, rect);
|
|
611
|
+
if (rect.width <= 0 || rect.height <= 0 || visible.right <= visible.left || visible.bottom <= visible.top) continue;
|
|
612
|
+
const fragmentIndex = emittedFragmentCounts.get(anchorId) ?? 0;
|
|
613
|
+
emittedFragmentCounts.set(anchorId, fragmentIndex + 1);
|
|
614
|
+
element.dataset.pageNumber = String(page.pageNumber);
|
|
615
|
+
element.dataset.fragmentIndex = String(fragmentIndex);
|
|
616
|
+
element.dataset.pageFragmentId = `p${page.pageNumber}-f${fragmentIndex}-${anchorId}`;
|
|
617
|
+
}
|
|
618
|
+
}
|
|
619
|
+
}
|
|
620
|
+
/**
|
|
621
|
+
* Intersect an element with every ancestor that establishes an overflow clip before the page
|
|
622
|
+
* root. getBoundingClientRect() reports layout outside those clips, which is not rendered and
|
|
623
|
+
* therefore must not satisfy PageMap completeness or inflate portable geometry.
|
|
624
|
+
*/
|
|
625
|
+
intersectWithClippingAncestors(element, page, pageRect, rect) {
|
|
626
|
+
let left = Math.max(rect.left, pageRect.left);
|
|
627
|
+
let top = Math.max(rect.top, pageRect.top);
|
|
628
|
+
let right = Math.min(rect.right, pageRect.right);
|
|
629
|
+
let bottom = Math.min(rect.bottom, pageRect.bottom);
|
|
630
|
+
const clips = (value) => value === "hidden" || value === "clip" || value === "scroll" || value === "auto";
|
|
631
|
+
for (let ancestor = element.parentElement; ancestor && ancestor !== page; ancestor = ancestor.parentElement) {
|
|
632
|
+
const style = this.view.getComputedStyle(ancestor);
|
|
633
|
+
const clipsX = clips(style.overflowX);
|
|
634
|
+
const clipsY = clips(style.overflowY);
|
|
635
|
+
if (!clipsX && !clipsY) continue;
|
|
636
|
+
const ancestorRect = ancestor.getBoundingClientRect();
|
|
637
|
+
if (clipsX) {
|
|
638
|
+
left = Math.max(left, ancestorRect.left);
|
|
639
|
+
right = Math.min(right, ancestorRect.right);
|
|
640
|
+
}
|
|
641
|
+
if (clipsY) {
|
|
642
|
+
top = Math.max(top, ancestorRect.top);
|
|
643
|
+
bottom = Math.min(bottom, ancestorRect.bottom);
|
|
644
|
+
}
|
|
645
|
+
}
|
|
646
|
+
return { left, top, right, bottom };
|
|
647
|
+
}
|
|
648
|
+
storyForCanonicalAnchor(anchorId) {
|
|
649
|
+
const first = anchorId.indexOf(":");
|
|
650
|
+
const second = first < 0 ? -1 : anchorId.indexOf(":", first + 1);
|
|
651
|
+
const scope = first >= 0 && second > first ? anchorId.slice(first + 1, second) : "body";
|
|
652
|
+
if (scope.startsWith("hdr")) return "header";
|
|
653
|
+
if (scope.startsWith("ftr")) return "footer";
|
|
654
|
+
if (scope === "fn") return "footnote";
|
|
655
|
+
if (scope === "en") return "endnote";
|
|
656
|
+
if (scope === "cmt") return "comment";
|
|
657
|
+
return "body";
|
|
658
|
+
}
|
|
659
|
+
/**
|
|
660
|
+
* Add canonical IDs from an addressable source subtree to the pre-pagination inventory.
|
|
661
|
+
* Registry wrappers are excluded when scanning staging because selectable registry contents are
|
|
662
|
+
* inventoried separately. Producers may explicitly mark content that has no visual substrate
|
|
663
|
+
* with `data-page-map-exclude="true"`; native hidden semantics carry the same signal.
|
|
664
|
+
*/
|
|
665
|
+
collectExpectedSourceAnchors(source, destination, excludeRegistries = false) {
|
|
666
|
+
const candidates = source.matches("[data-source-anchor-id]") ? [source] : [];
|
|
667
|
+
candidates.push(...Array.from(source.querySelectorAll("[data-source-anchor-id]")));
|
|
668
|
+
for (const element of candidates) {
|
|
669
|
+
if (excludeRegistries && element.closest(
|
|
670
|
+
"#pagination-hf-registry, #pagination-footnote-registry, #pagination-comment-margin-registry"
|
|
671
|
+
)) {
|
|
672
|
+
continue;
|
|
673
|
+
}
|
|
674
|
+
if (this.isZeroHeightExplicitBreakCarrier(element)) {
|
|
675
|
+
element.dataset.pageMapExclude = "true";
|
|
676
|
+
}
|
|
677
|
+
if (this.isDeliberatelyUnrenderedSource(element, source)) continue;
|
|
678
|
+
const anchorId = element.dataset.sourceAnchorId;
|
|
679
|
+
if (anchorId) destination.add(anchorId);
|
|
680
|
+
}
|
|
681
|
+
}
|
|
682
|
+
isZeroHeightExplicitBreakCarrier(element) {
|
|
683
|
+
const next = element.nextElementSibling;
|
|
684
|
+
const bounds = element.getBoundingClientRect();
|
|
685
|
+
return element.hasAttribute("data-source-anchor-id") && (element.textContent ?? "").replace(/\u00a0/g, "").trim() === "" && bounds.height <= 0.01 && !element.querySelector("[data-source-anchor-id]") && !element.querySelector("img,svg,canvas,table,hr,input,textarea,select") && (next?.dataset.pageBreak === "true" || next?.classList.contains(`${this.cssPrefix}break`) === true);
|
|
686
|
+
}
|
|
687
|
+
isDeliberatelyUnrenderedSource(element, sourceRoot) {
|
|
688
|
+
for (let current = element; current; current = current.parentElement) {
|
|
689
|
+
const zeroHeightExplicitBreakCarrier = current === element && this.isZeroHeightExplicitBreakCarrier(current);
|
|
690
|
+
if (current.dataset.pageMapExclude === "true" || current.dataset.pageBreak === "true" || current.classList.contains(`${this.cssPrefix}break`) || zeroHeightExplicitBreakCarrier || current.hidden || current.getAttribute("aria-hidden") === "true" || current.style.display === "none" || current.style.visibility === "hidden") {
|
|
691
|
+
return true;
|
|
692
|
+
}
|
|
693
|
+
if (current === sourceRoot) break;
|
|
694
|
+
}
|
|
695
|
+
return false;
|
|
696
|
+
}
|
|
697
|
+
/**
|
|
698
|
+
* Keep exactly one active bare-Unid editor anchor per source block. Presentation clones use
|
|
699
|
+
* canonical source identity plus page/fragment qualification instead.
|
|
700
|
+
*/
|
|
701
|
+
qualifyPageFragments(pages) {
|
|
702
|
+
const fragmentCounts = /* @__PURE__ */ new Map();
|
|
703
|
+
const activeCanonicalIds = /* @__PURE__ */ new Set();
|
|
704
|
+
const activeBareAnchorIds = /* @__PURE__ */ new Set();
|
|
705
|
+
const makeInactive = (element) => {
|
|
706
|
+
element.removeAttribute("data-anchor");
|
|
707
|
+
element.removeAttribute("data-committed-text");
|
|
708
|
+
if (element.hasAttribute("contenteditable")) element.setAttribute("contenteditable", "false");
|
|
709
|
+
};
|
|
710
|
+
for (const page of pages) {
|
|
711
|
+
const nodes = page.element.querySelectorAll("[data-source-anchor-id]");
|
|
712
|
+
for (const element of Array.from(nodes)) {
|
|
713
|
+
const anchorId = element.dataset.sourceAnchorId;
|
|
714
|
+
if (!anchorId) continue;
|
|
715
|
+
const fragmentIndex = fragmentCounts.get(anchorId) ?? 0;
|
|
716
|
+
fragmentCounts.set(anchorId, fragmentIndex + 1);
|
|
717
|
+
element.dataset.pageNumber = String(page.pageNumber);
|
|
718
|
+
element.dataset.fragmentIndex = String(fragmentIndex);
|
|
719
|
+
element.dataset.pageFragmentId = `p${page.pageNumber}-f${fragmentIndex}-${anchorId}`;
|
|
720
|
+
const story = this.storyForCanonicalAnchor(anchorId);
|
|
721
|
+
const mayOwnActiveEditorAnchor = story === "body" || story === "comment" || story === "footnote" || story === "endnote";
|
|
722
|
+
if (element.hasAttribute("data-anchor")) {
|
|
723
|
+
const bareAnchorId = element.dataset.anchor;
|
|
724
|
+
if (mayOwnActiveEditorAnchor && !activeCanonicalIds.has(anchorId) && !activeBareAnchorIds.has(bareAnchorId)) {
|
|
725
|
+
activeCanonicalIds.add(anchorId);
|
|
726
|
+
activeBareAnchorIds.add(bareAnchorId);
|
|
727
|
+
} else {
|
|
728
|
+
makeInactive(element);
|
|
729
|
+
}
|
|
210
730
|
}
|
|
211
731
|
}
|
|
212
|
-
const sectionPages = this.flowToPages(blocks, run.dims, pageNumber, run.sectionIndex);
|
|
213
|
-
pages.push(...sectionPages);
|
|
214
|
-
pageNumber += sectionPages.length;
|
|
215
732
|
}
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
733
|
+
const activeStagingCanonicalIds = /* @__PURE__ */ new Set();
|
|
734
|
+
for (const element of Array.from(
|
|
735
|
+
this.stagingElement.querySelectorAll("[data-source-anchor-id][data-anchor]")
|
|
736
|
+
)) {
|
|
737
|
+
const anchorId = element.dataset.sourceAnchorId;
|
|
738
|
+
if (!anchorId) continue;
|
|
739
|
+
const bareAnchorId = element.dataset.anchor;
|
|
740
|
+
if (activeCanonicalIds.has(anchorId) || activeStagingCanonicalIds.has(anchorId) || activeBareAnchorIds.has(bareAnchorId)) {
|
|
741
|
+
makeInactive(element);
|
|
742
|
+
} else {
|
|
743
|
+
activeStagingCanonicalIds.add(anchorId);
|
|
744
|
+
activeBareAnchorIds.add(bareAnchorId);
|
|
745
|
+
}
|
|
746
|
+
}
|
|
747
|
+
}
|
|
748
|
+
/**
|
|
749
|
+
* Page flow clones source blocks while the hidden staging tree stays in the document. Any HTML
|
|
750
|
+
* fragment target copied into a visible page would therefore resolve to its earlier hidden
|
|
751
|
+
* source. Transfer target ownership to the page presentation after flow is complete; registry
|
|
752
|
+
* and wrapper IDs that have no visible counterpart remain available to pagination internals.
|
|
753
|
+
*/
|
|
754
|
+
transferVisibleFragmentTargets() {
|
|
755
|
+
const visibleIds = new Set(Array.from(
|
|
756
|
+
this.containerElement.querySelectorAll("[id]")
|
|
757
|
+
).map((element) => element.id).filter(Boolean));
|
|
758
|
+
if (visibleIds.size === 0) return;
|
|
759
|
+
for (const source of Array.from(
|
|
760
|
+
this.stagingElement.querySelectorAll("[id]")
|
|
761
|
+
)) {
|
|
762
|
+
if (visibleIds.has(source.id)) source.removeAttribute("id");
|
|
763
|
+
}
|
|
219
764
|
}
|
|
220
765
|
/** Read each section's `w:pgNumType` off its wrapper (see {@link SectionPageNumbering}). */
|
|
221
766
|
parsePageNumbering(sections) {
|
|
@@ -259,9 +804,8 @@ var DocxodusPagination = (() => {
|
|
|
259
804
|
if (markers.length === 0) continue;
|
|
260
805
|
const sectionIndex = parseInt(box.dataset.sectionIndex || "0", 10);
|
|
261
806
|
const pageNumber = parseInt(box.dataset.pageNumber || "1", 10);
|
|
262
|
-
const pageInSection = parseInt(box.dataset.pageInSection || "1", 10);
|
|
263
807
|
const numbering = this.pageNumbering.get(sectionIndex) ?? {};
|
|
264
|
-
const displayed =
|
|
808
|
+
const displayed = parseInt(box.dataset.displayedPageNumber || String(pageNumber), 10);
|
|
265
809
|
for (const marker of Array.from(markers)) {
|
|
266
810
|
const kind = marker.dataset.field;
|
|
267
811
|
if (kind !== "PAGE" && kind !== "NUMPAGES") continue;
|
|
@@ -273,17 +817,26 @@ var DocxodusPagination = (() => {
|
|
|
273
817
|
/**
|
|
274
818
|
* Measures all content blocks in a section.
|
|
275
819
|
*/
|
|
276
|
-
measureBlocks(section, dims) {
|
|
820
|
+
measureBlocks(section, dims, sectionIndex) {
|
|
277
821
|
const blocks = [];
|
|
278
822
|
const children = Array.from(section.children);
|
|
279
823
|
for (const child of children) {
|
|
824
|
+
this.checkpoint();
|
|
280
825
|
if (child.dataset.sectionIndex !== void 0) {
|
|
281
|
-
const
|
|
826
|
+
const nestedSectionIndex = parseInt(child.dataset.sectionIndex || String(sectionIndex), 10);
|
|
827
|
+
const nestedBlocks = this.measureBlocks(child, dims, nestedSectionIndex);
|
|
282
828
|
blocks.push(...nestedBlocks);
|
|
283
829
|
continue;
|
|
284
830
|
}
|
|
831
|
+
if (child.matches("section.endnotes")) {
|
|
832
|
+
const endnoteBlocks = this.measureSafeEndnoteBlocks(child, dims, sectionIndex);
|
|
833
|
+
if (endnoteBlocks) {
|
|
834
|
+
blocks.push(...endnoteBlocks);
|
|
835
|
+
continue;
|
|
836
|
+
}
|
|
837
|
+
}
|
|
285
838
|
const rect = child.getBoundingClientRect();
|
|
286
|
-
const style =
|
|
839
|
+
const style = this.view.getComputedStyle(child);
|
|
287
840
|
const marginTopPx = parseFloat(style.marginTop) || 0;
|
|
288
841
|
const marginBottomPx = parseFloat(style.marginBottom) || 0;
|
|
289
842
|
const heightPt = pxToPt(rect.height);
|
|
@@ -292,6 +845,7 @@ var DocxodusPagination = (() => {
|
|
|
292
845
|
const isPageBreak = child.dataset.pageBreak === "true" || child.classList.contains(`${this.cssPrefix}break`);
|
|
293
846
|
blocks.push({
|
|
294
847
|
element: child,
|
|
848
|
+
sectionIndex,
|
|
295
849
|
heightPt,
|
|
296
850
|
marginTopPt,
|
|
297
851
|
marginBottomPt,
|
|
@@ -304,6 +858,95 @@ var DocxodusPagination = (() => {
|
|
|
304
858
|
}
|
|
305
859
|
return blocks;
|
|
306
860
|
}
|
|
861
|
+
/**
|
|
862
|
+
* Flatten the converter's `section.endnotes > ol > li > p` presentation into
|
|
863
|
+
* ordinary paragraph blocks. This preserves paragraph formatting and canonical
|
|
864
|
+
* p:en/en:en identities while allowing the existing paragraph fragmenter to
|
|
865
|
+
* split a long endnote across page boundaries.
|
|
866
|
+
*/
|
|
867
|
+
measureSafeEndnoteBlocks(section, dims, sectionIndex) {
|
|
868
|
+
const sectionChildren = Array.from(section.children);
|
|
869
|
+
const list = sectionChildren.find((child) => child.tagName === "OL");
|
|
870
|
+
if (!list || sectionChildren.some((child) => child.tagName !== "HR" && child !== list) || Array.from(list.children).some((child) => child.tagName !== "LI")) {
|
|
871
|
+
return null;
|
|
872
|
+
}
|
|
873
|
+
const items = Array.from(list.children);
|
|
874
|
+
if (items.length === 0 || items.some(
|
|
875
|
+
(item) => item.children.length === 0 || Array.from(item.children).some((child) => child.tagName !== "P")
|
|
876
|
+
)) {
|
|
877
|
+
return null;
|
|
878
|
+
}
|
|
879
|
+
const blocks = [];
|
|
880
|
+
const sectionStyle = this.view.getComputedStyle(section);
|
|
881
|
+
for (const rule of sectionChildren.filter((child) => child.tagName === "HR")) {
|
|
882
|
+
this.checkpoint();
|
|
883
|
+
const clonedRule = rule.cloneNode(true);
|
|
884
|
+
if (blocks.length === 0) clonedRule.style.marginTop = sectionStyle.marginTop;
|
|
885
|
+
blocks.push(this.measureElement(clonedRule, dims, sectionIndex));
|
|
886
|
+
}
|
|
887
|
+
const listStyle = this.view.getComputedStyle(list);
|
|
888
|
+
for (let itemIndex = 0; itemIndex < items.length; itemIndex++) {
|
|
889
|
+
this.checkpoint();
|
|
890
|
+
const item = items[itemIndex];
|
|
891
|
+
const ownerAnchorId = item.dataset.sourceAnchorId;
|
|
892
|
+
const paragraphs = Array.from(item.children);
|
|
893
|
+
for (let paragraphIndex = 0; paragraphIndex < paragraphs.length; paragraphIndex++) {
|
|
894
|
+
this.checkpoint();
|
|
895
|
+
const paragraph = paragraphs[paragraphIndex];
|
|
896
|
+
if (!this.hasRangeFragmentSafeLayout(paragraph)) {
|
|
897
|
+
return null;
|
|
898
|
+
}
|
|
899
|
+
const clone = paragraph.cloneNode(true);
|
|
900
|
+
clone.dataset.paginationSafeEndnote = "true";
|
|
901
|
+
clone.style.fontSize ||= sectionStyle.fontSize;
|
|
902
|
+
clone.style.lineHeight ||= sectionStyle.lineHeight;
|
|
903
|
+
clone.style.paddingLeft ||= listStyle.paddingLeft;
|
|
904
|
+
if (ownerAnchorId && !Array.from(
|
|
905
|
+
clone.querySelectorAll("[data-source-anchor-id]")
|
|
906
|
+
).some((node) => node.dataset.sourceAnchorId === ownerAnchorId)) {
|
|
907
|
+
const owner = this.document.createElement("span");
|
|
908
|
+
owner.dataset.sourceAnchorId = ownerAnchorId;
|
|
909
|
+
while (clone.firstChild) owner.appendChild(clone.firstChild);
|
|
910
|
+
clone.appendChild(owner);
|
|
911
|
+
}
|
|
912
|
+
if (paragraphIndex === 0) {
|
|
913
|
+
const itemId = item.id;
|
|
914
|
+
if (itemId) {
|
|
915
|
+
if (clone.id && clone.id !== itemId) return null;
|
|
916
|
+
clone.id = itemId;
|
|
917
|
+
}
|
|
918
|
+
const value = parseInt(item.getAttribute("value") || String(itemIndex + 1), 10);
|
|
919
|
+
const marker = this.formatOrderedListMarker(
|
|
920
|
+
Number.isFinite(value) ? value : itemIndex + 1,
|
|
921
|
+
listStyle.listStyleType
|
|
922
|
+
);
|
|
923
|
+
clone.insertBefore(this.document.createTextNode(`${marker}. `), clone.firstChild);
|
|
924
|
+
}
|
|
925
|
+
blocks.push(this.measureElement(clone, dims, sectionIndex));
|
|
926
|
+
}
|
|
927
|
+
}
|
|
928
|
+
return blocks;
|
|
929
|
+
}
|
|
930
|
+
/** Render the CSS ordered-list formats emitted by the converter after an endnote is flattened. */
|
|
931
|
+
formatOrderedListMarker(value, listStyleType) {
|
|
932
|
+
const format = (() => {
|
|
933
|
+
switch (listStyleType) {
|
|
934
|
+
case "lower-roman":
|
|
935
|
+
return "lowerRoman";
|
|
936
|
+
case "upper-roman":
|
|
937
|
+
return "upperRoman";
|
|
938
|
+
case "lower-alpha":
|
|
939
|
+
case "lower-latin":
|
|
940
|
+
return "lowerLetter";
|
|
941
|
+
case "upper-alpha":
|
|
942
|
+
case "upper-latin":
|
|
943
|
+
return "upperLetter";
|
|
944
|
+
default:
|
|
945
|
+
return "decimal";
|
|
946
|
+
}
|
|
947
|
+
})();
|
|
948
|
+
return formatPageNumber(value, format);
|
|
949
|
+
}
|
|
307
950
|
/**
|
|
308
951
|
* Flows a multi-column (`w:cols`) section's children into CSS-multicol container
|
|
309
952
|
* blocks. Word lays such a section out as N columns inside the same body extent;
|
|
@@ -321,7 +964,7 @@ var DocxodusPagination = (() => {
|
|
|
321
964
|
const maxFragmentHeight = this.smallestEffectiveContentHeight(dims, sectionIndex);
|
|
322
965
|
const isBreak = (child) => child.dataset.pageBreak === "true" || child.classList.contains(`${this.cssPrefix}break`);
|
|
323
966
|
const makeContainer = (slice) => {
|
|
324
|
-
const container = document.createElement("div");
|
|
967
|
+
const container = this.document.createElement("div");
|
|
325
968
|
container.style.columnCount = String(columnCount);
|
|
326
969
|
container.style.columnGap = `${columnGapPt}pt`;
|
|
327
970
|
for (const child of slice) {
|
|
@@ -331,17 +974,19 @@ var DocxodusPagination = (() => {
|
|
|
331
974
|
};
|
|
332
975
|
let start = 0;
|
|
333
976
|
while (start < children.length) {
|
|
977
|
+
this.checkpoint();
|
|
334
978
|
if (isBreak(children[start])) {
|
|
335
|
-
blocks.push(this.measureElement(children[start], dims));
|
|
979
|
+
blocks.push(this.measureElement(children[start], dims, sectionIndex));
|
|
336
980
|
start++;
|
|
337
981
|
continue;
|
|
338
982
|
}
|
|
339
983
|
let end = start + 1;
|
|
340
984
|
let container = makeContainer(children.slice(start, end));
|
|
341
|
-
let measured = this.measureElement(container, dims);
|
|
985
|
+
let measured = this.measureElement(container, dims, sectionIndex);
|
|
342
986
|
while (end < children.length && !isBreak(children[end])) {
|
|
987
|
+
this.checkpoint();
|
|
343
988
|
const candidate = makeContainer(children.slice(start, end + 1));
|
|
344
|
-
const candidateMeasured = this.measureElement(candidate, dims);
|
|
989
|
+
const candidateMeasured = this.measureElement(candidate, dims, sectionIndex);
|
|
345
990
|
if (candidateMeasured.heightPt > maxFragmentHeight) break;
|
|
346
991
|
container = candidate;
|
|
347
992
|
measured = candidateMeasured;
|
|
@@ -349,6 +994,7 @@ var DocxodusPagination = (() => {
|
|
|
349
994
|
}
|
|
350
995
|
blocks.push({
|
|
351
996
|
element: container,
|
|
997
|
+
sectionIndex,
|
|
352
998
|
heightPt: measured.heightPt,
|
|
353
999
|
marginTopPt: measured.marginTopPt,
|
|
354
1000
|
marginBottomPt: measured.marginBottomPt,
|
|
@@ -367,8 +1013,8 @@ var DocxodusPagination = (() => {
|
|
|
367
1013
|
* This is intentionally DOM-based: table row heights cannot be inferred from individual
|
|
368
1014
|
* rows because wrapping and collapsed borders change the height of a fragment.
|
|
369
1015
|
*/
|
|
370
|
-
measureElement(element, dims) {
|
|
371
|
-
const measurementHost = document.createElement("div");
|
|
1016
|
+
measureElement(element, dims, sectionIndex) {
|
|
1017
|
+
const measurementHost = this.document.createElement("div");
|
|
372
1018
|
measurementHost.style.position = "absolute";
|
|
373
1019
|
measurementHost.style.visibility = "hidden";
|
|
374
1020
|
measurementHost.style.left = "-9999px";
|
|
@@ -377,9 +1023,10 @@ var DocxodusPagination = (() => {
|
|
|
377
1023
|
measurementHost.appendChild(measuredElement);
|
|
378
1024
|
this.stagingElement.appendChild(measurementHost);
|
|
379
1025
|
const rect = measuredElement.getBoundingClientRect();
|
|
380
|
-
const style =
|
|
1026
|
+
const style = this.view.getComputedStyle(measuredElement);
|
|
381
1027
|
const measured = {
|
|
382
1028
|
element,
|
|
1029
|
+
sectionIndex,
|
|
383
1030
|
heightPt: pxToPt(rect.height),
|
|
384
1031
|
marginTopPt: pxToPt(parseFloat(style.marginTop) || 0),
|
|
385
1032
|
marginBottomPt: pxToPt(parseFloat(style.marginBottom) || 0),
|
|
@@ -473,6 +1120,7 @@ var DocxodusPagination = (() => {
|
|
|
473
1120
|
const knownIds = new Set(existingFootnoteIds);
|
|
474
1121
|
const newIds = [];
|
|
475
1122
|
for (const block of blocks) {
|
|
1123
|
+
this.checkpoint();
|
|
476
1124
|
for (const id of this.extractFootnoteRefs(block.element)) {
|
|
477
1125
|
if (!knownIds.has(id)) {
|
|
478
1126
|
knownIds.add(id);
|
|
@@ -490,8 +1138,8 @@ var DocxodusPagination = (() => {
|
|
|
490
1138
|
smallestEffectiveContentHeight(dims, sectionIndex) {
|
|
491
1139
|
return Math.min(
|
|
492
1140
|
this.getPageBands(dims, sectionIndex, 1, 1).bodyHeight,
|
|
493
|
-
this.getPageBands(dims, sectionIndex, 2,
|
|
494
|
-
this.getPageBands(dims, sectionIndex,
|
|
1141
|
+
this.getPageBands(dims, sectionIndex, 2, 2).bodyHeight,
|
|
1142
|
+
this.getPageBands(dims, sectionIndex, 3, 3).bodyHeight
|
|
495
1143
|
);
|
|
496
1144
|
}
|
|
497
1145
|
/**
|
|
@@ -529,10 +1177,11 @@ var DocxodusPagination = (() => {
|
|
|
529
1177
|
if (wrapper.tagName !== "DIV" || wrapper.children.length !== 1 || block.keepWithNext || block.keepLines || block.pageBreakBefore || block.isPageBreak) {
|
|
530
1178
|
return null;
|
|
531
1179
|
}
|
|
532
|
-
const
|
|
533
|
-
if (!
|
|
1180
|
+
const tableElement = wrapper.firstElementChild;
|
|
1181
|
+
if (!tableElement || tableElement.localName !== "table") {
|
|
534
1182
|
return null;
|
|
535
1183
|
}
|
|
1184
|
+
const table = tableElement;
|
|
536
1185
|
const body = table.tBodies.length === 1 ? table.tBodies[0] : null;
|
|
537
1186
|
if (!body || table.tHead || table.tFoot || body.rows.length < 2 || Array.from(table.children).some((child) => child !== body && child.tagName !== "COLGROUP") || table.querySelector("table, [rowspan], [colspan], [data-footnote-id]") || wrapper.querySelector("[data-footnote-id]")) {
|
|
538
1187
|
return null;
|
|
@@ -546,8 +1195,10 @@ var DocxodusPagination = (() => {
|
|
|
546
1195
|
const groups = [];
|
|
547
1196
|
let start = 0;
|
|
548
1197
|
while (start < rows.length) {
|
|
1198
|
+
this.checkpoint();
|
|
549
1199
|
let end = start;
|
|
550
1200
|
while (end < rows.length) {
|
|
1201
|
+
this.checkpoint();
|
|
551
1202
|
const candidate = this.createSimpleTableFragment(
|
|
552
1203
|
wrapper,
|
|
553
1204
|
table,
|
|
@@ -555,7 +1206,7 @@ var DocxodusPagination = (() => {
|
|
|
555
1206
|
rows.slice(start, end + 1),
|
|
556
1207
|
start === 0
|
|
557
1208
|
);
|
|
558
|
-
const measured = this.measureElement(candidate, dims);
|
|
1209
|
+
const measured = this.measureElement(candidate, dims, block.sectionIndex);
|
|
559
1210
|
if (measured.heightPt > maximumFragmentHeight) {
|
|
560
1211
|
break;
|
|
561
1212
|
}
|
|
@@ -587,7 +1238,7 @@ var DocxodusPagination = (() => {
|
|
|
587
1238
|
if (!isLast) {
|
|
588
1239
|
fragment.style.setProperty("margin-bottom", "0", "important");
|
|
589
1240
|
}
|
|
590
|
-
const measured = this.measureElement(fragment, dims);
|
|
1241
|
+
const measured = this.measureElement(fragment, dims, block.sectionIndex);
|
|
591
1242
|
if (measured.heightPt + measured.marginTopPt + measured.marginBottomPt > minimumContentHeight) {
|
|
592
1243
|
return null;
|
|
593
1244
|
}
|
|
@@ -602,35 +1253,198 @@ var DocxodusPagination = (() => {
|
|
|
602
1253
|
return fragments;
|
|
603
1254
|
}
|
|
604
1255
|
/**
|
|
605
|
-
*
|
|
606
|
-
*
|
|
607
|
-
*
|
|
1256
|
+
* DOM endpoints that can finish a paragraph fragment. The flattened UTF-16
|
|
1257
|
+
* offsets are checked against the browser's Unicode grapheme segmenter, so a
|
|
1258
|
+
* formatting-run boundary can never bisect a surrogate pair, combining
|
|
1259
|
+
* sequence, or joined emoji. NBSP/word-joiner boundaries remain indivisible.
|
|
1260
|
+
* PAGE/NUMPAGES field results are atomic because splitting their marker would
|
|
1261
|
+
* make later substitution duplicate or replace only half of the field.
|
|
608
1262
|
*/
|
|
609
|
-
paragraphFragmentEndpoints(paragraph) {
|
|
610
|
-
const
|
|
611
|
-
const
|
|
1263
|
+
paragraphFragmentEndpoints(paragraph, emergencyGraphemeBreaks = false) {
|
|
1264
|
+
const textNodes = [];
|
|
1265
|
+
const textStarts = /* @__PURE__ */ new Map();
|
|
1266
|
+
const lastTextByField = /* @__PURE__ */ new Map();
|
|
1267
|
+
const walker = this.document.createTreeWalker(paragraph, this.view.NodeFilter.SHOW_TEXT);
|
|
1268
|
+
const flattenedChunks = [];
|
|
1269
|
+
let flattenedLength = 0;
|
|
612
1270
|
let textNode;
|
|
613
1271
|
while (textNode = walker.nextNode()) {
|
|
614
|
-
|
|
615
|
-
|
|
616
|
-
|
|
1272
|
+
this.checkpoint();
|
|
1273
|
+
textStarts.set(textNode, flattenedLength);
|
|
1274
|
+
textNodes.push(textNode);
|
|
1275
|
+
flattenedChunks.push(textNode.data);
|
|
1276
|
+
flattenedLength += textNode.data.length;
|
|
1277
|
+
let field = textNode.parentElement?.closest("[data-field]") ?? null;
|
|
1278
|
+
while (field?.parentElement?.closest("[data-field]")) {
|
|
1279
|
+
field = field.parentElement.closest("[data-field]");
|
|
1280
|
+
}
|
|
1281
|
+
if (field && paragraph.contains(field)) lastTextByField.set(field, textNode);
|
|
1282
|
+
}
|
|
1283
|
+
const flattenedText = flattenedChunks.join("");
|
|
1284
|
+
if (!this.hasVisibleText(flattenedText)) return [];
|
|
1285
|
+
const invisible = /[\s\u200B-\u200F\uFEFF]/;
|
|
1286
|
+
let firstVisibleOffset = -1;
|
|
1287
|
+
let lastVisibleOffset = -1;
|
|
1288
|
+
for (let index = 0; index < flattenedText.length; index++) {
|
|
1289
|
+
if (index % 4096 === 0) this.checkpoint();
|
|
1290
|
+
if (invisible.test(flattenedText[index])) continue;
|
|
1291
|
+
if (firstVisibleOffset < 0) firstVisibleOffset = index;
|
|
1292
|
+
lastVisibleOffset = index;
|
|
1293
|
+
}
|
|
1294
|
+
const graphemeBoundaries = this.graphemeBoundaryOffsets(flattenedText);
|
|
1295
|
+
const candidates = [];
|
|
1296
|
+
const addCandidate = (candidate) => {
|
|
1297
|
+
const { textOffset } = candidate;
|
|
1298
|
+
if (textOffset <= 0 || textOffset >= flattenedText.length) return;
|
|
1299
|
+
if (textOffset <= firstVisibleOffset || textOffset > lastVisibleOffset) return;
|
|
1300
|
+
if (this.isNonBreakingTextBoundary(flattenedText, textOffset)) return;
|
|
1301
|
+
if (!this.isLegalParagraphLineBoundary(paragraph, flattenedText, textOffset)) return;
|
|
1302
|
+
if (graphemeBoundaries) {
|
|
1303
|
+
if (!graphemeBoundaries.has(textOffset)) return;
|
|
1304
|
+
} else if (!this.isConservativeFallbackTextBoundary(flattenedText, textOffset)) {
|
|
1305
|
+
return;
|
|
1306
|
+
}
|
|
1307
|
+
candidates.push(candidate);
|
|
1308
|
+
};
|
|
1309
|
+
for (const node of textNodes) {
|
|
1310
|
+
this.checkpoint();
|
|
1311
|
+
const start = textStarts.get(node);
|
|
1312
|
+
const atomic = node.parentElement?.closest(
|
|
1313
|
+
"[data-field], [data-list-marker], a[data-comment-id]"
|
|
1314
|
+
);
|
|
1315
|
+
if (atomic && paragraph.contains(atomic)) continue;
|
|
1316
|
+
const whitespace = /[\u0009-\u000D\u0020]+/g;
|
|
617
1317
|
let match;
|
|
618
|
-
|
|
619
|
-
|
|
1318
|
+
let matchesSinceCheckpoint = 0;
|
|
1319
|
+
while ((match = whitespace.exec(node.data)) !== null) {
|
|
1320
|
+
if (++matchesSinceCheckpoint % 256 === 0) this.checkpoint();
|
|
1321
|
+
const offset = match.index + match[0].length;
|
|
1322
|
+
addCandidate({ node, offset, textOffset: start + offset, priority: 0 });
|
|
620
1323
|
}
|
|
621
|
-
|
|
622
|
-
|
|
1324
|
+
addCandidate({
|
|
1325
|
+
node,
|
|
1326
|
+
offset: node.data.length,
|
|
1327
|
+
textOffset: start + node.data.length,
|
|
1328
|
+
priority: 0
|
|
1329
|
+
});
|
|
1330
|
+
if (emergencyGraphemeBreaks) {
|
|
1331
|
+
for (let offset = 1; offset < node.data.length; offset++) {
|
|
1332
|
+
if (offset % 256 === 0) this.checkpoint();
|
|
1333
|
+
addCandidate({
|
|
1334
|
+
node,
|
|
1335
|
+
offset,
|
|
1336
|
+
textOffset: start + offset,
|
|
1337
|
+
priority: -1
|
|
1338
|
+
});
|
|
1339
|
+
}
|
|
1340
|
+
}
|
|
1341
|
+
}
|
|
1342
|
+
for (const field of Array.from(paragraph.querySelectorAll("[data-field]"))) {
|
|
1343
|
+
this.checkpoint();
|
|
1344
|
+
if (field.parentElement?.closest("[data-field]")) continue;
|
|
1345
|
+
const last = lastTextByField.get(field);
|
|
1346
|
+
const parent = field.parentNode;
|
|
1347
|
+
if (!last || !parent) continue;
|
|
1348
|
+
const childIndex = Array.prototype.indexOf.call(parent.childNodes, field);
|
|
1349
|
+
if (childIndex < 0) continue;
|
|
1350
|
+
addCandidate({
|
|
1351
|
+
node: parent,
|
|
1352
|
+
offset: childIndex + 1,
|
|
1353
|
+
textOffset: textStarts.get(last) + last.data.length,
|
|
1354
|
+
priority: 1
|
|
1355
|
+
});
|
|
1356
|
+
}
|
|
1357
|
+
candidates.sort((left, right) => left.textOffset - right.textOffset || right.priority - left.priority);
|
|
1358
|
+
const endpoints = [];
|
|
1359
|
+
for (const candidate of candidates) {
|
|
1360
|
+
if (endpoints[endpoints.length - 1]?.textOffset !== candidate.textOffset) {
|
|
1361
|
+
endpoints.push(candidate);
|
|
623
1362
|
}
|
|
624
1363
|
}
|
|
625
1364
|
return endpoints;
|
|
626
1365
|
}
|
|
1366
|
+
/** Browser-native UAX #29 boundaries; null keeps older runtimes conservative. */
|
|
1367
|
+
graphemeBoundaryOffsets(text) {
|
|
1368
|
+
const SegmenterConstructor = this.view.Intl.Segmenter;
|
|
1369
|
+
if (!SegmenterConstructor) return null;
|
|
1370
|
+
const boundaries = /* @__PURE__ */ new Set([0, text.length]);
|
|
1371
|
+
let segmentCount = 0;
|
|
1372
|
+
for (const part of new SegmenterConstructor("en", { granularity: "grapheme" }).segment(text)) {
|
|
1373
|
+
if (++segmentCount % 256 === 0) this.checkpoint();
|
|
1374
|
+
boundaries.add(part.index);
|
|
1375
|
+
boundaries.add(part.index + part.segment.length);
|
|
1376
|
+
}
|
|
1377
|
+
return boundaries;
|
|
1378
|
+
}
|
|
1379
|
+
/**
|
|
1380
|
+
* Conservative UAX #14/CSS wrapping opportunities used for synthetic page
|
|
1381
|
+
* boundaries. In particular, a formatting-run boundary is not itself a word
|
|
1382
|
+
* boundary, and Japanese opening/closing punctuation stays with its pair.
|
|
1383
|
+
* Arbitrary grapheme breaks are admitted only when the paragraph's CSS asks
|
|
1384
|
+
* the browser to wrap anywhere.
|
|
1385
|
+
*/
|
|
1386
|
+
isLegalParagraphLineBoundary(paragraph, text, offset) {
|
|
1387
|
+
const lastCodeUnit = text.charCodeAt(offset - 1);
|
|
1388
|
+
const beforeOffset = lastCodeUnit >= 56320 && lastCodeUnit <= 57343 ? Math.max(0, offset - 2) : offset - 1;
|
|
1389
|
+
const beforeCodePoint = text.codePointAt(beforeOffset);
|
|
1390
|
+
const afterCodePoint = text.codePointAt(offset);
|
|
1391
|
+
const before = beforeCodePoint === void 0 ? "" : String.fromCodePoint(beforeCodePoint);
|
|
1392
|
+
const after = afterCodePoint === void 0 ? "" : String.fromCodePoint(afterCodePoint);
|
|
1393
|
+
if (/^[\u0009-\u000D\u0020]$/.test(before) || /^[\u0009-\u000D\u0020]$/.test(after)) return true;
|
|
1394
|
+
if (/[-\u00AD\u2010]$/u.test(before)) return true;
|
|
1395
|
+
const style = this.view.getComputedStyle(paragraph);
|
|
1396
|
+
if (style.wordBreak === "break-all" || style.overflowWrap === "anywhere" || style.overflowWrap === "break-word") return true;
|
|
1397
|
+
const eastAsian = /[\p{Script=Han}\p{Script=Hiragana}\p{Script=Katakana}\p{Script=Hangul}]/u;
|
|
1398
|
+
if (!eastAsian.test(before) && !eastAsian.test(after)) return false;
|
|
1399
|
+
const opening = "([{<\u2018\u201C\u3008\u300A\u300C\u300E\u3010\u3014\u3016\u3018\u301A\uFF08\uFF3B\uFF5B";
|
|
1400
|
+
const closing = ")]}>\u2019\u201D\u3001\u3002\u3009\u300B\u300D\u300F\u3011\u3015\u3017\u3019\u301B\uFF01\uFF05\uFF09\uFF0C\uFF0E\uFF1A\uFF1B\uFF1F\uFF3D\uFF5D";
|
|
1401
|
+
return !opening.includes(before) && !closing.includes(after);
|
|
1402
|
+
}
|
|
1403
|
+
/** Never fragment across characters whose line-break meaning is explicitly non-breaking. */
|
|
1404
|
+
isNonBreakingTextBoundary(text, offset) {
|
|
1405
|
+
const codePointBefore = (index) => {
|
|
1406
|
+
if (index <= 0) return void 0;
|
|
1407
|
+
const last = text.charCodeAt(index - 1);
|
|
1408
|
+
const start = last >= 56320 && last <= 57343 ? index - 2 : index - 1;
|
|
1409
|
+
return text.codePointAt(Math.max(0, start));
|
|
1410
|
+
};
|
|
1411
|
+
const nonBreaking = /* @__PURE__ */ new Set([
|
|
1412
|
+
160,
|
|
1413
|
+
8206,
|
|
1414
|
+
8207,
|
|
1415
|
+
8209,
|
|
1416
|
+
8234,
|
|
1417
|
+
8235,
|
|
1418
|
+
8236,
|
|
1419
|
+
8237,
|
|
1420
|
+
8238,
|
|
1421
|
+
8239,
|
|
1422
|
+
8288,
|
|
1423
|
+
8294,
|
|
1424
|
+
8295,
|
|
1425
|
+
8296,
|
|
1426
|
+
8297,
|
|
1427
|
+
65279
|
|
1428
|
+
]);
|
|
1429
|
+
return nonBreaking.has(codePointBefore(offset) ?? -1) || nonBreaking.has(text.codePointAt(offset) ?? -1);
|
|
1430
|
+
}
|
|
1431
|
+
/**
|
|
1432
|
+
* Without Intl.Segmenter, admit only an all-ASCII boundary. This still
|
|
1433
|
+
* fragments ordinary prose while refusing to guess about Unicode clusters.
|
|
1434
|
+
*/
|
|
1435
|
+
isConservativeFallbackTextBoundary(text, offset) {
|
|
1436
|
+
return text.charCodeAt(offset - 1) <= 127 && text.charCodeAt(offset) <= 127;
|
|
1437
|
+
}
|
|
627
1438
|
/**
|
|
628
1439
|
* Whether a range contains visible text after ignoring bidi/zero-width marks.
|
|
629
1440
|
* Paragraph fragmentation deliberately excludes non-textual descendants, so
|
|
630
1441
|
* this is enough to reject empty head or tail fragments.
|
|
631
1442
|
*/
|
|
632
1443
|
hasVisibleFragmentText(fragment) {
|
|
633
|
-
return (fragment.textContent || "")
|
|
1444
|
+
return this.hasVisibleText(fragment.textContent || "");
|
|
1445
|
+
}
|
|
1446
|
+
hasVisibleText(text) {
|
|
1447
|
+
return text.replace(/[\u200B-\u200F\uFEFF]/g, "").trim().length > 0;
|
|
634
1448
|
}
|
|
635
1449
|
/**
|
|
636
1450
|
* Phase one only handles text-like paragraph descendants. Objects, explicit
|
|
@@ -639,13 +1453,25 @@ var DocxodusPagination = (() => {
|
|
|
639
1453
|
* fallback instead of risking broken content or duplicate anchors.
|
|
640
1454
|
*/
|
|
641
1455
|
canFragmentParagraph(block) {
|
|
642
|
-
if (!this.fragmentParagraphs
|
|
1456
|
+
if (!this.fragmentParagraphs) {
|
|
643
1457
|
return false;
|
|
644
1458
|
}
|
|
645
1459
|
const paragraph = block.element;
|
|
646
1460
|
if (block.keepWithNext || block.keepLines || block.pageBreakBefore || block.isPageBreak || paragraph.dataset.widowControl === "true" || paragraph.hasAttribute("contenteditable")) {
|
|
647
1461
|
return false;
|
|
648
1462
|
}
|
|
1463
|
+
return this.canRangeFragmentParagraph(paragraph);
|
|
1464
|
+
}
|
|
1465
|
+
/**
|
|
1466
|
+
* Shared structural gate for body and note paragraph fragmentation. Footnote
|
|
1467
|
+
* first paragraphs are inline beside their marker, so that one known layout
|
|
1468
|
+
* context may opt into an inline root while retaining every descendant and
|
|
1469
|
+
* break-safety restriction used for body text.
|
|
1470
|
+
*/
|
|
1471
|
+
canRangeFragmentParagraph(paragraph, allowInlineRoot = false) {
|
|
1472
|
+
if (paragraph.tagName !== "P" || paragraph.dataset.keepWithNext === "true" || paragraph.dataset.keepLines === "true" || paragraph.dataset.pageBreakBefore === "true" || paragraph.dataset.widowControl === "true" || paragraph.hasAttribute("contenteditable")) {
|
|
1473
|
+
return false;
|
|
1474
|
+
}
|
|
649
1475
|
const unsupportedDescendants = [
|
|
650
1476
|
"br",
|
|
651
1477
|
"img",
|
|
@@ -676,21 +1502,27 @@ var DocxodusPagination = (() => {
|
|
|
676
1502
|
"fieldset",
|
|
677
1503
|
"details",
|
|
678
1504
|
"[data-footnote-id]",
|
|
679
|
-
"[data-list-marker]",
|
|
680
|
-
"[data-anchor]",
|
|
681
|
-
"[id]",
|
|
682
1505
|
"[contenteditable]"
|
|
683
1506
|
].join(", ");
|
|
684
1507
|
if (paragraph.querySelector(unsupportedDescendants)) {
|
|
685
1508
|
return false;
|
|
686
1509
|
}
|
|
687
|
-
const
|
|
688
|
-
|
|
1510
|
+
const isValidatedEndnote = paragraph.dataset.paginationSafeEndnote === "true";
|
|
1511
|
+
return isValidatedEndnote || this.hasRangeFragmentSafeLayout(paragraph, allowInlineRoot);
|
|
1512
|
+
}
|
|
1513
|
+
/**
|
|
1514
|
+
* A range clone preserves nested inline formatting exactly. Anything that establishes its own
|
|
1515
|
+
* box/layout context is deferred until a future fragmenter can model it accurately. Callers
|
|
1516
|
+
* must invoke this while the paragraph is attached to the styled document.
|
|
1517
|
+
*/
|
|
1518
|
+
hasRangeFragmentSafeLayout(paragraph, allowInlineRoot = false) {
|
|
1519
|
+
const paragraphStyle = this.view.getComputedStyle(paragraph);
|
|
1520
|
+
if (paragraphStyle.display !== "block" && !(allowInlineRoot && paragraphStyle.display === "inline") || paragraphStyle.position !== "static" || paragraphStyle.float !== "none" || paragraphStyle.whiteSpace !== "normal" && paragraphStyle.whiteSpace !== "pre-wrap" || paragraphStyle.breakBefore !== "auto" || paragraphStyle.breakAfter !== "auto" || paragraphStyle.breakInside === "avoid" || paragraphStyle.pageBreakBefore !== "auto" || paragraphStyle.pageBreakAfter !== "auto" || paragraphStyle.pageBreakInside === "avoid") {
|
|
689
1521
|
return false;
|
|
690
1522
|
}
|
|
691
1523
|
for (const descendant of Array.from(paragraph.querySelectorAll("*"))) {
|
|
692
|
-
const style =
|
|
693
|
-
if (style.display !== "inline" || style.position !== "static" || style.float !== "none" || style.whiteSpace !== "normal") {
|
|
1524
|
+
const style = this.view.getComputedStyle(descendant);
|
|
1525
|
+
if (style.display !== "inline" || style.position !== "static" || style.float !== "none" || style.whiteSpace !== "normal" && style.whiteSpace !== "pre-wrap") {
|
|
694
1526
|
return false;
|
|
695
1527
|
}
|
|
696
1528
|
}
|
|
@@ -717,58 +1549,122 @@ var DocxodusPagination = (() => {
|
|
|
717
1549
|
return fragment;
|
|
718
1550
|
}
|
|
719
1551
|
/**
|
|
720
|
-
*
|
|
721
|
-
*
|
|
722
|
-
*
|
|
1552
|
+
* Range clones repeat an inline ancestor when the split lands inside it. Keep
|
|
1553
|
+
* semantic wrappers (links, comments, formatting) on both sides, but retain a
|
|
1554
|
+
* duplicated HTML/editor identity only on the leading fragment. Targets that
|
|
1555
|
+
* occur wholly after the split are absent from `head` and remain on `tail`.
|
|
723
1556
|
*/
|
|
724
|
-
|
|
725
|
-
|
|
726
|
-
|
|
1557
|
+
reconcileParagraphFragmentIdentities(head, tail) {
|
|
1558
|
+
const elements = (root, selector) => [
|
|
1559
|
+
...root.matches(selector) ? [root] : [],
|
|
1560
|
+
...Array.from(root.querySelectorAll(selector))
|
|
1561
|
+
];
|
|
1562
|
+
const headIds = new Set(elements(head, "[id]").map((element) => element.id));
|
|
1563
|
+
for (const element of elements(tail, "[id]")) {
|
|
1564
|
+
if (headIds.has(element.id)) element.removeAttribute("id");
|
|
727
1565
|
}
|
|
728
|
-
const
|
|
729
|
-
const
|
|
730
|
-
|
|
731
|
-
|
|
1566
|
+
const headAnchors = new Set(elements(head, "[data-anchor]").map((element) => element.dataset.anchor).filter((anchor) => Boolean(anchor)));
|
|
1567
|
+
for (const element of elements(tail, "[data-anchor]")) {
|
|
1568
|
+
if (!element.dataset.anchor || !headAnchors.has(element.dataset.anchor)) continue;
|
|
1569
|
+
element.removeAttribute("data-anchor");
|
|
1570
|
+
element.removeAttribute("data-committed-text");
|
|
732
1571
|
}
|
|
733
|
-
|
|
734
|
-
|
|
735
|
-
|
|
736
|
-
|
|
737
|
-
|
|
738
|
-
|
|
739
|
-
|
|
740
|
-
|
|
741
|
-
|
|
742
|
-
|
|
743
|
-
|
|
744
|
-
|
|
745
|
-
|
|
1572
|
+
}
|
|
1573
|
+
/**
|
|
1574
|
+
* Range-clone the largest safe prefix accepted by `fits`. The measurement
|
|
1575
|
+
* policy stays with the caller, allowing body blocks and note bands to share
|
|
1576
|
+
* one DOM fragmenter while measuring in their respective layout contexts.
|
|
1577
|
+
*/
|
|
1578
|
+
splitParagraphAtLargestFit(paragraph, fits, options = {}) {
|
|
1579
|
+
const largestFit = (endpoints2) => {
|
|
1580
|
+
const lastEndpoint = endpoints2[endpoints2.length - 1];
|
|
1581
|
+
if (lastEndpoint) {
|
|
1582
|
+
const lastRange = this.document.createRange();
|
|
1583
|
+
lastRange.setStart(paragraph, 0);
|
|
1584
|
+
lastRange.setEnd(lastEndpoint.node, lastEndpoint.offset);
|
|
1585
|
+
const lastContents = lastRange.cloneContents();
|
|
1586
|
+
if (this.hasVisibleFragmentText(lastContents)) {
|
|
1587
|
+
const lastHead = this.createParagraphFragment(paragraph, lastRange, true, false);
|
|
1588
|
+
if (fits(lastHead)) return { endpoint: lastEndpoint, head: lastHead };
|
|
1589
|
+
}
|
|
746
1590
|
}
|
|
747
|
-
|
|
748
|
-
|
|
749
|
-
|
|
750
|
-
|
|
751
|
-
|
|
752
|
-
|
|
753
|
-
|
|
1591
|
+
let low = 0;
|
|
1592
|
+
let high = endpoints2.length - 2;
|
|
1593
|
+
let best2 = null;
|
|
1594
|
+
while (low <= high) {
|
|
1595
|
+
this.checkpoint();
|
|
1596
|
+
const middle = Math.floor((low + high) / 2);
|
|
1597
|
+
const endpoint = endpoints2[middle];
|
|
1598
|
+
const headRange = this.document.createRange();
|
|
1599
|
+
headRange.setStart(paragraph, 0);
|
|
1600
|
+
headRange.setEnd(endpoint.node, endpoint.offset);
|
|
1601
|
+
const headContents = headRange.cloneContents();
|
|
1602
|
+
if (!this.hasVisibleFragmentText(headContents)) {
|
|
1603
|
+
low = middle + 1;
|
|
1604
|
+
continue;
|
|
1605
|
+
}
|
|
1606
|
+
const head = this.createParagraphFragment(paragraph, headRange, true, false);
|
|
1607
|
+
if (fits(head)) {
|
|
1608
|
+
best2 = { endpoint, head };
|
|
1609
|
+
low = middle + 1;
|
|
1610
|
+
} else {
|
|
1611
|
+
high = middle - 1;
|
|
1612
|
+
}
|
|
754
1613
|
}
|
|
1614
|
+
return best2;
|
|
1615
|
+
};
|
|
1616
|
+
let endpoints = this.paragraphFragmentEndpoints(paragraph);
|
|
1617
|
+
let best = largestFit(endpoints);
|
|
1618
|
+
if (!best && options.emergencyGraphemeBreaks) {
|
|
1619
|
+
endpoints = this.paragraphFragmentEndpoints(paragraph, true);
|
|
1620
|
+
best = largestFit(endpoints);
|
|
1621
|
+
}
|
|
1622
|
+
if (!best && options.forceFirstOnNoFit && endpoints.length > 0) {
|
|
1623
|
+
const endpoint = endpoints[0];
|
|
1624
|
+
const headRange = this.document.createRange();
|
|
1625
|
+
headRange.setStart(paragraph, 0);
|
|
1626
|
+
headRange.setEnd(endpoint.node, endpoint.offset);
|
|
1627
|
+
best = {
|
|
1628
|
+
endpoint,
|
|
1629
|
+
head: this.createParagraphFragment(paragraph, headRange, true, false)
|
|
1630
|
+
};
|
|
755
1631
|
}
|
|
756
1632
|
if (!best) {
|
|
757
|
-
return
|
|
1633
|
+
return endpoints.length > 0 ? { kind: "no-fit" } : { kind: "indivisible" };
|
|
758
1634
|
}
|
|
759
|
-
const tailRange = document.createRange();
|
|
1635
|
+
const tailRange = this.document.createRange();
|
|
760
1636
|
tailRange.setStart(best.endpoint.node, best.endpoint.offset);
|
|
761
1637
|
tailRange.setEnd(paragraph, paragraph.childNodes.length);
|
|
762
1638
|
const tailContents = tailRange.cloneContents();
|
|
763
|
-
if (!this.hasVisibleFragmentText(tailContents)) {
|
|
1639
|
+
if (!this.hasVisibleFragmentText(tailContents)) return { kind: "indivisible" };
|
|
1640
|
+
const tail = this.createParagraphFragment(paragraph, tailRange, false, true);
|
|
1641
|
+
this.reconcileParagraphFragmentIdentities(best.head, tail);
|
|
1642
|
+
return {
|
|
1643
|
+
kind: "split",
|
|
1644
|
+
head: best.head,
|
|
1645
|
+
tail
|
|
1646
|
+
};
|
|
1647
|
+
}
|
|
1648
|
+
/**
|
|
1649
|
+
* Splits a simple paragraph at the largest DOM Range endpoint that fits the
|
|
1650
|
+
* currently available body space. The caller then processes the tail normally,
|
|
1651
|
+
* allowing it to fragment again on later pages when necessary.
|
|
1652
|
+
*/
|
|
1653
|
+
tryFragmentParagraph(block, dims, availableHeightPt, effectiveMarginTopPt) {
|
|
1654
|
+
if (!this.canFragmentParagraph(block) || availableHeightPt <= effectiveMarginTopPt) {
|
|
764
1655
|
return null;
|
|
765
1656
|
}
|
|
766
|
-
const
|
|
767
|
-
|
|
1657
|
+
const split = this.splitParagraphAtLargestFit(block.element, (head) => {
|
|
1658
|
+
const measured = this.measureElement(head, dims, block.sectionIndex);
|
|
1659
|
+
return effectiveMarginTopPt + measured.heightPt <= availableHeightPt;
|
|
1660
|
+
});
|
|
1661
|
+
if (split.kind !== "split") return null;
|
|
1662
|
+
const headMeasured = this.measureElement(split.head, dims, block.sectionIndex);
|
|
1663
|
+
const tailMeasured = this.measureElement(split.tail, dims, block.sectionIndex);
|
|
768
1664
|
return [
|
|
769
1665
|
{
|
|
770
|
-
...
|
|
771
|
-
element:
|
|
1666
|
+
...headMeasured,
|
|
1667
|
+
element: split.head,
|
|
772
1668
|
keepWithNext: false,
|
|
773
1669
|
keepLines: false,
|
|
774
1670
|
pageBreakBefore: false,
|
|
@@ -776,7 +1672,7 @@ var DocxodusPagination = (() => {
|
|
|
776
1672
|
},
|
|
777
1673
|
{
|
|
778
1674
|
...tailMeasured,
|
|
779
|
-
element: tail,
|
|
1675
|
+
element: split.tail,
|
|
780
1676
|
keepWithNext: false,
|
|
781
1677
|
keepLines: false,
|
|
782
1678
|
pageBreakBefore: false,
|
|
@@ -847,7 +1743,7 @@ var DocxodusPagination = (() => {
|
|
|
847
1743
|
const registry = /* @__PURE__ */ new Map();
|
|
848
1744
|
const registryEl = this.stagingElement.querySelector("#pagination-footnote-registry");
|
|
849
1745
|
if (!registryEl) return registry;
|
|
850
|
-
const entries = Array.from(registryEl.
|
|
1746
|
+
const entries = Array.from(registryEl.children).filter((entry) => entry instanceof this.view.HTMLElement && entry.hasAttribute("data-footnote-id"));
|
|
851
1747
|
for (const entry of entries) {
|
|
852
1748
|
const footnoteId = entry.dataset.footnoteId;
|
|
853
1749
|
if (footnoteId) {
|
|
@@ -856,6 +1752,68 @@ var DocxodusPagination = (() => {
|
|
|
856
1752
|
}
|
|
857
1753
|
return registry;
|
|
858
1754
|
}
|
|
1755
|
+
/** Read Word's optional normal and continuation separator stories. */
|
|
1756
|
+
parseFootnoteSeparators() {
|
|
1757
|
+
const registry = this.stagingElement.querySelector("#pagination-footnote-registry");
|
|
1758
|
+
const clone = (kind) => {
|
|
1759
|
+
const source = Array.from(registry?.children ?? []).find(
|
|
1760
|
+
(entry) => entry instanceof this.view.HTMLElement && entry.getAttribute("data-footnote-separator") === kind
|
|
1761
|
+
);
|
|
1762
|
+
return source?.cloneNode(true);
|
|
1763
|
+
};
|
|
1764
|
+
return {
|
|
1765
|
+
normal: clone("normal") ?? null,
|
|
1766
|
+
continuation: clone("continuation") ?? null
|
|
1767
|
+
};
|
|
1768
|
+
}
|
|
1769
|
+
/** Append the exact separator story selected for this initial/continued note band. */
|
|
1770
|
+
appendFootnoteSeparator(container, continuation) {
|
|
1771
|
+
const kind = continuation ? "continuation" : "normal";
|
|
1772
|
+
const source = continuation ? this.footnoteContinuationSeparator : this.footnoteSeparator;
|
|
1773
|
+
if (!source) {
|
|
1774
|
+
const fallback = this.document.createElement("hr");
|
|
1775
|
+
fallback.dataset.footnoteSeparator = kind;
|
|
1776
|
+
container.appendChild(fallback);
|
|
1777
|
+
return;
|
|
1778
|
+
}
|
|
1779
|
+
const clone = source.cloneNode(true);
|
|
1780
|
+
for (const element of [clone, ...Array.from(clone.querySelectorAll("*"))]) {
|
|
1781
|
+
element.removeAttribute("id");
|
|
1782
|
+
element.removeAttribute("data-anchor");
|
|
1783
|
+
element.removeAttribute("data-committed-text");
|
|
1784
|
+
element.removeAttribute("data-source-anchor-id");
|
|
1785
|
+
element.removeAttribute("data-page-fragment-id");
|
|
1786
|
+
element.removeAttribute("data-fragment-index");
|
|
1787
|
+
element.removeAttribute("data-footnote-id");
|
|
1788
|
+
element.removeAttribute("data-comment-id");
|
|
1789
|
+
element.removeAttribute("name");
|
|
1790
|
+
if (element instanceof this.view.HTMLAnchorElement && element.getAttribute("href")?.startsWith("#")) {
|
|
1791
|
+
element.removeAttribute("href");
|
|
1792
|
+
}
|
|
1793
|
+
element.setAttribute("contenteditable", "false");
|
|
1794
|
+
}
|
|
1795
|
+
container.appendChild(clone);
|
|
1796
|
+
}
|
|
1797
|
+
/** Parses the hidden source notes used to render paginated margin comments. */
|
|
1798
|
+
parseCommentMarginRegistry() {
|
|
1799
|
+
const registry = /* @__PURE__ */ new Map();
|
|
1800
|
+
const registryEl = this.stagingElement.querySelector(
|
|
1801
|
+
"#pagination-comment-margin-registry"
|
|
1802
|
+
);
|
|
1803
|
+
if (!registryEl) return registry;
|
|
1804
|
+
for (const entry of Array.from(
|
|
1805
|
+
registryEl.querySelectorAll("[data-comment-id]")
|
|
1806
|
+
)) {
|
|
1807
|
+
const commentId = entry.dataset.commentId;
|
|
1808
|
+
if (!commentId) continue;
|
|
1809
|
+
let root = entry;
|
|
1810
|
+
for (let parent = root.parentElement?.closest("[data-comment-id]"); parent && registryEl.contains(parent); parent = root.parentElement?.closest("[data-comment-id]")) {
|
|
1811
|
+
root = parent;
|
|
1812
|
+
}
|
|
1813
|
+
registry.set(commentId, root.cloneNode(true));
|
|
1814
|
+
}
|
|
1815
|
+
return registry;
|
|
1816
|
+
}
|
|
859
1817
|
/**
|
|
860
1818
|
* Extracts footnote reference IDs from an element.
|
|
861
1819
|
*/
|
|
@@ -870,6 +1828,92 @@ var DocxodusPagination = (() => {
|
|
|
870
1828
|
}
|
|
871
1829
|
return ids;
|
|
872
1830
|
}
|
|
1831
|
+
/** Clone a note child for the continuation queue with its original selector position. */
|
|
1832
|
+
cloneFootnoteElementForContinuation(element, sourceIndex) {
|
|
1833
|
+
const clone = element.cloneNode(true);
|
|
1834
|
+
clone.setAttribute(
|
|
1835
|
+
FOOTNOTE_SOURCE_POSITION_ATTR,
|
|
1836
|
+
sourceIndex === 0 ? "first" : "later"
|
|
1837
|
+
);
|
|
1838
|
+
return clone;
|
|
1839
|
+
}
|
|
1840
|
+
/** Remove identities that belong only to the initial registry presentation shell. */
|
|
1841
|
+
makeContinuationShellInert(element) {
|
|
1842
|
+
element.removeAttribute("id");
|
|
1843
|
+
element.removeAttribute("data-anchor");
|
|
1844
|
+
element.removeAttribute("data-committed-text");
|
|
1845
|
+
}
|
|
1846
|
+
/**
|
|
1847
|
+
* Build the exact continuation shape shared by measurement and paint.
|
|
1848
|
+
*
|
|
1849
|
+
* Keep the source `.footnote-item > .footnote-content` ancestry: document
|
|
1850
|
+
* CSS, inherited direction/language, and custom classes frequently target
|
|
1851
|
+
* those shells. Only the number and initial HTML/editor identities are
|
|
1852
|
+
* omitted. A hidden sentinel preserves `p:not(:first-of-type)` for a page
|
|
1853
|
+
* whose first carried element was a later source paragraph.
|
|
1854
|
+
*/
|
|
1855
|
+
createFootnoteContinuationWrapper(continuation, elements = continuation.remainingElements) {
|
|
1856
|
+
const source = this.footnoteRegistry.get(continuation.footnoteId);
|
|
1857
|
+
const wrapper = source ? source.cloneNode(false) : this.document.createElement("div");
|
|
1858
|
+
this.makeContinuationShellInert(wrapper);
|
|
1859
|
+
wrapper.classList.add("footnote-continuation");
|
|
1860
|
+
wrapper.dataset.footnoteId = continuation.footnoteId;
|
|
1861
|
+
if (continuation.sourceAnchorId) {
|
|
1862
|
+
wrapper.dataset.sourceAnchorId = continuation.sourceAnchorId;
|
|
1863
|
+
}
|
|
1864
|
+
const sourceContent = source?.querySelector(".footnote-content");
|
|
1865
|
+
const content = sourceContent ? sourceContent.cloneNode(false) : this.document.createElement("span");
|
|
1866
|
+
this.makeContinuationShellInert(content);
|
|
1867
|
+
if (!sourceContent) content.className = "footnote-content";
|
|
1868
|
+
const first = elements[0];
|
|
1869
|
+
if (first?.tagName === "P" && first.getAttribute(FOOTNOTE_SOURCE_POSITION_ATTR) === "later") {
|
|
1870
|
+
const sentinel = this.document.createElement("p");
|
|
1871
|
+
sentinel.className = "footnote-continuation-position-sentinel";
|
|
1872
|
+
sentinel.setAttribute("aria-hidden", "true");
|
|
1873
|
+
sentinel.style.setProperty("display", "none", "important");
|
|
1874
|
+
content.appendChild(sentinel);
|
|
1875
|
+
}
|
|
1876
|
+
for (let index = 0; index < elements.length; index++) {
|
|
1877
|
+
this.checkpoint();
|
|
1878
|
+
const element = elements[index];
|
|
1879
|
+
const clone = element.cloneNode(true);
|
|
1880
|
+
clone.removeAttribute(FOOTNOTE_SOURCE_POSITION_ATTR);
|
|
1881
|
+
if (index === 0 && clone.tagName === "P") {
|
|
1882
|
+
clone.style.setProperty("display", "block", "important");
|
|
1883
|
+
}
|
|
1884
|
+
content.appendChild(clone);
|
|
1885
|
+
}
|
|
1886
|
+
wrapper.appendChild(content);
|
|
1887
|
+
return wrapper;
|
|
1888
|
+
}
|
|
1889
|
+
/** Build the exact partial-note item shape shared by measurement and paint. */
|
|
1890
|
+
createPartialFootnoteItem(footnote, fittingElements) {
|
|
1891
|
+
const item = footnote.cloneNode(false);
|
|
1892
|
+
const number = footnote.querySelector(".footnote-number");
|
|
1893
|
+
if (number) item.appendChild(number.cloneNode(true));
|
|
1894
|
+
const sourceContent = footnote.querySelector(".footnote-content");
|
|
1895
|
+
const content = sourceContent ? sourceContent.cloneNode(false) : this.document.createElement("span");
|
|
1896
|
+
if (!sourceContent) content.className = "footnote-content";
|
|
1897
|
+
for (const element of fittingElements) {
|
|
1898
|
+
this.checkpoint();
|
|
1899
|
+
content.appendChild(element.cloneNode(true));
|
|
1900
|
+
}
|
|
1901
|
+
item.appendChild(content);
|
|
1902
|
+
return item;
|
|
1903
|
+
}
|
|
1904
|
+
/**
|
|
1905
|
+
* Where a note measurement tree is attached.
|
|
1906
|
+
*
|
|
1907
|
+
* A note band is reserved from a measurement and then painted; if the two
|
|
1908
|
+
* happen under different inherited typography the painted notes overflow the
|
|
1909
|
+
* reserve, which is the invisible clipping this engine exists to avoid. The
|
|
1910
|
+
* registry lives in the staging tree but a band paints inside the output
|
|
1911
|
+
* container, so measure there. The host is never a page box, so a page's
|
|
1912
|
+
* `zoom` cannot scale a reserve that is accounted for in unscaled points.
|
|
1913
|
+
*/
|
|
1914
|
+
noteMeasurementHost() {
|
|
1915
|
+
return this.containerElement.isConnected ? this.containerElement : this.stagingElement;
|
|
1916
|
+
}
|
|
873
1917
|
/**
|
|
874
1918
|
* Measures the height of footnotes for given IDs (in points).
|
|
875
1919
|
* Creates a temporary container to measure the footnotes.
|
|
@@ -877,140 +1921,246 @@ var DocxodusPagination = (() => {
|
|
|
877
1921
|
* @param contentWidth - Width for measurement
|
|
878
1922
|
* @param continuation - Optional continuation content to include first
|
|
879
1923
|
*/
|
|
880
|
-
measureFootnotesHeight(footnoteIds, contentWidth, continuation) {
|
|
1924
|
+
measureFootnotesHeight(footnoteIds, contentWidth, continuation, partialFootnotes) {
|
|
881
1925
|
const hasContinuation = continuation && continuation.remainingElements.length > 0;
|
|
882
|
-
if (footnoteIds.length === 0 && !hasContinuation
|
|
1926
|
+
if (footnoteIds.length === 0 && !hasContinuation) {
|
|
1927
|
+
return 0;
|
|
1928
|
+
}
|
|
1929
|
+
if (this.footnoteRegistry.size === 0 && !hasContinuation) {
|
|
883
1930
|
return 0;
|
|
884
1931
|
}
|
|
885
|
-
const measureContainer = document.createElement("div");
|
|
1932
|
+
const measureContainer = this.document.createElement("div");
|
|
886
1933
|
measureContainer.style.position = "absolute";
|
|
887
1934
|
measureContainer.style.visibility = "hidden";
|
|
888
1935
|
measureContainer.style.width = `${contentWidth}pt`;
|
|
889
1936
|
measureContainer.style.left = "-9999px";
|
|
890
1937
|
measureContainer.className = this.cssPrefix + "footnotes";
|
|
891
|
-
|
|
892
|
-
measureContainer.appendChild(hr);
|
|
1938
|
+
this.appendFootnoteSeparator(measureContainer, Boolean(hasContinuation));
|
|
893
1939
|
if (hasContinuation) {
|
|
894
|
-
|
|
895
|
-
contWrapper.className = "footnote-continuation";
|
|
896
|
-
for (const el of continuation.remainingElements) {
|
|
897
|
-
contWrapper.appendChild(el.cloneNode(true));
|
|
898
|
-
}
|
|
899
|
-
measureContainer.appendChild(contWrapper);
|
|
1940
|
+
measureContainer.appendChild(this.createFootnoteContinuationWrapper(continuation));
|
|
900
1941
|
}
|
|
1942
|
+
const partialById = new Map(
|
|
1943
|
+
partialFootnotes?.map((partial) => [partial.footnoteId, partial]) ?? []
|
|
1944
|
+
);
|
|
901
1945
|
for (const id of footnoteIds) {
|
|
1946
|
+
this.checkpoint();
|
|
902
1947
|
const footnote = this.footnoteRegistry.get(id);
|
|
903
1948
|
if (footnote) {
|
|
904
|
-
|
|
1949
|
+
const partial = partialById.get(id);
|
|
1950
|
+
measureContainer.appendChild(partial ? this.createPartialFootnoteItem(footnote, partial.fittingElements) : footnote.cloneNode(true));
|
|
905
1951
|
}
|
|
906
1952
|
}
|
|
907
|
-
this.
|
|
908
|
-
|
|
909
|
-
|
|
910
|
-
|
|
911
|
-
|
|
1953
|
+
this.noteMeasurementHost().appendChild(measureContainer);
|
|
1954
|
+
try {
|
|
1955
|
+
return pxToPt(measureContainer.getBoundingClientRect().height);
|
|
1956
|
+
} finally {
|
|
1957
|
+
measureContainer.remove();
|
|
1958
|
+
}
|
|
912
1959
|
}
|
|
913
1960
|
/**
|
|
914
|
-
*
|
|
1961
|
+
* Partition a continuation for one page's note band, preferring complete children
|
|
1962
|
+
* and range-fragmenting an eligible paragraph when necessary. Always advances by
|
|
1963
|
+
* at least one element so an indivisible oversized paragraph follows the established
|
|
1964
|
+
* clipped fallback without trapping pagination in a loop.
|
|
915
1965
|
*/
|
|
916
|
-
|
|
917
|
-
|
|
918
|
-
|
|
919
|
-
|
|
920
|
-
const measureContainer = document.createElement("div");
|
|
1966
|
+
splitContinuationForPage(continuation, availableHeightPt, contentWidth) {
|
|
1967
|
+
const fitting = [];
|
|
1968
|
+
let remaining = [];
|
|
1969
|
+
const measureContainer = this.document.createElement("div");
|
|
921
1970
|
measureContainer.style.position = "absolute";
|
|
922
1971
|
measureContainer.style.visibility = "hidden";
|
|
923
1972
|
measureContainer.style.width = `${contentWidth}pt`;
|
|
924
1973
|
measureContainer.style.left = "-9999px";
|
|
925
1974
|
measureContainer.className = this.cssPrefix + "footnotes";
|
|
926
|
-
|
|
927
|
-
|
|
928
|
-
|
|
929
|
-
|
|
930
|
-
|
|
931
|
-
|
|
932
|
-
|
|
933
|
-
|
|
934
|
-
|
|
935
|
-
|
|
936
|
-
|
|
937
|
-
|
|
938
|
-
|
|
939
|
-
|
|
940
|
-
|
|
941
|
-
|
|
942
|
-
|
|
943
|
-
|
|
944
|
-
|
|
945
|
-
|
|
946
|
-
|
|
947
|
-
|
|
948
|
-
|
|
949
|
-
|
|
950
|
-
|
|
951
|
-
|
|
952
|
-
|
|
953
|
-
|
|
954
|
-
|
|
955
|
-
|
|
956
|
-
|
|
957
|
-
|
|
958
|
-
|
|
959
|
-
|
|
960
|
-
|
|
961
|
-
|
|
962
|
-
|
|
963
|
-
|
|
964
|
-
|
|
965
|
-
|
|
966
|
-
|
|
967
|
-
|
|
968
|
-
|
|
969
|
-
|
|
970
|
-
|
|
971
|
-
|
|
972
|
-
|
|
973
|
-
|
|
974
|
-
|
|
975
|
-
|
|
976
|
-
|
|
977
|
-
|
|
978
|
-
|
|
979
|
-
|
|
980
|
-
|
|
981
|
-
|
|
982
|
-
|
|
983
|
-
|
|
984
|
-
|
|
985
|
-
|
|
986
|
-
|
|
987
|
-
|
|
988
|
-
|
|
989
|
-
|
|
1975
|
+
this.appendFootnoteSeparator(measureContainer, true);
|
|
1976
|
+
const wrapper = this.createFootnoteContinuationWrapper(continuation, []);
|
|
1977
|
+
const content = wrapper.querySelector(":scope > .footnote-content");
|
|
1978
|
+
if (!content) {
|
|
1979
|
+
throw new Error("Footnote continuation is missing its content shell");
|
|
1980
|
+
}
|
|
1981
|
+
measureContainer.appendChild(wrapper);
|
|
1982
|
+
this.noteMeasurementHost().appendChild(measureContainer);
|
|
1983
|
+
try {
|
|
1984
|
+
for (let index = 0; index < continuation.remainingElements.length; index++) {
|
|
1985
|
+
this.checkpoint();
|
|
1986
|
+
const element = continuation.remainingElements[index];
|
|
1987
|
+
const sourcePosition = element.getAttribute(FOOTNOTE_SOURCE_POSITION_ATTR);
|
|
1988
|
+
if (fitting.length === 0 && element.tagName === "P" && element.getAttribute(FOOTNOTE_SOURCE_POSITION_ATTR) === "later") {
|
|
1989
|
+
const sentinel = this.document.createElement("p");
|
|
1990
|
+
sentinel.className = "footnote-continuation-position-sentinel";
|
|
1991
|
+
sentinel.setAttribute("aria-hidden", "true");
|
|
1992
|
+
sentinel.style.setProperty("display", "none", "important");
|
|
1993
|
+
content.appendChild(sentinel);
|
|
1994
|
+
}
|
|
1995
|
+
const candidate = element.cloneNode(true);
|
|
1996
|
+
candidate.removeAttribute(FOOTNOTE_SOURCE_POSITION_ATTR);
|
|
1997
|
+
if (fitting.length === 0 && candidate.tagName === "P") {
|
|
1998
|
+
candidate.style.setProperty("display", "block", "important");
|
|
1999
|
+
}
|
|
2000
|
+
content.appendChild(candidate);
|
|
2001
|
+
const candidateHeight = pxToPt(measureContainer.getBoundingClientRect().height);
|
|
2002
|
+
if (candidateHeight <= availableHeightPt) {
|
|
2003
|
+
fitting.push(element);
|
|
2004
|
+
continue;
|
|
2005
|
+
}
|
|
2006
|
+
const canSplitCandidate = candidate.tagName === "P" && this.canRangeFragmentParagraph(candidate);
|
|
2007
|
+
candidate.remove();
|
|
2008
|
+
let split = null;
|
|
2009
|
+
if (canSplitCandidate) {
|
|
2010
|
+
split = this.splitParagraphAtLargestFit(candidate, (head) => {
|
|
2011
|
+
content.appendChild(head);
|
|
2012
|
+
try {
|
|
2013
|
+
return pxToPt(measureContainer.getBoundingClientRect().height) <= availableHeightPt;
|
|
2014
|
+
} finally {
|
|
2015
|
+
head.remove();
|
|
2016
|
+
}
|
|
2017
|
+
}, {
|
|
2018
|
+
// Both fallbacks below cut where ordinary line breaking would not,
|
|
2019
|
+
// so they are only ever right for an element that owns the whole
|
|
2020
|
+
// band: if this page already carries earlier content, a paragraph
|
|
2021
|
+
// that will not start here belongs intact in the next note band.
|
|
2022
|
+
emergencyGraphemeBreaks: fitting.length === 0,
|
|
2023
|
+
// A continuation page owns the full note band. If even one legal
|
|
2024
|
+
// grapheme is taller than it, clip only that unit and keep draining.
|
|
2025
|
+
forceFirstOnNoFit: fitting.length === 0
|
|
2026
|
+
});
|
|
2027
|
+
}
|
|
2028
|
+
if (split?.kind === "split") {
|
|
2029
|
+
if (sourcePosition) {
|
|
2030
|
+
split.head.setAttribute(FOOTNOTE_SOURCE_POSITION_ATTR, sourcePosition);
|
|
2031
|
+
split.tail.setAttribute(FOOTNOTE_SOURCE_POSITION_ATTR, sourcePosition);
|
|
2032
|
+
}
|
|
2033
|
+
fitting.push(split.head);
|
|
2034
|
+
remaining = [split.tail, ...continuation.remainingElements.slice(index + 1)];
|
|
2035
|
+
} else if (fitting.length === 0) {
|
|
2036
|
+
fitting.push(element);
|
|
2037
|
+
remaining = continuation.remainingElements.slice(index + 1);
|
|
2038
|
+
} else {
|
|
2039
|
+
remaining = continuation.remainingElements.slice(index);
|
|
990
2040
|
}
|
|
991
2041
|
break;
|
|
992
2042
|
}
|
|
2043
|
+
} finally {
|
|
2044
|
+
measureContainer.remove();
|
|
993
2045
|
}
|
|
994
|
-
return {
|
|
2046
|
+
return {
|
|
2047
|
+
current: {
|
|
2048
|
+
footnoteId: continuation.footnoteId,
|
|
2049
|
+
sourceAnchorId: continuation.sourceAnchorId,
|
|
2050
|
+
remainingElements: fitting
|
|
2051
|
+
},
|
|
2052
|
+
overflow: remaining.length > 0 ? {
|
|
2053
|
+
footnoteId: continuation.footnoteId,
|
|
2054
|
+
sourceAnchorId: continuation.sourceAnchorId,
|
|
2055
|
+
remainingElements: remaining
|
|
2056
|
+
} : null
|
|
2057
|
+
};
|
|
995
2058
|
}
|
|
996
2059
|
/**
|
|
997
|
-
*
|
|
2060
|
+
* Splits a footnote element into parts that fit within the available height.
|
|
2061
|
+
* Returns the elements that fit and the elements that need to continue.
|
|
998
2062
|
*/
|
|
999
|
-
|
|
1000
|
-
const
|
|
1001
|
-
|
|
1002
|
-
|
|
2063
|
+
splitFootnoteToFit(footnoteElement, availableHeightPt, contentWidth, forceProgress = false, existingPayload) {
|
|
2064
|
+
const layoutContext = this.document.createElement("div");
|
|
2065
|
+
layoutContext.style.position = "absolute";
|
|
2066
|
+
layoutContext.style.visibility = "hidden";
|
|
2067
|
+
layoutContext.style.width = `${contentWidth}pt`;
|
|
2068
|
+
layoutContext.style.left = "-9999px";
|
|
2069
|
+
layoutContext.className = this.cssPrefix + "footnotes";
|
|
2070
|
+
this.appendFootnoteSeparator(layoutContext, false);
|
|
2071
|
+
const attachedFootnote = footnoteElement.cloneNode(true);
|
|
2072
|
+
layoutContext.appendChild(attachedFootnote);
|
|
2073
|
+
this.noteMeasurementHost().appendChild(layoutContext);
|
|
2074
|
+
const measureContainer = this.document.createElement("div");
|
|
1003
2075
|
measureContainer.style.position = "absolute";
|
|
1004
2076
|
measureContainer.style.visibility = "hidden";
|
|
1005
2077
|
measureContainer.style.width = `${contentWidth}pt`;
|
|
1006
2078
|
measureContainer.style.left = "-9999px";
|
|
1007
2079
|
measureContainer.className = this.cssPrefix + "footnotes";
|
|
1008
|
-
|
|
1009
|
-
this.
|
|
1010
|
-
|
|
1011
|
-
|
|
1012
|
-
|
|
1013
|
-
|
|
2080
|
+
const hasContinuation = Boolean(existingPayload?.continuation?.remainingElements.length);
|
|
2081
|
+
this.appendFootnoteSeparator(measureContainer, hasContinuation);
|
|
2082
|
+
if (hasContinuation) {
|
|
2083
|
+
measureContainer.appendChild(this.createFootnoteContinuationWrapper(
|
|
2084
|
+
existingPayload.continuation
|
|
2085
|
+
));
|
|
2086
|
+
}
|
|
2087
|
+
const partialById = new Map(
|
|
2088
|
+
existingPayload?.partialFootnotes?.map((partial) => [partial.footnoteId, partial]) ?? []
|
|
2089
|
+
);
|
|
2090
|
+
for (const id of existingPayload?.footnoteIds ?? []) {
|
|
2091
|
+
this.checkpoint();
|
|
2092
|
+
const source = this.footnoteRegistry.get(id);
|
|
2093
|
+
if (!source) continue;
|
|
2094
|
+
const partial = partialById.get(id);
|
|
2095
|
+
measureContainer.appendChild(partial ? this.createPartialFootnoteItem(source, partial.fittingElements) : source.cloneNode(true));
|
|
2096
|
+
}
|
|
2097
|
+
const measuredPartial = this.createPartialFootnoteItem(attachedFootnote, []);
|
|
2098
|
+
const measuredContent = measuredPartial.querySelector(":scope > .footnote-content");
|
|
2099
|
+
if (!measuredContent) {
|
|
2100
|
+
layoutContext.remove();
|
|
2101
|
+
throw new Error("Footnote is missing its content shell");
|
|
2102
|
+
}
|
|
2103
|
+
measureContainer.appendChild(measuredPartial);
|
|
2104
|
+
this.noteMeasurementHost().appendChild(measureContainer);
|
|
2105
|
+
try {
|
|
2106
|
+
const footnoteContent = attachedFootnote.querySelector(".footnote-content");
|
|
2107
|
+
if (!footnoteContent) {
|
|
2108
|
+
return {
|
|
2109
|
+
fits: Array.from(attachedFootnote.children).map((el) => el.cloneNode(true)),
|
|
2110
|
+
overflow: []
|
|
2111
|
+
};
|
|
2112
|
+
}
|
|
2113
|
+
const children = Array.from(footnoteContent.children);
|
|
2114
|
+
const fits = [];
|
|
2115
|
+
let overflow = [];
|
|
2116
|
+
for (let i = 0; i < children.length; i++) {
|
|
2117
|
+
this.checkpoint();
|
|
2118
|
+
const child = children[i];
|
|
2119
|
+
const measuredCandidate = child.cloneNode(true);
|
|
2120
|
+
measuredContent.appendChild(measuredCandidate);
|
|
2121
|
+
const candidateFits = pxToPt(measureContainer.getBoundingClientRect().height) <= availableHeightPt;
|
|
2122
|
+
if (candidateFits) {
|
|
2123
|
+
fits.push(child.cloneNode(true));
|
|
2124
|
+
continue;
|
|
2125
|
+
}
|
|
2126
|
+
measuredCandidate.remove();
|
|
2127
|
+
const split = this.canRangeFragmentParagraph(child, true) ? this.splitParagraphAtLargestFit(child, (head) => {
|
|
2128
|
+
measuredContent.appendChild(head);
|
|
2129
|
+
try {
|
|
2130
|
+
return pxToPt(measureContainer.getBoundingClientRect().height) <= availableHeightPt;
|
|
2131
|
+
} finally {
|
|
2132
|
+
head.remove();
|
|
2133
|
+
}
|
|
2134
|
+
}, {
|
|
2135
|
+
// Both fallbacks cut where ordinary line breaking would not, so they
|
|
2136
|
+
// are only ever right for an element that owns the whole band: with
|
|
2137
|
+
// earlier siblings already packed here, a paragraph that will not
|
|
2138
|
+
// start belongs intact in the next note band.
|
|
2139
|
+
emergencyGraphemeBreaks: fits.length === 0 && forceProgress,
|
|
2140
|
+
forceFirstOnNoFit: fits.length === 0 && forceProgress
|
|
2141
|
+
}) : null;
|
|
2142
|
+
if (split?.kind === "split") {
|
|
2143
|
+
fits.push(split.head);
|
|
2144
|
+
split.tail.setAttribute(
|
|
2145
|
+
FOOTNOTE_SOURCE_POSITION_ATTR,
|
|
2146
|
+
i === 0 ? "first" : "later"
|
|
2147
|
+
);
|
|
2148
|
+
overflow = [split.tail, ...children.slice(i + 1).map((element, offset) => this.cloneFootnoteElementForContinuation(element, i + 1 + offset))];
|
|
2149
|
+
} else if (split?.kind === "no-fit") {
|
|
2150
|
+
overflow = children.slice(i).map((element, offset) => this.cloneFootnoteElementForContinuation(element, i + offset));
|
|
2151
|
+
} else if (fits.length === 0 && forceProgress) {
|
|
2152
|
+
fits.push(child.cloneNode(true));
|
|
2153
|
+
overflow = children.slice(i + 1).map((element, offset) => this.cloneFootnoteElementForContinuation(element, i + 1 + offset));
|
|
2154
|
+
} else {
|
|
2155
|
+
overflow = children.slice(i).map((element, offset) => this.cloneFootnoteElementForContinuation(element, i + offset));
|
|
2156
|
+
}
|
|
2157
|
+
break;
|
|
2158
|
+
}
|
|
2159
|
+
return { fits, overflow };
|
|
2160
|
+
} finally {
|
|
2161
|
+
measureContainer.remove();
|
|
2162
|
+
layoutContext.remove();
|
|
2163
|
+
}
|
|
1014
2164
|
}
|
|
1015
2165
|
/**
|
|
1016
2166
|
* Adds footnotes to a page container, including continuation content.
|
|
@@ -1023,12 +2173,11 @@ var DocxodusPagination = (() => {
|
|
|
1023
2173
|
if (this.footnoteRegistry.size === 0 && !hasContinuation) {
|
|
1024
2174
|
return;
|
|
1025
2175
|
}
|
|
1026
|
-
const partialFootnoteIds = new Set(partialFootnotes?.map((p) => p.footnoteId) || []);
|
|
1027
2176
|
const maxFootnoteHeight = Math.min(
|
|
1028
2177
|
footnoteHeight,
|
|
1029
2178
|
bands.bodyHeight * MAX_FOOTNOTE_AREA_RATIO
|
|
1030
2179
|
);
|
|
1031
|
-
const footnotesDiv = document.createElement("div");
|
|
2180
|
+
const footnotesDiv = this.document.createElement("div");
|
|
1032
2181
|
footnotesDiv.className = `${this.cssPrefix}footnotes`;
|
|
1033
2182
|
footnotesDiv.style.position = "absolute";
|
|
1034
2183
|
footnotesDiv.style.bottom = `${dims.pageHeight - (bands.bodyTop + bands.bodyHeight)}pt`;
|
|
@@ -1037,35 +2186,18 @@ var DocxodusPagination = (() => {
|
|
|
1037
2186
|
footnotesDiv.style.boxSizing = "border-box";
|
|
1038
2187
|
footnotesDiv.style.maxHeight = `${maxFootnoteHeight}pt`;
|
|
1039
2188
|
footnotesDiv.style.overflow = "hidden";
|
|
1040
|
-
|
|
1041
|
-
footnotesDiv.appendChild(hr);
|
|
2189
|
+
this.appendFootnoteSeparator(footnotesDiv, Boolean(hasContinuation));
|
|
1042
2190
|
if (hasContinuation) {
|
|
1043
|
-
|
|
1044
|
-
contWrapper.className = "footnote-continuation";
|
|
1045
|
-
for (const el of continuation.remainingElements) {
|
|
1046
|
-
contWrapper.appendChild(el.cloneNode(true));
|
|
1047
|
-
}
|
|
1048
|
-
footnotesDiv.appendChild(contWrapper);
|
|
2191
|
+
footnotesDiv.appendChild(this.createFootnoteContinuationWrapper(continuation));
|
|
1049
2192
|
}
|
|
1050
2193
|
for (const id of footnoteIds) {
|
|
1051
2194
|
const partial = partialFootnotes?.find((p) => p.footnoteId === id);
|
|
1052
2195
|
if (partial) {
|
|
1053
2196
|
const footnote = this.footnoteRegistry.get(id);
|
|
1054
2197
|
if (footnote) {
|
|
1055
|
-
|
|
1056
|
-
|
|
1057
|
-
|
|
1058
|
-
const numberSpan = footnote.querySelector(".footnote-number");
|
|
1059
|
-
if (numberSpan) {
|
|
1060
|
-
partialDiv.appendChild(numberSpan.cloneNode(true));
|
|
1061
|
-
}
|
|
1062
|
-
const contentSpan = document.createElement("span");
|
|
1063
|
-
contentSpan.className = "footnote-content";
|
|
1064
|
-
for (const el of partial.fittingElements) {
|
|
1065
|
-
contentSpan.appendChild(el.cloneNode(true));
|
|
1066
|
-
}
|
|
1067
|
-
partialDiv.appendChild(contentSpan);
|
|
1068
|
-
footnotesDiv.appendChild(partialDiv);
|
|
2198
|
+
footnotesDiv.appendChild(
|
|
2199
|
+
this.createPartialFootnoteItem(footnote, partial.fittingElements)
|
|
2200
|
+
);
|
|
1069
2201
|
}
|
|
1070
2202
|
} else {
|
|
1071
2203
|
const footnote = this.footnoteRegistry.get(id);
|
|
@@ -1076,30 +2208,26 @@ var DocxodusPagination = (() => {
|
|
|
1076
2208
|
}
|
|
1077
2209
|
pageBox.appendChild(footnotesDiv);
|
|
1078
2210
|
}
|
|
1079
|
-
/**
|
|
1080
|
-
|
|
1081
|
-
*/
|
|
1082
|
-
selectHeader(sectionIndex, pageInSection, globalPageNumber) {
|
|
2211
|
+
/** Select the section's first/odd/even header from its one-based page position. */
|
|
2212
|
+
selectHeader(sectionIndex, pageInSection, displayedPageNumber) {
|
|
1083
2213
|
const sectionHf = this.hfRegistry.get(sectionIndex);
|
|
1084
2214
|
if (!sectionHf) return void 0;
|
|
1085
2215
|
if (pageInSection === 1 && sectionHf.headerFirst) {
|
|
1086
2216
|
return sectionHf.headerFirst;
|
|
1087
2217
|
}
|
|
1088
|
-
if (
|
|
2218
|
+
if (displayedPageNumber % 2 === 0 && sectionHf.headerEven) {
|
|
1089
2219
|
return sectionHf.headerEven;
|
|
1090
2220
|
}
|
|
1091
2221
|
return sectionHf.headerDefault;
|
|
1092
2222
|
}
|
|
1093
|
-
/**
|
|
1094
|
-
|
|
1095
|
-
*/
|
|
1096
|
-
selectFooter(sectionIndex, pageInSection, globalPageNumber) {
|
|
2223
|
+
/** Select the section's first/odd/even footer from its one-based page position. */
|
|
2224
|
+
selectFooter(sectionIndex, pageInSection, displayedPageNumber) {
|
|
1097
2225
|
const sectionHf = this.hfRegistry.get(sectionIndex);
|
|
1098
2226
|
if (!sectionHf) return void 0;
|
|
1099
2227
|
if (pageInSection === 1 && sectionHf.footerFirst) {
|
|
1100
2228
|
return sectionHf.footerFirst;
|
|
1101
2229
|
}
|
|
1102
|
-
if (
|
|
2230
|
+
if (displayedPageNumber % 2 === 0 && sectionHf.footerEven) {
|
|
1103
2231
|
return sectionHf.footerEven;
|
|
1104
2232
|
}
|
|
1105
2233
|
return sectionHf.footerDefault;
|
|
@@ -1114,7 +2242,7 @@ var DocxodusPagination = (() => {
|
|
|
1114
2242
|
* Deterministic: it depends only on the section's page setup and the registry's pre-measured
|
|
1115
2243
|
* story heights, never on the page's content, which is what keeps it lazy-loading compatible.
|
|
1116
2244
|
*/
|
|
1117
|
-
getPageBands(dims, sectionIndex, pageInSection,
|
|
2245
|
+
getPageBands(dims, sectionIndex, pageInSection, displayedPageNumber) {
|
|
1118
2246
|
const sectionHf = this.hfRegistry.get(sectionIndex);
|
|
1119
2247
|
return resolvePageBands(
|
|
1120
2248
|
dims,
|
|
@@ -1123,14 +2251,14 @@ var DocxodusPagination = (() => {
|
|
|
1123
2251
|
sectionHf?.headerEvenHeight,
|
|
1124
2252
|
sectionHf?.headerDefaultHeight,
|
|
1125
2253
|
pageInSection,
|
|
1126
|
-
|
|
2254
|
+
displayedPageNumber
|
|
1127
2255
|
),
|
|
1128
2256
|
this.selectStoryHeight(
|
|
1129
2257
|
sectionHf?.footerFirstHeight,
|
|
1130
2258
|
sectionHf?.footerEvenHeight,
|
|
1131
2259
|
sectionHf?.footerDefaultHeight,
|
|
1132
2260
|
pageInSection,
|
|
1133
|
-
|
|
2261
|
+
displayedPageNumber
|
|
1134
2262
|
)
|
|
1135
2263
|
);
|
|
1136
2264
|
}
|
|
@@ -1138,9 +2266,9 @@ var DocxodusPagination = (() => {
|
|
|
1138
2266
|
* The measured height of the running story this page position selects, mirroring
|
|
1139
2267
|
* {@link selectHeader}/{@link selectFooter}. Zero when the page has no such story.
|
|
1140
2268
|
*/
|
|
1141
|
-
selectStoryHeight(first, even, fallback, pageInSection,
|
|
2269
|
+
selectStoryHeight(first, even, fallback, pageInSection, displayedPageNumber) {
|
|
1142
2270
|
if (pageInSection === 1 && first != null) return first;
|
|
1143
|
-
if (
|
|
2271
|
+
if (displayedPageNumber % 2 === 0 && even != null) return even;
|
|
1144
2272
|
return fallback ?? 0;
|
|
1145
2273
|
}
|
|
1146
2274
|
/**
|
|
@@ -1152,7 +2280,7 @@ var DocxodusPagination = (() => {
|
|
|
1152
2280
|
* silently starts overlapping body text.
|
|
1153
2281
|
*/
|
|
1154
2282
|
measureHeaderFooterHeight(source, contentWidth) {
|
|
1155
|
-
const measureContainer = document.createElement("div");
|
|
2283
|
+
const measureContainer = this.document.createElement("div");
|
|
1156
2284
|
measureContainer.style.position = "absolute";
|
|
1157
2285
|
measureContainer.style.visibility = "hidden";
|
|
1158
2286
|
measureContainer.style.width = `${contentWidth}pt`;
|
|
@@ -1171,79 +2299,275 @@ var DocxodusPagination = (() => {
|
|
|
1171
2299
|
* Implements a single-pass, forward-only algorithm that is compatible with future lazy loading.
|
|
1172
2300
|
* Supports footnote continuation - long footnotes can split across pages.
|
|
1173
2301
|
*/
|
|
1174
|
-
flowToPages(blocks, dims, startPageNumber, sectionIndex) {
|
|
2302
|
+
flowToPages(blocks, dims, startPageNumber, sectionIndex, sectionDimensions) {
|
|
1175
2303
|
const pages = [];
|
|
1176
2304
|
let currentContent = [];
|
|
1177
2305
|
let pageNumber = startPageNumber;
|
|
1178
|
-
let
|
|
2306
|
+
let pageSectionIndex = sectionIndex;
|
|
2307
|
+
const sectionStartPages = /* @__PURE__ */ new Map([[sectionIndex, startPageNumber]]);
|
|
2308
|
+
const pageInSection = (owner = pageSectionIndex, physicalPage = pageNumber) => physicalPage - (sectionStartPages.get(owner) ?? physicalPage) + 1;
|
|
2309
|
+
const dimensionsFor = (owner = pageSectionIndex) => sectionDimensions.get(owner) ?? dims;
|
|
2310
|
+
const markSectionPlaced = (owner) => {
|
|
2311
|
+
if (!sectionStartPages.has(owner)) sectionStartPages.set(owner, pageNumber);
|
|
2312
|
+
};
|
|
2313
|
+
const previousBox = this.containerElement.querySelector(
|
|
2314
|
+
`.${this.cssPrefix}box:last-of-type`
|
|
2315
|
+
);
|
|
2316
|
+
let precedingDisplayedPageNumber = parseInt(
|
|
2317
|
+
previousBox?.dataset.displayedPageNumber ?? "0",
|
|
2318
|
+
10
|
|
2319
|
+
);
|
|
2320
|
+
const displayedPageNumber = (owner = pageSectionIndex, ownerPageInSection = pageInSection(owner), physicalPage = pageNumber) => {
|
|
2321
|
+
const numbering = this.pageNumbering.get(owner) ?? {};
|
|
2322
|
+
if (numbering.start !== void 0) {
|
|
2323
|
+
return numbering.start + ownerPageInSection - 1;
|
|
2324
|
+
}
|
|
2325
|
+
const ownerNumbering = this.pageNumbering.get(pageSectionIndex) ?? {};
|
|
2326
|
+
const currentPhysicalNumber = ownerNumbering.start !== void 0 ? ownerNumbering.start + pageInSection(pageSectionIndex) - 1 : precedingDisplayedPageNumber + 1;
|
|
2327
|
+
return currentPhysicalNumber + physicalPage - pageNumber;
|
|
2328
|
+
};
|
|
1179
2329
|
let { bodyHeight: effectiveContentHeight } = this.getPageBands(
|
|
1180
|
-
|
|
1181
|
-
|
|
1182
|
-
pageInSection,
|
|
1183
|
-
|
|
2330
|
+
dimensionsFor(),
|
|
2331
|
+
pageSectionIndex,
|
|
2332
|
+
pageInSection(),
|
|
2333
|
+
displayedPageNumber()
|
|
1184
2334
|
);
|
|
1185
2335
|
let remainingHeight = effectiveContentHeight;
|
|
1186
2336
|
let prevMarginBottomPt = 0;
|
|
1187
2337
|
let currentFootnoteIds = [];
|
|
2338
|
+
let currentPageHasFootnoteReference = false;
|
|
1188
2339
|
let currentFootnoteHeight = 0;
|
|
1189
2340
|
let currentContinuation = this.pendingFootnoteContinuation;
|
|
1190
2341
|
let nextPageContinuation = null;
|
|
2342
|
+
let currentContinuationPartitioned = false;
|
|
2343
|
+
let currentPageAdmitted = false;
|
|
1191
2344
|
let deferredFootnoteIds = [];
|
|
1192
2345
|
let currentPartialFootnotes = [];
|
|
1193
|
-
|
|
1194
|
-
|
|
1195
|
-
|
|
1196
|
-
|
|
2346
|
+
const adoptEmptyPageOwner = (owner) => {
|
|
2347
|
+
pageSectionIndex = owner;
|
|
2348
|
+
const bands = this.getPageBands(
|
|
2349
|
+
dimensionsFor(owner),
|
|
2350
|
+
owner,
|
|
2351
|
+
pageInSection(owner),
|
|
2352
|
+
displayedPageNumber(owner)
|
|
2353
|
+
);
|
|
2354
|
+
effectiveContentHeight = bands.bodyHeight;
|
|
2355
|
+
remainingHeight = effectiveContentHeight;
|
|
2356
|
+
};
|
|
2357
|
+
const admitCurrentPage = () => {
|
|
2358
|
+
if (currentPageAdmitted) return;
|
|
2359
|
+
this.admitPageAllocation();
|
|
2360
|
+
currentPageAdmitted = true;
|
|
2361
|
+
};
|
|
2362
|
+
const prepareCurrentContinuation = () => {
|
|
2363
|
+
if (currentContinuationPartitioned || (currentContinuation?.remainingElements.length ?? 0) === 0) return;
|
|
2364
|
+
admitCurrentPage();
|
|
2365
|
+
const ownedDimensions = dimensionsFor();
|
|
2366
|
+
const pageBands = this.getPageBands(
|
|
2367
|
+
ownedDimensions,
|
|
2368
|
+
pageSectionIndex,
|
|
2369
|
+
pageInSection(),
|
|
2370
|
+
displayedPageNumber()
|
|
2371
|
+
);
|
|
2372
|
+
const partition = this.splitContinuationForPage(
|
|
2373
|
+
currentContinuation,
|
|
2374
|
+
pageBands.bodyHeight * MAX_FOOTNOTE_AREA_RATIO,
|
|
2375
|
+
ownedDimensions.contentWidth
|
|
2376
|
+
);
|
|
2377
|
+
currentContinuation = partition.current;
|
|
2378
|
+
if (partition.overflow) nextPageContinuation = partition.overflow;
|
|
2379
|
+
currentContinuationPartitioned = true;
|
|
2380
|
+
currentFootnoteHeight = this.measureFootnotesHeight(
|
|
2381
|
+
currentFootnoteIds,
|
|
2382
|
+
ownedDimensions.contentWidth,
|
|
2383
|
+
currentContinuation,
|
|
2384
|
+
currentPartialFootnotes
|
|
2385
|
+
);
|
|
2386
|
+
};
|
|
2387
|
+
const finishPage = (nextPageSectionIndex = pageSectionIndex, forceEmptyPage = false) => {
|
|
1197
2388
|
const hasCurrentContinuation = (currentContinuation?.remainingElements.length ?? 0) > 0;
|
|
1198
|
-
if (currentContent.length === 0 && !hasCurrentContinuation)
|
|
2389
|
+
if (!forceEmptyPage && currentContent.length === 0 && currentFootnoteIds.length === 0 && !hasCurrentContinuation) {
|
|
2390
|
+
adoptEmptyPageOwner(nextPageSectionIndex);
|
|
2391
|
+
return;
|
|
2392
|
+
}
|
|
2393
|
+
prepareCurrentContinuation();
|
|
2394
|
+
admitCurrentPage();
|
|
2395
|
+
let pageContinuation = currentContinuation;
|
|
2396
|
+
const ownedPageInSection = pageInSection();
|
|
2397
|
+
const ownedDimensions = dimensionsFor();
|
|
2398
|
+
const ownedDisplayedPageNumber = displayedPageNumber(
|
|
2399
|
+
pageSectionIndex,
|
|
2400
|
+
ownedPageInSection
|
|
2401
|
+
);
|
|
2402
|
+
const pageBands = this.getPageBands(
|
|
2403
|
+
ownedDimensions,
|
|
2404
|
+
pageSectionIndex,
|
|
2405
|
+
ownedPageInSection,
|
|
2406
|
+
ownedDisplayedPageNumber
|
|
2407
|
+
);
|
|
2408
|
+
const maxFootnoteHeight = pageBands.bodyHeight * MAX_FOOTNOTE_AREA_RATIO;
|
|
2409
|
+
pageContinuation = currentContinuation;
|
|
2410
|
+
currentFootnoteHeight = this.measureFootnotesHeight(
|
|
2411
|
+
currentFootnoteIds,
|
|
2412
|
+
ownedDimensions.contentWidth,
|
|
2413
|
+
pageContinuation,
|
|
2414
|
+
currentPartialFootnotes
|
|
2415
|
+
);
|
|
2416
|
+
if (currentContent.length === 0 && currentFootnoteIds.length > 0 && currentPartialFootnotes.length === 0) {
|
|
2417
|
+
const fittingIds = [];
|
|
2418
|
+
for (let index = 0; index < currentFootnoteIds.length; index++) {
|
|
2419
|
+
const footnoteId = currentFootnoteIds[index];
|
|
2420
|
+
const candidateIds = [...fittingIds, footnoteId];
|
|
2421
|
+
const candidateHeight = this.measureFootnotesHeight(
|
|
2422
|
+
candidateIds,
|
|
2423
|
+
ownedDimensions.contentWidth,
|
|
2424
|
+
pageContinuation
|
|
2425
|
+
);
|
|
2426
|
+
const guardedCandidateHeight = candidateHeight + FOOTNOTE_MEASUREMENT_GUARD_PT;
|
|
2427
|
+
if (guardedCandidateHeight <= maxFootnoteHeight) {
|
|
2428
|
+
fittingIds.push(footnoteId);
|
|
2429
|
+
currentFootnoteHeight = guardedCandidateHeight;
|
|
2430
|
+
continue;
|
|
2431
|
+
}
|
|
2432
|
+
const hasPageContinuation = (pageContinuation?.remainingElements.length ?? 0) > 0;
|
|
2433
|
+
if (fittingIds.length === 0 && !hasPageContinuation) {
|
|
2434
|
+
const source = this.footnoteRegistry.get(footnoteId);
|
|
2435
|
+
const split = source ? this.splitFootnoteToFit(
|
|
2436
|
+
source,
|
|
2437
|
+
maxFootnoteHeight - FOOTNOTE_MEASUREMENT_GUARD_PT,
|
|
2438
|
+
ownedDimensions.contentWidth,
|
|
2439
|
+
true
|
|
2440
|
+
) : null;
|
|
2441
|
+
fittingIds.push(footnoteId);
|
|
2442
|
+
if (source && split && split.fits.length > 0 && split.overflow.length > 0) {
|
|
2443
|
+
currentPartialFootnotes.push({ footnoteId, fittingElements: split.fits });
|
|
2444
|
+
nextPageContinuation = {
|
|
2445
|
+
footnoteId,
|
|
2446
|
+
sourceAnchorId: source.dataset.sourceAnchorId,
|
|
2447
|
+
remainingElements: split.overflow
|
|
2448
|
+
};
|
|
2449
|
+
currentFootnoteHeight = maxFootnoteHeight;
|
|
2450
|
+
} else {
|
|
2451
|
+
currentFootnoteHeight = guardedCandidateHeight;
|
|
2452
|
+
}
|
|
2453
|
+
deferredFootnoteIds.push(...currentFootnoteIds.slice(index + 1));
|
|
2454
|
+
} else {
|
|
2455
|
+
deferredFootnoteIds.push(...currentFootnoteIds.slice(index));
|
|
2456
|
+
}
|
|
2457
|
+
break;
|
|
2458
|
+
}
|
|
2459
|
+
currentFootnoteIds = fittingIds;
|
|
2460
|
+
}
|
|
1199
2461
|
const page = this.createPage(
|
|
1200
|
-
|
|
2462
|
+
ownedDimensions,
|
|
1201
2463
|
pageNumber,
|
|
1202
|
-
|
|
2464
|
+
pageSectionIndex,
|
|
2465
|
+
ownedDisplayedPageNumber,
|
|
1203
2466
|
currentContent,
|
|
1204
|
-
|
|
2467
|
+
ownedPageInSection,
|
|
1205
2468
|
currentFootnoteIds,
|
|
1206
2469
|
currentFootnoteHeight,
|
|
1207
|
-
|
|
1208
|
-
currentPartialFootnotes.length > 0 ? currentPartialFootnotes : void 0
|
|
2470
|
+
pageContinuation,
|
|
2471
|
+
currentPartialFootnotes.length > 0 ? currentPartialFootnotes : void 0,
|
|
2472
|
+
false,
|
|
2473
|
+
currentPageAdmitted
|
|
1209
2474
|
);
|
|
1210
2475
|
pages.push(page);
|
|
2476
|
+
precedingDisplayedPageNumber = ownedDisplayedPageNumber;
|
|
1211
2477
|
pageNumber++;
|
|
1212
|
-
pageInSection++;
|
|
1213
2478
|
currentContent = [];
|
|
1214
|
-
|
|
2479
|
+
pageSectionIndex = nextPageSectionIndex;
|
|
2480
|
+
if (!sectionStartPages.has(pageSectionIndex)) {
|
|
2481
|
+
sectionStartPages.set(pageSectionIndex, pageNumber);
|
|
2482
|
+
}
|
|
2483
|
+
const newBands = this.getPageBands(
|
|
2484
|
+
dimensionsFor(),
|
|
2485
|
+
pageSectionIndex,
|
|
2486
|
+
pageInSection(),
|
|
2487
|
+
displayedPageNumber()
|
|
2488
|
+
);
|
|
1215
2489
|
effectiveContentHeight = newBands.bodyHeight;
|
|
1216
2490
|
remainingHeight = effectiveContentHeight;
|
|
1217
2491
|
prevMarginBottomPt = 0;
|
|
1218
2492
|
currentFootnoteIds = [];
|
|
2493
|
+
currentPageHasFootnoteReference = false;
|
|
1219
2494
|
currentPartialFootnotes = [];
|
|
1220
2495
|
currentContinuation = nextPageContinuation;
|
|
1221
2496
|
nextPageContinuation = null;
|
|
2497
|
+
currentContinuationPartitioned = false;
|
|
2498
|
+
currentPageAdmitted = false;
|
|
1222
2499
|
if (deferredFootnoteIds.length > 0) {
|
|
1223
2500
|
currentFootnoteIds = [...deferredFootnoteIds];
|
|
1224
2501
|
deferredFootnoteIds = [];
|
|
1225
2502
|
}
|
|
1226
|
-
|
|
1227
|
-
|
|
1228
|
-
} else {
|
|
1229
|
-
currentFootnoteHeight = 0;
|
|
1230
|
-
}
|
|
1231
|
-
if (currentFootnoteIds.length > 0) {
|
|
1232
|
-
currentFootnoteHeight += this.measureFootnotesHeight(
|
|
1233
|
-
currentFootnoteIds,
|
|
1234
|
-
dims.contentWidth,
|
|
1235
|
-
null
|
|
1236
|
-
);
|
|
1237
|
-
}
|
|
2503
|
+
const nextDimensions = dimensionsFor();
|
|
2504
|
+
currentFootnoteHeight = currentContinuation ? 0 : this.measureFootnotesHeight(currentFootnoteIds, nextDimensions.contentWidth);
|
|
1238
2505
|
};
|
|
2506
|
+
const pageHasPayload = () => currentContent.length > 0 || currentFootnoteIds.length > 0 || (currentContinuation?.remainingElements.length ?? 0) > 0;
|
|
1239
2507
|
for (let i = 0; i < blocks.length; i++) {
|
|
2508
|
+
this.checkpoint();
|
|
1240
2509
|
const block = blocks[i];
|
|
2510
|
+
const allBlockFootnoteIds = this.extractFootnoteRefs(block.element);
|
|
2511
|
+
if (block.sectionIndex !== pageSectionIndex && currentPageHasFootnoteReference && (!this.footnoteLayoutLikeWord8 || allBlockFootnoteIds.length > 0)) {
|
|
2512
|
+
finishPage(block.sectionIndex);
|
|
2513
|
+
}
|
|
2514
|
+
const pageIsEmpty = currentContent.length === 0 && currentFootnoteIds.length === 0 && (currentContinuation?.remainingElements.length ?? 0) === 0;
|
|
2515
|
+
if (pageIsEmpty && block.sectionIndex !== pageSectionIndex) {
|
|
2516
|
+
adoptEmptyPageOwner(block.sectionIndex);
|
|
2517
|
+
}
|
|
2518
|
+
prepareCurrentContinuation();
|
|
2519
|
+
if (currentFootnoteHeight > 0) {
|
|
2520
|
+
const ownerDimensions = dimensionsFor();
|
|
2521
|
+
const ownerBands = this.getPageBands(
|
|
2522
|
+
ownerDimensions,
|
|
2523
|
+
pageSectionIndex,
|
|
2524
|
+
pageInSection(),
|
|
2525
|
+
displayedPageNumber()
|
|
2526
|
+
);
|
|
2527
|
+
const ownerMaxFootnoteArea = ownerBands.bodyHeight * MAX_FOOTNOTE_AREA_RATIO;
|
|
2528
|
+
if (currentFootnoteHeight > ownerMaxFootnoteArea) {
|
|
2529
|
+
const fittingIds = [];
|
|
2530
|
+
let fittingHeight = 0;
|
|
2531
|
+
if ((currentContinuation?.remainingElements.length ?? 0) === 0 && currentPartialFootnotes.length === 0) {
|
|
2532
|
+
for (const footnoteId of currentFootnoteIds) {
|
|
2533
|
+
this.checkpoint();
|
|
2534
|
+
const candidateHeight = this.measureFootnotesHeight(
|
|
2535
|
+
[...fittingIds, footnoteId],
|
|
2536
|
+
ownerDimensions.contentWidth
|
|
2537
|
+
);
|
|
2538
|
+
if (candidateHeight + FOOTNOTE_MEASUREMENT_GUARD_PT > ownerMaxFootnoteArea) break;
|
|
2539
|
+
fittingIds.push(footnoteId);
|
|
2540
|
+
fittingHeight = candidateHeight;
|
|
2541
|
+
}
|
|
2542
|
+
}
|
|
2543
|
+
if (fittingIds.length > 0) {
|
|
2544
|
+
deferredFootnoteIds = [
|
|
2545
|
+
...currentFootnoteIds.slice(fittingIds.length),
|
|
2546
|
+
...deferredFootnoteIds
|
|
2547
|
+
];
|
|
2548
|
+
currentFootnoteIds = fittingIds;
|
|
2549
|
+
currentFootnoteHeight = fittingHeight;
|
|
2550
|
+
} else {
|
|
2551
|
+
finishPage(block.sectionIndex);
|
|
2552
|
+
i--;
|
|
2553
|
+
continue;
|
|
2554
|
+
}
|
|
2555
|
+
}
|
|
2556
|
+
}
|
|
2557
|
+
const blockDimensions = dimensionsFor(block.sectionIndex);
|
|
2558
|
+
const nextBlockPageInSection = pageInSection(block.sectionIndex, pageNumber + 1);
|
|
2559
|
+
const freshBlockPageBodyHeight = this.getPageBands(
|
|
2560
|
+
blockDimensions,
|
|
2561
|
+
block.sectionIndex,
|
|
2562
|
+
nextBlockPageInSection,
|
|
2563
|
+
displayedPageNumber(block.sectionIndex, nextBlockPageInSection, pageNumber + 1)
|
|
2564
|
+
).bodyHeight;
|
|
1241
2565
|
if (block.isPageBreak) {
|
|
1242
|
-
finishPage();
|
|
2566
|
+
finishPage(block.sectionIndex);
|
|
1243
2567
|
continue;
|
|
1244
2568
|
}
|
|
1245
2569
|
if (block.pageBreakBefore && currentContent.length > 0) {
|
|
1246
|
-
finishPage();
|
|
2570
|
+
finishPage(block.sectionIndex);
|
|
1247
2571
|
}
|
|
1248
2572
|
const previousBlock = blocks[i - 1];
|
|
1249
2573
|
const startsKeepChain = !previousBlock || !previousBlock.keepWithNext || previousBlock.isPageBreak || block.pageBreakBefore;
|
|
@@ -1259,7 +2583,7 @@ var DocxodusPagination = (() => {
|
|
|
1259
2583
|
if (newChainFootnoteIds.length > 0 && this.footnoteRegistry.size > 0) {
|
|
1260
2584
|
const totalChainFootnoteHeight = this.measureFootnotesHeight(
|
|
1261
2585
|
[...currentFootnoteIds, ...newChainFootnoteIds],
|
|
1262
|
-
|
|
2586
|
+
blockDimensions.contentWidth,
|
|
1263
2587
|
currentContinuation
|
|
1264
2588
|
);
|
|
1265
2589
|
additionalChainFootnoteHeight = Math.max(
|
|
@@ -1271,61 +2595,68 @@ var DocxodusPagination = (() => {
|
|
|
1271
2595
|
keepChain,
|
|
1272
2596
|
prevMarginBottomPt,
|
|
1273
2597
|
currentContent.length === 0,
|
|
1274
|
-
pageInSection
|
|
2598
|
+
pageInSection(block.sectionIndex)
|
|
1275
2599
|
) + additionalChainFootnoteHeight;
|
|
1276
2600
|
const currentAvailableHeight = remainingHeight - currentFootnoteHeight;
|
|
1277
2601
|
if (currentChainHeight > currentAvailableHeight) {
|
|
1278
2602
|
const nextPageBands = this.getPageBands(
|
|
1279
|
-
|
|
1280
|
-
sectionIndex,
|
|
1281
|
-
pageInSection + 1,
|
|
1282
|
-
|
|
2603
|
+
dimensionsFor(block.sectionIndex),
|
|
2604
|
+
block.sectionIndex,
|
|
2605
|
+
pageInSection(block.sectionIndex, pageNumber + 1),
|
|
2606
|
+
displayedPageNumber(
|
|
2607
|
+
block.sectionIndex,
|
|
2608
|
+
pageInSection(block.sectionIndex, pageNumber + 1),
|
|
2609
|
+
pageNumber + 1
|
|
2610
|
+
)
|
|
1283
2611
|
);
|
|
1284
2612
|
const freshChainBodyHeight = this.measureKeepWithNextChainBodyHeight(
|
|
1285
2613
|
keepChain,
|
|
1286
2614
|
0,
|
|
1287
2615
|
true,
|
|
1288
|
-
pageInSection + 1
|
|
2616
|
+
pageInSection(block.sectionIndex, pageNumber + 1)
|
|
1289
2617
|
);
|
|
1290
2618
|
const freshChainFootnoteHeight = this.measureFootnotesHeight(
|
|
1291
2619
|
newChainFootnoteIds,
|
|
1292
|
-
|
|
2620
|
+
blockDimensions.contentWidth,
|
|
1293
2621
|
nextPageContinuation
|
|
1294
2622
|
);
|
|
1295
2623
|
if (freshChainBodyHeight + freshChainFootnoteHeight <= nextPageBands.bodyHeight) {
|
|
1296
|
-
finishPage();
|
|
2624
|
+
finishPage(block.sectionIndex);
|
|
1297
2625
|
}
|
|
1298
2626
|
}
|
|
1299
2627
|
}
|
|
1300
2628
|
}
|
|
1301
|
-
const allBlockFootnoteIds = this.extractFootnoteRefs(block.element);
|
|
1302
2629
|
const newFootnoteIds = this.collectNewFootnoteIds([block], currentFootnoteIds);
|
|
2630
|
+
let combinedFootnoteHeight = currentFootnoteHeight;
|
|
1303
2631
|
let additionalFootnoteHeight = 0;
|
|
1304
2632
|
if (newFootnoteIds.length > 0 && this.footnoteRegistry.size > 0) {
|
|
1305
2633
|
const combinedFootnoteIds = [...currentFootnoteIds, ...newFootnoteIds];
|
|
1306
|
-
|
|
2634
|
+
combinedFootnoteHeight = this.measureFootnotesHeight(
|
|
1307
2635
|
combinedFootnoteIds,
|
|
1308
|
-
|
|
1309
|
-
currentContinuation
|
|
2636
|
+
blockDimensions.contentWidth,
|
|
2637
|
+
currentContinuation,
|
|
2638
|
+
currentPartialFootnotes
|
|
2639
|
+
);
|
|
2640
|
+
additionalFootnoteHeight = Math.max(
|
|
2641
|
+
0,
|
|
2642
|
+
combinedFootnoteHeight - currentFootnoteHeight
|
|
1310
2643
|
);
|
|
1311
|
-
additionalFootnoteHeight = totalFootnoteHeight - currentFootnoteHeight;
|
|
1312
2644
|
}
|
|
1313
2645
|
const isFirstOnPage = currentContent.length === 0;
|
|
1314
2646
|
const effectiveMarginTop = this.effectiveBlockMarginTop(
|
|
1315
2647
|
block,
|
|
1316
2648
|
prevMarginBottomPt,
|
|
1317
2649
|
isFirstOnPage,
|
|
1318
|
-
pageInSection
|
|
2650
|
+
pageInSection(block.sectionIndex)
|
|
1319
2651
|
);
|
|
1320
2652
|
const blockSpace = effectiveMarginTop + block.heightPt + additionalFootnoteHeight;
|
|
1321
2653
|
const effectiveRemainingHeight = remainingHeight - currentFootnoteHeight;
|
|
1322
2654
|
const bodyContentUsed = effectiveContentHeight - remainingHeight;
|
|
1323
2655
|
const maxFootnoteArea = effectiveContentHeight * MAX_FOOTNOTE_AREA_RATIO;
|
|
1324
|
-
const maxFootnoteExpansion = Math.max(0, maxFootnoteArea - currentFootnoteHeight);
|
|
1325
2656
|
if (blockSpace > effectiveRemainingHeight) {
|
|
1326
2657
|
const paragraphFragments = this.tryFragmentParagraph(
|
|
1327
2658
|
block,
|
|
1328
|
-
|
|
2659
|
+
blockDimensions,
|
|
1329
2660
|
effectiveRemainingHeight,
|
|
1330
2661
|
effectiveMarginTop
|
|
1331
2662
|
);
|
|
@@ -1335,40 +2666,74 @@ var DocxodusPagination = (() => {
|
|
|
1335
2666
|
continue;
|
|
1336
2667
|
}
|
|
1337
2668
|
}
|
|
1338
|
-
if (blockSpace <= effectiveRemainingHeight) {
|
|
1339
|
-
|
|
2669
|
+
if (blockSpace <= effectiveRemainingHeight && (combinedFootnoteHeight === 0 || combinedFootnoteHeight + FOOTNOTE_MEASUREMENT_GUARD_PT <= maxFootnoteArea)) {
|
|
2670
|
+
markSectionPlaced(block.sectionIndex);
|
|
2671
|
+
currentContent.push(this.cloneBlockForPage(
|
|
2672
|
+
block,
|
|
2673
|
+
isFirstOnPage,
|
|
2674
|
+
pageInSection(block.sectionIndex)
|
|
2675
|
+
));
|
|
1340
2676
|
remainingHeight -= effectiveMarginTop + block.heightPt + block.marginBottomPt;
|
|
1341
2677
|
prevMarginBottomPt = block.marginBottomPt;
|
|
1342
2678
|
if (newFootnoteIds.length > 0) {
|
|
1343
2679
|
currentFootnoteIds.push(...newFootnoteIds);
|
|
1344
|
-
currentFootnoteHeight
|
|
2680
|
+
currentFootnoteHeight = combinedFootnoteHeight;
|
|
1345
2681
|
}
|
|
1346
|
-
|
|
2682
|
+
currentPageHasFootnoteReference ||= allBlockFootnoteIds.length > 0;
|
|
2683
|
+
} else if (block.heightPt + this.effectiveBlockMarginTop(
|
|
2684
|
+
block,
|
|
2685
|
+
0,
|
|
2686
|
+
true,
|
|
2687
|
+
pageInSection(block.sectionIndex, pageNumber + 1)
|
|
2688
|
+
) <= freshBlockPageBodyHeight) {
|
|
1347
2689
|
const blockSpaceWithoutFootnotes = effectiveMarginTop + block.heightPt;
|
|
1348
|
-
const minBodySpaceNeeded = bodyContentUsed + blockSpaceWithoutFootnotes + MIN_BODY_CONTENT_HEIGHT;
|
|
1349
|
-
const canExpandFootnotes = minBodySpaceNeeded <= effectiveContentHeight;
|
|
1350
2690
|
if (newFootnoteIds.length > 0 && blockSpaceWithoutFootnotes <= effectiveRemainingHeight) {
|
|
1351
|
-
|
|
2691
|
+
markSectionPlaced(block.sectionIndex);
|
|
2692
|
+
currentContent.push(this.cloneBlockForPage(
|
|
2693
|
+
block,
|
|
2694
|
+
isFirstOnPage,
|
|
2695
|
+
pageInSection(block.sectionIndex)
|
|
2696
|
+
));
|
|
1352
2697
|
remainingHeight -= effectiveMarginTop + block.heightPt + block.marginBottomPt;
|
|
1353
2698
|
prevMarginBottomPt = block.marginBottomPt;
|
|
1354
2699
|
const availableForFootnotes = Math.min(
|
|
1355
2700
|
maxFootnoteArea,
|
|
1356
2701
|
effectiveContentHeight - bodyContentUsed - blockSpaceWithoutFootnotes
|
|
1357
2702
|
);
|
|
1358
|
-
|
|
2703
|
+
const nextPageMaxFootnoteArea = freshBlockPageBodyHeight * MAX_FOOTNOTE_AREA_RATIO;
|
|
2704
|
+
const deferralCannotHelp = availableForFootnotes >= nextPageMaxFootnoteArea;
|
|
2705
|
+
for (let noteIndex = 0; noteIndex < newFootnoteIds.length; noteIndex++) {
|
|
2706
|
+
this.checkpoint();
|
|
2707
|
+
const footnoteId = newFootnoteIds[noteIndex];
|
|
1359
2708
|
const footnote = this.footnoteRegistry.get(footnoteId);
|
|
1360
2709
|
if (!footnote) continue;
|
|
1361
|
-
|
|
2710
|
+
if (nextPageContinuation) {
|
|
2711
|
+
deferredFootnoteIds.push(...newFootnoteIds.slice(noteIndex));
|
|
2712
|
+
break;
|
|
2713
|
+
}
|
|
2714
|
+
const candidateIds = [...currentFootnoteIds, footnoteId];
|
|
2715
|
+
const candidateHeight = this.measureFootnotesHeight(
|
|
2716
|
+
candidateIds,
|
|
2717
|
+
blockDimensions.contentWidth,
|
|
2718
|
+
currentContinuation,
|
|
2719
|
+
currentPartialFootnotes
|
|
2720
|
+
);
|
|
1362
2721
|
const spaceLeftForFootnotes = availableForFootnotes - currentFootnoteHeight;
|
|
1363
|
-
if (
|
|
2722
|
+
if (candidateHeight + FOOTNOTE_MEASUREMENT_GUARD_PT <= availableForFootnotes) {
|
|
1364
2723
|
currentFootnoteIds.push(footnoteId);
|
|
1365
|
-
currentFootnoteHeight
|
|
2724
|
+
currentFootnoteHeight = candidateHeight;
|
|
1366
2725
|
} else {
|
|
1367
2726
|
if (spaceLeftForFootnotes > 20) {
|
|
1368
2727
|
const { fits, overflow } = this.splitFootnoteToFit(
|
|
1369
2728
|
footnote,
|
|
1370
|
-
|
|
1371
|
-
|
|
2729
|
+
availableForFootnotes,
|
|
2730
|
+
blockDimensions.contentWidth,
|
|
2731
|
+
deferralCannotHelp,
|
|
2732
|
+
{
|
|
2733
|
+
footnoteIds: currentFootnoteIds,
|
|
2734
|
+
continuation: currentContinuation,
|
|
2735
|
+
partialFootnotes: currentPartialFootnotes
|
|
2736
|
+
}
|
|
1372
2737
|
);
|
|
1373
2738
|
if (fits.length > 0) {
|
|
1374
2739
|
currentFootnoteIds.push(footnoteId);
|
|
@@ -1379,78 +2744,70 @@ var DocxodusPagination = (() => {
|
|
|
1379
2744
|
if (overflow.length > 0) {
|
|
1380
2745
|
nextPageContinuation = {
|
|
1381
2746
|
footnoteId,
|
|
2747
|
+
sourceAnchorId: footnote.dataset.sourceAnchorId,
|
|
1382
2748
|
remainingElements: overflow
|
|
1383
2749
|
};
|
|
1384
2750
|
}
|
|
1385
|
-
currentFootnoteHeight =
|
|
2751
|
+
currentFootnoteHeight = this.measureFootnotesHeight(
|
|
2752
|
+
currentFootnoteIds,
|
|
2753
|
+
blockDimensions.contentWidth,
|
|
2754
|
+
currentContinuation,
|
|
2755
|
+
currentPartialFootnotes
|
|
2756
|
+
);
|
|
2757
|
+
if (overflow.length > 0) {
|
|
2758
|
+
deferredFootnoteIds.push(...newFootnoteIds.slice(noteIndex + 1));
|
|
2759
|
+
break;
|
|
2760
|
+
}
|
|
1386
2761
|
} else {
|
|
1387
|
-
deferredFootnoteIds.push(
|
|
2762
|
+
deferredFootnoteIds.push(...newFootnoteIds.slice(noteIndex));
|
|
2763
|
+
break;
|
|
1388
2764
|
}
|
|
1389
2765
|
} else {
|
|
1390
|
-
deferredFootnoteIds.push(
|
|
2766
|
+
deferredFootnoteIds.push(...newFootnoteIds.slice(noteIndex));
|
|
2767
|
+
break;
|
|
1391
2768
|
}
|
|
1392
2769
|
}
|
|
1393
2770
|
}
|
|
1394
|
-
|
|
1395
|
-
const expandedFootnoteSpace = Math.min(maxFootnoteArea, additionalFootnoteHeight + currentFootnoteHeight);
|
|
1396
|
-
const bodySpaceAfterExpansion = effectiveContentHeight - expandedFootnoteSpace;
|
|
1397
|
-
if (blockSpaceWithoutFootnotes <= bodySpaceAfterExpansion - bodyContentUsed) {
|
|
1398
|
-
currentContent.push(this.cloneBlockForPage(block, isFirstOnPage, pageInSection));
|
|
1399
|
-
remainingHeight = bodySpaceAfterExpansion - bodyContentUsed - blockSpaceWithoutFootnotes;
|
|
1400
|
-
prevMarginBottomPt = block.marginBottomPt;
|
|
1401
|
-
currentFootnoteIds.push(...newFootnoteIds);
|
|
1402
|
-
currentFootnoteHeight = expandedFootnoteSpace;
|
|
1403
|
-
} else {
|
|
1404
|
-
finishPage();
|
|
1405
|
-
const newPageFootnoteHeight = allBlockFootnoteIds.length > 0 ? this.measureFootnotesHeight(allBlockFootnoteIds, dims.contentWidth, currentContinuation) : currentContinuation ? this.measureContinuationHeight(currentContinuation, dims.contentWidth) : 0;
|
|
1406
|
-
const newPageMarginTop = this.effectiveBlockMarginTop(
|
|
1407
|
-
block,
|
|
1408
|
-
0,
|
|
1409
|
-
true,
|
|
1410
|
-
pageInSection
|
|
1411
|
-
);
|
|
1412
|
-
const newPageSpace = newPageMarginTop + block.heightPt + block.marginBottomPt;
|
|
1413
|
-
currentContent.push(this.cloneBlockForPage(block, true, pageInSection));
|
|
1414
|
-
remainingHeight = effectiveContentHeight - newPageSpace;
|
|
1415
|
-
prevMarginBottomPt = block.marginBottomPt;
|
|
1416
|
-
currentFootnoteIds = [...currentFootnoteIds, ...allBlockFootnoteIds];
|
|
1417
|
-
currentFootnoteHeight = newPageFootnoteHeight;
|
|
1418
|
-
}
|
|
2771
|
+
currentPageHasFootnoteReference ||= allBlockFootnoteIds.length > 0;
|
|
1419
2772
|
} else {
|
|
1420
|
-
finishPage();
|
|
1421
|
-
|
|
1422
|
-
|
|
1423
|
-
block,
|
|
1424
|
-
0,
|
|
1425
|
-
true,
|
|
1426
|
-
pageInSection
|
|
1427
|
-
);
|
|
1428
|
-
const newPageSpace = newPageMarginTop + block.heightPt + block.marginBottomPt;
|
|
1429
|
-
currentContent.push(this.cloneBlockForPage(block, true, pageInSection));
|
|
1430
|
-
remainingHeight = effectiveContentHeight - newPageSpace;
|
|
1431
|
-
prevMarginBottomPt = block.marginBottomPt;
|
|
1432
|
-
currentFootnoteIds = [...currentFootnoteIds, ...allBlockFootnoteIds];
|
|
1433
|
-
currentFootnoteHeight = newPageFootnoteHeight;
|
|
2773
|
+
finishPage(block.sectionIndex, !pageHasPayload());
|
|
2774
|
+
i--;
|
|
2775
|
+
continue;
|
|
1434
2776
|
}
|
|
1435
2777
|
} else {
|
|
1436
|
-
const tableFragments = this.trySplitSimpleOversizedTable(
|
|
2778
|
+
const tableFragments = this.trySplitSimpleOversizedTable(
|
|
2779
|
+
block,
|
|
2780
|
+
dimensionsFor(block.sectionIndex),
|
|
2781
|
+
block.sectionIndex
|
|
2782
|
+
);
|
|
1437
2783
|
if (tableFragments) {
|
|
1438
2784
|
if (currentContent.length > 0 || currentContinuation) {
|
|
1439
|
-
finishPage();
|
|
2785
|
+
finishPage(block.sectionIndex);
|
|
1440
2786
|
}
|
|
1441
2787
|
blocks.splice(i, 1, ...tableFragments);
|
|
1442
2788
|
i--;
|
|
1443
2789
|
continue;
|
|
1444
2790
|
}
|
|
1445
|
-
if (
|
|
1446
|
-
finishPage();
|
|
2791
|
+
if (pageHasPayload()) {
|
|
2792
|
+
finishPage(block.sectionIndex);
|
|
2793
|
+
i--;
|
|
2794
|
+
continue;
|
|
1447
2795
|
}
|
|
1448
|
-
|
|
1449
|
-
|
|
1450
|
-
|
|
2796
|
+
markSectionPlaced(block.sectionIndex);
|
|
2797
|
+
currentContent.push(this.cloneBlockForPage(
|
|
2798
|
+
block,
|
|
2799
|
+
true,
|
|
2800
|
+
pageInSection(block.sectionIndex)
|
|
2801
|
+
));
|
|
2802
|
+
deferredFootnoteIds.push(...newFootnoteIds);
|
|
2803
|
+
currentPageHasFootnoteReference ||= allBlockFootnoteIds.length > 0;
|
|
2804
|
+
finishPage(block.sectionIndex);
|
|
1451
2805
|
}
|
|
1452
2806
|
}
|
|
1453
2807
|
finishPage();
|
|
2808
|
+
while (currentFootnoteIds.length > 0 || deferredFootnoteIds.length > 0 || (currentContinuation?.remainingElements.length ?? 0) > 0) {
|
|
2809
|
+
finishPage();
|
|
2810
|
+
}
|
|
1454
2811
|
this.pendingFootnoteContinuation = nextPageContinuation;
|
|
1455
2812
|
return pages;
|
|
1456
2813
|
}
|
|
@@ -1475,6 +2832,19 @@ var DocxodusPagination = (() => {
|
|
|
1475
2832
|
if (el.getAttribute("contenteditable") !== null) el.setAttribute("contenteditable", "false");
|
|
1476
2833
|
}
|
|
1477
2834
|
}
|
|
2835
|
+
/** A repeated margin note is presentation, not a second bookmark/link target. */
|
|
2836
|
+
makeClonedMarginCommentInert(root) {
|
|
2837
|
+
this.makeClonedStoryInert(root);
|
|
2838
|
+
const nodes = [root, ...Array.from(root.querySelectorAll("*"))];
|
|
2839
|
+
for (const element of nodes) {
|
|
2840
|
+
element.removeAttribute("id");
|
|
2841
|
+
if (element.localName === "a" && element.getAttribute("href")?.startsWith("#")) {
|
|
2842
|
+
element.removeAttribute("href");
|
|
2843
|
+
element.setAttribute("aria-disabled", "true");
|
|
2844
|
+
element.tabIndex = -1;
|
|
2845
|
+
}
|
|
2846
|
+
}
|
|
2847
|
+
}
|
|
1478
2848
|
/**
|
|
1479
2849
|
* Resolves floating DrawingML objects after their anchor paragraphs have landed on a page.
|
|
1480
2850
|
*
|
|
@@ -1500,7 +2870,7 @@ var DocxodusPagination = (() => {
|
|
|
1500
2870
|
const context = {
|
|
1501
2871
|
staticLeft: toPageX(staticRect.left),
|
|
1502
2872
|
staticTop: toPageY(staticRect.top),
|
|
1503
|
-
lineHeight: paragraph ? (parseFloat(getComputedStyle(paragraph).lineHeight) || staticRect.height) / pixelsPerPoint : staticRect.height / pixelsPerPoint,
|
|
2873
|
+
lineHeight: paragraph ? (parseFloat(this.view.getComputedStyle(paragraph).lineHeight) || staticRect.height) / pixelsPerPoint : staticRect.height / pixelsPerPoint,
|
|
1504
2874
|
paragraphLeft: toPageX(paragraphRect.left),
|
|
1505
2875
|
paragraphTop: toPageY(paragraphRect.top),
|
|
1506
2876
|
paragraphWidth: paragraphRect.width / pixelsPerPoint,
|
|
@@ -1630,36 +3000,48 @@ var DocxodusPagination = (() => {
|
|
|
1630
3000
|
else if (alignment === "outside") fraction = pageNumber % 2 === 1 ? 1 : 0;
|
|
1631
3001
|
return reference.start + (reference.size - objectSize) * fraction;
|
|
1632
3002
|
}
|
|
3003
|
+
/** Charge a physical page before any page-owned partitioning or DOM allocation. */
|
|
3004
|
+
admitPageAllocation() {
|
|
3005
|
+
const prospectivePageCount = this.createdPageCount + 1;
|
|
3006
|
+
this.pageCountCheckpoint?.(prospectivePageCount);
|
|
3007
|
+
this.createdPageCount = prospectivePageCount;
|
|
3008
|
+
}
|
|
1633
3009
|
/**
|
|
1634
3010
|
* Creates a page container element.
|
|
1635
3011
|
*/
|
|
1636
|
-
createPage(dims, pageNumber, sectionIndex, content, pageInSection, footnoteIds = [], footnoteHeight = 0, continuation, partialFootnotes) {
|
|
1637
|
-
|
|
3012
|
+
createPage(dims, pageNumber, sectionIndex, displayedPageNumber, content, pageInSection, footnoteIds = [], footnoteHeight = 0, continuation, partialFootnotes, isSectionFiller = false, pageAlreadyAdmitted = false) {
|
|
3013
|
+
if (!pageAlreadyAdmitted) this.admitPageAllocation();
|
|
3014
|
+
const pageBox = this.document.createElement("div");
|
|
1638
3015
|
pageBox.className = `${this.cssPrefix}box`;
|
|
1639
3016
|
pageBox.style.width = `${dims.pageWidth}pt`;
|
|
1640
3017
|
pageBox.style.height = `${dims.pageHeight}pt`;
|
|
1641
3018
|
pageBox.style.overflow = "hidden";
|
|
1642
3019
|
pageBox.style.position = "relative";
|
|
1643
3020
|
if (this.scale !== 1) {
|
|
1644
|
-
|
|
1645
|
-
|
|
1646
|
-
|
|
1647
|
-
|
|
1648
|
-
|
|
1649
|
-
|
|
1650
|
-
|
|
1651
|
-
|
|
1652
|
-
|
|
3021
|
+
if (this.view.CSS?.supports("zoom", "1")) {
|
|
3022
|
+
pageBox.style.zoom = String(this.scale);
|
|
3023
|
+
} else {
|
|
3024
|
+
pageBox.style.transform = `scale(${this.scale})`;
|
|
3025
|
+
pageBox.style.transformOrigin = "top left";
|
|
3026
|
+
const heightReductionPt = dims.pageHeight * (1 - this.scale);
|
|
3027
|
+
const widthReductionPt = dims.pageWidth * (1 - this.scale);
|
|
3028
|
+
const heightReductionPx = ptToPx(heightReductionPt);
|
|
3029
|
+
const widthReductionPx = ptToPx(widthReductionPt);
|
|
3030
|
+
pageBox.style.marginRight = `-${widthReductionPx}px`;
|
|
3031
|
+
pageBox.style.marginBottom = `${this.pageGap - heightReductionPx}px`;
|
|
3032
|
+
}
|
|
1653
3033
|
}
|
|
1654
3034
|
pageBox.style.willChange = "transform";
|
|
1655
3035
|
pageBox.style.contain = "layout paint";
|
|
1656
3036
|
pageBox.dataset.pageNumber = String(pageNumber);
|
|
1657
3037
|
pageBox.dataset.sectionIndex = String(sectionIndex);
|
|
3038
|
+
pageBox.dataset.displayedPageNumber = String(displayedPageNumber);
|
|
3039
|
+
if (isSectionFiller) pageBox.dataset.sectionFiller = "true";
|
|
1658
3040
|
pageBox.dataset.pageInSection = String(pageInSection);
|
|
1659
|
-
const bands = this.getPageBands(dims, sectionIndex, pageInSection,
|
|
1660
|
-
const headerSource = this.selectHeader(sectionIndex, pageInSection,
|
|
3041
|
+
const bands = this.getPageBands(dims, sectionIndex, pageInSection, displayedPageNumber);
|
|
3042
|
+
const headerSource = isSectionFiller ? void 0 : this.selectHeader(sectionIndex, pageInSection, displayedPageNumber);
|
|
1661
3043
|
if (headerSource) {
|
|
1662
|
-
const headerDiv = document.createElement("div");
|
|
3044
|
+
const headerDiv = this.document.createElement("div");
|
|
1663
3045
|
headerDiv.className = `${this.cssPrefix}header`;
|
|
1664
3046
|
headerDiv.style.position = "absolute";
|
|
1665
3047
|
headerDiv.style.top = `${bands.headerTop}pt`;
|
|
@@ -1681,7 +3063,7 @@ var DocxodusPagination = (() => {
|
|
|
1681
3063
|
}
|
|
1682
3064
|
const contentAreaTop = bands.bodyTop;
|
|
1683
3065
|
const contentAreaHeight = bands.bodyHeight;
|
|
1684
|
-
const contentArea = document.createElement("div");
|
|
3066
|
+
const contentArea = this.document.createElement("div");
|
|
1685
3067
|
contentArea.className = `${this.cssPrefix}content`;
|
|
1686
3068
|
contentArea.style.position = "absolute";
|
|
1687
3069
|
contentArea.style.top = `${contentAreaTop}pt`;
|
|
@@ -1694,12 +3076,46 @@ var DocxodusPagination = (() => {
|
|
|
1694
3076
|
}
|
|
1695
3077
|
pageBox.appendChild(contentArea);
|
|
1696
3078
|
const hasContinuation = continuation && continuation.remainingElements.length > 0;
|
|
1697
|
-
if (footnoteIds.length > 0 || hasContinuation) {
|
|
3079
|
+
if (!isSectionFiller && (footnoteIds.length > 0 || hasContinuation)) {
|
|
1698
3080
|
this.addPageFootnotes(pageBox, footnoteIds, dims, bands, footnoteHeight, continuation, partialFootnotes);
|
|
1699
3081
|
}
|
|
1700
|
-
const
|
|
3082
|
+
const pageCommentIds = [];
|
|
3083
|
+
for (const marker of Array.from(
|
|
3084
|
+
pageBox.querySelectorAll("[data-comment-id]")
|
|
3085
|
+
)) {
|
|
3086
|
+
if (marker.closest(`.${this.cssPrefix}comment-margin`)) continue;
|
|
3087
|
+
const id = marker.dataset.commentId;
|
|
3088
|
+
if (id && this.commentMarginRegistry.has(id) && !pageCommentIds.includes(id)) {
|
|
3089
|
+
pageCommentIds.push(id);
|
|
3090
|
+
}
|
|
3091
|
+
}
|
|
3092
|
+
if (pageCommentIds.length > 0) {
|
|
3093
|
+
const marginColumn = this.document.createElement("aside");
|
|
3094
|
+
marginColumn.className = `${this.cssPrefix}comment-margin`;
|
|
3095
|
+
marginColumn.style.position = "absolute";
|
|
3096
|
+
marginColumn.style.top = `${contentAreaTop}pt`;
|
|
3097
|
+
marginColumn.style.left = `${dims.marginLeft + dims.contentWidth + 3}pt`;
|
|
3098
|
+
marginColumn.style.width = `${Math.max(12, dims.marginRight - 6)}pt`;
|
|
3099
|
+
marginColumn.style.maxHeight = `${contentAreaHeight}pt`;
|
|
3100
|
+
marginColumn.style.overflow = "hidden";
|
|
3101
|
+
marginColumn.style.boxSizing = "border-box";
|
|
3102
|
+
const placedThreadRoots = /* @__PURE__ */ new Set();
|
|
3103
|
+
for (const id of pageCommentIds) {
|
|
3104
|
+
const source = this.commentMarginRegistry.get(id);
|
|
3105
|
+
if (source) {
|
|
3106
|
+
const rootId = source.dataset.commentId ?? id;
|
|
3107
|
+
if (placedThreadRoots.has(rootId)) continue;
|
|
3108
|
+
placedThreadRoots.add(rootId);
|
|
3109
|
+
const clone = source.cloneNode(true);
|
|
3110
|
+
this.makeClonedMarginCommentInert(clone);
|
|
3111
|
+
marginColumn.appendChild(clone);
|
|
3112
|
+
}
|
|
3113
|
+
}
|
|
3114
|
+
pageBox.appendChild(marginColumn);
|
|
3115
|
+
}
|
|
3116
|
+
const footerSource = isSectionFiller ? void 0 : this.selectFooter(sectionIndex, pageInSection, displayedPageNumber);
|
|
1701
3117
|
if (footerSource) {
|
|
1702
|
-
const footerDiv = document.createElement("div");
|
|
3118
|
+
const footerDiv = this.document.createElement("div");
|
|
1703
3119
|
footerDiv.className = `${this.cssPrefix}footer`;
|
|
1704
3120
|
footerDiv.style.position = "absolute";
|
|
1705
3121
|
footerDiv.style.top = "auto";
|
|
@@ -1719,8 +3135,8 @@ var DocxodusPagination = (() => {
|
|
|
1719
3135
|
}
|
|
1720
3136
|
pageBox.appendChild(footerDiv);
|
|
1721
3137
|
}
|
|
1722
|
-
if (this.showPageNumbers) {
|
|
1723
|
-
const pageNum = document.createElement("div");
|
|
3138
|
+
if (this.showPageNumbers && !isSectionFiller) {
|
|
3139
|
+
const pageNum = this.document.createElement("div");
|
|
1724
3140
|
pageNum.className = `${this.cssPrefix}number`;
|
|
1725
3141
|
pageNum.textContent = String(pageNumber);
|
|
1726
3142
|
pageBox.appendChild(pageNum);
|
|
@@ -1743,7 +3159,8 @@ var DocxodusPagination = (() => {
|
|
|
1743
3159
|
}
|
|
1744
3160
|
};
|
|
1745
3161
|
function paginateHtml(html, container, options = {}) {
|
|
1746
|
-
const
|
|
3162
|
+
const ownerDocument = typeof container === "string" ? globalThis.document : container.ownerDocument;
|
|
3163
|
+
const containerEl = typeof container === "string" ? ownerDocument.getElementById(container) : container;
|
|
1747
3164
|
if (!containerEl) {
|
|
1748
3165
|
throw new Error("Container element not found");
|
|
1749
3166
|
}
|