docxodus 9.8.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 +2024 -388
- 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 +2912 -437
- package/dist/embed.iife.js +2911 -436
- 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 +1937 -380
- package/dist/pagination.d.ts +277 -15
- package/dist/pagination.d.ts.map +1 -1
- package/dist/pagination.js +2055 -438
- 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 +46 -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.NonGeneric.wasm +0 -0
- package/dist/wasm/_framework/System.Collections.NonGeneric.wasm.br +0 -0
- package/dist/wasm/_framework/System.Collections.Specialized.wasm +0 -0
- package/dist/wasm/_framework/System.Collections.Specialized.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.ComponentModel.Primitives.wasm +0 -0
- package/dist/wasm/_framework/System.ComponentModel.Primitives.wasm.br +0 -0
- package/dist/wasm/_framework/System.ComponentModel.TypeConverter.wasm +0 -0
- package/dist/wasm/_framework/System.ComponentModel.TypeConverter.wasm.br +0 -0
- package/dist/wasm/_framework/System.ComponentModel.wasm +0 -0
- package/dist/wasm/_framework/System.ComponentModel.wasm.br +0 -0
- package/dist/wasm/_framework/System.Console.wasm +0 -0
- package/dist/wasm/_framework/System.Console.wasm.br +0 -0
- package/dist/wasm/_framework/System.Diagnostics.Process.wasm +0 -0
- package/dist/wasm/_framework/System.Diagnostics.Process.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.IO.Pipelines.wasm +0 -0
- package/dist/wasm/_framework/System.IO.Pipelines.wasm.br +0 -0
- package/dist/wasm/_framework/System.Linq.Expressions.wasm +0 -0
- package/dist/wasm/_framework/System.Linq.Expressions.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.Memory.wasm +0 -0
- package/dist/wasm/_framework/System.Memory.wasm.br +0 -0
- package/dist/wasm/_framework/System.Net.Http.wasm +0 -0
- package/dist/wasm/_framework/System.Net.Http.wasm.br +0 -0
- package/dist/wasm/_framework/System.Net.Primitives.wasm +0 -0
- package/dist/wasm/_framework/System.Net.Primitives.wasm.br +0 -0
- package/dist/wasm/_framework/System.ObjectModel.wasm +0 -0
- package/dist/wasm/_framework/System.ObjectModel.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.Encodings.Web.wasm +0 -0
- package/dist/wasm/_framework/System.Text.Encodings.Web.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/System.Xml.Linq.wasm +0 -0
- package/dist/wasm/_framework/System.Xml.Linq.wasm.br +0 -0
- package/dist/wasm/_framework/System.Xml.XDocument.wasm +0 -0
- package/dist/wasm/_framework/System.Xml.XDocument.wasm.br +0 -0
- package/dist/wasm/_framework/System.wasm +0 -0
- package/dist/wasm/_framework/System.wasm.br +0 -0
- package/dist/wasm/_framework/dotnet.boot.js +38 -38
- package/dist/wasm/_framework/dotnet.boot.js.br +0 -0
- package/dist/wasm/_framework/dotnet.js +1 -1
- package/dist/wasm/_framework/dotnet.js.br +0 -0
- package/dist/wasm/_framework/dotnet.native.js +3 -3
- package/dist/wasm/_framework/dotnet.native.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/wasm/_framework/dotnet.runtime.js +1 -1
- package/dist/wasm/_framework/dotnet.runtime.js.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 +24 -7
|
@@ -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,
|
|
@@ -125,8 +152,96 @@ var DocxodusPagination = (() => {
|
|
|
125
152
|
}
|
|
126
153
|
|
|
127
154
|
// src/pagination.ts
|
|
155
|
+
function samePageBox(a, b) {
|
|
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;
|
|
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
|
+
}
|
|
128
242
|
var MAX_FOOTNOTE_AREA_RATIO = 0.6;
|
|
129
|
-
var
|
|
243
|
+
var FOOTNOTE_MEASUREMENT_GUARD_PT = 2;
|
|
244
|
+
var FOOTNOTE_SOURCE_POSITION_ATTR = "data-pagination-footnote-source-position";
|
|
130
245
|
var PaginationEngine = class {
|
|
131
246
|
/**
|
|
132
247
|
* Creates a new pagination engine.
|
|
@@ -136,24 +251,46 @@ var DocxodusPagination = (() => {
|
|
|
136
251
|
* @param options - Pagination options
|
|
137
252
|
*/
|
|
138
253
|
constructor(staging, container, options = {}) {
|
|
254
|
+
this.createdPageCount = 0;
|
|
255
|
+
this.footnoteSeparator = null;
|
|
256
|
+
this.footnoteContinuationSeparator = null;
|
|
257
|
+
this.footnoteLayoutLikeWord8 = false;
|
|
139
258
|
this.pendingFootnoteContinuation = null;
|
|
140
259
|
/** Per-section `w:pgNumType` (start / format), read off the section wrappers. */
|
|
141
260
|
this.pageNumbering = /* @__PURE__ */ new Map();
|
|
142
|
-
this.
|
|
143
|
-
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;
|
|
144
267
|
if (!this.stagingElement) {
|
|
145
268
|
throw new Error("Staging element not found");
|
|
146
269
|
}
|
|
147
270
|
if (!this.containerElement) {
|
|
148
271
|
throw new Error("Container element not found");
|
|
149
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;
|
|
150
282
|
this.scale = options.scale ?? 1;
|
|
151
283
|
this.cssPrefix = options.cssPrefix ?? "page-";
|
|
152
284
|
this.showPageNumbers = options.showPageNumbers ?? true;
|
|
153
285
|
this.pageGap = options.pageGap ?? 20;
|
|
154
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;
|
|
155
291
|
this.hfRegistry = /* @__PURE__ */ new Map();
|
|
156
292
|
this.footnoteRegistry = /* @__PURE__ */ new Map();
|
|
293
|
+
this.commentMarginRegistry = /* @__PURE__ */ new Map();
|
|
157
294
|
}
|
|
158
295
|
/**
|
|
159
296
|
* Runs the pagination process.
|
|
@@ -161,31 +298,469 @@ var DocxodusPagination = (() => {
|
|
|
161
298
|
* @returns PaginationResult with page information
|
|
162
299
|
*/
|
|
163
300
|
paginate() {
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
"
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
this.
|
|
179
|
-
this.
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
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
|
|
578
|
+
});
|
|
579
|
+
}
|
|
580
|
+
}
|
|
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
|
+
}
|
|
730
|
+
}
|
|
731
|
+
}
|
|
732
|
+
}
|
|
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
|
+
}
|
|
189
764
|
}
|
|
190
765
|
/** Read each section's `w:pgNumType` off its wrapper (see {@link SectionPageNumbering}). */
|
|
191
766
|
parsePageNumbering(sections) {
|
|
@@ -229,9 +804,8 @@ var DocxodusPagination = (() => {
|
|
|
229
804
|
if (markers.length === 0) continue;
|
|
230
805
|
const sectionIndex = parseInt(box.dataset.sectionIndex || "0", 10);
|
|
231
806
|
const pageNumber = parseInt(box.dataset.pageNumber || "1", 10);
|
|
232
|
-
const pageInSection = parseInt(box.dataset.pageInSection || "1", 10);
|
|
233
807
|
const numbering = this.pageNumbering.get(sectionIndex) ?? {};
|
|
234
|
-
const displayed =
|
|
808
|
+
const displayed = parseInt(box.dataset.displayedPageNumber || String(pageNumber), 10);
|
|
235
809
|
for (const marker of Array.from(markers)) {
|
|
236
810
|
const kind = marker.dataset.field;
|
|
237
811
|
if (kind !== "PAGE" && kind !== "NUMPAGES") continue;
|
|
@@ -243,17 +817,26 @@ var DocxodusPagination = (() => {
|
|
|
243
817
|
/**
|
|
244
818
|
* Measures all content blocks in a section.
|
|
245
819
|
*/
|
|
246
|
-
measureBlocks(section, dims) {
|
|
820
|
+
measureBlocks(section, dims, sectionIndex) {
|
|
247
821
|
const blocks = [];
|
|
248
822
|
const children = Array.from(section.children);
|
|
249
823
|
for (const child of children) {
|
|
824
|
+
this.checkpoint();
|
|
250
825
|
if (child.dataset.sectionIndex !== void 0) {
|
|
251
|
-
const
|
|
826
|
+
const nestedSectionIndex = parseInt(child.dataset.sectionIndex || String(sectionIndex), 10);
|
|
827
|
+
const nestedBlocks = this.measureBlocks(child, dims, nestedSectionIndex);
|
|
252
828
|
blocks.push(...nestedBlocks);
|
|
253
829
|
continue;
|
|
254
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
|
+
}
|
|
255
838
|
const rect = child.getBoundingClientRect();
|
|
256
|
-
const style =
|
|
839
|
+
const style = this.view.getComputedStyle(child);
|
|
257
840
|
const marginTopPx = parseFloat(style.marginTop) || 0;
|
|
258
841
|
const marginBottomPx = parseFloat(style.marginBottom) || 0;
|
|
259
842
|
const heightPt = pxToPt(rect.height);
|
|
@@ -262,24 +845,176 @@ var DocxodusPagination = (() => {
|
|
|
262
845
|
const isPageBreak = child.dataset.pageBreak === "true" || child.classList.contains(`${this.cssPrefix}break`);
|
|
263
846
|
blocks.push({
|
|
264
847
|
element: child,
|
|
848
|
+
sectionIndex,
|
|
265
849
|
heightPt,
|
|
266
850
|
marginTopPt,
|
|
267
851
|
marginBottomPt,
|
|
268
852
|
keepWithNext: child.dataset.keepWithNext === "true",
|
|
269
853
|
keepLines: child.dataset.keepLines === "true",
|
|
270
854
|
pageBreakBefore: child.dataset.pageBreakBefore === "true",
|
|
271
|
-
isPageBreak
|
|
855
|
+
isPageBreak,
|
|
856
|
+
isWordParagraph: this.isWordParagraphElement(child)
|
|
272
857
|
});
|
|
273
858
|
}
|
|
274
859
|
return blocks;
|
|
275
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
|
+
}
|
|
950
|
+
/**
|
|
951
|
+
* Flows a multi-column (`w:cols`) section's children into CSS-multicol container
|
|
952
|
+
* blocks. Word lays such a section out as N columns inside the same body extent;
|
|
953
|
+
* a balanced `column-count` container reproduces that geometry, and the paginator
|
|
954
|
+
* then places each container as one ordinary measured block. A container grows
|
|
955
|
+
* greedily until its balanced height would exceed the smallest page body available
|
|
956
|
+
* to the section, so a long columned section still splits across pages at block
|
|
957
|
+
* boundaries. Each child lands in exactly one container, so anchors never
|
|
958
|
+
* duplicate. An explicit page break child passes through as its own block, which
|
|
959
|
+
* ends the current container and lets the normal flow logic turn the page.
|
|
960
|
+
*/
|
|
961
|
+
buildColumnBlocks(section, dims, sectionIndex, columnCount, columnGapPt) {
|
|
962
|
+
const children = Array.from(section.children);
|
|
963
|
+
const blocks = [];
|
|
964
|
+
const maxFragmentHeight = this.smallestEffectiveContentHeight(dims, sectionIndex);
|
|
965
|
+
const isBreak = (child) => child.dataset.pageBreak === "true" || child.classList.contains(`${this.cssPrefix}break`);
|
|
966
|
+
const makeContainer = (slice) => {
|
|
967
|
+
const container = this.document.createElement("div");
|
|
968
|
+
container.style.columnCount = String(columnCount);
|
|
969
|
+
container.style.columnGap = `${columnGapPt}pt`;
|
|
970
|
+
for (const child of slice) {
|
|
971
|
+
container.appendChild(child.cloneNode(true));
|
|
972
|
+
}
|
|
973
|
+
return container;
|
|
974
|
+
};
|
|
975
|
+
let start = 0;
|
|
976
|
+
while (start < children.length) {
|
|
977
|
+
this.checkpoint();
|
|
978
|
+
if (isBreak(children[start])) {
|
|
979
|
+
blocks.push(this.measureElement(children[start], dims, sectionIndex));
|
|
980
|
+
start++;
|
|
981
|
+
continue;
|
|
982
|
+
}
|
|
983
|
+
let end = start + 1;
|
|
984
|
+
let container = makeContainer(children.slice(start, end));
|
|
985
|
+
let measured = this.measureElement(container, dims, sectionIndex);
|
|
986
|
+
while (end < children.length && !isBreak(children[end])) {
|
|
987
|
+
this.checkpoint();
|
|
988
|
+
const candidate = makeContainer(children.slice(start, end + 1));
|
|
989
|
+
const candidateMeasured = this.measureElement(candidate, dims, sectionIndex);
|
|
990
|
+
if (candidateMeasured.heightPt > maxFragmentHeight) break;
|
|
991
|
+
container = candidate;
|
|
992
|
+
measured = candidateMeasured;
|
|
993
|
+
end++;
|
|
994
|
+
}
|
|
995
|
+
blocks.push({
|
|
996
|
+
element: container,
|
|
997
|
+
sectionIndex,
|
|
998
|
+
heightPt: measured.heightPt,
|
|
999
|
+
marginTopPt: measured.marginTopPt,
|
|
1000
|
+
marginBottomPt: measured.marginBottomPt,
|
|
1001
|
+
keepWithNext: false,
|
|
1002
|
+
keepLines: false,
|
|
1003
|
+
pageBreakBefore: false,
|
|
1004
|
+
isPageBreak: false,
|
|
1005
|
+
isWordParagraph: false
|
|
1006
|
+
});
|
|
1007
|
+
start = end;
|
|
1008
|
+
}
|
|
1009
|
+
return blocks;
|
|
1010
|
+
}
|
|
276
1011
|
/**
|
|
277
1012
|
* Measures one element in the same hidden staging context used for the source blocks.
|
|
278
1013
|
* This is intentionally DOM-based: table row heights cannot be inferred from individual
|
|
279
1014
|
* rows because wrapping and collapsed borders change the height of a fragment.
|
|
280
1015
|
*/
|
|
281
|
-
measureElement(element, dims) {
|
|
282
|
-
const measurementHost = document.createElement("div");
|
|
1016
|
+
measureElement(element, dims, sectionIndex) {
|
|
1017
|
+
const measurementHost = this.document.createElement("div");
|
|
283
1018
|
measurementHost.style.position = "absolute";
|
|
284
1019
|
measurementHost.style.visibility = "hidden";
|
|
285
1020
|
measurementHost.style.left = "-9999px";
|
|
@@ -288,16 +1023,18 @@ var DocxodusPagination = (() => {
|
|
|
288
1023
|
measurementHost.appendChild(measuredElement);
|
|
289
1024
|
this.stagingElement.appendChild(measurementHost);
|
|
290
1025
|
const rect = measuredElement.getBoundingClientRect();
|
|
291
|
-
const style =
|
|
1026
|
+
const style = this.view.getComputedStyle(measuredElement);
|
|
292
1027
|
const measured = {
|
|
293
1028
|
element,
|
|
1029
|
+
sectionIndex,
|
|
294
1030
|
heightPt: pxToPt(rect.height),
|
|
295
1031
|
marginTopPt: pxToPt(parseFloat(style.marginTop) || 0),
|
|
296
1032
|
marginBottomPt: pxToPt(parseFloat(style.marginBottom) || 0),
|
|
297
1033
|
keepWithNext: element.dataset.keepWithNext === "true",
|
|
298
1034
|
keepLines: element.dataset.keepLines === "true",
|
|
299
1035
|
pageBreakBefore: element.dataset.pageBreakBefore === "true",
|
|
300
|
-
isPageBreak: element.dataset.pageBreak === "true" || element.classList.contains(`${this.cssPrefix}break`)
|
|
1036
|
+
isPageBreak: element.dataset.pageBreak === "true" || element.classList.contains(`${this.cssPrefix}break`),
|
|
1037
|
+
isWordParagraph: this.isWordParagraphElement(element)
|
|
301
1038
|
};
|
|
302
1039
|
this.stagingElement.removeChild(measurementHost);
|
|
303
1040
|
return measured;
|
|
@@ -329,10 +1066,15 @@ var DocxodusPagination = (() => {
|
|
|
329
1066
|
* collapsed-margin rules as normal block placement. The trailing margin is
|
|
330
1067
|
* intentionally excluded, matching the individual block fit check.
|
|
331
1068
|
*/
|
|
332
|
-
measureKeepWithNextChainBodyHeight(chain, previousMarginBottomPt, isFirstOnPage) {
|
|
1069
|
+
measureKeepWithNextChainBodyHeight(chain, previousMarginBottomPt, isFirstOnPage, pageInSection) {
|
|
333
1070
|
const firstBlock = chain[0];
|
|
334
1071
|
if (!firstBlock) return 0;
|
|
335
|
-
const firstMarginTop =
|
|
1072
|
+
const firstMarginTop = this.effectiveBlockMarginTop(
|
|
1073
|
+
firstBlock,
|
|
1074
|
+
previousMarginBottomPt,
|
|
1075
|
+
isFirstOnPage,
|
|
1076
|
+
pageInSection
|
|
1077
|
+
);
|
|
336
1078
|
let bodyHeight = firstMarginTop + firstBlock.heightPt;
|
|
337
1079
|
for (let index = 1; index < chain.length; index++) {
|
|
338
1080
|
const previousBlock = chain[index - 1];
|
|
@@ -341,6 +1083,35 @@ var DocxodusPagination = (() => {
|
|
|
341
1083
|
}
|
|
342
1084
|
return bodyHeight;
|
|
343
1085
|
}
|
|
1086
|
+
/** Word paragraphs render as `p`, or as `h1`–`h6` when their style has an outline level. */
|
|
1087
|
+
isWordParagraphElement(element) {
|
|
1088
|
+
return element.tagName === "P" || /^H[1-6]$/.test(element.tagName);
|
|
1089
|
+
}
|
|
1090
|
+
shouldSuppressPageTopSpacing(block, isFirstOnPage, pageInSection) {
|
|
1091
|
+
return isFirstOnPage && pageInSection > 1 && block.isWordParagraph === true;
|
|
1092
|
+
}
|
|
1093
|
+
/**
|
|
1094
|
+
* Resolves the part of a block's top margin that consumes the current page.
|
|
1095
|
+
*
|
|
1096
|
+
* In Word's native DOCX layout, paragraph space-before is suppressed when a paragraph is the
|
|
1097
|
+
* first body block on a later page of the SAME section. The first page of a document/section is
|
|
1098
|
+
* the exception and keeps its spacing. Tables and other block margins are not paragraph spacing,
|
|
1099
|
+
* so they continue to use the ordinary CSS collapsing rule.
|
|
1100
|
+
*/
|
|
1101
|
+
effectiveBlockMarginTop(block, previousMarginBottomPt, isFirstOnPage, pageInSection) {
|
|
1102
|
+
if (this.shouldSuppressPageTopSpacing(block, isFirstOnPage, pageInSection)) {
|
|
1103
|
+
return 0;
|
|
1104
|
+
}
|
|
1105
|
+
return isFirstOnPage ? block.marginTopPt : Math.max(block.marginTopPt, previousMarginBottomPt) - previousMarginBottomPt;
|
|
1106
|
+
}
|
|
1107
|
+
/** Clone a source block with the same page-top spacing decision used by the height budget. */
|
|
1108
|
+
cloneBlockForPage(block, isFirstOnPage, pageInSection) {
|
|
1109
|
+
const clone = block.element.cloneNode(true);
|
|
1110
|
+
if (this.shouldSuppressPageTopSpacing(block, isFirstOnPage, pageInSection)) {
|
|
1111
|
+
clone.style.setProperty("margin-top", "0", "important");
|
|
1112
|
+
}
|
|
1113
|
+
return clone;
|
|
1114
|
+
}
|
|
344
1115
|
/**
|
|
345
1116
|
* Finds footnote references introduced by a sequence of blocks, preserving
|
|
346
1117
|
* document order and excluding references already assigned to the page.
|
|
@@ -349,6 +1120,7 @@ var DocxodusPagination = (() => {
|
|
|
349
1120
|
const knownIds = new Set(existingFootnoteIds);
|
|
350
1121
|
const newIds = [];
|
|
351
1122
|
for (const block of blocks) {
|
|
1123
|
+
this.checkpoint();
|
|
352
1124
|
for (const id of this.extractFootnoteRefs(block.element)) {
|
|
353
1125
|
if (!knownIds.has(id)) {
|
|
354
1126
|
knownIds.add(id);
|
|
@@ -366,8 +1138,8 @@ var DocxodusPagination = (() => {
|
|
|
366
1138
|
smallestEffectiveContentHeight(dims, sectionIndex) {
|
|
367
1139
|
return Math.min(
|
|
368
1140
|
this.getPageBands(dims, sectionIndex, 1, 1).bodyHeight,
|
|
369
|
-
this.getPageBands(dims, sectionIndex, 2,
|
|
370
|
-
this.getPageBands(dims, sectionIndex,
|
|
1141
|
+
this.getPageBands(dims, sectionIndex, 2, 2).bodyHeight,
|
|
1142
|
+
this.getPageBands(dims, sectionIndex, 3, 3).bodyHeight
|
|
371
1143
|
);
|
|
372
1144
|
}
|
|
373
1145
|
/**
|
|
@@ -405,10 +1177,11 @@ var DocxodusPagination = (() => {
|
|
|
405
1177
|
if (wrapper.tagName !== "DIV" || wrapper.children.length !== 1 || block.keepWithNext || block.keepLines || block.pageBreakBefore || block.isPageBreak) {
|
|
406
1178
|
return null;
|
|
407
1179
|
}
|
|
408
|
-
const
|
|
409
|
-
if (!
|
|
1180
|
+
const tableElement = wrapper.firstElementChild;
|
|
1181
|
+
if (!tableElement || tableElement.localName !== "table") {
|
|
410
1182
|
return null;
|
|
411
1183
|
}
|
|
1184
|
+
const table = tableElement;
|
|
412
1185
|
const body = table.tBodies.length === 1 ? table.tBodies[0] : null;
|
|
413
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]")) {
|
|
414
1187
|
return null;
|
|
@@ -422,8 +1195,10 @@ var DocxodusPagination = (() => {
|
|
|
422
1195
|
const groups = [];
|
|
423
1196
|
let start = 0;
|
|
424
1197
|
while (start < rows.length) {
|
|
1198
|
+
this.checkpoint();
|
|
425
1199
|
let end = start;
|
|
426
1200
|
while (end < rows.length) {
|
|
1201
|
+
this.checkpoint();
|
|
427
1202
|
const candidate = this.createSimpleTableFragment(
|
|
428
1203
|
wrapper,
|
|
429
1204
|
table,
|
|
@@ -431,7 +1206,7 @@ var DocxodusPagination = (() => {
|
|
|
431
1206
|
rows.slice(start, end + 1),
|
|
432
1207
|
start === 0
|
|
433
1208
|
);
|
|
434
|
-
const measured = this.measureElement(candidate, dims);
|
|
1209
|
+
const measured = this.measureElement(candidate, dims, block.sectionIndex);
|
|
435
1210
|
if (measured.heightPt > maximumFragmentHeight) {
|
|
436
1211
|
break;
|
|
437
1212
|
}
|
|
@@ -463,7 +1238,7 @@ var DocxodusPagination = (() => {
|
|
|
463
1238
|
if (!isLast) {
|
|
464
1239
|
fragment.style.setProperty("margin-bottom", "0", "important");
|
|
465
1240
|
}
|
|
466
|
-
const measured = this.measureElement(fragment, dims);
|
|
1241
|
+
const measured = this.measureElement(fragment, dims, block.sectionIndex);
|
|
467
1242
|
if (measured.heightPt + measured.marginTopPt + measured.marginBottomPt > minimumContentHeight) {
|
|
468
1243
|
return null;
|
|
469
1244
|
}
|
|
@@ -478,35 +1253,198 @@ var DocxodusPagination = (() => {
|
|
|
478
1253
|
return fragments;
|
|
479
1254
|
}
|
|
480
1255
|
/**
|
|
481
|
-
*
|
|
482
|
-
*
|
|
483
|
-
*
|
|
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.
|
|
484
1262
|
*/
|
|
485
|
-
paragraphFragmentEndpoints(paragraph) {
|
|
486
|
-
const
|
|
487
|
-
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;
|
|
488
1270
|
let textNode;
|
|
489
1271
|
while (textNode = walker.nextNode()) {
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
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;
|
|
493
1317
|
let match;
|
|
494
|
-
|
|
495
|
-
|
|
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 });
|
|
496
1323
|
}
|
|
497
|
-
|
|
498
|
-
|
|
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);
|
|
499
1362
|
}
|
|
500
1363
|
}
|
|
501
1364
|
return endpoints;
|
|
502
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
|
+
}
|
|
503
1438
|
/**
|
|
504
1439
|
* Whether a range contains visible text after ignoring bidi/zero-width marks.
|
|
505
1440
|
* Paragraph fragmentation deliberately excludes non-textual descendants, so
|
|
506
1441
|
* this is enough to reject empty head or tail fragments.
|
|
507
1442
|
*/
|
|
508
1443
|
hasVisibleFragmentText(fragment) {
|
|
509
|
-
return (fragment.textContent || "")
|
|
1444
|
+
return this.hasVisibleText(fragment.textContent || "");
|
|
1445
|
+
}
|
|
1446
|
+
hasVisibleText(text) {
|
|
1447
|
+
return text.replace(/[\u200B-\u200F\uFEFF]/g, "").trim().length > 0;
|
|
510
1448
|
}
|
|
511
1449
|
/**
|
|
512
1450
|
* Phase one only handles text-like paragraph descendants. Objects, explicit
|
|
@@ -515,13 +1453,25 @@ var DocxodusPagination = (() => {
|
|
|
515
1453
|
* fallback instead of risking broken content or duplicate anchors.
|
|
516
1454
|
*/
|
|
517
1455
|
canFragmentParagraph(block) {
|
|
518
|
-
if (!this.fragmentParagraphs
|
|
1456
|
+
if (!this.fragmentParagraphs) {
|
|
519
1457
|
return false;
|
|
520
1458
|
}
|
|
521
1459
|
const paragraph = block.element;
|
|
522
1460
|
if (block.keepWithNext || block.keepLines || block.pageBreakBefore || block.isPageBreak || paragraph.dataset.widowControl === "true" || paragraph.hasAttribute("contenteditable")) {
|
|
523
1461
|
return false;
|
|
524
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
|
+
}
|
|
525
1475
|
const unsupportedDescendants = [
|
|
526
1476
|
"br",
|
|
527
1477
|
"img",
|
|
@@ -552,21 +1502,27 @@ var DocxodusPagination = (() => {
|
|
|
552
1502
|
"fieldset",
|
|
553
1503
|
"details",
|
|
554
1504
|
"[data-footnote-id]",
|
|
555
|
-
"[data-list-marker]",
|
|
556
|
-
"[data-anchor]",
|
|
557
|
-
"[id]",
|
|
558
1505
|
"[contenteditable]"
|
|
559
1506
|
].join(", ");
|
|
560
1507
|
if (paragraph.querySelector(unsupportedDescendants)) {
|
|
561
1508
|
return false;
|
|
562
1509
|
}
|
|
563
|
-
const
|
|
564
|
-
|
|
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") {
|
|
565
1521
|
return false;
|
|
566
1522
|
}
|
|
567
1523
|
for (const descendant of Array.from(paragraph.querySelectorAll("*"))) {
|
|
568
|
-
const style =
|
|
569
|
-
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") {
|
|
570
1526
|
return false;
|
|
571
1527
|
}
|
|
572
1528
|
}
|
|
@@ -593,58 +1549,122 @@ var DocxodusPagination = (() => {
|
|
|
593
1549
|
return fragment;
|
|
594
1550
|
}
|
|
595
1551
|
/**
|
|
596
|
-
*
|
|
597
|
-
*
|
|
598
|
-
*
|
|
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`.
|
|
599
1556
|
*/
|
|
600
|
-
|
|
601
|
-
|
|
602
|
-
|
|
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");
|
|
603
1565
|
}
|
|
604
|
-
const
|
|
605
|
-
const
|
|
606
|
-
|
|
607
|
-
|
|
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");
|
|
608
1571
|
}
|
|
609
|
-
|
|
610
|
-
|
|
611
|
-
|
|
612
|
-
|
|
613
|
-
|
|
614
|
-
|
|
615
|
-
|
|
616
|
-
|
|
617
|
-
|
|
618
|
-
|
|
619
|
-
|
|
620
|
-
|
|
621
|
-
|
|
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
|
+
}
|
|
622
1590
|
}
|
|
623
|
-
|
|
624
|
-
|
|
625
|
-
|
|
626
|
-
|
|
627
|
-
|
|
628
|
-
|
|
629
|
-
|
|
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
|
+
}
|
|
630
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
|
+
};
|
|
631
1631
|
}
|
|
632
1632
|
if (!best) {
|
|
633
|
-
return
|
|
1633
|
+
return endpoints.length > 0 ? { kind: "no-fit" } : { kind: "indivisible" };
|
|
634
1634
|
}
|
|
635
|
-
const tailRange = document.createRange();
|
|
1635
|
+
const tailRange = this.document.createRange();
|
|
636
1636
|
tailRange.setStart(best.endpoint.node, best.endpoint.offset);
|
|
637
1637
|
tailRange.setEnd(paragraph, paragraph.childNodes.length);
|
|
638
1638
|
const tailContents = tailRange.cloneContents();
|
|
639
|
-
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) {
|
|
640
1655
|
return null;
|
|
641
1656
|
}
|
|
642
|
-
const
|
|
643
|
-
|
|
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);
|
|
644
1664
|
return [
|
|
645
1665
|
{
|
|
646
|
-
...
|
|
647
|
-
element:
|
|
1666
|
+
...headMeasured,
|
|
1667
|
+
element: split.head,
|
|
648
1668
|
keepWithNext: false,
|
|
649
1669
|
keepLines: false,
|
|
650
1670
|
pageBreakBefore: false,
|
|
@@ -652,7 +1672,7 @@ var DocxodusPagination = (() => {
|
|
|
652
1672
|
},
|
|
653
1673
|
{
|
|
654
1674
|
...tailMeasured,
|
|
655
|
-
element: tail,
|
|
1675
|
+
element: split.tail,
|
|
656
1676
|
keepWithNext: false,
|
|
657
1677
|
keepLines: false,
|
|
658
1678
|
pageBreakBefore: false,
|
|
@@ -723,7 +1743,7 @@ var DocxodusPagination = (() => {
|
|
|
723
1743
|
const registry = /* @__PURE__ */ new Map();
|
|
724
1744
|
const registryEl = this.stagingElement.querySelector("#pagination-footnote-registry");
|
|
725
1745
|
if (!registryEl) return registry;
|
|
726
|
-
const entries = Array.from(registryEl.
|
|
1746
|
+
const entries = Array.from(registryEl.children).filter((entry) => entry instanceof this.view.HTMLElement && entry.hasAttribute("data-footnote-id"));
|
|
727
1747
|
for (const entry of entries) {
|
|
728
1748
|
const footnoteId = entry.dataset.footnoteId;
|
|
729
1749
|
if (footnoteId) {
|
|
@@ -732,6 +1752,68 @@ var DocxodusPagination = (() => {
|
|
|
732
1752
|
}
|
|
733
1753
|
return registry;
|
|
734
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
|
+
}
|
|
735
1817
|
/**
|
|
736
1818
|
* Extracts footnote reference IDs from an element.
|
|
737
1819
|
*/
|
|
@@ -746,6 +1828,92 @@ var DocxodusPagination = (() => {
|
|
|
746
1828
|
}
|
|
747
1829
|
return ids;
|
|
748
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
|
+
}
|
|
749
1917
|
/**
|
|
750
1918
|
* Measures the height of footnotes for given IDs (in points).
|
|
751
1919
|
* Creates a temporary container to measure the footnotes.
|
|
@@ -753,140 +1921,246 @@ var DocxodusPagination = (() => {
|
|
|
753
1921
|
* @param contentWidth - Width for measurement
|
|
754
1922
|
* @param continuation - Optional continuation content to include first
|
|
755
1923
|
*/
|
|
756
|
-
measureFootnotesHeight(footnoteIds, contentWidth, continuation) {
|
|
1924
|
+
measureFootnotesHeight(footnoteIds, contentWidth, continuation, partialFootnotes) {
|
|
757
1925
|
const hasContinuation = continuation && continuation.remainingElements.length > 0;
|
|
758
|
-
if (footnoteIds.length === 0 && !hasContinuation
|
|
1926
|
+
if (footnoteIds.length === 0 && !hasContinuation) {
|
|
759
1927
|
return 0;
|
|
760
1928
|
}
|
|
761
|
-
|
|
1929
|
+
if (this.footnoteRegistry.size === 0 && !hasContinuation) {
|
|
1930
|
+
return 0;
|
|
1931
|
+
}
|
|
1932
|
+
const measureContainer = this.document.createElement("div");
|
|
762
1933
|
measureContainer.style.position = "absolute";
|
|
763
1934
|
measureContainer.style.visibility = "hidden";
|
|
764
1935
|
measureContainer.style.width = `${contentWidth}pt`;
|
|
765
1936
|
measureContainer.style.left = "-9999px";
|
|
766
1937
|
measureContainer.className = this.cssPrefix + "footnotes";
|
|
767
|
-
|
|
768
|
-
measureContainer.appendChild(hr);
|
|
1938
|
+
this.appendFootnoteSeparator(measureContainer, Boolean(hasContinuation));
|
|
769
1939
|
if (hasContinuation) {
|
|
770
|
-
|
|
771
|
-
contWrapper.className = "footnote-continuation";
|
|
772
|
-
for (const el of continuation.remainingElements) {
|
|
773
|
-
contWrapper.appendChild(el.cloneNode(true));
|
|
774
|
-
}
|
|
775
|
-
measureContainer.appendChild(contWrapper);
|
|
1940
|
+
measureContainer.appendChild(this.createFootnoteContinuationWrapper(continuation));
|
|
776
1941
|
}
|
|
1942
|
+
const partialById = new Map(
|
|
1943
|
+
partialFootnotes?.map((partial) => [partial.footnoteId, partial]) ?? []
|
|
1944
|
+
);
|
|
777
1945
|
for (const id of footnoteIds) {
|
|
1946
|
+
this.checkpoint();
|
|
778
1947
|
const footnote = this.footnoteRegistry.get(id);
|
|
779
1948
|
if (footnote) {
|
|
780
|
-
|
|
1949
|
+
const partial = partialById.get(id);
|
|
1950
|
+
measureContainer.appendChild(partial ? this.createPartialFootnoteItem(footnote, partial.fittingElements) : footnote.cloneNode(true));
|
|
781
1951
|
}
|
|
782
1952
|
}
|
|
783
|
-
this.
|
|
784
|
-
|
|
785
|
-
|
|
786
|
-
|
|
787
|
-
|
|
1953
|
+
this.noteMeasurementHost().appendChild(measureContainer);
|
|
1954
|
+
try {
|
|
1955
|
+
return pxToPt(measureContainer.getBoundingClientRect().height);
|
|
1956
|
+
} finally {
|
|
1957
|
+
measureContainer.remove();
|
|
1958
|
+
}
|
|
788
1959
|
}
|
|
789
1960
|
/**
|
|
790
|
-
*
|
|
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.
|
|
791
1965
|
*/
|
|
792
|
-
|
|
793
|
-
|
|
794
|
-
|
|
795
|
-
|
|
796
|
-
const measureContainer = document.createElement("div");
|
|
1966
|
+
splitContinuationForPage(continuation, availableHeightPt, contentWidth) {
|
|
1967
|
+
const fitting = [];
|
|
1968
|
+
let remaining = [];
|
|
1969
|
+
const measureContainer = this.document.createElement("div");
|
|
797
1970
|
measureContainer.style.position = "absolute";
|
|
798
1971
|
measureContainer.style.visibility = "hidden";
|
|
799
1972
|
measureContainer.style.width = `${contentWidth}pt`;
|
|
800
1973
|
measureContainer.style.left = "-9999px";
|
|
801
1974
|
measureContainer.className = this.cssPrefix + "footnotes";
|
|
802
|
-
|
|
803
|
-
|
|
804
|
-
|
|
805
|
-
|
|
806
|
-
|
|
807
|
-
|
|
808
|
-
|
|
809
|
-
|
|
810
|
-
|
|
811
|
-
|
|
812
|
-
|
|
813
|
-
|
|
814
|
-
|
|
815
|
-
|
|
816
|
-
|
|
817
|
-
|
|
818
|
-
|
|
819
|
-
|
|
820
|
-
|
|
821
|
-
|
|
822
|
-
|
|
823
|
-
|
|
824
|
-
|
|
825
|
-
|
|
826
|
-
|
|
827
|
-
|
|
828
|
-
|
|
829
|
-
|
|
830
|
-
|
|
831
|
-
|
|
832
|
-
|
|
833
|
-
|
|
834
|
-
|
|
835
|
-
|
|
836
|
-
|
|
837
|
-
|
|
838
|
-
|
|
839
|
-
|
|
840
|
-
|
|
841
|
-
|
|
842
|
-
|
|
843
|
-
|
|
844
|
-
|
|
845
|
-
|
|
846
|
-
|
|
847
|
-
|
|
848
|
-
|
|
849
|
-
|
|
850
|
-
|
|
851
|
-
|
|
852
|
-
|
|
853
|
-
|
|
854
|
-
|
|
855
|
-
|
|
856
|
-
|
|
857
|
-
|
|
858
|
-
|
|
859
|
-
|
|
860
|
-
|
|
861
|
-
|
|
862
|
-
|
|
863
|
-
|
|
864
|
-
|
|
865
|
-
|
|
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);
|
|
866
2040
|
}
|
|
867
2041
|
break;
|
|
868
2042
|
}
|
|
2043
|
+
} finally {
|
|
2044
|
+
measureContainer.remove();
|
|
869
2045
|
}
|
|
870
|
-
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
|
+
};
|
|
871
2058
|
}
|
|
872
2059
|
/**
|
|
873
|
-
*
|
|
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.
|
|
874
2062
|
*/
|
|
875
|
-
|
|
876
|
-
const
|
|
877
|
-
|
|
878
|
-
|
|
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");
|
|
879
2075
|
measureContainer.style.position = "absolute";
|
|
880
2076
|
measureContainer.style.visibility = "hidden";
|
|
881
2077
|
measureContainer.style.width = `${contentWidth}pt`;
|
|
882
2078
|
measureContainer.style.left = "-9999px";
|
|
883
2079
|
measureContainer.className = this.cssPrefix + "footnotes";
|
|
884
|
-
|
|
885
|
-
this.
|
|
886
|
-
|
|
887
|
-
|
|
888
|
-
|
|
889
|
-
|
|
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
|
+
}
|
|
890
2164
|
}
|
|
891
2165
|
/**
|
|
892
2166
|
* Adds footnotes to a page container, including continuation content.
|
|
@@ -899,12 +2173,11 @@ var DocxodusPagination = (() => {
|
|
|
899
2173
|
if (this.footnoteRegistry.size === 0 && !hasContinuation) {
|
|
900
2174
|
return;
|
|
901
2175
|
}
|
|
902
|
-
const partialFootnoteIds = new Set(partialFootnotes?.map((p) => p.footnoteId) || []);
|
|
903
2176
|
const maxFootnoteHeight = Math.min(
|
|
904
2177
|
footnoteHeight,
|
|
905
2178
|
bands.bodyHeight * MAX_FOOTNOTE_AREA_RATIO
|
|
906
2179
|
);
|
|
907
|
-
const footnotesDiv = document.createElement("div");
|
|
2180
|
+
const footnotesDiv = this.document.createElement("div");
|
|
908
2181
|
footnotesDiv.className = `${this.cssPrefix}footnotes`;
|
|
909
2182
|
footnotesDiv.style.position = "absolute";
|
|
910
2183
|
footnotesDiv.style.bottom = `${dims.pageHeight - (bands.bodyTop + bands.bodyHeight)}pt`;
|
|
@@ -913,35 +2186,18 @@ var DocxodusPagination = (() => {
|
|
|
913
2186
|
footnotesDiv.style.boxSizing = "border-box";
|
|
914
2187
|
footnotesDiv.style.maxHeight = `${maxFootnoteHeight}pt`;
|
|
915
2188
|
footnotesDiv.style.overflow = "hidden";
|
|
916
|
-
|
|
917
|
-
footnotesDiv.appendChild(hr);
|
|
2189
|
+
this.appendFootnoteSeparator(footnotesDiv, Boolean(hasContinuation));
|
|
918
2190
|
if (hasContinuation) {
|
|
919
|
-
|
|
920
|
-
contWrapper.className = "footnote-continuation";
|
|
921
|
-
for (const el of continuation.remainingElements) {
|
|
922
|
-
contWrapper.appendChild(el.cloneNode(true));
|
|
923
|
-
}
|
|
924
|
-
footnotesDiv.appendChild(contWrapper);
|
|
2191
|
+
footnotesDiv.appendChild(this.createFootnoteContinuationWrapper(continuation));
|
|
925
2192
|
}
|
|
926
2193
|
for (const id of footnoteIds) {
|
|
927
2194
|
const partial = partialFootnotes?.find((p) => p.footnoteId === id);
|
|
928
2195
|
if (partial) {
|
|
929
2196
|
const footnote = this.footnoteRegistry.get(id);
|
|
930
2197
|
if (footnote) {
|
|
931
|
-
|
|
932
|
-
|
|
933
|
-
|
|
934
|
-
const numberSpan = footnote.querySelector(".footnote-number");
|
|
935
|
-
if (numberSpan) {
|
|
936
|
-
partialDiv.appendChild(numberSpan.cloneNode(true));
|
|
937
|
-
}
|
|
938
|
-
const contentSpan = document.createElement("span");
|
|
939
|
-
contentSpan.className = "footnote-content";
|
|
940
|
-
for (const el of partial.fittingElements) {
|
|
941
|
-
contentSpan.appendChild(el.cloneNode(true));
|
|
942
|
-
}
|
|
943
|
-
partialDiv.appendChild(contentSpan);
|
|
944
|
-
footnotesDiv.appendChild(partialDiv);
|
|
2198
|
+
footnotesDiv.appendChild(
|
|
2199
|
+
this.createPartialFootnoteItem(footnote, partial.fittingElements)
|
|
2200
|
+
);
|
|
945
2201
|
}
|
|
946
2202
|
} else {
|
|
947
2203
|
const footnote = this.footnoteRegistry.get(id);
|
|
@@ -952,30 +2208,26 @@ var DocxodusPagination = (() => {
|
|
|
952
2208
|
}
|
|
953
2209
|
pageBox.appendChild(footnotesDiv);
|
|
954
2210
|
}
|
|
955
|
-
/**
|
|
956
|
-
|
|
957
|
-
*/
|
|
958
|
-
selectHeader(sectionIndex, pageInSection, globalPageNumber) {
|
|
2211
|
+
/** Select the section's first/odd/even header from its one-based page position. */
|
|
2212
|
+
selectHeader(sectionIndex, pageInSection, displayedPageNumber) {
|
|
959
2213
|
const sectionHf = this.hfRegistry.get(sectionIndex);
|
|
960
2214
|
if (!sectionHf) return void 0;
|
|
961
2215
|
if (pageInSection === 1 && sectionHf.headerFirst) {
|
|
962
2216
|
return sectionHf.headerFirst;
|
|
963
2217
|
}
|
|
964
|
-
if (
|
|
2218
|
+
if (displayedPageNumber % 2 === 0 && sectionHf.headerEven) {
|
|
965
2219
|
return sectionHf.headerEven;
|
|
966
2220
|
}
|
|
967
2221
|
return sectionHf.headerDefault;
|
|
968
2222
|
}
|
|
969
|
-
/**
|
|
970
|
-
|
|
971
|
-
*/
|
|
972
|
-
selectFooter(sectionIndex, pageInSection, globalPageNumber) {
|
|
2223
|
+
/** Select the section's first/odd/even footer from its one-based page position. */
|
|
2224
|
+
selectFooter(sectionIndex, pageInSection, displayedPageNumber) {
|
|
973
2225
|
const sectionHf = this.hfRegistry.get(sectionIndex);
|
|
974
2226
|
if (!sectionHf) return void 0;
|
|
975
2227
|
if (pageInSection === 1 && sectionHf.footerFirst) {
|
|
976
2228
|
return sectionHf.footerFirst;
|
|
977
2229
|
}
|
|
978
|
-
if (
|
|
2230
|
+
if (displayedPageNumber % 2 === 0 && sectionHf.footerEven) {
|
|
979
2231
|
return sectionHf.footerEven;
|
|
980
2232
|
}
|
|
981
2233
|
return sectionHf.footerDefault;
|
|
@@ -990,7 +2242,7 @@ var DocxodusPagination = (() => {
|
|
|
990
2242
|
* Deterministic: it depends only on the section's page setup and the registry's pre-measured
|
|
991
2243
|
* story heights, never on the page's content, which is what keeps it lazy-loading compatible.
|
|
992
2244
|
*/
|
|
993
|
-
getPageBands(dims, sectionIndex, pageInSection,
|
|
2245
|
+
getPageBands(dims, sectionIndex, pageInSection, displayedPageNumber) {
|
|
994
2246
|
const sectionHf = this.hfRegistry.get(sectionIndex);
|
|
995
2247
|
return resolvePageBands(
|
|
996
2248
|
dims,
|
|
@@ -999,14 +2251,14 @@ var DocxodusPagination = (() => {
|
|
|
999
2251
|
sectionHf?.headerEvenHeight,
|
|
1000
2252
|
sectionHf?.headerDefaultHeight,
|
|
1001
2253
|
pageInSection,
|
|
1002
|
-
|
|
2254
|
+
displayedPageNumber
|
|
1003
2255
|
),
|
|
1004
2256
|
this.selectStoryHeight(
|
|
1005
2257
|
sectionHf?.footerFirstHeight,
|
|
1006
2258
|
sectionHf?.footerEvenHeight,
|
|
1007
2259
|
sectionHf?.footerDefaultHeight,
|
|
1008
2260
|
pageInSection,
|
|
1009
|
-
|
|
2261
|
+
displayedPageNumber
|
|
1010
2262
|
)
|
|
1011
2263
|
);
|
|
1012
2264
|
}
|
|
@@ -1014,9 +2266,9 @@ var DocxodusPagination = (() => {
|
|
|
1014
2266
|
* The measured height of the running story this page position selects, mirroring
|
|
1015
2267
|
* {@link selectHeader}/{@link selectFooter}. Zero when the page has no such story.
|
|
1016
2268
|
*/
|
|
1017
|
-
selectStoryHeight(first, even, fallback, pageInSection,
|
|
2269
|
+
selectStoryHeight(first, even, fallback, pageInSection, displayedPageNumber) {
|
|
1018
2270
|
if (pageInSection === 1 && first != null) return first;
|
|
1019
|
-
if (
|
|
2271
|
+
if (displayedPageNumber % 2 === 0 && even != null) return even;
|
|
1020
2272
|
return fallback ?? 0;
|
|
1021
2273
|
}
|
|
1022
2274
|
/**
|
|
@@ -1028,7 +2280,7 @@ var DocxodusPagination = (() => {
|
|
|
1028
2280
|
* silently starts overlapping body text.
|
|
1029
2281
|
*/
|
|
1030
2282
|
measureHeaderFooterHeight(source, contentWidth) {
|
|
1031
|
-
const measureContainer = document.createElement("div");
|
|
2283
|
+
const measureContainer = this.document.createElement("div");
|
|
1032
2284
|
measureContainer.style.position = "absolute";
|
|
1033
2285
|
measureContainer.style.visibility = "hidden";
|
|
1034
2286
|
measureContainer.style.width = `${contentWidth}pt`;
|
|
@@ -1047,79 +2299,275 @@ var DocxodusPagination = (() => {
|
|
|
1047
2299
|
* Implements a single-pass, forward-only algorithm that is compatible with future lazy loading.
|
|
1048
2300
|
* Supports footnote continuation - long footnotes can split across pages.
|
|
1049
2301
|
*/
|
|
1050
|
-
flowToPages(blocks, dims, startPageNumber, sectionIndex) {
|
|
2302
|
+
flowToPages(blocks, dims, startPageNumber, sectionIndex, sectionDimensions) {
|
|
1051
2303
|
const pages = [];
|
|
1052
2304
|
let currentContent = [];
|
|
1053
2305
|
let pageNumber = startPageNumber;
|
|
1054
|
-
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
|
+
};
|
|
1055
2329
|
let { bodyHeight: effectiveContentHeight } = this.getPageBands(
|
|
1056
|
-
|
|
1057
|
-
|
|
1058
|
-
pageInSection,
|
|
1059
|
-
|
|
2330
|
+
dimensionsFor(),
|
|
2331
|
+
pageSectionIndex,
|
|
2332
|
+
pageInSection(),
|
|
2333
|
+
displayedPageNumber()
|
|
1060
2334
|
);
|
|
1061
2335
|
let remainingHeight = effectiveContentHeight;
|
|
1062
2336
|
let prevMarginBottomPt = 0;
|
|
1063
2337
|
let currentFootnoteIds = [];
|
|
2338
|
+
let currentPageHasFootnoteReference = false;
|
|
1064
2339
|
let currentFootnoteHeight = 0;
|
|
1065
2340
|
let currentContinuation = this.pendingFootnoteContinuation;
|
|
1066
2341
|
let nextPageContinuation = null;
|
|
2342
|
+
let currentContinuationPartitioned = false;
|
|
2343
|
+
let currentPageAdmitted = false;
|
|
1067
2344
|
let deferredFootnoteIds = [];
|
|
1068
2345
|
let currentPartialFootnotes = [];
|
|
1069
|
-
|
|
1070
|
-
|
|
1071
|
-
|
|
1072
|
-
|
|
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) => {
|
|
1073
2388
|
const hasCurrentContinuation = (currentContinuation?.remainingElements.length ?? 0) > 0;
|
|
1074
|
-
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
|
+
}
|
|
1075
2461
|
const page = this.createPage(
|
|
1076
|
-
|
|
2462
|
+
ownedDimensions,
|
|
1077
2463
|
pageNumber,
|
|
1078
|
-
|
|
2464
|
+
pageSectionIndex,
|
|
2465
|
+
ownedDisplayedPageNumber,
|
|
1079
2466
|
currentContent,
|
|
1080
|
-
|
|
2467
|
+
ownedPageInSection,
|
|
1081
2468
|
currentFootnoteIds,
|
|
1082
2469
|
currentFootnoteHeight,
|
|
1083
|
-
|
|
1084
|
-
currentPartialFootnotes.length > 0 ? currentPartialFootnotes : void 0
|
|
2470
|
+
pageContinuation,
|
|
2471
|
+
currentPartialFootnotes.length > 0 ? currentPartialFootnotes : void 0,
|
|
2472
|
+
false,
|
|
2473
|
+
currentPageAdmitted
|
|
1085
2474
|
);
|
|
1086
2475
|
pages.push(page);
|
|
2476
|
+
precedingDisplayedPageNumber = ownedDisplayedPageNumber;
|
|
1087
2477
|
pageNumber++;
|
|
1088
|
-
pageInSection++;
|
|
1089
2478
|
currentContent = [];
|
|
1090
|
-
|
|
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
|
+
);
|
|
1091
2489
|
effectiveContentHeight = newBands.bodyHeight;
|
|
1092
2490
|
remainingHeight = effectiveContentHeight;
|
|
1093
2491
|
prevMarginBottomPt = 0;
|
|
1094
2492
|
currentFootnoteIds = [];
|
|
2493
|
+
currentPageHasFootnoteReference = false;
|
|
1095
2494
|
currentPartialFootnotes = [];
|
|
1096
2495
|
currentContinuation = nextPageContinuation;
|
|
1097
2496
|
nextPageContinuation = null;
|
|
2497
|
+
currentContinuationPartitioned = false;
|
|
2498
|
+
currentPageAdmitted = false;
|
|
1098
2499
|
if (deferredFootnoteIds.length > 0) {
|
|
1099
2500
|
currentFootnoteIds = [...deferredFootnoteIds];
|
|
1100
2501
|
deferredFootnoteIds = [];
|
|
1101
2502
|
}
|
|
1102
|
-
|
|
1103
|
-
|
|
1104
|
-
} else {
|
|
1105
|
-
currentFootnoteHeight = 0;
|
|
1106
|
-
}
|
|
1107
|
-
if (currentFootnoteIds.length > 0) {
|
|
1108
|
-
currentFootnoteHeight += this.measureFootnotesHeight(
|
|
1109
|
-
currentFootnoteIds,
|
|
1110
|
-
dims.contentWidth,
|
|
1111
|
-
null
|
|
1112
|
-
);
|
|
1113
|
-
}
|
|
2503
|
+
const nextDimensions = dimensionsFor();
|
|
2504
|
+
currentFootnoteHeight = currentContinuation ? 0 : this.measureFootnotesHeight(currentFootnoteIds, nextDimensions.contentWidth);
|
|
1114
2505
|
};
|
|
2506
|
+
const pageHasPayload = () => currentContent.length > 0 || currentFootnoteIds.length > 0 || (currentContinuation?.remainingElements.length ?? 0) > 0;
|
|
1115
2507
|
for (let i = 0; i < blocks.length; i++) {
|
|
2508
|
+
this.checkpoint();
|
|
1116
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;
|
|
1117
2565
|
if (block.isPageBreak) {
|
|
1118
|
-
finishPage();
|
|
2566
|
+
finishPage(block.sectionIndex);
|
|
1119
2567
|
continue;
|
|
1120
2568
|
}
|
|
1121
2569
|
if (block.pageBreakBefore && currentContent.length > 0) {
|
|
1122
|
-
finishPage();
|
|
2570
|
+
finishPage(block.sectionIndex);
|
|
1123
2571
|
}
|
|
1124
2572
|
const previousBlock = blocks[i - 1];
|
|
1125
2573
|
const startsKeepChain = !previousBlock || !previousBlock.keepWithNext || previousBlock.isPageBreak || block.pageBreakBefore;
|
|
@@ -1135,7 +2583,7 @@ var DocxodusPagination = (() => {
|
|
|
1135
2583
|
if (newChainFootnoteIds.length > 0 && this.footnoteRegistry.size > 0) {
|
|
1136
2584
|
const totalChainFootnoteHeight = this.measureFootnotesHeight(
|
|
1137
2585
|
[...currentFootnoteIds, ...newChainFootnoteIds],
|
|
1138
|
-
|
|
2586
|
+
blockDimensions.contentWidth,
|
|
1139
2587
|
currentContinuation
|
|
1140
2588
|
);
|
|
1141
2589
|
additionalChainFootnoteHeight = Math.max(
|
|
@@ -1146,58 +2594,69 @@ var DocxodusPagination = (() => {
|
|
|
1146
2594
|
const currentChainHeight = this.measureKeepWithNextChainBodyHeight(
|
|
1147
2595
|
keepChain,
|
|
1148
2596
|
prevMarginBottomPt,
|
|
1149
|
-
currentContent.length === 0
|
|
2597
|
+
currentContent.length === 0,
|
|
2598
|
+
pageInSection(block.sectionIndex)
|
|
1150
2599
|
) + additionalChainFootnoteHeight;
|
|
1151
2600
|
const currentAvailableHeight = remainingHeight - currentFootnoteHeight;
|
|
1152
2601
|
if (currentChainHeight > currentAvailableHeight) {
|
|
1153
2602
|
const nextPageBands = this.getPageBands(
|
|
1154
|
-
|
|
1155
|
-
sectionIndex,
|
|
1156
|
-
pageInSection + 1,
|
|
1157
|
-
|
|
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
|
+
)
|
|
1158
2611
|
);
|
|
1159
2612
|
const freshChainBodyHeight = this.measureKeepWithNextChainBodyHeight(
|
|
1160
2613
|
keepChain,
|
|
1161
2614
|
0,
|
|
1162
|
-
true
|
|
2615
|
+
true,
|
|
2616
|
+
pageInSection(block.sectionIndex, pageNumber + 1)
|
|
1163
2617
|
);
|
|
1164
2618
|
const freshChainFootnoteHeight = this.measureFootnotesHeight(
|
|
1165
2619
|
newChainFootnoteIds,
|
|
1166
|
-
|
|
2620
|
+
blockDimensions.contentWidth,
|
|
1167
2621
|
nextPageContinuation
|
|
1168
2622
|
);
|
|
1169
2623
|
if (freshChainBodyHeight + freshChainFootnoteHeight <= nextPageBands.bodyHeight) {
|
|
1170
|
-
finishPage();
|
|
2624
|
+
finishPage(block.sectionIndex);
|
|
1171
2625
|
}
|
|
1172
2626
|
}
|
|
1173
2627
|
}
|
|
1174
2628
|
}
|
|
1175
|
-
const allBlockFootnoteIds = this.extractFootnoteRefs(block.element);
|
|
1176
2629
|
const newFootnoteIds = this.collectNewFootnoteIds([block], currentFootnoteIds);
|
|
2630
|
+
let combinedFootnoteHeight = currentFootnoteHeight;
|
|
1177
2631
|
let additionalFootnoteHeight = 0;
|
|
1178
2632
|
if (newFootnoteIds.length > 0 && this.footnoteRegistry.size > 0) {
|
|
1179
2633
|
const combinedFootnoteIds = [...currentFootnoteIds, ...newFootnoteIds];
|
|
1180
|
-
|
|
2634
|
+
combinedFootnoteHeight = this.measureFootnotesHeight(
|
|
1181
2635
|
combinedFootnoteIds,
|
|
1182
|
-
|
|
1183
|
-
currentContinuation
|
|
2636
|
+
blockDimensions.contentWidth,
|
|
2637
|
+
currentContinuation,
|
|
2638
|
+
currentPartialFootnotes
|
|
2639
|
+
);
|
|
2640
|
+
additionalFootnoteHeight = Math.max(
|
|
2641
|
+
0,
|
|
2642
|
+
combinedFootnoteHeight - currentFootnoteHeight
|
|
1184
2643
|
);
|
|
1185
|
-
additionalFootnoteHeight = totalFootnoteHeight - currentFootnoteHeight;
|
|
1186
2644
|
}
|
|
1187
2645
|
const isFirstOnPage = currentContent.length === 0;
|
|
1188
|
-
|
|
1189
|
-
|
|
1190
|
-
|
|
1191
|
-
|
|
2646
|
+
const effectiveMarginTop = this.effectiveBlockMarginTop(
|
|
2647
|
+
block,
|
|
2648
|
+
prevMarginBottomPt,
|
|
2649
|
+
isFirstOnPage,
|
|
2650
|
+
pageInSection(block.sectionIndex)
|
|
2651
|
+
);
|
|
1192
2652
|
const blockSpace = effectiveMarginTop + block.heightPt + additionalFootnoteHeight;
|
|
1193
2653
|
const effectiveRemainingHeight = remainingHeight - currentFootnoteHeight;
|
|
1194
2654
|
const bodyContentUsed = effectiveContentHeight - remainingHeight;
|
|
1195
2655
|
const maxFootnoteArea = effectiveContentHeight * MAX_FOOTNOTE_AREA_RATIO;
|
|
1196
|
-
const maxFootnoteExpansion = Math.max(0, maxFootnoteArea - currentFootnoteHeight);
|
|
1197
2656
|
if (blockSpace > effectiveRemainingHeight) {
|
|
1198
2657
|
const paragraphFragments = this.tryFragmentParagraph(
|
|
1199
2658
|
block,
|
|
1200
|
-
|
|
2659
|
+
blockDimensions,
|
|
1201
2660
|
effectiveRemainingHeight,
|
|
1202
2661
|
effectiveMarginTop
|
|
1203
2662
|
);
|
|
@@ -1207,40 +2666,74 @@ var DocxodusPagination = (() => {
|
|
|
1207
2666
|
continue;
|
|
1208
2667
|
}
|
|
1209
2668
|
}
|
|
1210
|
-
if (blockSpace <= effectiveRemainingHeight) {
|
|
1211
|
-
|
|
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
|
+
));
|
|
1212
2676
|
remainingHeight -= effectiveMarginTop + block.heightPt + block.marginBottomPt;
|
|
1213
2677
|
prevMarginBottomPt = block.marginBottomPt;
|
|
1214
2678
|
if (newFootnoteIds.length > 0) {
|
|
1215
2679
|
currentFootnoteIds.push(...newFootnoteIds);
|
|
1216
|
-
currentFootnoteHeight
|
|
2680
|
+
currentFootnoteHeight = combinedFootnoteHeight;
|
|
1217
2681
|
}
|
|
1218
|
-
|
|
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) {
|
|
1219
2689
|
const blockSpaceWithoutFootnotes = effectiveMarginTop + block.heightPt;
|
|
1220
|
-
const minBodySpaceNeeded = bodyContentUsed + blockSpaceWithoutFootnotes + MIN_BODY_CONTENT_HEIGHT;
|
|
1221
|
-
const canExpandFootnotes = minBodySpaceNeeded <= effectiveContentHeight;
|
|
1222
2690
|
if (newFootnoteIds.length > 0 && blockSpaceWithoutFootnotes <= effectiveRemainingHeight) {
|
|
1223
|
-
|
|
2691
|
+
markSectionPlaced(block.sectionIndex);
|
|
2692
|
+
currentContent.push(this.cloneBlockForPage(
|
|
2693
|
+
block,
|
|
2694
|
+
isFirstOnPage,
|
|
2695
|
+
pageInSection(block.sectionIndex)
|
|
2696
|
+
));
|
|
1224
2697
|
remainingHeight -= effectiveMarginTop + block.heightPt + block.marginBottomPt;
|
|
1225
2698
|
prevMarginBottomPt = block.marginBottomPt;
|
|
1226
2699
|
const availableForFootnotes = Math.min(
|
|
1227
2700
|
maxFootnoteArea,
|
|
1228
2701
|
effectiveContentHeight - bodyContentUsed - blockSpaceWithoutFootnotes
|
|
1229
2702
|
);
|
|
1230
|
-
|
|
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];
|
|
1231
2708
|
const footnote = this.footnoteRegistry.get(footnoteId);
|
|
1232
2709
|
if (!footnote) continue;
|
|
1233
|
-
|
|
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
|
+
);
|
|
1234
2721
|
const spaceLeftForFootnotes = availableForFootnotes - currentFootnoteHeight;
|
|
1235
|
-
if (
|
|
2722
|
+
if (candidateHeight + FOOTNOTE_MEASUREMENT_GUARD_PT <= availableForFootnotes) {
|
|
1236
2723
|
currentFootnoteIds.push(footnoteId);
|
|
1237
|
-
currentFootnoteHeight
|
|
2724
|
+
currentFootnoteHeight = candidateHeight;
|
|
1238
2725
|
} else {
|
|
1239
2726
|
if (spaceLeftForFootnotes > 20) {
|
|
1240
2727
|
const { fits, overflow } = this.splitFootnoteToFit(
|
|
1241
2728
|
footnote,
|
|
1242
|
-
|
|
1243
|
-
|
|
2729
|
+
availableForFootnotes,
|
|
2730
|
+
blockDimensions.contentWidth,
|
|
2731
|
+
deferralCannotHelp,
|
|
2732
|
+
{
|
|
2733
|
+
footnoteIds: currentFootnoteIds,
|
|
2734
|
+
continuation: currentContinuation,
|
|
2735
|
+
partialFootnotes: currentPartialFootnotes
|
|
2736
|
+
}
|
|
1244
2737
|
);
|
|
1245
2738
|
if (fits.length > 0) {
|
|
1246
2739
|
currentFootnoteIds.push(footnoteId);
|
|
@@ -1251,66 +2744,70 @@ var DocxodusPagination = (() => {
|
|
|
1251
2744
|
if (overflow.length > 0) {
|
|
1252
2745
|
nextPageContinuation = {
|
|
1253
2746
|
footnoteId,
|
|
2747
|
+
sourceAnchorId: footnote.dataset.sourceAnchorId,
|
|
1254
2748
|
remainingElements: overflow
|
|
1255
2749
|
};
|
|
1256
2750
|
}
|
|
1257
|
-
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
|
+
}
|
|
1258
2761
|
} else {
|
|
1259
|
-
deferredFootnoteIds.push(
|
|
2762
|
+
deferredFootnoteIds.push(...newFootnoteIds.slice(noteIndex));
|
|
2763
|
+
break;
|
|
1260
2764
|
}
|
|
1261
2765
|
} else {
|
|
1262
|
-
deferredFootnoteIds.push(
|
|
2766
|
+
deferredFootnoteIds.push(...newFootnoteIds.slice(noteIndex));
|
|
2767
|
+
break;
|
|
1263
2768
|
}
|
|
1264
2769
|
}
|
|
1265
2770
|
}
|
|
1266
|
-
|
|
1267
|
-
const expandedFootnoteSpace = Math.min(maxFootnoteArea, additionalFootnoteHeight + currentFootnoteHeight);
|
|
1268
|
-
const bodySpaceAfterExpansion = effectiveContentHeight - expandedFootnoteSpace;
|
|
1269
|
-
if (blockSpaceWithoutFootnotes <= bodySpaceAfterExpansion - bodyContentUsed) {
|
|
1270
|
-
currentContent.push(block.element.cloneNode(true));
|
|
1271
|
-
remainingHeight = bodySpaceAfterExpansion - bodyContentUsed - blockSpaceWithoutFootnotes;
|
|
1272
|
-
prevMarginBottomPt = block.marginBottomPt;
|
|
1273
|
-
currentFootnoteIds.push(...newFootnoteIds);
|
|
1274
|
-
currentFootnoteHeight = expandedFootnoteSpace;
|
|
1275
|
-
} else {
|
|
1276
|
-
finishPage();
|
|
1277
|
-
const newPageFootnoteHeight = allBlockFootnoteIds.length > 0 ? this.measureFootnotesHeight(allBlockFootnoteIds, dims.contentWidth, currentContinuation) : currentContinuation ? this.measureContinuationHeight(currentContinuation, dims.contentWidth) : 0;
|
|
1278
|
-
const newPageSpace = block.marginTopPt + block.heightPt + block.marginBottomPt;
|
|
1279
|
-
currentContent.push(block.element.cloneNode(true));
|
|
1280
|
-
remainingHeight = effectiveContentHeight - newPageSpace;
|
|
1281
|
-
prevMarginBottomPt = block.marginBottomPt;
|
|
1282
|
-
currentFootnoteIds = [...currentFootnoteIds, ...allBlockFootnoteIds];
|
|
1283
|
-
currentFootnoteHeight = newPageFootnoteHeight;
|
|
1284
|
-
}
|
|
2771
|
+
currentPageHasFootnoteReference ||= allBlockFootnoteIds.length > 0;
|
|
1285
2772
|
} else {
|
|
1286
|
-
finishPage();
|
|
1287
|
-
|
|
1288
|
-
|
|
1289
|
-
currentContent.push(block.element.cloneNode(true));
|
|
1290
|
-
remainingHeight = effectiveContentHeight - newPageSpace;
|
|
1291
|
-
prevMarginBottomPt = block.marginBottomPt;
|
|
1292
|
-
currentFootnoteIds = [...currentFootnoteIds, ...allBlockFootnoteIds];
|
|
1293
|
-
currentFootnoteHeight = newPageFootnoteHeight;
|
|
2773
|
+
finishPage(block.sectionIndex, !pageHasPayload());
|
|
2774
|
+
i--;
|
|
2775
|
+
continue;
|
|
1294
2776
|
}
|
|
1295
2777
|
} else {
|
|
1296
|
-
const tableFragments = this.trySplitSimpleOversizedTable(
|
|
2778
|
+
const tableFragments = this.trySplitSimpleOversizedTable(
|
|
2779
|
+
block,
|
|
2780
|
+
dimensionsFor(block.sectionIndex),
|
|
2781
|
+
block.sectionIndex
|
|
2782
|
+
);
|
|
1297
2783
|
if (tableFragments) {
|
|
1298
2784
|
if (currentContent.length > 0 || currentContinuation) {
|
|
1299
|
-
finishPage();
|
|
2785
|
+
finishPage(block.sectionIndex);
|
|
1300
2786
|
}
|
|
1301
2787
|
blocks.splice(i, 1, ...tableFragments);
|
|
1302
2788
|
i--;
|
|
1303
2789
|
continue;
|
|
1304
2790
|
}
|
|
1305
|
-
if (
|
|
1306
|
-
finishPage();
|
|
2791
|
+
if (pageHasPayload()) {
|
|
2792
|
+
finishPage(block.sectionIndex);
|
|
2793
|
+
i--;
|
|
2794
|
+
continue;
|
|
1307
2795
|
}
|
|
1308
|
-
|
|
1309
|
-
|
|
1310
|
-
|
|
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);
|
|
1311
2805
|
}
|
|
1312
2806
|
}
|
|
1313
2807
|
finishPage();
|
|
2808
|
+
while (currentFootnoteIds.length > 0 || deferredFootnoteIds.length > 0 || (currentContinuation?.remainingElements.length ?? 0) > 0) {
|
|
2809
|
+
finishPage();
|
|
2810
|
+
}
|
|
1314
2811
|
this.pendingFootnoteContinuation = nextPageContinuation;
|
|
1315
2812
|
return pages;
|
|
1316
2813
|
}
|
|
@@ -1335,6 +2832,19 @@ var DocxodusPagination = (() => {
|
|
|
1335
2832
|
if (el.getAttribute("contenteditable") !== null) el.setAttribute("contenteditable", "false");
|
|
1336
2833
|
}
|
|
1337
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
|
+
}
|
|
1338
2848
|
/**
|
|
1339
2849
|
* Resolves floating DrawingML objects after their anchor paragraphs have landed on a page.
|
|
1340
2850
|
*
|
|
@@ -1360,7 +2870,7 @@ var DocxodusPagination = (() => {
|
|
|
1360
2870
|
const context = {
|
|
1361
2871
|
staticLeft: toPageX(staticRect.left),
|
|
1362
2872
|
staticTop: toPageY(staticRect.top),
|
|
1363
|
-
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,
|
|
1364
2874
|
paragraphLeft: toPageX(paragraphRect.left),
|
|
1365
2875
|
paragraphTop: toPageY(paragraphRect.top),
|
|
1366
2876
|
paragraphWidth: paragraphRect.width / pixelsPerPoint,
|
|
@@ -1490,36 +3000,48 @@ var DocxodusPagination = (() => {
|
|
|
1490
3000
|
else if (alignment === "outside") fraction = pageNumber % 2 === 1 ? 1 : 0;
|
|
1491
3001
|
return reference.start + (reference.size - objectSize) * fraction;
|
|
1492
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
|
+
}
|
|
1493
3009
|
/**
|
|
1494
3010
|
* Creates a page container element.
|
|
1495
3011
|
*/
|
|
1496
|
-
createPage(dims, pageNumber, sectionIndex, content, pageInSection, footnoteIds = [], footnoteHeight = 0, continuation, partialFootnotes) {
|
|
1497
|
-
|
|
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");
|
|
1498
3015
|
pageBox.className = `${this.cssPrefix}box`;
|
|
1499
3016
|
pageBox.style.width = `${dims.pageWidth}pt`;
|
|
1500
3017
|
pageBox.style.height = `${dims.pageHeight}pt`;
|
|
1501
3018
|
pageBox.style.overflow = "hidden";
|
|
1502
3019
|
pageBox.style.position = "relative";
|
|
1503
3020
|
if (this.scale !== 1) {
|
|
1504
|
-
|
|
1505
|
-
|
|
1506
|
-
|
|
1507
|
-
|
|
1508
|
-
|
|
1509
|
-
|
|
1510
|
-
|
|
1511
|
-
|
|
1512
|
-
|
|
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
|
+
}
|
|
1513
3033
|
}
|
|
1514
3034
|
pageBox.style.willChange = "transform";
|
|
1515
3035
|
pageBox.style.contain = "layout paint";
|
|
1516
3036
|
pageBox.dataset.pageNumber = String(pageNumber);
|
|
1517
3037
|
pageBox.dataset.sectionIndex = String(sectionIndex);
|
|
3038
|
+
pageBox.dataset.displayedPageNumber = String(displayedPageNumber);
|
|
3039
|
+
if (isSectionFiller) pageBox.dataset.sectionFiller = "true";
|
|
1518
3040
|
pageBox.dataset.pageInSection = String(pageInSection);
|
|
1519
|
-
const bands = this.getPageBands(dims, sectionIndex, pageInSection,
|
|
1520
|
-
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);
|
|
1521
3043
|
if (headerSource) {
|
|
1522
|
-
const headerDiv = document.createElement("div");
|
|
3044
|
+
const headerDiv = this.document.createElement("div");
|
|
1523
3045
|
headerDiv.className = `${this.cssPrefix}header`;
|
|
1524
3046
|
headerDiv.style.position = "absolute";
|
|
1525
3047
|
headerDiv.style.top = `${bands.headerTop}pt`;
|
|
@@ -1541,7 +3063,7 @@ var DocxodusPagination = (() => {
|
|
|
1541
3063
|
}
|
|
1542
3064
|
const contentAreaTop = bands.bodyTop;
|
|
1543
3065
|
const contentAreaHeight = bands.bodyHeight;
|
|
1544
|
-
const contentArea = document.createElement("div");
|
|
3066
|
+
const contentArea = this.document.createElement("div");
|
|
1545
3067
|
contentArea.className = `${this.cssPrefix}content`;
|
|
1546
3068
|
contentArea.style.position = "absolute";
|
|
1547
3069
|
contentArea.style.top = `${contentAreaTop}pt`;
|
|
@@ -1554,12 +3076,46 @@ var DocxodusPagination = (() => {
|
|
|
1554
3076
|
}
|
|
1555
3077
|
pageBox.appendChild(contentArea);
|
|
1556
3078
|
const hasContinuation = continuation && continuation.remainingElements.length > 0;
|
|
1557
|
-
if (footnoteIds.length > 0 || hasContinuation) {
|
|
3079
|
+
if (!isSectionFiller && (footnoteIds.length > 0 || hasContinuation)) {
|
|
1558
3080
|
this.addPageFootnotes(pageBox, footnoteIds, dims, bands, footnoteHeight, continuation, partialFootnotes);
|
|
1559
3081
|
}
|
|
1560
|
-
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);
|
|
1561
3117
|
if (footerSource) {
|
|
1562
|
-
const footerDiv = document.createElement("div");
|
|
3118
|
+
const footerDiv = this.document.createElement("div");
|
|
1563
3119
|
footerDiv.className = `${this.cssPrefix}footer`;
|
|
1564
3120
|
footerDiv.style.position = "absolute";
|
|
1565
3121
|
footerDiv.style.top = "auto";
|
|
@@ -1579,8 +3135,8 @@ var DocxodusPagination = (() => {
|
|
|
1579
3135
|
}
|
|
1580
3136
|
pageBox.appendChild(footerDiv);
|
|
1581
3137
|
}
|
|
1582
|
-
if (this.showPageNumbers) {
|
|
1583
|
-
const pageNum = document.createElement("div");
|
|
3138
|
+
if (this.showPageNumbers && !isSectionFiller) {
|
|
3139
|
+
const pageNum = this.document.createElement("div");
|
|
1584
3140
|
pageNum.className = `${this.cssPrefix}number`;
|
|
1585
3141
|
pageNum.textContent = String(pageNumber);
|
|
1586
3142
|
pageBox.appendChild(pageNum);
|
|
@@ -1603,7 +3159,8 @@ var DocxodusPagination = (() => {
|
|
|
1603
3159
|
}
|
|
1604
3160
|
};
|
|
1605
3161
|
function paginateHtml(html, container, options = {}) {
|
|
1606
|
-
const
|
|
3162
|
+
const ownerDocument = typeof container === "string" ? globalThis.document : container.ownerDocument;
|
|
3163
|
+
const containerEl = typeof container === "string" ? ownerDocument.getElementById(container) : container;
|
|
1607
3164
|
if (!containerEl) {
|
|
1608
3165
|
throw new Error("Container element not found");
|
|
1609
3166
|
}
|