docxodus 7.1.0 → 9.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 +65 -24
- package/dist/docxodus.worker.js +2 -2
- package/dist/docxodus.worker.js.map +1 -1
- package/dist/editor-headerfooter.d.ts +166 -0
- package/dist/editor-headerfooter.d.ts.map +1 -0
- package/dist/editor-headerfooter.js +530 -0
- package/dist/editor-headerfooter.js.map +1 -0
- package/dist/editor-reconcile.d.ts +75 -0
- package/dist/editor-reconcile.d.ts.map +1 -0
- package/dist/editor-reconcile.js +125 -0
- package/dist/editor-reconcile.js.map +1 -0
- package/dist/editor.bundle.js +1861 -110
- package/dist/editor.d.ts +200 -6
- package/dist/editor.d.ts.map +1 -1
- package/dist/editor.js +868 -86
- package/dist/editor.js.map +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +3 -4
- package/dist/index.js.map +1 -1
- package/dist/page-number-format.d.ts +22 -0
- package/dist/page-number-format.d.ts.map +1 -0
- package/dist/page-number-format.js +60 -0
- package/dist/page-number-format.js.map +1 -0
- package/dist/pagination.bundle.js +635 -37
- package/dist/pagination.d.ts +119 -0
- package/dist/pagination.d.ts.map +1 -1
- package/dist/pagination.js +706 -52
- package/dist/pagination.js.map +1 -1
- package/dist/react.d.ts +3 -1
- package/dist/react.d.ts.map +1 -1
- package/dist/react.js +6 -4
- package/dist/react.js.map +1 -1
- package/dist/session.bundle.js +241 -4
- package/dist/session.d.ts +160 -6
- package/dist/session.d.ts.map +1 -1
- package/dist/session.js +202 -4
- package/dist/session.js.map +1 -1
- package/dist/types.d.ts +238 -14
- package/dist/types.d.ts.map +1 -1
- package/dist/types.js +9 -11
- package/dist/types.js.map +1 -1
- package/dist/wasm/_framework/Docxodus.wasm +0 -0
- package/dist/wasm/_framework/DocxodusWasm.wasm +0 -0
- package/dist/wasm/_framework/System.Collections.Concurrent.wasm +0 -0
- package/dist/wasm/_framework/System.Collections.Immutable.wasm +0 -0
- package/dist/wasm/_framework/System.Collections.NonGeneric.wasm +0 -0
- package/dist/wasm/_framework/System.Collections.Specialized.wasm +0 -0
- package/dist/wasm/_framework/System.Collections.wasm +0 -0
- package/dist/wasm/_framework/System.ComponentModel.Primitives.wasm +0 -0
- package/dist/wasm/_framework/System.ComponentModel.TypeConverter.wasm +0 -0
- package/dist/wasm/_framework/System.ComponentModel.wasm +0 -0
- package/dist/wasm/_framework/System.Console.wasm +0 -0
- package/dist/wasm/_framework/System.Diagnostics.Process.wasm +0 -0
- package/dist/wasm/_framework/System.IO.Compression.wasm +0 -0
- package/dist/wasm/_framework/System.IO.Pipelines.wasm +0 -0
- package/dist/wasm/_framework/System.Linq.Expressions.wasm +0 -0
- package/dist/wasm/_framework/System.Linq.wasm +0 -0
- package/dist/wasm/_framework/System.Memory.wasm +0 -0
- package/dist/wasm/_framework/System.Net.Http.wasm +0 -0
- package/dist/wasm/_framework/System.Net.Primitives.wasm +0 -0
- package/dist/wasm/_framework/System.ObjectModel.wasm +0 -0
- package/dist/wasm/_framework/System.Private.CoreLib.wasm +0 -0
- package/dist/wasm/_framework/System.Private.Uri.wasm +0 -0
- package/dist/wasm/_framework/System.Private.Xml.Linq.wasm +0 -0
- package/dist/wasm/_framework/System.Private.Xml.wasm +0 -0
- package/dist/wasm/_framework/System.Runtime.InteropServices.JavaScript.wasm +0 -0
- package/dist/wasm/_framework/System.Runtime.wasm +0 -0
- package/dist/wasm/_framework/System.Security.Cryptography.wasm +0 -0
- package/dist/wasm/_framework/System.Text.Encoding.Extensions.wasm +0 -0
- package/dist/wasm/_framework/System.Text.Encodings.Web.wasm +0 -0
- package/dist/wasm/_framework/System.Text.Json.wasm +0 -0
- package/dist/wasm/_framework/System.Text.RegularExpressions.wasm +0 -0
- package/dist/wasm/_framework/System.Threading.Thread.wasm +0 -0
- package/dist/wasm/_framework/System.Threading.wasm +0 -0
- package/dist/wasm/_framework/System.Xml.Linq.wasm +0 -0
- package/dist/wasm/_framework/System.Xml.ReaderWriter.wasm +0 -0
- package/dist/wasm/_framework/System.Xml.XDocument.wasm +0 -0
- package/dist/wasm/_framework/System.Xml.XPath.XDocument.wasm +0 -0
- package/dist/wasm/_framework/System.Xml.XPath.wasm +0 -0
- package/dist/wasm/_framework/System.wasm +0 -0
- package/dist/wasm/_framework/dotnet.boot.js +41 -41
- package/dist/wasm/_framework/dotnet.js +1 -1
- package/dist/wasm/_framework/dotnet.js.map +1 -1
- package/dist/wasm/_framework/dotnet.native.js +3 -3
- package/dist/wasm/_framework/dotnet.native.js.symbols +2102 -2101
- package/dist/wasm/_framework/dotnet.native.wasm +0 -0
- package/dist/wasm/_framework/dotnet.runtime.js +1 -1
- package/dist/wasm/_framework/dotnet.runtime.js.map +1 -1
- package/dist/wasm/index.html +2 -1
- package/package.json +17 -4
|
@@ -24,6 +24,42 @@ var DocxodusPagination = (() => {
|
|
|
24
24
|
PaginationEngine: () => PaginationEngine,
|
|
25
25
|
paginateHtml: () => paginateHtml
|
|
26
26
|
});
|
|
27
|
+
|
|
28
|
+
// src/page-number-format.ts
|
|
29
|
+
var ROMAN_ONES = ["", "i", "ii", "iii", "iv", "v", "vi", "vii", "viii", "ix"];
|
|
30
|
+
var ROMAN_TENS = ["", "x", "xx", "xxx", "xl", "l", "lx", "lxx", "lxxx", "xc"];
|
|
31
|
+
var ROMAN_HUNDREDS = ["", "c", "cc", "ccc", "cd", "d", "dc", "dcc", "dccc", "cm"];
|
|
32
|
+
var ROMAN_THOUSANDS = ["", "m", "mm", "mmm"];
|
|
33
|
+
function toRoman(value) {
|
|
34
|
+
if (value <= 0 || value >= 4e3) return String(value);
|
|
35
|
+
return ROMAN_THOUSANDS[Math.floor(value / 1e3)] + ROMAN_HUNDREDS[Math.floor(value % 1e3 / 100)] + ROMAN_TENS[Math.floor(value % 100 / 10)] + ROMAN_ONES[value % 10];
|
|
36
|
+
}
|
|
37
|
+
function toLetter(value) {
|
|
38
|
+
if (value <= 0) return String(value);
|
|
39
|
+
const wrapped = value % 780 === 0 ? 780 : value % 780;
|
|
40
|
+
const repeats = Math.floor((wrapped - 1) / 26) + 1;
|
|
41
|
+
return "abcdefghijklmnopqrstuvwxyz".charAt((wrapped - 1) % 26).repeat(repeats);
|
|
42
|
+
}
|
|
43
|
+
var RENDERERS = {
|
|
44
|
+
// ST_NumberFormat tokens (w:pgNumType/@w:fmt, w:numFmt).
|
|
45
|
+
decimal: (v) => String(v),
|
|
46
|
+
lowerRoman: toRoman,
|
|
47
|
+
upperRoman: (v) => toRoman(v).toUpperCase(),
|
|
48
|
+
lowerLetter: toLetter,
|
|
49
|
+
upperLetter: (v) => toLetter(v).toUpperCase(),
|
|
50
|
+
// Field `\*` general-formatting switch arguments.
|
|
51
|
+
Arabic: (v) => String(v),
|
|
52
|
+
roman: toRoman,
|
|
53
|
+
ROMAN: (v) => toRoman(v).toUpperCase(),
|
|
54
|
+
alphabetic: toLetter,
|
|
55
|
+
ALPHABETIC: (v) => toLetter(v).toUpperCase()
|
|
56
|
+
};
|
|
57
|
+
function formatPageNumber(value, format) {
|
|
58
|
+
const renderer = format ? RENDERERS[format] : void 0;
|
|
59
|
+
return (renderer ?? RENDERERS.decimal)(value);
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
// src/pagination.ts
|
|
27
63
|
var DEFAULT_PAGE_WIDTH = 612;
|
|
28
64
|
var DEFAULT_PAGE_HEIGHT = 792;
|
|
29
65
|
var DEFAULT_MARGIN = 72;
|
|
@@ -70,6 +106,8 @@ var DocxodusPagination = (() => {
|
|
|
70
106
|
*/
|
|
71
107
|
constructor(staging, container, options = {}) {
|
|
72
108
|
this.pendingFootnoteContinuation = null;
|
|
109
|
+
/** Per-section `w:pgNumType` (start / format), read off the section wrappers. */
|
|
110
|
+
this.pageNumbering = /* @__PURE__ */ new Map();
|
|
73
111
|
this.stagingElement = typeof staging === "string" ? document.getElementById(staging) : staging;
|
|
74
112
|
this.containerElement = typeof container === "string" ? document.getElementById(container) : container;
|
|
75
113
|
if (!this.stagingElement) {
|
|
@@ -82,6 +120,7 @@ var DocxodusPagination = (() => {
|
|
|
82
120
|
this.cssPrefix = options.cssPrefix ?? "page-";
|
|
83
121
|
this.showPageNumbers = options.showPageNumbers ?? true;
|
|
84
122
|
this.pageGap = options.pageGap ?? 20;
|
|
123
|
+
this.fragmentParagraphs = options.fragmentParagraphs ?? false;
|
|
85
124
|
this.hfRegistry = /* @__PURE__ */ new Map();
|
|
86
125
|
this.footnoteRegistry = /* @__PURE__ */ new Map();
|
|
87
126
|
}
|
|
@@ -98,6 +137,7 @@ var DocxodusPagination = (() => {
|
|
|
98
137
|
const sections = this.stagingElement.querySelectorAll(
|
|
99
138
|
"[data-section-index]"
|
|
100
139
|
);
|
|
140
|
+
this.pageNumbering = this.parsePageNumbering(sections);
|
|
101
141
|
const sectionsToProcess = sections.length > 0 ? Array.from(sections) : [this.stagingElement];
|
|
102
142
|
for (const section of sectionsToProcess) {
|
|
103
143
|
const sectionIndex = parseInt(section.dataset.sectionIndex || "0", 10);
|
|
@@ -113,8 +153,62 @@ var DocxodusPagination = (() => {
|
|
|
113
153
|
pageNumber += sectionPages.length;
|
|
114
154
|
}
|
|
115
155
|
this.stagingElement.style.display = "none";
|
|
156
|
+
this.substitutePageNumberFields(pages.length);
|
|
116
157
|
return { totalPages: pages.length, pages };
|
|
117
158
|
}
|
|
159
|
+
/** Read each section's `w:pgNumType` off its wrapper (see {@link SectionPageNumbering}). */
|
|
160
|
+
parsePageNumbering(sections) {
|
|
161
|
+
const map = /* @__PURE__ */ new Map();
|
|
162
|
+
for (const section of Array.from(sections)) {
|
|
163
|
+
const index = parseInt(section.dataset.sectionIndex || "0", 10);
|
|
164
|
+
const rawStart = section.dataset.pageNumStart;
|
|
165
|
+
const start = rawStart === void 0 ? void 0 : parseInt(rawStart, 10);
|
|
166
|
+
map.set(index, {
|
|
167
|
+
start: start !== void 0 && Number.isFinite(start) ? start : void 0,
|
|
168
|
+
format: section.dataset.pageNumFmt
|
|
169
|
+
});
|
|
170
|
+
}
|
|
171
|
+
return map;
|
|
172
|
+
}
|
|
173
|
+
/**
|
|
174
|
+
* Fill in the page-number fields inside every page's cloned header/footer.
|
|
175
|
+
*
|
|
176
|
+
* A header/footer is authored once and cloned onto each page, so a PAGE field's single cached
|
|
177
|
+
* result would otherwise show the same number on every page — the whole reason the converter
|
|
178
|
+
* marks these. `data-field-format` (the field's own `\*` switch) wins over the section's format
|
|
179
|
+
* when present, which is exactly how Word resolves the two.
|
|
180
|
+
*
|
|
181
|
+
* Runs after layout because NUMPAGES cannot be known before the last page exists. The
|
|
182
|
+
* substituted text can therefore be marginally wider than the cached result the header was
|
|
183
|
+
* measured with; the header band clips, so the failure mode is a hair of overflow rather than
|
|
184
|
+
* a layout that disagrees with itself.
|
|
185
|
+
*
|
|
186
|
+
* Scoped to the CLONED header/footer regions on purpose. A page-number field in body text is
|
|
187
|
+
* ordinary run content that the editor may make editable, and committing an edited block writes
|
|
188
|
+
* back whatever text the DOM holds — rewriting it here would mean a body field commits a number
|
|
189
|
+
* the document never contained. Body content is also not cloned, so it does not have the problem
|
|
190
|
+
* this method exists to solve.
|
|
191
|
+
*/
|
|
192
|
+
substitutePageNumberFields(totalPages) {
|
|
193
|
+
const boxes = this.containerElement.querySelectorAll(`.${this.cssPrefix}box`);
|
|
194
|
+
for (const box of Array.from(boxes)) {
|
|
195
|
+
const markers = box.querySelectorAll(
|
|
196
|
+
`.${this.cssPrefix}header [data-field], .${this.cssPrefix}footer [data-field]`
|
|
197
|
+
);
|
|
198
|
+
if (markers.length === 0) continue;
|
|
199
|
+
const sectionIndex = parseInt(box.dataset.sectionIndex || "0", 10);
|
|
200
|
+
const pageNumber = parseInt(box.dataset.pageNumber || "1", 10);
|
|
201
|
+
const pageInSection = parseInt(box.dataset.pageInSection || "1", 10);
|
|
202
|
+
const numbering = this.pageNumbering.get(sectionIndex) ?? {};
|
|
203
|
+
const displayed = numbering.start !== void 0 ? numbering.start + pageInSection - 1 : pageNumber;
|
|
204
|
+
for (const marker of Array.from(markers)) {
|
|
205
|
+
const kind = marker.dataset.field;
|
|
206
|
+
if (kind !== "PAGE" && kind !== "NUMPAGES") continue;
|
|
207
|
+
const format = marker.dataset.fieldFormat ?? numbering.format;
|
|
208
|
+
marker.textContent = formatPageNumber(kind === "PAGE" ? displayed : totalPages, format);
|
|
209
|
+
}
|
|
210
|
+
}
|
|
211
|
+
}
|
|
118
212
|
/**
|
|
119
213
|
* Measures all content blocks in a section.
|
|
120
214
|
*/
|
|
@@ -148,6 +242,393 @@ var DocxodusPagination = (() => {
|
|
|
148
242
|
}
|
|
149
243
|
return blocks;
|
|
150
244
|
}
|
|
245
|
+
/**
|
|
246
|
+
* Measures one element in the same hidden staging context used for the source blocks.
|
|
247
|
+
* This is intentionally DOM-based: table row heights cannot be inferred from individual
|
|
248
|
+
* rows because wrapping and collapsed borders change the height of a fragment.
|
|
249
|
+
*/
|
|
250
|
+
measureElement(element, dims) {
|
|
251
|
+
const measurementHost = document.createElement("div");
|
|
252
|
+
measurementHost.style.position = "absolute";
|
|
253
|
+
measurementHost.style.visibility = "hidden";
|
|
254
|
+
measurementHost.style.left = "-9999px";
|
|
255
|
+
measurementHost.style.width = `${dims.contentWidth}pt`;
|
|
256
|
+
const measuredElement = element.cloneNode(true);
|
|
257
|
+
measurementHost.appendChild(measuredElement);
|
|
258
|
+
this.stagingElement.appendChild(measurementHost);
|
|
259
|
+
const rect = measuredElement.getBoundingClientRect();
|
|
260
|
+
const style = window.getComputedStyle(measuredElement);
|
|
261
|
+
const measured = {
|
|
262
|
+
element,
|
|
263
|
+
heightPt: pxToPt(rect.height),
|
|
264
|
+
marginTopPt: pxToPt(parseFloat(style.marginTop) || 0),
|
|
265
|
+
marginBottomPt: pxToPt(parseFloat(style.marginBottom) || 0),
|
|
266
|
+
keepWithNext: element.dataset.keepWithNext === "true",
|
|
267
|
+
keepLines: element.dataset.keepLines === "true",
|
|
268
|
+
pageBreakBefore: element.dataset.pageBreakBefore === "true",
|
|
269
|
+
isPageBreak: element.dataset.pageBreak === "true" || element.classList.contains(`${this.cssPrefix}break`)
|
|
270
|
+
};
|
|
271
|
+
this.stagingElement.removeChild(measurementHost);
|
|
272
|
+
return measured;
|
|
273
|
+
}
|
|
274
|
+
/**
|
|
275
|
+
* Returns the contiguous keep-with-next chain beginning at a block.
|
|
276
|
+
*
|
|
277
|
+
* A hard page break or a page-break-before directive is stronger than a
|
|
278
|
+
* keep-with-next directive, so it terminates the chain. The caller only
|
|
279
|
+
* keeps a chain together when the whole chain can fit on a fresh page.
|
|
280
|
+
*/
|
|
281
|
+
getKeepWithNextChain(blocks, startIndex) {
|
|
282
|
+
const firstBlock = blocks[startIndex];
|
|
283
|
+
if (!firstBlock) return [];
|
|
284
|
+
const chain = [firstBlock];
|
|
285
|
+
let lastIndex = startIndex;
|
|
286
|
+
while (blocks[lastIndex].keepWithNext) {
|
|
287
|
+
const nextBlock = blocks[lastIndex + 1];
|
|
288
|
+
if (!nextBlock || nextBlock.isPageBreak || nextBlock.pageBreakBefore) {
|
|
289
|
+
break;
|
|
290
|
+
}
|
|
291
|
+
chain.push(nextBlock);
|
|
292
|
+
lastIndex++;
|
|
293
|
+
}
|
|
294
|
+
return chain;
|
|
295
|
+
}
|
|
296
|
+
/**
|
|
297
|
+
* Measures the visible body height of a keep-with-next chain using the same
|
|
298
|
+
* collapsed-margin rules as normal block placement. The trailing margin is
|
|
299
|
+
* intentionally excluded, matching the individual block fit check.
|
|
300
|
+
*/
|
|
301
|
+
measureKeepWithNextChainBodyHeight(chain, previousMarginBottomPt, isFirstOnPage) {
|
|
302
|
+
const firstBlock = chain[0];
|
|
303
|
+
if (!firstBlock) return 0;
|
|
304
|
+
const firstMarginTop = isFirstOnPage ? firstBlock.marginTopPt : Math.max(firstBlock.marginTopPt, previousMarginBottomPt) - previousMarginBottomPt;
|
|
305
|
+
let bodyHeight = firstMarginTop + firstBlock.heightPt;
|
|
306
|
+
for (let index = 1; index < chain.length; index++) {
|
|
307
|
+
const previousBlock = chain[index - 1];
|
|
308
|
+
const block = chain[index];
|
|
309
|
+
bodyHeight += Math.max(previousBlock.marginBottomPt, block.marginTopPt) + block.heightPt;
|
|
310
|
+
}
|
|
311
|
+
return bodyHeight;
|
|
312
|
+
}
|
|
313
|
+
/**
|
|
314
|
+
* Finds footnote references introduced by a sequence of blocks, preserving
|
|
315
|
+
* document order and excluding references already assigned to the page.
|
|
316
|
+
*/
|
|
317
|
+
collectNewFootnoteIds(blocks, existingFootnoteIds) {
|
|
318
|
+
const knownIds = new Set(existingFootnoteIds);
|
|
319
|
+
const newIds = [];
|
|
320
|
+
for (const block of blocks) {
|
|
321
|
+
for (const id of this.extractFootnoteRefs(block.element)) {
|
|
322
|
+
if (!knownIds.has(id)) {
|
|
323
|
+
knownIds.add(id);
|
|
324
|
+
newIds.push(id);
|
|
325
|
+
}
|
|
326
|
+
}
|
|
327
|
+
}
|
|
328
|
+
return newIds;
|
|
329
|
+
}
|
|
330
|
+
/**
|
|
331
|
+
* The shortest body available to a section's first, default, or even page.
|
|
332
|
+
* A row fragment must fit every variant, otherwise a later header/footer could
|
|
333
|
+
* send it through the oversized-block fallback again.
|
|
334
|
+
*/
|
|
335
|
+
smallestEffectiveContentHeight(dims, sectionIndex) {
|
|
336
|
+
return Math.min(
|
|
337
|
+
this.getEffectiveHeights(dims, sectionIndex, 1, 1).contentHeight,
|
|
338
|
+
this.getEffectiveHeights(dims, sectionIndex, 2, 1).contentHeight,
|
|
339
|
+
this.getEffectiveHeights(dims, sectionIndex, 2, 2).contentHeight
|
|
340
|
+
);
|
|
341
|
+
}
|
|
342
|
+
/**
|
|
343
|
+
* Builds a clone of a simple table wrapper containing a contiguous run of rows.
|
|
344
|
+
* Complex table features are deliberately rejected by the caller: a split across
|
|
345
|
+
* merged cells, nested tables, or footnotes cannot be made correct by cloning rows.
|
|
346
|
+
*/
|
|
347
|
+
createSimpleTableFragment(wrapper, table, body, rows, retainAnchor) {
|
|
348
|
+
const wrapperClone = wrapper.cloneNode(false);
|
|
349
|
+
const tableClone = table.cloneNode(false);
|
|
350
|
+
for (const child of Array.from(table.children)) {
|
|
351
|
+
if (child !== body) {
|
|
352
|
+
tableClone.appendChild(child.cloneNode(true));
|
|
353
|
+
}
|
|
354
|
+
}
|
|
355
|
+
const bodyClone = body.cloneNode(false);
|
|
356
|
+
for (const row of rows) {
|
|
357
|
+
bodyClone.appendChild(row.cloneNode(true));
|
|
358
|
+
}
|
|
359
|
+
tableClone.appendChild(bodyClone);
|
|
360
|
+
if (!retainAnchor) {
|
|
361
|
+
wrapperClone.removeAttribute("data-anchor");
|
|
362
|
+
tableClone.removeAttribute("data-anchor");
|
|
363
|
+
}
|
|
364
|
+
wrapperClone.appendChild(tableClone);
|
|
365
|
+
return wrapperClone;
|
|
366
|
+
}
|
|
367
|
+
/**
|
|
368
|
+
* Splits an oversized, ordinary table at row boundaries. This only participates
|
|
369
|
+
* in the existing oversized-block fallback; unsupported tables keep the previous
|
|
370
|
+
* overflow behavior rather than risking broken table semantics.
|
|
371
|
+
*/
|
|
372
|
+
trySplitSimpleOversizedTable(block, dims, sectionIndex) {
|
|
373
|
+
const wrapper = block.element;
|
|
374
|
+
if (wrapper.tagName !== "DIV" || wrapper.children.length !== 1 || block.keepWithNext || block.keepLines || block.pageBreakBefore || block.isPageBreak) {
|
|
375
|
+
return null;
|
|
376
|
+
}
|
|
377
|
+
const table = wrapper.firstElementChild;
|
|
378
|
+
if (!(table instanceof HTMLTableElement)) {
|
|
379
|
+
return null;
|
|
380
|
+
}
|
|
381
|
+
const body = table.tBodies.length === 1 ? table.tBodies[0] : null;
|
|
382
|
+
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]")) {
|
|
383
|
+
return null;
|
|
384
|
+
}
|
|
385
|
+
const rows = Array.from(body.rows);
|
|
386
|
+
const minimumContentHeight = this.smallestEffectiveContentHeight(dims, sectionIndex);
|
|
387
|
+
const maximumFragmentHeight = minimumContentHeight - block.marginTopPt - block.marginBottomPt;
|
|
388
|
+
if (maximumFragmentHeight <= 0) {
|
|
389
|
+
return null;
|
|
390
|
+
}
|
|
391
|
+
const groups = [];
|
|
392
|
+
let start = 0;
|
|
393
|
+
while (start < rows.length) {
|
|
394
|
+
let end = start;
|
|
395
|
+
while (end < rows.length) {
|
|
396
|
+
const candidate = this.createSimpleTableFragment(
|
|
397
|
+
wrapper,
|
|
398
|
+
table,
|
|
399
|
+
body,
|
|
400
|
+
rows.slice(start, end + 1),
|
|
401
|
+
start === 0
|
|
402
|
+
);
|
|
403
|
+
const measured = this.measureElement(candidate, dims);
|
|
404
|
+
if (measured.heightPt > maximumFragmentHeight) {
|
|
405
|
+
break;
|
|
406
|
+
}
|
|
407
|
+
end++;
|
|
408
|
+
}
|
|
409
|
+
if (end === start) {
|
|
410
|
+
return null;
|
|
411
|
+
}
|
|
412
|
+
groups.push(rows.slice(start, end));
|
|
413
|
+
start = end;
|
|
414
|
+
}
|
|
415
|
+
if (groups.length < 2) {
|
|
416
|
+
return null;
|
|
417
|
+
}
|
|
418
|
+
const fragments = [];
|
|
419
|
+
for (let index = 0; index < groups.length; index++) {
|
|
420
|
+
const isFirst = index === 0;
|
|
421
|
+
const isLast = index === groups.length - 1;
|
|
422
|
+
const fragment = this.createSimpleTableFragment(
|
|
423
|
+
wrapper,
|
|
424
|
+
table,
|
|
425
|
+
body,
|
|
426
|
+
groups[index],
|
|
427
|
+
isFirst
|
|
428
|
+
);
|
|
429
|
+
if (!isFirst) {
|
|
430
|
+
fragment.style.setProperty("margin-top", "0", "important");
|
|
431
|
+
}
|
|
432
|
+
if (!isLast) {
|
|
433
|
+
fragment.style.setProperty("margin-bottom", "0", "important");
|
|
434
|
+
}
|
|
435
|
+
const measured = this.measureElement(fragment, dims);
|
|
436
|
+
if (measured.heightPt + measured.marginTopPt + measured.marginBottomPt > minimumContentHeight) {
|
|
437
|
+
return null;
|
|
438
|
+
}
|
|
439
|
+
fragments.push({
|
|
440
|
+
...measured,
|
|
441
|
+
keepWithNext: false,
|
|
442
|
+
keepLines: false,
|
|
443
|
+
pageBreakBefore: false,
|
|
444
|
+
isPageBreak: false
|
|
445
|
+
});
|
|
446
|
+
}
|
|
447
|
+
return fragments;
|
|
448
|
+
}
|
|
449
|
+
/**
|
|
450
|
+
* A DOM endpoint that can finish a paragraph fragment. Endpoints are chosen
|
|
451
|
+
* after whitespace or at a run boundary so the paginator never deliberately
|
|
452
|
+
* cuts through an ordinary word merely to fill a little more of a page.
|
|
453
|
+
*/
|
|
454
|
+
paragraphFragmentEndpoints(paragraph) {
|
|
455
|
+
const endpoints = [];
|
|
456
|
+
const walker = document.createTreeWalker(paragraph, NodeFilter.SHOW_TEXT);
|
|
457
|
+
let textNode;
|
|
458
|
+
while (textNode = walker.nextNode()) {
|
|
459
|
+
const text = textNode.data;
|
|
460
|
+
if (text.length === 0) continue;
|
|
461
|
+
const whitespace = /\s+/g;
|
|
462
|
+
let match;
|
|
463
|
+
while ((match = whitespace.exec(text)) !== null) {
|
|
464
|
+
endpoints.push({ node: textNode, offset: match.index + match[0].length });
|
|
465
|
+
}
|
|
466
|
+
if (endpoints.length === 0 || endpoints[endpoints.length - 1].node !== textNode || endpoints[endpoints.length - 1].offset !== text.length) {
|
|
467
|
+
endpoints.push({ node: textNode, offset: text.length });
|
|
468
|
+
}
|
|
469
|
+
}
|
|
470
|
+
return endpoints;
|
|
471
|
+
}
|
|
472
|
+
/**
|
|
473
|
+
* Whether a range contains visible text after ignoring bidi/zero-width marks.
|
|
474
|
+
* Paragraph fragmentation deliberately excludes non-textual descendants, so
|
|
475
|
+
* this is enough to reject empty head or tail fragments.
|
|
476
|
+
*/
|
|
477
|
+
hasVisibleFragmentText(fragment) {
|
|
478
|
+
return (fragment.textContent || "").replace(/[\u200B-\u200F\uFEFF]/g, "").trim().length > 0;
|
|
479
|
+
}
|
|
480
|
+
/**
|
|
481
|
+
* Phase one only handles text-like paragraph descendants. Objects, explicit
|
|
482
|
+
* line breaks, list markers, notes, and out-of-flow/inline-block content all
|
|
483
|
+
* require their own line-layout rules and retain the established whole-block
|
|
484
|
+
* fallback instead of risking broken content or duplicate anchors.
|
|
485
|
+
*/
|
|
486
|
+
canFragmentParagraph(block) {
|
|
487
|
+
if (!this.fragmentParagraphs || block.element.tagName !== "P") {
|
|
488
|
+
return false;
|
|
489
|
+
}
|
|
490
|
+
const paragraph = block.element;
|
|
491
|
+
if (block.keepWithNext || block.keepLines || block.pageBreakBefore || block.isPageBreak || paragraph.dataset.widowControl === "true" || paragraph.hasAttribute("contenteditable")) {
|
|
492
|
+
return false;
|
|
493
|
+
}
|
|
494
|
+
const unsupportedDescendants = [
|
|
495
|
+
"br",
|
|
496
|
+
"img",
|
|
497
|
+
"picture",
|
|
498
|
+
"svg",
|
|
499
|
+
"math",
|
|
500
|
+
"canvas",
|
|
501
|
+
"video",
|
|
502
|
+
"audio",
|
|
503
|
+
"iframe",
|
|
504
|
+
"object",
|
|
505
|
+
"embed",
|
|
506
|
+
"input",
|
|
507
|
+
"button",
|
|
508
|
+
"select",
|
|
509
|
+
"textarea",
|
|
510
|
+
"table",
|
|
511
|
+
"ol",
|
|
512
|
+
"ul",
|
|
513
|
+
"li",
|
|
514
|
+
"dl",
|
|
515
|
+
"div",
|
|
516
|
+
"p",
|
|
517
|
+
"section",
|
|
518
|
+
"article",
|
|
519
|
+
"aside",
|
|
520
|
+
"figure",
|
|
521
|
+
"fieldset",
|
|
522
|
+
"details",
|
|
523
|
+
"[data-footnote-id]",
|
|
524
|
+
"[data-list-marker]",
|
|
525
|
+
"[data-anchor]",
|
|
526
|
+
"[id]",
|
|
527
|
+
"[contenteditable]"
|
|
528
|
+
].join(", ");
|
|
529
|
+
if (paragraph.querySelector(unsupportedDescendants)) {
|
|
530
|
+
return false;
|
|
531
|
+
}
|
|
532
|
+
const paragraphStyle = window.getComputedStyle(paragraph);
|
|
533
|
+
if (paragraphStyle.display !== "block" || paragraphStyle.position !== "static" || paragraphStyle.float !== "none" || paragraphStyle.whiteSpace !== "normal" || paragraphStyle.breakBefore !== "auto" || paragraphStyle.breakAfter !== "auto" || paragraphStyle.breakInside === "avoid" || paragraphStyle.pageBreakBefore !== "auto" || paragraphStyle.pageBreakAfter !== "auto" || paragraphStyle.pageBreakInside === "avoid") {
|
|
534
|
+
return false;
|
|
535
|
+
}
|
|
536
|
+
for (const descendant of Array.from(paragraph.querySelectorAll("*"))) {
|
|
537
|
+
const style = window.getComputedStyle(descendant);
|
|
538
|
+
if (style.display !== "inline" || style.position !== "static" || style.float !== "none" || style.whiteSpace !== "normal") {
|
|
539
|
+
return false;
|
|
540
|
+
}
|
|
541
|
+
}
|
|
542
|
+
return true;
|
|
543
|
+
}
|
|
544
|
+
/**
|
|
545
|
+
* Builds one range-cloned paragraph fragment. Only the leading fragment keeps
|
|
546
|
+
* the source paragraph's addressability; continuations must not duplicate an
|
|
547
|
+
* id/data-anchor in the rendered document.
|
|
548
|
+
*/
|
|
549
|
+
createParagraphFragment(paragraph, range, retainSourceIdentity, isFinalFragment) {
|
|
550
|
+
const fragment = paragraph.cloneNode(false);
|
|
551
|
+
const contents = range.cloneContents();
|
|
552
|
+
fragment.appendChild(contents);
|
|
553
|
+
if (!retainSourceIdentity) {
|
|
554
|
+
fragment.removeAttribute("id");
|
|
555
|
+
fragment.removeAttribute("data-anchor");
|
|
556
|
+
fragment.style.setProperty("margin-top", "0", "important");
|
|
557
|
+
fragment.style.setProperty("text-indent", "0", "important");
|
|
558
|
+
}
|
|
559
|
+
if (!isFinalFragment) {
|
|
560
|
+
fragment.style.setProperty("margin-bottom", "0", "important");
|
|
561
|
+
}
|
|
562
|
+
return fragment;
|
|
563
|
+
}
|
|
564
|
+
/**
|
|
565
|
+
* Splits a simple paragraph at the largest DOM Range endpoint that fits the
|
|
566
|
+
* currently available body space. The caller then processes the tail normally,
|
|
567
|
+
* allowing it to fragment again on later pages when necessary.
|
|
568
|
+
*/
|
|
569
|
+
tryFragmentParagraph(block, dims, availableHeightPt, effectiveMarginTopPt) {
|
|
570
|
+
if (!this.canFragmentParagraph(block) || availableHeightPt <= effectiveMarginTopPt) {
|
|
571
|
+
return null;
|
|
572
|
+
}
|
|
573
|
+
const paragraph = block.element;
|
|
574
|
+
const endpoints = this.paragraphFragmentEndpoints(paragraph);
|
|
575
|
+
if (endpoints.length < 2) {
|
|
576
|
+
return null;
|
|
577
|
+
}
|
|
578
|
+
let low = 0;
|
|
579
|
+
let high = endpoints.length - 2;
|
|
580
|
+
let best = null;
|
|
581
|
+
while (low <= high) {
|
|
582
|
+
const middle = Math.floor((low + high) / 2);
|
|
583
|
+
const endpoint = endpoints[middle];
|
|
584
|
+
const headRange = document.createRange();
|
|
585
|
+
headRange.setStart(paragraph, 0);
|
|
586
|
+
headRange.setEnd(endpoint.node, endpoint.offset);
|
|
587
|
+
const headContents = headRange.cloneContents();
|
|
588
|
+
if (!this.hasVisibleFragmentText(headContents)) {
|
|
589
|
+
low = middle + 1;
|
|
590
|
+
continue;
|
|
591
|
+
}
|
|
592
|
+
const head = this.createParagraphFragment(paragraph, headRange, true, false);
|
|
593
|
+
const measured = this.measureElement(head, dims);
|
|
594
|
+
if (effectiveMarginTopPt + measured.heightPt <= availableHeightPt) {
|
|
595
|
+
best = { endpoint, element: head, measured };
|
|
596
|
+
low = middle + 1;
|
|
597
|
+
} else {
|
|
598
|
+
high = middle - 1;
|
|
599
|
+
}
|
|
600
|
+
}
|
|
601
|
+
if (!best) {
|
|
602
|
+
return null;
|
|
603
|
+
}
|
|
604
|
+
const tailRange = document.createRange();
|
|
605
|
+
tailRange.setStart(best.endpoint.node, best.endpoint.offset);
|
|
606
|
+
tailRange.setEnd(paragraph, paragraph.childNodes.length);
|
|
607
|
+
const tailContents = tailRange.cloneContents();
|
|
608
|
+
if (!this.hasVisibleFragmentText(tailContents)) {
|
|
609
|
+
return null;
|
|
610
|
+
}
|
|
611
|
+
const tail = this.createParagraphFragment(paragraph, tailRange, false, true);
|
|
612
|
+
const tailMeasured = this.measureElement(tail, dims);
|
|
613
|
+
return [
|
|
614
|
+
{
|
|
615
|
+
...best.measured,
|
|
616
|
+
element: best.element,
|
|
617
|
+
keepWithNext: false,
|
|
618
|
+
keepLines: false,
|
|
619
|
+
pageBreakBefore: false,
|
|
620
|
+
isPageBreak: false
|
|
621
|
+
},
|
|
622
|
+
{
|
|
623
|
+
...tailMeasured,
|
|
624
|
+
element: tail,
|
|
625
|
+
keepWithNext: false,
|
|
626
|
+
keepLines: false,
|
|
627
|
+
pageBreakBefore: false,
|
|
628
|
+
isPageBreak: false
|
|
629
|
+
}
|
|
630
|
+
];
|
|
631
|
+
}
|
|
151
632
|
/**
|
|
152
633
|
* Parses the header/footer registry from the staging element.
|
|
153
634
|
* Also measures heights during parsing for lazy-loading compatibility.
|
|
@@ -251,6 +732,7 @@ var DocxodusPagination = (() => {
|
|
|
251
732
|
measureContainer.style.visibility = "hidden";
|
|
252
733
|
measureContainer.style.width = `${contentWidth}pt`;
|
|
253
734
|
measureContainer.style.left = "-9999px";
|
|
735
|
+
measureContainer.className = this.cssPrefix + "footnotes";
|
|
254
736
|
const hr = document.createElement("hr");
|
|
255
737
|
measureContainer.appendChild(hr);
|
|
256
738
|
if (hasContinuation) {
|
|
@@ -285,6 +767,7 @@ var DocxodusPagination = (() => {
|
|
|
285
767
|
measureContainer.style.visibility = "hidden";
|
|
286
768
|
measureContainer.style.width = `${contentWidth}pt`;
|
|
287
769
|
measureContainer.style.left = "-9999px";
|
|
770
|
+
measureContainer.className = this.cssPrefix + "footnotes";
|
|
288
771
|
const hr = document.createElement("hr");
|
|
289
772
|
measureContainer.appendChild(hr);
|
|
290
773
|
for (const el of continuation.remainingElements) {
|
|
@@ -303,11 +786,17 @@ var DocxodusPagination = (() => {
|
|
|
303
786
|
splitFootnoteToFit(footnoteElement, availableHeightPt, contentWidth) {
|
|
304
787
|
const footnoteContent = footnoteElement.querySelector(".footnote-content");
|
|
305
788
|
if (!footnoteContent) {
|
|
306
|
-
return {
|
|
789
|
+
return {
|
|
790
|
+
fits: Array.from(footnoteElement.children).map((el) => el.cloneNode(true)),
|
|
791
|
+
overflow: []
|
|
792
|
+
};
|
|
307
793
|
}
|
|
308
794
|
const children = Array.from(footnoteContent.children);
|
|
309
795
|
if (children.length <= 1) {
|
|
310
|
-
return {
|
|
796
|
+
return {
|
|
797
|
+
fits: children.map((el) => el.cloneNode(true)),
|
|
798
|
+
overflow: []
|
|
799
|
+
};
|
|
311
800
|
}
|
|
312
801
|
const fits = [];
|
|
313
802
|
const overflow = [];
|
|
@@ -317,6 +806,7 @@ var DocxodusPagination = (() => {
|
|
|
317
806
|
hrMeasure.style.visibility = "hidden";
|
|
318
807
|
hrMeasure.style.width = `${contentWidth}pt`;
|
|
319
808
|
hrMeasure.style.left = "-9999px";
|
|
809
|
+
hrMeasure.className = this.cssPrefix + "footnotes";
|
|
320
810
|
const hr = document.createElement("hr");
|
|
321
811
|
hrMeasure.appendChild(hr);
|
|
322
812
|
this.stagingElement.appendChild(hrMeasure);
|
|
@@ -331,6 +821,7 @@ var DocxodusPagination = (() => {
|
|
|
331
821
|
measureContainer.style.visibility = "hidden";
|
|
332
822
|
measureContainer.style.width = `${contentWidth}pt`;
|
|
333
823
|
measureContainer.style.left = "-9999px";
|
|
824
|
+
measureContainer.className = this.cssPrefix + "footnotes";
|
|
334
825
|
measureContainer.appendChild(child.cloneNode(true));
|
|
335
826
|
this.stagingElement.appendChild(measureContainer);
|
|
336
827
|
const childHeight = pxToPt(measureContainer.getBoundingClientRect().height);
|
|
@@ -358,6 +849,7 @@ var DocxodusPagination = (() => {
|
|
|
358
849
|
measureContainer.style.visibility = "hidden";
|
|
359
850
|
measureContainer.style.width = `${contentWidth}pt`;
|
|
360
851
|
measureContainer.style.left = "-9999px";
|
|
852
|
+
measureContainer.className = this.cssPrefix + "footnotes";
|
|
361
853
|
measureContainer.appendChild(footnote.cloneNode(true));
|
|
362
854
|
this.stagingElement.appendChild(measureContainer);
|
|
363
855
|
const rect = measureContainer.getBoundingClientRect();
|
|
@@ -541,12 +1033,14 @@ var DocxodusPagination = (() => {
|
|
|
541
1033
|
let currentFootnoteHeight = 0;
|
|
542
1034
|
let currentContinuation = this.pendingFootnoteContinuation;
|
|
543
1035
|
let nextPageContinuation = null;
|
|
1036
|
+
let deferredFootnoteIds = [];
|
|
544
1037
|
let currentPartialFootnotes = [];
|
|
545
1038
|
if (currentContinuation && currentContinuation.remainingElements.length > 0) {
|
|
546
1039
|
currentFootnoteHeight = this.measureContinuationHeight(currentContinuation, dims.contentWidth);
|
|
547
1040
|
}
|
|
548
1041
|
const finishPage = () => {
|
|
549
|
-
|
|
1042
|
+
const hasCurrentContinuation = (currentContinuation?.remainingElements.length ?? 0) > 0;
|
|
1043
|
+
if (currentContent.length === 0 && !hasCurrentContinuation) return;
|
|
550
1044
|
const page = this.createPage(
|
|
551
1045
|
dims,
|
|
552
1046
|
pageNumber,
|
|
@@ -570,15 +1064,25 @@ var DocxodusPagination = (() => {
|
|
|
570
1064
|
currentPartialFootnotes = [];
|
|
571
1065
|
currentContinuation = nextPageContinuation;
|
|
572
1066
|
nextPageContinuation = null;
|
|
1067
|
+
if (deferredFootnoteIds.length > 0) {
|
|
1068
|
+
currentFootnoteIds = [...deferredFootnoteIds];
|
|
1069
|
+
deferredFootnoteIds = [];
|
|
1070
|
+
}
|
|
573
1071
|
if (currentContinuation && currentContinuation.remainingElements.length > 0) {
|
|
574
1072
|
currentFootnoteHeight = this.measureContinuationHeight(currentContinuation, dims.contentWidth);
|
|
575
1073
|
} else {
|
|
576
1074
|
currentFootnoteHeight = 0;
|
|
577
1075
|
}
|
|
1076
|
+
if (currentFootnoteIds.length > 0) {
|
|
1077
|
+
currentFootnoteHeight += this.measureFootnotesHeight(
|
|
1078
|
+
currentFootnoteIds,
|
|
1079
|
+
dims.contentWidth,
|
|
1080
|
+
null
|
|
1081
|
+
);
|
|
1082
|
+
}
|
|
578
1083
|
};
|
|
579
1084
|
for (let i = 0; i < blocks.length; i++) {
|
|
580
1085
|
const block = blocks[i];
|
|
581
|
-
const nextBlock = blocks[i + 1];
|
|
582
1086
|
if (block.isPageBreak) {
|
|
583
1087
|
finishPage();
|
|
584
1088
|
continue;
|
|
@@ -586,8 +1090,59 @@ var DocxodusPagination = (() => {
|
|
|
586
1090
|
if (block.pageBreakBefore && currentContent.length > 0) {
|
|
587
1091
|
finishPage();
|
|
588
1092
|
}
|
|
589
|
-
const
|
|
590
|
-
const
|
|
1093
|
+
const previousBlock = blocks[i - 1];
|
|
1094
|
+
const startsKeepChain = !previousBlock || !previousBlock.keepWithNext || previousBlock.isPageBreak || block.pageBreakBefore;
|
|
1095
|
+
const pageHasOccupiedSpace = currentContent.length > 0 || currentFootnoteHeight > 0;
|
|
1096
|
+
if (pageHasOccupiedSpace && block.keepWithNext && startsKeepChain) {
|
|
1097
|
+
const keepChain = this.getKeepWithNextChain(blocks, i);
|
|
1098
|
+
if (keepChain.length > 1) {
|
|
1099
|
+
const newChainFootnoteIds = this.collectNewFootnoteIds(
|
|
1100
|
+
keepChain,
|
|
1101
|
+
currentFootnoteIds
|
|
1102
|
+
);
|
|
1103
|
+
let additionalChainFootnoteHeight = 0;
|
|
1104
|
+
if (newChainFootnoteIds.length > 0 && this.footnoteRegistry.size > 0) {
|
|
1105
|
+
const totalChainFootnoteHeight = this.measureFootnotesHeight(
|
|
1106
|
+
[...currentFootnoteIds, ...newChainFootnoteIds],
|
|
1107
|
+
dims.contentWidth,
|
|
1108
|
+
currentContinuation
|
|
1109
|
+
);
|
|
1110
|
+
additionalChainFootnoteHeight = Math.max(
|
|
1111
|
+
0,
|
|
1112
|
+
totalChainFootnoteHeight - currentFootnoteHeight
|
|
1113
|
+
);
|
|
1114
|
+
}
|
|
1115
|
+
const currentChainHeight = this.measureKeepWithNextChainBodyHeight(
|
|
1116
|
+
keepChain,
|
|
1117
|
+
prevMarginBottomPt,
|
|
1118
|
+
currentContent.length === 0
|
|
1119
|
+
) + additionalChainFootnoteHeight;
|
|
1120
|
+
const currentAvailableHeight = remainingHeight - currentFootnoteHeight;
|
|
1121
|
+
if (currentChainHeight > currentAvailableHeight) {
|
|
1122
|
+
const nextPageHeights = this.getEffectiveHeights(
|
|
1123
|
+
dims,
|
|
1124
|
+
sectionIndex,
|
|
1125
|
+
pageInSection + 1,
|
|
1126
|
+
pageNumber + 1
|
|
1127
|
+
);
|
|
1128
|
+
const freshChainBodyHeight = this.measureKeepWithNextChainBodyHeight(
|
|
1129
|
+
keepChain,
|
|
1130
|
+
0,
|
|
1131
|
+
true
|
|
1132
|
+
);
|
|
1133
|
+
const freshChainFootnoteHeight = this.measureFootnotesHeight(
|
|
1134
|
+
newChainFootnoteIds,
|
|
1135
|
+
dims.contentWidth,
|
|
1136
|
+
nextPageContinuation
|
|
1137
|
+
);
|
|
1138
|
+
if (freshChainBodyHeight + freshChainFootnoteHeight <= nextPageHeights.contentHeight) {
|
|
1139
|
+
finishPage();
|
|
1140
|
+
}
|
|
1141
|
+
}
|
|
1142
|
+
}
|
|
1143
|
+
}
|
|
1144
|
+
const allBlockFootnoteIds = this.extractFootnoteRefs(block.element);
|
|
1145
|
+
const newFootnoteIds = this.collectNewFootnoteIds([block], currentFootnoteIds);
|
|
591
1146
|
let additionalFootnoteHeight = 0;
|
|
592
1147
|
if (newFootnoteIds.length > 0 && this.footnoteRegistry.size > 0) {
|
|
593
1148
|
const combinedFootnoteIds = [...currentFootnoteIds, ...newFootnoteIds];
|
|
@@ -604,15 +1159,23 @@ var DocxodusPagination = (() => {
|
|
|
604
1159
|
effectiveMarginTop = Math.max(block.marginTopPt, prevMarginBottomPt) - prevMarginBottomPt;
|
|
605
1160
|
}
|
|
606
1161
|
const blockSpace = effectiveMarginTop + block.heightPt + additionalFootnoteHeight;
|
|
607
|
-
let neededHeight = blockSpace;
|
|
608
|
-
if (block.keepWithNext && nextBlock && !nextBlock.isPageBreak) {
|
|
609
|
-
const collapsedMargin = Math.max(block.marginBottomPt, nextBlock.marginTopPt);
|
|
610
|
-
neededHeight = effectiveMarginTop + block.heightPt + collapsedMargin + nextBlock.heightPt + additionalFootnoteHeight;
|
|
611
|
-
}
|
|
612
1162
|
const effectiveRemainingHeight = remainingHeight - currentFootnoteHeight;
|
|
613
1163
|
const bodyContentUsed = effectiveContentHeight - remainingHeight;
|
|
614
1164
|
const maxFootnoteArea = effectiveContentHeight * MAX_FOOTNOTE_AREA_RATIO;
|
|
615
1165
|
const maxFootnoteExpansion = Math.max(0, maxFootnoteArea - currentFootnoteHeight);
|
|
1166
|
+
if (blockSpace > effectiveRemainingHeight) {
|
|
1167
|
+
const paragraphFragments = this.tryFragmentParagraph(
|
|
1168
|
+
block,
|
|
1169
|
+
dims,
|
|
1170
|
+
effectiveRemainingHeight,
|
|
1171
|
+
effectiveMarginTop
|
|
1172
|
+
);
|
|
1173
|
+
if (paragraphFragments) {
|
|
1174
|
+
blocks.splice(i, 1, ...paragraphFragments);
|
|
1175
|
+
i--;
|
|
1176
|
+
continue;
|
|
1177
|
+
}
|
|
1178
|
+
}
|
|
616
1179
|
if (blockSpace <= effectiveRemainingHeight) {
|
|
617
1180
|
currentContent.push(block.element.cloneNode(true));
|
|
618
1181
|
remainingHeight -= effectiveMarginTop + block.heightPt + block.marginBottomPt;
|
|
@@ -654,28 +1217,18 @@ var DocxodusPagination = (() => {
|
|
|
654
1217
|
footnoteId,
|
|
655
1218
|
fittingElements: fits
|
|
656
1219
|
});
|
|
657
|
-
|
|
658
|
-
|
|
659
|
-
|
|
660
|
-
|
|
1220
|
+
if (overflow.length > 0) {
|
|
1221
|
+
nextPageContinuation = {
|
|
1222
|
+
footnoteId,
|
|
1223
|
+
remainingElements: overflow
|
|
1224
|
+
};
|
|
1225
|
+
}
|
|
661
1226
|
currentFootnoteHeight = availableForFootnotes;
|
|
662
1227
|
} else {
|
|
663
|
-
|
|
664
|
-
footnoteId,
|
|
665
|
-
remainingElements: Array.from(footnote.querySelectorAll(".footnote-content > *")).map((el) => el.cloneNode(true))
|
|
666
|
-
};
|
|
667
|
-
if (nextPageContinuation.remainingElements.length === 0) {
|
|
668
|
-
nextPageContinuation.remainingElements = [footnote.cloneNode(true)];
|
|
669
|
-
}
|
|
1228
|
+
deferredFootnoteIds.push(footnoteId);
|
|
670
1229
|
}
|
|
671
1230
|
} else {
|
|
672
|
-
|
|
673
|
-
footnoteId,
|
|
674
|
-
remainingElements: Array.from(footnote.querySelectorAll(".footnote-content > *")).map((el) => el.cloneNode(true))
|
|
675
|
-
};
|
|
676
|
-
if (nextPageContinuation.remainingElements.length === 0) {
|
|
677
|
-
nextPageContinuation.remainingElements = [footnote.cloneNode(true)];
|
|
678
|
-
}
|
|
1231
|
+
deferredFootnoteIds.push(footnoteId);
|
|
679
1232
|
}
|
|
680
1233
|
}
|
|
681
1234
|
}
|
|
@@ -690,30 +1243,39 @@ var DocxodusPagination = (() => {
|
|
|
690
1243
|
currentFootnoteHeight = expandedFootnoteSpace;
|
|
691
1244
|
} else {
|
|
692
1245
|
finishPage();
|
|
693
|
-
const newPageFootnoteHeight =
|
|
1246
|
+
const newPageFootnoteHeight = allBlockFootnoteIds.length > 0 ? this.measureFootnotesHeight(allBlockFootnoteIds, dims.contentWidth, currentContinuation) : currentContinuation ? this.measureContinuationHeight(currentContinuation, dims.contentWidth) : 0;
|
|
694
1247
|
const newPageSpace = block.marginTopPt + block.heightPt + block.marginBottomPt;
|
|
695
1248
|
currentContent.push(block.element.cloneNode(true));
|
|
696
1249
|
remainingHeight = effectiveContentHeight - newPageSpace;
|
|
697
1250
|
prevMarginBottomPt = block.marginBottomPt;
|
|
698
|
-
currentFootnoteIds = [...
|
|
1251
|
+
currentFootnoteIds = [...currentFootnoteIds, ...allBlockFootnoteIds];
|
|
699
1252
|
currentFootnoteHeight = newPageFootnoteHeight;
|
|
700
1253
|
}
|
|
701
1254
|
} else {
|
|
702
1255
|
finishPage();
|
|
703
|
-
const newPageFootnoteHeight =
|
|
1256
|
+
const newPageFootnoteHeight = allBlockFootnoteIds.length > 0 ? this.measureFootnotesHeight(allBlockFootnoteIds, dims.contentWidth, currentContinuation) : currentContinuation ? this.measureContinuationHeight(currentContinuation, dims.contentWidth) : 0;
|
|
704
1257
|
const newPageSpace = block.marginTopPt + block.heightPt + block.marginBottomPt;
|
|
705
1258
|
currentContent.push(block.element.cloneNode(true));
|
|
706
1259
|
remainingHeight = effectiveContentHeight - newPageSpace;
|
|
707
1260
|
prevMarginBottomPt = block.marginBottomPt;
|
|
708
|
-
currentFootnoteIds = [...
|
|
1261
|
+
currentFootnoteIds = [...currentFootnoteIds, ...allBlockFootnoteIds];
|
|
709
1262
|
currentFootnoteHeight = newPageFootnoteHeight;
|
|
710
1263
|
}
|
|
711
1264
|
} else {
|
|
1265
|
+
const tableFragments = this.trySplitSimpleOversizedTable(block, dims, sectionIndex);
|
|
1266
|
+
if (tableFragments) {
|
|
1267
|
+
if (currentContent.length > 0 || currentContinuation) {
|
|
1268
|
+
finishPage();
|
|
1269
|
+
}
|
|
1270
|
+
blocks.splice(i, 1, ...tableFragments);
|
|
1271
|
+
i--;
|
|
1272
|
+
continue;
|
|
1273
|
+
}
|
|
712
1274
|
if (currentContent.length > 0) {
|
|
713
1275
|
finishPage();
|
|
714
1276
|
}
|
|
715
1277
|
currentContent.push(block.element.cloneNode(true));
|
|
716
|
-
currentFootnoteIds = [...
|
|
1278
|
+
currentFootnoteIds = [...currentFootnoteIds, ...allBlockFootnoteIds];
|
|
717
1279
|
finishPage();
|
|
718
1280
|
}
|
|
719
1281
|
}
|
|
@@ -721,6 +1283,27 @@ var DocxodusPagination = (() => {
|
|
|
721
1283
|
this.pendingFootnoteContinuation = nextPageContinuation;
|
|
722
1284
|
return pages;
|
|
723
1285
|
}
|
|
1286
|
+
/**
|
|
1287
|
+
* Strip block addressing from a header/footer node cloned into a page box.
|
|
1288
|
+
*
|
|
1289
|
+
* A running story is authored ONCE and cloned onto every page, so the clones all carry the same
|
|
1290
|
+
* `data-anchor` — on this document, 42 page boxes claiming one footer paragraph. Left editable,
|
|
1291
|
+
* committing any one of them writes back through that single shared anchor, and the per-page
|
|
1292
|
+
* page-number substitution makes it worse: each clone shows a DIFFERENT number, so a commit
|
|
1293
|
+
* writes that page's number into the story as literal text and destroys the PAGE field.
|
|
1294
|
+
*
|
|
1295
|
+
* Page-box header/footer content is presentation. The docked editing bands
|
|
1296
|
+
* (`editor-headerfooter.ts`) are the addressable affordance, and they exist precisely because a
|
|
1297
|
+
* cloned node cannot be uniquely addressed.
|
|
1298
|
+
*/
|
|
1299
|
+
makeClonedStoryInert(root) {
|
|
1300
|
+
const nodes = [root, ...Array.from(root.querySelectorAll("*"))];
|
|
1301
|
+
for (const el of nodes) {
|
|
1302
|
+
el.removeAttribute("data-anchor");
|
|
1303
|
+
el.removeAttribute("data-committed-text");
|
|
1304
|
+
if (el.getAttribute("contenteditable") !== null) el.setAttribute("contenteditable", "false");
|
|
1305
|
+
}
|
|
1306
|
+
}
|
|
724
1307
|
/**
|
|
725
1308
|
* Creates a page container element.
|
|
726
1309
|
*/
|
|
@@ -746,6 +1329,7 @@ var DocxodusPagination = (() => {
|
|
|
746
1329
|
pageBox.style.contain = "layout paint";
|
|
747
1330
|
pageBox.dataset.pageNumber = String(pageNumber);
|
|
748
1331
|
pageBox.dataset.sectionIndex = String(sectionIndex);
|
|
1332
|
+
pageBox.dataset.pageInSection = String(pageInSection);
|
|
749
1333
|
const effectiveHeights = this.getEffectiveHeights(dims, sectionIndex, pageInSection, pageNumber);
|
|
750
1334
|
const headerSource = this.selectHeader(sectionIndex, pageInSection, pageNumber);
|
|
751
1335
|
if (headerSource) {
|
|
@@ -763,7 +1347,9 @@ var DocxodusPagination = (() => {
|
|
|
763
1347
|
headerDiv.style.justifyContent = "flex-end";
|
|
764
1348
|
headerDiv.style.paddingBottom = "4pt";
|
|
765
1349
|
for (const child of Array.from(headerSource.childNodes)) {
|
|
766
|
-
|
|
1350
|
+
const clonedheaderDiv = child.cloneNode(true);
|
|
1351
|
+
if (clonedheaderDiv.nodeType === 1) this.makeClonedStoryInert(clonedheaderDiv);
|
|
1352
|
+
headerDiv.appendChild(clonedheaderDiv);
|
|
767
1353
|
}
|
|
768
1354
|
pageBox.appendChild(headerDiv);
|
|
769
1355
|
}
|
|
@@ -801,7 +1387,9 @@ var DocxodusPagination = (() => {
|
|
|
801
1387
|
footerDiv.style.justifyContent = "flex-start";
|
|
802
1388
|
footerDiv.style.paddingTop = "4pt";
|
|
803
1389
|
for (const child of Array.from(footerSource.childNodes)) {
|
|
804
|
-
|
|
1390
|
+
const clonedfooterDiv = child.cloneNode(true);
|
|
1391
|
+
if (clonedfooterDiv.nodeType === 1) this.makeClonedStoryInert(clonedfooterDiv);
|
|
1392
|
+
footerDiv.appendChild(clonedfooterDiv);
|
|
805
1393
|
}
|
|
806
1394
|
pageBox.appendChild(footerDiv);
|
|
807
1395
|
}
|
|
@@ -812,6 +1400,13 @@ var DocxodusPagination = (() => {
|
|
|
812
1400
|
pageBox.appendChild(pageNum);
|
|
813
1401
|
}
|
|
814
1402
|
this.containerElement.appendChild(pageBox);
|
|
1403
|
+
const notesEl = pageBox.querySelector(`.${this.cssPrefix}footnotes`);
|
|
1404
|
+
if (notesEl) {
|
|
1405
|
+
const notesHeightPt = pxToPt(notesEl.getBoundingClientRect().height);
|
|
1406
|
+
if (notesHeightPt > 0) {
|
|
1407
|
+
contentArea.style.height = `${Math.max(0, contentAreaHeight - notesHeightPt)}pt`;
|
|
1408
|
+
}
|
|
1409
|
+
}
|
|
815
1410
|
return {
|
|
816
1411
|
pageNumber,
|
|
817
1412
|
sectionIndex,
|
|
@@ -837,7 +1432,10 @@ var DocxodusPagination = (() => {
|
|
|
837
1432
|
if (!pageContainer) {
|
|
838
1433
|
throw new Error("Pagination container element not found");
|
|
839
1434
|
}
|
|
840
|
-
const engine = new PaginationEngine(staging, pageContainer,
|
|
1435
|
+
const engine = new PaginationEngine(staging, pageContainer, {
|
|
1436
|
+
...options,
|
|
1437
|
+
fragmentParagraphs: options.fragmentParagraphs ?? true
|
|
1438
|
+
});
|
|
841
1439
|
return engine.paginate();
|
|
842
1440
|
}
|
|
843
1441
|
return __toCommonJS(pagination_exports);
|