docxodus 7.0.1 → 8.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/dist/docxodus.worker.js +2 -2
- package/dist/docxodus.worker.js.map +1 -1
- package/dist/editor-headerfooter.d.ts +130 -0
- package/dist/editor-headerfooter.d.ts.map +1 -0
- package/dist/editor-headerfooter.js +414 -0
- package/dist/editor-headerfooter.js.map +1 -0
- package/dist/editor.bundle.js +1075 -81
- package/dist/editor.d.ts +82 -4
- package/dist/editor.d.ts.map +1 -1
- package/dist/editor.js +304 -63
- package/dist/editor.js.map +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/pagination.bundle.js +480 -19
- package/dist/pagination.d.ts +81 -0
- package/dist/pagination.d.ts.map +1 -1
- package/dist/pagination.js +528 -26
- 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 +41 -0
- package/dist/session.d.ts +33 -1
- package/dist/session.d.ts.map +1 -1
- package/dist/session.js +41 -0
- package/dist/session.js.map +1 -1
- package/dist/types.d.ts +59 -10
- 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.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 +1 -1
|
@@ -82,6 +82,7 @@ var DocxodusPagination = (() => {
|
|
|
82
82
|
this.cssPrefix = options.cssPrefix ?? "page-";
|
|
83
83
|
this.showPageNumbers = options.showPageNumbers ?? true;
|
|
84
84
|
this.pageGap = options.pageGap ?? 20;
|
|
85
|
+
this.fragmentParagraphs = options.fragmentParagraphs ?? false;
|
|
85
86
|
this.hfRegistry = /* @__PURE__ */ new Map();
|
|
86
87
|
this.footnoteRegistry = /* @__PURE__ */ new Map();
|
|
87
88
|
}
|
|
@@ -148,6 +149,393 @@ var DocxodusPagination = (() => {
|
|
|
148
149
|
}
|
|
149
150
|
return blocks;
|
|
150
151
|
}
|
|
152
|
+
/**
|
|
153
|
+
* Measures one element in the same hidden staging context used for the source blocks.
|
|
154
|
+
* This is intentionally DOM-based: table row heights cannot be inferred from individual
|
|
155
|
+
* rows because wrapping and collapsed borders change the height of a fragment.
|
|
156
|
+
*/
|
|
157
|
+
measureElement(element, dims) {
|
|
158
|
+
const measurementHost = document.createElement("div");
|
|
159
|
+
measurementHost.style.position = "absolute";
|
|
160
|
+
measurementHost.style.visibility = "hidden";
|
|
161
|
+
measurementHost.style.left = "-9999px";
|
|
162
|
+
measurementHost.style.width = `${dims.contentWidth}pt`;
|
|
163
|
+
const measuredElement = element.cloneNode(true);
|
|
164
|
+
measurementHost.appendChild(measuredElement);
|
|
165
|
+
this.stagingElement.appendChild(measurementHost);
|
|
166
|
+
const rect = measuredElement.getBoundingClientRect();
|
|
167
|
+
const style = window.getComputedStyle(measuredElement);
|
|
168
|
+
const measured = {
|
|
169
|
+
element,
|
|
170
|
+
heightPt: pxToPt(rect.height),
|
|
171
|
+
marginTopPt: pxToPt(parseFloat(style.marginTop) || 0),
|
|
172
|
+
marginBottomPt: pxToPt(parseFloat(style.marginBottom) || 0),
|
|
173
|
+
keepWithNext: element.dataset.keepWithNext === "true",
|
|
174
|
+
keepLines: element.dataset.keepLines === "true",
|
|
175
|
+
pageBreakBefore: element.dataset.pageBreakBefore === "true",
|
|
176
|
+
isPageBreak: element.dataset.pageBreak === "true" || element.classList.contains(`${this.cssPrefix}break`)
|
|
177
|
+
};
|
|
178
|
+
this.stagingElement.removeChild(measurementHost);
|
|
179
|
+
return measured;
|
|
180
|
+
}
|
|
181
|
+
/**
|
|
182
|
+
* Returns the contiguous keep-with-next chain beginning at a block.
|
|
183
|
+
*
|
|
184
|
+
* A hard page break or a page-break-before directive is stronger than a
|
|
185
|
+
* keep-with-next directive, so it terminates the chain. The caller only
|
|
186
|
+
* keeps a chain together when the whole chain can fit on a fresh page.
|
|
187
|
+
*/
|
|
188
|
+
getKeepWithNextChain(blocks, startIndex) {
|
|
189
|
+
const firstBlock = blocks[startIndex];
|
|
190
|
+
if (!firstBlock) return [];
|
|
191
|
+
const chain = [firstBlock];
|
|
192
|
+
let lastIndex = startIndex;
|
|
193
|
+
while (blocks[lastIndex].keepWithNext) {
|
|
194
|
+
const nextBlock = blocks[lastIndex + 1];
|
|
195
|
+
if (!nextBlock || nextBlock.isPageBreak || nextBlock.pageBreakBefore) {
|
|
196
|
+
break;
|
|
197
|
+
}
|
|
198
|
+
chain.push(nextBlock);
|
|
199
|
+
lastIndex++;
|
|
200
|
+
}
|
|
201
|
+
return chain;
|
|
202
|
+
}
|
|
203
|
+
/**
|
|
204
|
+
* Measures the visible body height of a keep-with-next chain using the same
|
|
205
|
+
* collapsed-margin rules as normal block placement. The trailing margin is
|
|
206
|
+
* intentionally excluded, matching the individual block fit check.
|
|
207
|
+
*/
|
|
208
|
+
measureKeepWithNextChainBodyHeight(chain, previousMarginBottomPt, isFirstOnPage) {
|
|
209
|
+
const firstBlock = chain[0];
|
|
210
|
+
if (!firstBlock) return 0;
|
|
211
|
+
const firstMarginTop = isFirstOnPage ? firstBlock.marginTopPt : Math.max(firstBlock.marginTopPt, previousMarginBottomPt) - previousMarginBottomPt;
|
|
212
|
+
let bodyHeight = firstMarginTop + firstBlock.heightPt;
|
|
213
|
+
for (let index = 1; index < chain.length; index++) {
|
|
214
|
+
const previousBlock = chain[index - 1];
|
|
215
|
+
const block = chain[index];
|
|
216
|
+
bodyHeight += Math.max(previousBlock.marginBottomPt, block.marginTopPt) + block.heightPt;
|
|
217
|
+
}
|
|
218
|
+
return bodyHeight;
|
|
219
|
+
}
|
|
220
|
+
/**
|
|
221
|
+
* Finds footnote references introduced by a sequence of blocks, preserving
|
|
222
|
+
* document order and excluding references already assigned to the page.
|
|
223
|
+
*/
|
|
224
|
+
collectNewFootnoteIds(blocks, existingFootnoteIds) {
|
|
225
|
+
const knownIds = new Set(existingFootnoteIds);
|
|
226
|
+
const newIds = [];
|
|
227
|
+
for (const block of blocks) {
|
|
228
|
+
for (const id of this.extractFootnoteRefs(block.element)) {
|
|
229
|
+
if (!knownIds.has(id)) {
|
|
230
|
+
knownIds.add(id);
|
|
231
|
+
newIds.push(id);
|
|
232
|
+
}
|
|
233
|
+
}
|
|
234
|
+
}
|
|
235
|
+
return newIds;
|
|
236
|
+
}
|
|
237
|
+
/**
|
|
238
|
+
* The shortest body available to a section's first, default, or even page.
|
|
239
|
+
* A row fragment must fit every variant, otherwise a later header/footer could
|
|
240
|
+
* send it through the oversized-block fallback again.
|
|
241
|
+
*/
|
|
242
|
+
smallestEffectiveContentHeight(dims, sectionIndex) {
|
|
243
|
+
return Math.min(
|
|
244
|
+
this.getEffectiveHeights(dims, sectionIndex, 1, 1).contentHeight,
|
|
245
|
+
this.getEffectiveHeights(dims, sectionIndex, 2, 1).contentHeight,
|
|
246
|
+
this.getEffectiveHeights(dims, sectionIndex, 2, 2).contentHeight
|
|
247
|
+
);
|
|
248
|
+
}
|
|
249
|
+
/**
|
|
250
|
+
* Builds a clone of a simple table wrapper containing a contiguous run of rows.
|
|
251
|
+
* Complex table features are deliberately rejected by the caller: a split across
|
|
252
|
+
* merged cells, nested tables, or footnotes cannot be made correct by cloning rows.
|
|
253
|
+
*/
|
|
254
|
+
createSimpleTableFragment(wrapper, table, body, rows, retainAnchor) {
|
|
255
|
+
const wrapperClone = wrapper.cloneNode(false);
|
|
256
|
+
const tableClone = table.cloneNode(false);
|
|
257
|
+
for (const child of Array.from(table.children)) {
|
|
258
|
+
if (child !== body) {
|
|
259
|
+
tableClone.appendChild(child.cloneNode(true));
|
|
260
|
+
}
|
|
261
|
+
}
|
|
262
|
+
const bodyClone = body.cloneNode(false);
|
|
263
|
+
for (const row of rows) {
|
|
264
|
+
bodyClone.appendChild(row.cloneNode(true));
|
|
265
|
+
}
|
|
266
|
+
tableClone.appendChild(bodyClone);
|
|
267
|
+
if (!retainAnchor) {
|
|
268
|
+
wrapperClone.removeAttribute("data-anchor");
|
|
269
|
+
tableClone.removeAttribute("data-anchor");
|
|
270
|
+
}
|
|
271
|
+
wrapperClone.appendChild(tableClone);
|
|
272
|
+
return wrapperClone;
|
|
273
|
+
}
|
|
274
|
+
/**
|
|
275
|
+
* Splits an oversized, ordinary table at row boundaries. This only participates
|
|
276
|
+
* in the existing oversized-block fallback; unsupported tables keep the previous
|
|
277
|
+
* overflow behavior rather than risking broken table semantics.
|
|
278
|
+
*/
|
|
279
|
+
trySplitSimpleOversizedTable(block, dims, sectionIndex) {
|
|
280
|
+
const wrapper = block.element;
|
|
281
|
+
if (wrapper.tagName !== "DIV" || wrapper.children.length !== 1 || block.keepWithNext || block.keepLines || block.pageBreakBefore || block.isPageBreak) {
|
|
282
|
+
return null;
|
|
283
|
+
}
|
|
284
|
+
const table = wrapper.firstElementChild;
|
|
285
|
+
if (!(table instanceof HTMLTableElement)) {
|
|
286
|
+
return null;
|
|
287
|
+
}
|
|
288
|
+
const body = table.tBodies.length === 1 ? table.tBodies[0] : null;
|
|
289
|
+
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]")) {
|
|
290
|
+
return null;
|
|
291
|
+
}
|
|
292
|
+
const rows = Array.from(body.rows);
|
|
293
|
+
const minimumContentHeight = this.smallestEffectiveContentHeight(dims, sectionIndex);
|
|
294
|
+
const maximumFragmentHeight = minimumContentHeight - block.marginTopPt - block.marginBottomPt;
|
|
295
|
+
if (maximumFragmentHeight <= 0) {
|
|
296
|
+
return null;
|
|
297
|
+
}
|
|
298
|
+
const groups = [];
|
|
299
|
+
let start = 0;
|
|
300
|
+
while (start < rows.length) {
|
|
301
|
+
let end = start;
|
|
302
|
+
while (end < rows.length) {
|
|
303
|
+
const candidate = this.createSimpleTableFragment(
|
|
304
|
+
wrapper,
|
|
305
|
+
table,
|
|
306
|
+
body,
|
|
307
|
+
rows.slice(start, end + 1),
|
|
308
|
+
start === 0
|
|
309
|
+
);
|
|
310
|
+
const measured = this.measureElement(candidate, dims);
|
|
311
|
+
if (measured.heightPt > maximumFragmentHeight) {
|
|
312
|
+
break;
|
|
313
|
+
}
|
|
314
|
+
end++;
|
|
315
|
+
}
|
|
316
|
+
if (end === start) {
|
|
317
|
+
return null;
|
|
318
|
+
}
|
|
319
|
+
groups.push(rows.slice(start, end));
|
|
320
|
+
start = end;
|
|
321
|
+
}
|
|
322
|
+
if (groups.length < 2) {
|
|
323
|
+
return null;
|
|
324
|
+
}
|
|
325
|
+
const fragments = [];
|
|
326
|
+
for (let index = 0; index < groups.length; index++) {
|
|
327
|
+
const isFirst = index === 0;
|
|
328
|
+
const isLast = index === groups.length - 1;
|
|
329
|
+
const fragment = this.createSimpleTableFragment(
|
|
330
|
+
wrapper,
|
|
331
|
+
table,
|
|
332
|
+
body,
|
|
333
|
+
groups[index],
|
|
334
|
+
isFirst
|
|
335
|
+
);
|
|
336
|
+
if (!isFirst) {
|
|
337
|
+
fragment.style.setProperty("margin-top", "0", "important");
|
|
338
|
+
}
|
|
339
|
+
if (!isLast) {
|
|
340
|
+
fragment.style.setProperty("margin-bottom", "0", "important");
|
|
341
|
+
}
|
|
342
|
+
const measured = this.measureElement(fragment, dims);
|
|
343
|
+
if (measured.heightPt + measured.marginTopPt + measured.marginBottomPt > minimumContentHeight) {
|
|
344
|
+
return null;
|
|
345
|
+
}
|
|
346
|
+
fragments.push({
|
|
347
|
+
...measured,
|
|
348
|
+
keepWithNext: false,
|
|
349
|
+
keepLines: false,
|
|
350
|
+
pageBreakBefore: false,
|
|
351
|
+
isPageBreak: false
|
|
352
|
+
});
|
|
353
|
+
}
|
|
354
|
+
return fragments;
|
|
355
|
+
}
|
|
356
|
+
/**
|
|
357
|
+
* A DOM endpoint that can finish a paragraph fragment. Endpoints are chosen
|
|
358
|
+
* after whitespace or at a run boundary so the paginator never deliberately
|
|
359
|
+
* cuts through an ordinary word merely to fill a little more of a page.
|
|
360
|
+
*/
|
|
361
|
+
paragraphFragmentEndpoints(paragraph) {
|
|
362
|
+
const endpoints = [];
|
|
363
|
+
const walker = document.createTreeWalker(paragraph, NodeFilter.SHOW_TEXT);
|
|
364
|
+
let textNode;
|
|
365
|
+
while (textNode = walker.nextNode()) {
|
|
366
|
+
const text = textNode.data;
|
|
367
|
+
if (text.length === 0) continue;
|
|
368
|
+
const whitespace = /\s+/g;
|
|
369
|
+
let match;
|
|
370
|
+
while ((match = whitespace.exec(text)) !== null) {
|
|
371
|
+
endpoints.push({ node: textNode, offset: match.index + match[0].length });
|
|
372
|
+
}
|
|
373
|
+
if (endpoints.length === 0 || endpoints[endpoints.length - 1].node !== textNode || endpoints[endpoints.length - 1].offset !== text.length) {
|
|
374
|
+
endpoints.push({ node: textNode, offset: text.length });
|
|
375
|
+
}
|
|
376
|
+
}
|
|
377
|
+
return endpoints;
|
|
378
|
+
}
|
|
379
|
+
/**
|
|
380
|
+
* Whether a range contains visible text after ignoring bidi/zero-width marks.
|
|
381
|
+
* Paragraph fragmentation deliberately excludes non-textual descendants, so
|
|
382
|
+
* this is enough to reject empty head or tail fragments.
|
|
383
|
+
*/
|
|
384
|
+
hasVisibleFragmentText(fragment) {
|
|
385
|
+
return (fragment.textContent || "").replace(/[\u200B-\u200F\uFEFF]/g, "").trim().length > 0;
|
|
386
|
+
}
|
|
387
|
+
/**
|
|
388
|
+
* Phase one only handles text-like paragraph descendants. Objects, explicit
|
|
389
|
+
* line breaks, list markers, notes, and out-of-flow/inline-block content all
|
|
390
|
+
* require their own line-layout rules and retain the established whole-block
|
|
391
|
+
* fallback instead of risking broken content or duplicate anchors.
|
|
392
|
+
*/
|
|
393
|
+
canFragmentParagraph(block) {
|
|
394
|
+
if (!this.fragmentParagraphs || block.element.tagName !== "P") {
|
|
395
|
+
return false;
|
|
396
|
+
}
|
|
397
|
+
const paragraph = block.element;
|
|
398
|
+
if (block.keepWithNext || block.keepLines || block.pageBreakBefore || block.isPageBreak || paragraph.dataset.widowControl === "true" || paragraph.hasAttribute("contenteditable")) {
|
|
399
|
+
return false;
|
|
400
|
+
}
|
|
401
|
+
const unsupportedDescendants = [
|
|
402
|
+
"br",
|
|
403
|
+
"img",
|
|
404
|
+
"picture",
|
|
405
|
+
"svg",
|
|
406
|
+
"math",
|
|
407
|
+
"canvas",
|
|
408
|
+
"video",
|
|
409
|
+
"audio",
|
|
410
|
+
"iframe",
|
|
411
|
+
"object",
|
|
412
|
+
"embed",
|
|
413
|
+
"input",
|
|
414
|
+
"button",
|
|
415
|
+
"select",
|
|
416
|
+
"textarea",
|
|
417
|
+
"table",
|
|
418
|
+
"ol",
|
|
419
|
+
"ul",
|
|
420
|
+
"li",
|
|
421
|
+
"dl",
|
|
422
|
+
"div",
|
|
423
|
+
"p",
|
|
424
|
+
"section",
|
|
425
|
+
"article",
|
|
426
|
+
"aside",
|
|
427
|
+
"figure",
|
|
428
|
+
"fieldset",
|
|
429
|
+
"details",
|
|
430
|
+
"[data-footnote-id]",
|
|
431
|
+
"[data-list-marker]",
|
|
432
|
+
"[data-anchor]",
|
|
433
|
+
"[id]",
|
|
434
|
+
"[contenteditable]"
|
|
435
|
+
].join(", ");
|
|
436
|
+
if (paragraph.querySelector(unsupportedDescendants)) {
|
|
437
|
+
return false;
|
|
438
|
+
}
|
|
439
|
+
const paragraphStyle = window.getComputedStyle(paragraph);
|
|
440
|
+
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") {
|
|
441
|
+
return false;
|
|
442
|
+
}
|
|
443
|
+
for (const descendant of Array.from(paragraph.querySelectorAll("*"))) {
|
|
444
|
+
const style = window.getComputedStyle(descendant);
|
|
445
|
+
if (style.display !== "inline" || style.position !== "static" || style.float !== "none" || style.whiteSpace !== "normal") {
|
|
446
|
+
return false;
|
|
447
|
+
}
|
|
448
|
+
}
|
|
449
|
+
return true;
|
|
450
|
+
}
|
|
451
|
+
/**
|
|
452
|
+
* Builds one range-cloned paragraph fragment. Only the leading fragment keeps
|
|
453
|
+
* the source paragraph's addressability; continuations must not duplicate an
|
|
454
|
+
* id/data-anchor in the rendered document.
|
|
455
|
+
*/
|
|
456
|
+
createParagraphFragment(paragraph, range, retainSourceIdentity, isFinalFragment) {
|
|
457
|
+
const fragment = paragraph.cloneNode(false);
|
|
458
|
+
const contents = range.cloneContents();
|
|
459
|
+
fragment.appendChild(contents);
|
|
460
|
+
if (!retainSourceIdentity) {
|
|
461
|
+
fragment.removeAttribute("id");
|
|
462
|
+
fragment.removeAttribute("data-anchor");
|
|
463
|
+
fragment.style.setProperty("margin-top", "0", "important");
|
|
464
|
+
fragment.style.setProperty("text-indent", "0", "important");
|
|
465
|
+
}
|
|
466
|
+
if (!isFinalFragment) {
|
|
467
|
+
fragment.style.setProperty("margin-bottom", "0", "important");
|
|
468
|
+
}
|
|
469
|
+
return fragment;
|
|
470
|
+
}
|
|
471
|
+
/**
|
|
472
|
+
* Splits a simple paragraph at the largest DOM Range endpoint that fits the
|
|
473
|
+
* currently available body space. The caller then processes the tail normally,
|
|
474
|
+
* allowing it to fragment again on later pages when necessary.
|
|
475
|
+
*/
|
|
476
|
+
tryFragmentParagraph(block, dims, availableHeightPt, effectiveMarginTopPt) {
|
|
477
|
+
if (!this.canFragmentParagraph(block) || availableHeightPt <= effectiveMarginTopPt) {
|
|
478
|
+
return null;
|
|
479
|
+
}
|
|
480
|
+
const paragraph = block.element;
|
|
481
|
+
const endpoints = this.paragraphFragmentEndpoints(paragraph);
|
|
482
|
+
if (endpoints.length < 2) {
|
|
483
|
+
return null;
|
|
484
|
+
}
|
|
485
|
+
let low = 0;
|
|
486
|
+
let high = endpoints.length - 2;
|
|
487
|
+
let best = null;
|
|
488
|
+
while (low <= high) {
|
|
489
|
+
const middle = Math.floor((low + high) / 2);
|
|
490
|
+
const endpoint = endpoints[middle];
|
|
491
|
+
const headRange = document.createRange();
|
|
492
|
+
headRange.setStart(paragraph, 0);
|
|
493
|
+
headRange.setEnd(endpoint.node, endpoint.offset);
|
|
494
|
+
const headContents = headRange.cloneContents();
|
|
495
|
+
if (!this.hasVisibleFragmentText(headContents)) {
|
|
496
|
+
low = middle + 1;
|
|
497
|
+
continue;
|
|
498
|
+
}
|
|
499
|
+
const head = this.createParagraphFragment(paragraph, headRange, true, false);
|
|
500
|
+
const measured = this.measureElement(head, dims);
|
|
501
|
+
if (effectiveMarginTopPt + measured.heightPt <= availableHeightPt) {
|
|
502
|
+
best = { endpoint, element: head, measured };
|
|
503
|
+
low = middle + 1;
|
|
504
|
+
} else {
|
|
505
|
+
high = middle - 1;
|
|
506
|
+
}
|
|
507
|
+
}
|
|
508
|
+
if (!best) {
|
|
509
|
+
return null;
|
|
510
|
+
}
|
|
511
|
+
const tailRange = document.createRange();
|
|
512
|
+
tailRange.setStart(best.endpoint.node, best.endpoint.offset);
|
|
513
|
+
tailRange.setEnd(paragraph, paragraph.childNodes.length);
|
|
514
|
+
const tailContents = tailRange.cloneContents();
|
|
515
|
+
if (!this.hasVisibleFragmentText(tailContents)) {
|
|
516
|
+
return null;
|
|
517
|
+
}
|
|
518
|
+
const tail = this.createParagraphFragment(paragraph, tailRange, false, true);
|
|
519
|
+
const tailMeasured = this.measureElement(tail, dims);
|
|
520
|
+
return [
|
|
521
|
+
{
|
|
522
|
+
...best.measured,
|
|
523
|
+
element: best.element,
|
|
524
|
+
keepWithNext: false,
|
|
525
|
+
keepLines: false,
|
|
526
|
+
pageBreakBefore: false,
|
|
527
|
+
isPageBreak: false
|
|
528
|
+
},
|
|
529
|
+
{
|
|
530
|
+
...tailMeasured,
|
|
531
|
+
element: tail,
|
|
532
|
+
keepWithNext: false,
|
|
533
|
+
keepLines: false,
|
|
534
|
+
pageBreakBefore: false,
|
|
535
|
+
isPageBreak: false
|
|
536
|
+
}
|
|
537
|
+
];
|
|
538
|
+
}
|
|
151
539
|
/**
|
|
152
540
|
* Parses the header/footer registry from the staging element.
|
|
153
541
|
* Also measures heights during parsing for lazy-loading compatibility.
|
|
@@ -546,7 +934,8 @@ var DocxodusPagination = (() => {
|
|
|
546
934
|
currentFootnoteHeight = this.measureContinuationHeight(currentContinuation, dims.contentWidth);
|
|
547
935
|
}
|
|
548
936
|
const finishPage = () => {
|
|
549
|
-
|
|
937
|
+
const hasCurrentContinuation = (currentContinuation?.remainingElements.length ?? 0) > 0;
|
|
938
|
+
if (currentContent.length === 0 && !hasCurrentContinuation) return;
|
|
550
939
|
const page = this.createPage(
|
|
551
940
|
dims,
|
|
552
941
|
pageNumber,
|
|
@@ -578,7 +967,6 @@ var DocxodusPagination = (() => {
|
|
|
578
967
|
};
|
|
579
968
|
for (let i = 0; i < blocks.length; i++) {
|
|
580
969
|
const block = blocks[i];
|
|
581
|
-
const nextBlock = blocks[i + 1];
|
|
582
970
|
if (block.isPageBreak) {
|
|
583
971
|
finishPage();
|
|
584
972
|
continue;
|
|
@@ -586,8 +974,59 @@ var DocxodusPagination = (() => {
|
|
|
586
974
|
if (block.pageBreakBefore && currentContent.length > 0) {
|
|
587
975
|
finishPage();
|
|
588
976
|
}
|
|
589
|
-
const
|
|
590
|
-
const
|
|
977
|
+
const previousBlock = blocks[i - 1];
|
|
978
|
+
const startsKeepChain = !previousBlock || !previousBlock.keepWithNext || previousBlock.isPageBreak || block.pageBreakBefore;
|
|
979
|
+
const pageHasOccupiedSpace = currentContent.length > 0 || currentFootnoteHeight > 0;
|
|
980
|
+
if (pageHasOccupiedSpace && block.keepWithNext && startsKeepChain) {
|
|
981
|
+
const keepChain = this.getKeepWithNextChain(blocks, i);
|
|
982
|
+
if (keepChain.length > 1) {
|
|
983
|
+
const newChainFootnoteIds = this.collectNewFootnoteIds(
|
|
984
|
+
keepChain,
|
|
985
|
+
currentFootnoteIds
|
|
986
|
+
);
|
|
987
|
+
let additionalChainFootnoteHeight = 0;
|
|
988
|
+
if (newChainFootnoteIds.length > 0 && this.footnoteRegistry.size > 0) {
|
|
989
|
+
const totalChainFootnoteHeight = this.measureFootnotesHeight(
|
|
990
|
+
[...currentFootnoteIds, ...newChainFootnoteIds],
|
|
991
|
+
dims.contentWidth,
|
|
992
|
+
currentContinuation
|
|
993
|
+
);
|
|
994
|
+
additionalChainFootnoteHeight = Math.max(
|
|
995
|
+
0,
|
|
996
|
+
totalChainFootnoteHeight - currentFootnoteHeight
|
|
997
|
+
);
|
|
998
|
+
}
|
|
999
|
+
const currentChainHeight = this.measureKeepWithNextChainBodyHeight(
|
|
1000
|
+
keepChain,
|
|
1001
|
+
prevMarginBottomPt,
|
|
1002
|
+
currentContent.length === 0
|
|
1003
|
+
) + additionalChainFootnoteHeight;
|
|
1004
|
+
const currentAvailableHeight = remainingHeight - currentFootnoteHeight;
|
|
1005
|
+
if (currentChainHeight > currentAvailableHeight) {
|
|
1006
|
+
const nextPageHeights = this.getEffectiveHeights(
|
|
1007
|
+
dims,
|
|
1008
|
+
sectionIndex,
|
|
1009
|
+
pageInSection + 1,
|
|
1010
|
+
pageNumber + 1
|
|
1011
|
+
);
|
|
1012
|
+
const freshChainBodyHeight = this.measureKeepWithNextChainBodyHeight(
|
|
1013
|
+
keepChain,
|
|
1014
|
+
0,
|
|
1015
|
+
true
|
|
1016
|
+
);
|
|
1017
|
+
const freshChainFootnoteHeight = this.measureFootnotesHeight(
|
|
1018
|
+
newChainFootnoteIds,
|
|
1019
|
+
dims.contentWidth,
|
|
1020
|
+
nextPageContinuation
|
|
1021
|
+
);
|
|
1022
|
+
if (freshChainBodyHeight + freshChainFootnoteHeight <= nextPageHeights.contentHeight) {
|
|
1023
|
+
finishPage();
|
|
1024
|
+
}
|
|
1025
|
+
}
|
|
1026
|
+
}
|
|
1027
|
+
}
|
|
1028
|
+
const allBlockFootnoteIds = this.extractFootnoteRefs(block.element);
|
|
1029
|
+
const newFootnoteIds = this.collectNewFootnoteIds([block], currentFootnoteIds);
|
|
591
1030
|
let additionalFootnoteHeight = 0;
|
|
592
1031
|
if (newFootnoteIds.length > 0 && this.footnoteRegistry.size > 0) {
|
|
593
1032
|
const combinedFootnoteIds = [...currentFootnoteIds, ...newFootnoteIds];
|
|
@@ -604,15 +1043,23 @@ var DocxodusPagination = (() => {
|
|
|
604
1043
|
effectiveMarginTop = Math.max(block.marginTopPt, prevMarginBottomPt) - prevMarginBottomPt;
|
|
605
1044
|
}
|
|
606
1045
|
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
1046
|
const effectiveRemainingHeight = remainingHeight - currentFootnoteHeight;
|
|
613
1047
|
const bodyContentUsed = effectiveContentHeight - remainingHeight;
|
|
614
1048
|
const maxFootnoteArea = effectiveContentHeight * MAX_FOOTNOTE_AREA_RATIO;
|
|
615
1049
|
const maxFootnoteExpansion = Math.max(0, maxFootnoteArea - currentFootnoteHeight);
|
|
1050
|
+
if (blockSpace > effectiveRemainingHeight) {
|
|
1051
|
+
const paragraphFragments = this.tryFragmentParagraph(
|
|
1052
|
+
block,
|
|
1053
|
+
dims,
|
|
1054
|
+
effectiveRemainingHeight,
|
|
1055
|
+
effectiveMarginTop
|
|
1056
|
+
);
|
|
1057
|
+
if (paragraphFragments) {
|
|
1058
|
+
blocks.splice(i, 1, ...paragraphFragments);
|
|
1059
|
+
i--;
|
|
1060
|
+
continue;
|
|
1061
|
+
}
|
|
1062
|
+
}
|
|
616
1063
|
if (blockSpace <= effectiveRemainingHeight) {
|
|
617
1064
|
currentContent.push(block.element.cloneNode(true));
|
|
618
1065
|
remainingHeight -= effectiveMarginTop + block.heightPt + block.marginBottomPt;
|
|
@@ -654,10 +1101,12 @@ var DocxodusPagination = (() => {
|
|
|
654
1101
|
footnoteId,
|
|
655
1102
|
fittingElements: fits
|
|
656
1103
|
});
|
|
657
|
-
|
|
658
|
-
|
|
659
|
-
|
|
660
|
-
|
|
1104
|
+
if (overflow.length > 0) {
|
|
1105
|
+
nextPageContinuation = {
|
|
1106
|
+
footnoteId,
|
|
1107
|
+
remainingElements: overflow
|
|
1108
|
+
};
|
|
1109
|
+
}
|
|
661
1110
|
currentFootnoteHeight = availableForFootnotes;
|
|
662
1111
|
} else {
|
|
663
1112
|
nextPageContinuation = {
|
|
@@ -690,30 +1139,39 @@ var DocxodusPagination = (() => {
|
|
|
690
1139
|
currentFootnoteHeight = expandedFootnoteSpace;
|
|
691
1140
|
} else {
|
|
692
1141
|
finishPage();
|
|
693
|
-
const newPageFootnoteHeight =
|
|
1142
|
+
const newPageFootnoteHeight = allBlockFootnoteIds.length > 0 ? this.measureFootnotesHeight(allBlockFootnoteIds, dims.contentWidth, currentContinuation) : currentContinuation ? this.measureContinuationHeight(currentContinuation, dims.contentWidth) : 0;
|
|
694
1143
|
const newPageSpace = block.marginTopPt + block.heightPt + block.marginBottomPt;
|
|
695
1144
|
currentContent.push(block.element.cloneNode(true));
|
|
696
1145
|
remainingHeight = effectiveContentHeight - newPageSpace;
|
|
697
1146
|
prevMarginBottomPt = block.marginBottomPt;
|
|
698
|
-
currentFootnoteIds = [...
|
|
1147
|
+
currentFootnoteIds = [...allBlockFootnoteIds];
|
|
699
1148
|
currentFootnoteHeight = newPageFootnoteHeight;
|
|
700
1149
|
}
|
|
701
1150
|
} else {
|
|
702
1151
|
finishPage();
|
|
703
|
-
const newPageFootnoteHeight =
|
|
1152
|
+
const newPageFootnoteHeight = allBlockFootnoteIds.length > 0 ? this.measureFootnotesHeight(allBlockFootnoteIds, dims.contentWidth, currentContinuation) : currentContinuation ? this.measureContinuationHeight(currentContinuation, dims.contentWidth) : 0;
|
|
704
1153
|
const newPageSpace = block.marginTopPt + block.heightPt + block.marginBottomPt;
|
|
705
1154
|
currentContent.push(block.element.cloneNode(true));
|
|
706
1155
|
remainingHeight = effectiveContentHeight - newPageSpace;
|
|
707
1156
|
prevMarginBottomPt = block.marginBottomPt;
|
|
708
|
-
currentFootnoteIds = [...
|
|
1157
|
+
currentFootnoteIds = [...allBlockFootnoteIds];
|
|
709
1158
|
currentFootnoteHeight = newPageFootnoteHeight;
|
|
710
1159
|
}
|
|
711
1160
|
} else {
|
|
1161
|
+
const tableFragments = this.trySplitSimpleOversizedTable(block, dims, sectionIndex);
|
|
1162
|
+
if (tableFragments) {
|
|
1163
|
+
if (currentContent.length > 0 || currentContinuation) {
|
|
1164
|
+
finishPage();
|
|
1165
|
+
}
|
|
1166
|
+
blocks.splice(i, 1, ...tableFragments);
|
|
1167
|
+
i--;
|
|
1168
|
+
continue;
|
|
1169
|
+
}
|
|
712
1170
|
if (currentContent.length > 0) {
|
|
713
1171
|
finishPage();
|
|
714
1172
|
}
|
|
715
1173
|
currentContent.push(block.element.cloneNode(true));
|
|
716
|
-
currentFootnoteIds = [...
|
|
1174
|
+
currentFootnoteIds = [...allBlockFootnoteIds];
|
|
717
1175
|
finishPage();
|
|
718
1176
|
}
|
|
719
1177
|
}
|
|
@@ -837,7 +1295,10 @@ var DocxodusPagination = (() => {
|
|
|
837
1295
|
if (!pageContainer) {
|
|
838
1296
|
throw new Error("Pagination container element not found");
|
|
839
1297
|
}
|
|
840
|
-
const engine = new PaginationEngine(staging, pageContainer,
|
|
1298
|
+
const engine = new PaginationEngine(staging, pageContainer, {
|
|
1299
|
+
...options,
|
|
1300
|
+
fragmentParagraphs: options.fragmentParagraphs ?? true
|
|
1301
|
+
});
|
|
841
1302
|
return engine.paginate();
|
|
842
1303
|
}
|
|
843
1304
|
return __toCommonJS(pagination_exports);
|
package/dist/pagination.d.ts
CHANGED
|
@@ -117,6 +117,12 @@ export interface PaginationOptions {
|
|
|
117
117
|
showPageNumbers?: boolean;
|
|
118
118
|
/** Gap between pages in pixels. Default: 20 */
|
|
119
119
|
pageGap?: number;
|
|
120
|
+
/**
|
|
121
|
+
* Whether ordinary paragraphs may be fragmented across page boundaries.
|
|
122
|
+
* Defaults to false for direct PaginationEngine callers; read-only viewer
|
|
123
|
+
* entry points opt in explicitly.
|
|
124
|
+
*/
|
|
125
|
+
fragmentParagraphs?: boolean;
|
|
120
126
|
}
|
|
121
127
|
/**
|
|
122
128
|
* Pagination engine that converts HTML with pagination metadata
|
|
@@ -133,6 +139,7 @@ export declare class PaginationEngine {
|
|
|
133
139
|
private cssPrefix;
|
|
134
140
|
private showPageNumbers;
|
|
135
141
|
private pageGap;
|
|
142
|
+
private fragmentParagraphs;
|
|
136
143
|
private hfRegistry;
|
|
137
144
|
private footnoteRegistry;
|
|
138
145
|
private pendingFootnoteContinuation;
|
|
@@ -154,6 +161,80 @@ export declare class PaginationEngine {
|
|
|
154
161
|
* Measures all content blocks in a section.
|
|
155
162
|
*/
|
|
156
163
|
private measureBlocks;
|
|
164
|
+
/**
|
|
165
|
+
* Measures one element in the same hidden staging context used for the source blocks.
|
|
166
|
+
* This is intentionally DOM-based: table row heights cannot be inferred from individual
|
|
167
|
+
* rows because wrapping and collapsed borders change the height of a fragment.
|
|
168
|
+
*/
|
|
169
|
+
private measureElement;
|
|
170
|
+
/**
|
|
171
|
+
* Returns the contiguous keep-with-next chain beginning at a block.
|
|
172
|
+
*
|
|
173
|
+
* A hard page break or a page-break-before directive is stronger than a
|
|
174
|
+
* keep-with-next directive, so it terminates the chain. The caller only
|
|
175
|
+
* keeps a chain together when the whole chain can fit on a fresh page.
|
|
176
|
+
*/
|
|
177
|
+
private getKeepWithNextChain;
|
|
178
|
+
/**
|
|
179
|
+
* Measures the visible body height of a keep-with-next chain using the same
|
|
180
|
+
* collapsed-margin rules as normal block placement. The trailing margin is
|
|
181
|
+
* intentionally excluded, matching the individual block fit check.
|
|
182
|
+
*/
|
|
183
|
+
private measureKeepWithNextChainBodyHeight;
|
|
184
|
+
/**
|
|
185
|
+
* Finds footnote references introduced by a sequence of blocks, preserving
|
|
186
|
+
* document order and excluding references already assigned to the page.
|
|
187
|
+
*/
|
|
188
|
+
private collectNewFootnoteIds;
|
|
189
|
+
/**
|
|
190
|
+
* The shortest body available to a section's first, default, or even page.
|
|
191
|
+
* A row fragment must fit every variant, otherwise a later header/footer could
|
|
192
|
+
* send it through the oversized-block fallback again.
|
|
193
|
+
*/
|
|
194
|
+
private smallestEffectiveContentHeight;
|
|
195
|
+
/**
|
|
196
|
+
* Builds a clone of a simple table wrapper containing a contiguous run of rows.
|
|
197
|
+
* Complex table features are deliberately rejected by the caller: a split across
|
|
198
|
+
* merged cells, nested tables, or footnotes cannot be made correct by cloning rows.
|
|
199
|
+
*/
|
|
200
|
+
private createSimpleTableFragment;
|
|
201
|
+
/**
|
|
202
|
+
* Splits an oversized, ordinary table at row boundaries. This only participates
|
|
203
|
+
* in the existing oversized-block fallback; unsupported tables keep the previous
|
|
204
|
+
* overflow behavior rather than risking broken table semantics.
|
|
205
|
+
*/
|
|
206
|
+
private trySplitSimpleOversizedTable;
|
|
207
|
+
/**
|
|
208
|
+
* A DOM endpoint that can finish a paragraph fragment. Endpoints are chosen
|
|
209
|
+
* after whitespace or at a run boundary so the paginator never deliberately
|
|
210
|
+
* cuts through an ordinary word merely to fill a little more of a page.
|
|
211
|
+
*/
|
|
212
|
+
private paragraphFragmentEndpoints;
|
|
213
|
+
/**
|
|
214
|
+
* Whether a range contains visible text after ignoring bidi/zero-width marks.
|
|
215
|
+
* Paragraph fragmentation deliberately excludes non-textual descendants, so
|
|
216
|
+
* this is enough to reject empty head or tail fragments.
|
|
217
|
+
*/
|
|
218
|
+
private hasVisibleFragmentText;
|
|
219
|
+
/**
|
|
220
|
+
* Phase one only handles text-like paragraph descendants. Objects, explicit
|
|
221
|
+
* line breaks, list markers, notes, and out-of-flow/inline-block content all
|
|
222
|
+
* require their own line-layout rules and retain the established whole-block
|
|
223
|
+
* fallback instead of risking broken content or duplicate anchors.
|
|
224
|
+
*/
|
|
225
|
+
private canFragmentParagraph;
|
|
226
|
+
/**
|
|
227
|
+
* Builds one range-cloned paragraph fragment. Only the leading fragment keeps
|
|
228
|
+
* the source paragraph's addressability; continuations must not duplicate an
|
|
229
|
+
* id/data-anchor in the rendered document.
|
|
230
|
+
*/
|
|
231
|
+
private createParagraphFragment;
|
|
232
|
+
/**
|
|
233
|
+
* Splits a simple paragraph at the largest DOM Range endpoint that fits the
|
|
234
|
+
* currently available body space. The caller then processes the tail normally,
|
|
235
|
+
* allowing it to fragment again on later pages when necessary.
|
|
236
|
+
*/
|
|
237
|
+
private tryFragmentParagraph;
|
|
157
238
|
/**
|
|
158
239
|
* Parses the header/footer registry from the staging element.
|
|
159
240
|
* Also measures heights during parsing for lazy-loading compatibility.
|