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.
Files changed (80) hide show
  1. package/dist/docxodus.worker.js +2 -2
  2. package/dist/docxodus.worker.js.map +1 -1
  3. package/dist/editor-headerfooter.d.ts +130 -0
  4. package/dist/editor-headerfooter.d.ts.map +1 -0
  5. package/dist/editor-headerfooter.js +414 -0
  6. package/dist/editor-headerfooter.js.map +1 -0
  7. package/dist/editor.bundle.js +1075 -81
  8. package/dist/editor.d.ts +82 -4
  9. package/dist/editor.d.ts.map +1 -1
  10. package/dist/editor.js +304 -63
  11. package/dist/editor.js.map +1 -1
  12. package/dist/index.d.ts.map +1 -1
  13. package/dist/index.js +3 -4
  14. package/dist/index.js.map +1 -1
  15. package/dist/pagination.bundle.js +480 -19
  16. package/dist/pagination.d.ts +81 -0
  17. package/dist/pagination.d.ts.map +1 -1
  18. package/dist/pagination.js +528 -26
  19. package/dist/pagination.js.map +1 -1
  20. package/dist/react.d.ts +3 -1
  21. package/dist/react.d.ts.map +1 -1
  22. package/dist/react.js +6 -4
  23. package/dist/react.js.map +1 -1
  24. package/dist/session.bundle.js +41 -0
  25. package/dist/session.d.ts +33 -1
  26. package/dist/session.d.ts.map +1 -1
  27. package/dist/session.js +41 -0
  28. package/dist/session.js.map +1 -1
  29. package/dist/types.d.ts +59 -10
  30. package/dist/types.d.ts.map +1 -1
  31. package/dist/types.js +9 -11
  32. package/dist/types.js.map +1 -1
  33. package/dist/wasm/_framework/Docxodus.wasm +0 -0
  34. package/dist/wasm/_framework/DocxodusWasm.wasm +0 -0
  35. package/dist/wasm/_framework/System.Collections.Concurrent.wasm +0 -0
  36. package/dist/wasm/_framework/System.Collections.Immutable.wasm +0 -0
  37. package/dist/wasm/_framework/System.Collections.NonGeneric.wasm +0 -0
  38. package/dist/wasm/_framework/System.Collections.Specialized.wasm +0 -0
  39. package/dist/wasm/_framework/System.Collections.wasm +0 -0
  40. package/dist/wasm/_framework/System.ComponentModel.Primitives.wasm +0 -0
  41. package/dist/wasm/_framework/System.ComponentModel.TypeConverter.wasm +0 -0
  42. package/dist/wasm/_framework/System.ComponentModel.wasm +0 -0
  43. package/dist/wasm/_framework/System.Console.wasm +0 -0
  44. package/dist/wasm/_framework/System.Diagnostics.Process.wasm +0 -0
  45. package/dist/wasm/_framework/System.IO.Compression.wasm +0 -0
  46. package/dist/wasm/_framework/System.IO.Pipelines.wasm +0 -0
  47. package/dist/wasm/_framework/System.Linq.Expressions.wasm +0 -0
  48. package/dist/wasm/_framework/System.Linq.wasm +0 -0
  49. package/dist/wasm/_framework/System.Memory.wasm +0 -0
  50. package/dist/wasm/_framework/System.Net.Http.wasm +0 -0
  51. package/dist/wasm/_framework/System.Net.Primitives.wasm +0 -0
  52. package/dist/wasm/_framework/System.ObjectModel.wasm +0 -0
  53. package/dist/wasm/_framework/System.Private.CoreLib.wasm +0 -0
  54. package/dist/wasm/_framework/System.Private.Uri.wasm +0 -0
  55. package/dist/wasm/_framework/System.Private.Xml.Linq.wasm +0 -0
  56. package/dist/wasm/_framework/System.Private.Xml.wasm +0 -0
  57. package/dist/wasm/_framework/System.Runtime.InteropServices.JavaScript.wasm +0 -0
  58. package/dist/wasm/_framework/System.Runtime.wasm +0 -0
  59. package/dist/wasm/_framework/System.Security.Cryptography.wasm +0 -0
  60. package/dist/wasm/_framework/System.Text.Encoding.Extensions.wasm +0 -0
  61. package/dist/wasm/_framework/System.Text.Encodings.Web.wasm +0 -0
  62. package/dist/wasm/_framework/System.Text.Json.wasm +0 -0
  63. package/dist/wasm/_framework/System.Text.RegularExpressions.wasm +0 -0
  64. package/dist/wasm/_framework/System.Threading.Thread.wasm +0 -0
  65. package/dist/wasm/_framework/System.Threading.wasm +0 -0
  66. package/dist/wasm/_framework/System.Xml.Linq.wasm +0 -0
  67. package/dist/wasm/_framework/System.Xml.ReaderWriter.wasm +0 -0
  68. package/dist/wasm/_framework/System.Xml.XDocument.wasm +0 -0
  69. package/dist/wasm/_framework/System.Xml.XPath.XDocument.wasm +0 -0
  70. package/dist/wasm/_framework/System.Xml.XPath.wasm +0 -0
  71. package/dist/wasm/_framework/System.wasm +0 -0
  72. package/dist/wasm/_framework/dotnet.boot.js +41 -41
  73. package/dist/wasm/_framework/dotnet.js +1 -1
  74. package/dist/wasm/_framework/dotnet.js.map +1 -1
  75. package/dist/wasm/_framework/dotnet.native.js +3 -3
  76. package/dist/wasm/_framework/dotnet.native.wasm +0 -0
  77. package/dist/wasm/_framework/dotnet.runtime.js +1 -1
  78. package/dist/wasm/_framework/dotnet.runtime.js.map +1 -1
  79. package/dist/wasm/index.html +2 -1
  80. package/package.json +1 -1
@@ -1 +1 @@
1
- {"version":3,"file":"pagination.d.ts","sourceRoot":"","sources":["../src/pagination.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"pagination.d.ts","sourceRoot":"","sources":["../src/pagination.ts"],"names":[],"mappings":"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"}
@@ -82,6 +82,7 @@ export class PaginationEngine {
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 = new Map();
86
87
  this.footnoteRegistry = new Map();
87
88
  }
@@ -161,6 +162,443 @@ export class PaginationEngine {
161
162
  }
162
163
  return blocks;
163
164
  }
165
+ /**
166
+ * Measures one element in the same hidden staging context used for the source blocks.
167
+ * This is intentionally DOM-based: table row heights cannot be inferred from individual
168
+ * rows because wrapping and collapsed borders change the height of a fragment.
169
+ */
170
+ measureElement(element, dims) {
171
+ const measurementHost = document.createElement("div");
172
+ measurementHost.style.position = "absolute";
173
+ measurementHost.style.visibility = "hidden";
174
+ measurementHost.style.left = "-9999px";
175
+ measurementHost.style.width = `${dims.contentWidth}pt`;
176
+ const measuredElement = element.cloneNode(true);
177
+ measurementHost.appendChild(measuredElement);
178
+ this.stagingElement.appendChild(measurementHost);
179
+ const rect = measuredElement.getBoundingClientRect();
180
+ const style = window.getComputedStyle(measuredElement);
181
+ const measured = {
182
+ element,
183
+ heightPt: pxToPt(rect.height),
184
+ marginTopPt: pxToPt(parseFloat(style.marginTop) || 0),
185
+ marginBottomPt: pxToPt(parseFloat(style.marginBottom) || 0),
186
+ keepWithNext: element.dataset.keepWithNext === "true",
187
+ keepLines: element.dataset.keepLines === "true",
188
+ pageBreakBefore: element.dataset.pageBreakBefore === "true",
189
+ isPageBreak: element.dataset.pageBreak === "true" ||
190
+ element.classList.contains(`${this.cssPrefix}break`),
191
+ };
192
+ this.stagingElement.removeChild(measurementHost);
193
+ return measured;
194
+ }
195
+ /**
196
+ * Returns the contiguous keep-with-next chain beginning at a block.
197
+ *
198
+ * A hard page break or a page-break-before directive is stronger than a
199
+ * keep-with-next directive, so it terminates the chain. The caller only
200
+ * keeps a chain together when the whole chain can fit on a fresh page.
201
+ */
202
+ getKeepWithNextChain(blocks, startIndex) {
203
+ const firstBlock = blocks[startIndex];
204
+ if (!firstBlock)
205
+ return [];
206
+ const chain = [firstBlock];
207
+ let lastIndex = startIndex;
208
+ while (blocks[lastIndex].keepWithNext) {
209
+ const nextBlock = blocks[lastIndex + 1];
210
+ if (!nextBlock || nextBlock.isPageBreak || nextBlock.pageBreakBefore) {
211
+ break;
212
+ }
213
+ chain.push(nextBlock);
214
+ lastIndex++;
215
+ }
216
+ return chain;
217
+ }
218
+ /**
219
+ * Measures the visible body height of a keep-with-next chain using the same
220
+ * collapsed-margin rules as normal block placement. The trailing margin is
221
+ * intentionally excluded, matching the individual block fit check.
222
+ */
223
+ measureKeepWithNextChainBodyHeight(chain, previousMarginBottomPt, isFirstOnPage) {
224
+ const firstBlock = chain[0];
225
+ if (!firstBlock)
226
+ return 0;
227
+ const firstMarginTop = isFirstOnPage
228
+ ? firstBlock.marginTopPt
229
+ : Math.max(firstBlock.marginTopPt, previousMarginBottomPt) - previousMarginBottomPt;
230
+ let bodyHeight = firstMarginTop + firstBlock.heightPt;
231
+ for (let index = 1; index < chain.length; index++) {
232
+ const previousBlock = chain[index - 1];
233
+ const block = chain[index];
234
+ bodyHeight += Math.max(previousBlock.marginBottomPt, block.marginTopPt) + block.heightPt;
235
+ }
236
+ return bodyHeight;
237
+ }
238
+ /**
239
+ * Finds footnote references introduced by a sequence of blocks, preserving
240
+ * document order and excluding references already assigned to the page.
241
+ */
242
+ collectNewFootnoteIds(blocks, existingFootnoteIds) {
243
+ const knownIds = new Set(existingFootnoteIds);
244
+ const newIds = [];
245
+ for (const block of blocks) {
246
+ for (const id of this.extractFootnoteRefs(block.element)) {
247
+ if (!knownIds.has(id)) {
248
+ knownIds.add(id);
249
+ newIds.push(id);
250
+ }
251
+ }
252
+ }
253
+ return newIds;
254
+ }
255
+ /**
256
+ * The shortest body available to a section's first, default, or even page.
257
+ * A row fragment must fit every variant, otherwise a later header/footer could
258
+ * send it through the oversized-block fallback again.
259
+ */
260
+ smallestEffectiveContentHeight(dims, sectionIndex) {
261
+ return Math.min(this.getEffectiveHeights(dims, sectionIndex, 1, 1).contentHeight, this.getEffectiveHeights(dims, sectionIndex, 2, 1).contentHeight, this.getEffectiveHeights(dims, sectionIndex, 2, 2).contentHeight);
262
+ }
263
+ /**
264
+ * Builds a clone of a simple table wrapper containing a contiguous run of rows.
265
+ * Complex table features are deliberately rejected by the caller: a split across
266
+ * merged cells, nested tables, or footnotes cannot be made correct by cloning rows.
267
+ */
268
+ createSimpleTableFragment(wrapper, table, body, rows, retainAnchor) {
269
+ const wrapperClone = wrapper.cloneNode(false);
270
+ const tableClone = table.cloneNode(false);
271
+ // The eligibility gate permits only colgroups alongside the body. Keep each
272
+ // colgroup so fixed and proportional column widths remain stable per fragment.
273
+ for (const child of Array.from(table.children)) {
274
+ if (child !== body) {
275
+ tableClone.appendChild(child.cloneNode(true));
276
+ }
277
+ }
278
+ const bodyClone = body.cloneNode(false);
279
+ for (const row of rows) {
280
+ bodyClone.appendChild(row.cloneNode(true));
281
+ }
282
+ tableClone.appendChild(bodyClone);
283
+ if (!retainAnchor) {
284
+ wrapperClone.removeAttribute("data-anchor");
285
+ tableClone.removeAttribute("data-anchor");
286
+ }
287
+ wrapperClone.appendChild(tableClone);
288
+ return wrapperClone;
289
+ }
290
+ /**
291
+ * Splits an oversized, ordinary table at row boundaries. This only participates
292
+ * in the existing oversized-block fallback; unsupported tables keep the previous
293
+ * overflow behavior rather than risking broken table semantics.
294
+ */
295
+ trySplitSimpleOversizedTable(block, dims, sectionIndex) {
296
+ const wrapper = block.element;
297
+ if (wrapper.tagName !== "DIV" ||
298
+ wrapper.children.length !== 1 ||
299
+ block.keepWithNext ||
300
+ block.keepLines ||
301
+ block.pageBreakBefore ||
302
+ block.isPageBreak) {
303
+ return null;
304
+ }
305
+ const table = wrapper.firstElementChild;
306
+ if (!(table instanceof HTMLTableElement)) {
307
+ return null;
308
+ }
309
+ const body = table.tBodies.length === 1 ? table.tBodies[0] : null;
310
+ if (!body ||
311
+ table.tHead ||
312
+ table.tFoot ||
313
+ body.rows.length < 2 ||
314
+ Array.from(table.children).some(child => child !== body && child.tagName !== "COLGROUP") ||
315
+ table.querySelector("table, [rowspan], [colspan], [data-footnote-id]") ||
316
+ wrapper.querySelector("[data-footnote-id]")) {
317
+ return null;
318
+ }
319
+ const rows = Array.from(body.rows);
320
+ const minimumContentHeight = this.smallestEffectiveContentHeight(dims, sectionIndex);
321
+ // Use the source's full vertical margins while forming groups. Continuation
322
+ // fragments later clear their joining margins, so this conservative bound
323
+ // cannot create a fragment that overflows a header/footer variant.
324
+ const maximumFragmentHeight = minimumContentHeight - block.marginTopPt - block.marginBottomPt;
325
+ if (maximumFragmentHeight <= 0) {
326
+ return null;
327
+ }
328
+ const groups = [];
329
+ let start = 0;
330
+ while (start < rows.length) {
331
+ let end = start;
332
+ while (end < rows.length) {
333
+ const candidate = this.createSimpleTableFragment(wrapper, table, body, rows.slice(start, end + 1), start === 0);
334
+ const measured = this.measureElement(candidate, dims);
335
+ if (measured.heightPt > maximumFragmentHeight) {
336
+ break;
337
+ }
338
+ end++;
339
+ }
340
+ // Even a one-row fragment cannot fit. Preserve the established overflow
341
+ // fallback rather than looping or clipping a partially split row.
342
+ if (end === start) {
343
+ return null;
344
+ }
345
+ groups.push(rows.slice(start, end));
346
+ start = end;
347
+ }
348
+ if (groups.length < 2) {
349
+ return null;
350
+ }
351
+ const fragments = [];
352
+ for (let index = 0; index < groups.length; index++) {
353
+ const isFirst = index === 0;
354
+ const isLast = index === groups.length - 1;
355
+ const fragment = this.createSimpleTableFragment(wrapper, table, body, groups[index], isFirst);
356
+ // Keep the source's outer spacing only at the table's real boundaries.
357
+ // Continuation margins would otherwise add blank space at the top/bottom
358
+ // of every paginated fragment.
359
+ if (!isFirst) {
360
+ fragment.style.setProperty("margin-top", "0", "important");
361
+ }
362
+ if (!isLast) {
363
+ fragment.style.setProperty("margin-bottom", "0", "important");
364
+ }
365
+ const measured = this.measureElement(fragment, dims);
366
+ if (measured.heightPt + measured.marginTopPt + measured.marginBottomPt >
367
+ minimumContentHeight) {
368
+ return null;
369
+ }
370
+ fragments.push({
371
+ ...measured,
372
+ keepWithNext: false,
373
+ keepLines: false,
374
+ pageBreakBefore: false,
375
+ isPageBreak: false,
376
+ });
377
+ }
378
+ return fragments;
379
+ }
380
+ /**
381
+ * A DOM endpoint that can finish a paragraph fragment. Endpoints are chosen
382
+ * after whitespace or at a run boundary so the paginator never deliberately
383
+ * cuts through an ordinary word merely to fill a little more of a page.
384
+ */
385
+ paragraphFragmentEndpoints(paragraph) {
386
+ const endpoints = [];
387
+ const walker = document.createTreeWalker(paragraph, NodeFilter.SHOW_TEXT);
388
+ let textNode;
389
+ while ((textNode = walker.nextNode())) {
390
+ const text = textNode.data;
391
+ if (text.length === 0)
392
+ continue;
393
+ // A whitespace boundary preserves normal word wrapping. Always retain the
394
+ // end of a run too: adjacent runs can change formatting without containing
395
+ // a whitespace character between them.
396
+ const whitespace = /\s+/g;
397
+ let match;
398
+ while ((match = whitespace.exec(text)) !== null) {
399
+ endpoints.push({ node: textNode, offset: match.index + match[0].length });
400
+ }
401
+ if (endpoints.length === 0 || endpoints[endpoints.length - 1].node !== textNode ||
402
+ endpoints[endpoints.length - 1].offset !== text.length) {
403
+ endpoints.push({ node: textNode, offset: text.length });
404
+ }
405
+ }
406
+ return endpoints;
407
+ }
408
+ /**
409
+ * Whether a range contains visible text after ignoring bidi/zero-width marks.
410
+ * Paragraph fragmentation deliberately excludes non-textual descendants, so
411
+ * this is enough to reject empty head or tail fragments.
412
+ */
413
+ hasVisibleFragmentText(fragment) {
414
+ return (fragment.textContent || "")
415
+ .replace(/[\u200B-\u200F\uFEFF]/g, "")
416
+ .trim()
417
+ .length > 0;
418
+ }
419
+ /**
420
+ * Phase one only handles text-like paragraph descendants. Objects, explicit
421
+ * line breaks, list markers, notes, and out-of-flow/inline-block content all
422
+ * require their own line-layout rules and retain the established whole-block
423
+ * fallback instead of risking broken content or duplicate anchors.
424
+ */
425
+ canFragmentParagraph(block) {
426
+ if (!this.fragmentParagraphs || block.element.tagName !== "P") {
427
+ return false;
428
+ }
429
+ const paragraph = block.element;
430
+ if (block.keepWithNext ||
431
+ block.keepLines ||
432
+ block.pageBreakBefore ||
433
+ block.isPageBreak ||
434
+ paragraph.dataset.widowControl === "true" ||
435
+ paragraph.hasAttribute("contenteditable")) {
436
+ return false;
437
+ }
438
+ // The outer paragraph may carry its source identity. Descendant identities
439
+ // are not safe to duplicate in a continuation fragment, so reject them.
440
+ const unsupportedDescendants = [
441
+ "br",
442
+ "img",
443
+ "picture",
444
+ "svg",
445
+ "math",
446
+ "canvas",
447
+ "video",
448
+ "audio",
449
+ "iframe",
450
+ "object",
451
+ "embed",
452
+ "input",
453
+ "button",
454
+ "select",
455
+ "textarea",
456
+ "table",
457
+ "ol",
458
+ "ul",
459
+ "li",
460
+ "dl",
461
+ "div",
462
+ "p",
463
+ "section",
464
+ "article",
465
+ "aside",
466
+ "figure",
467
+ "fieldset",
468
+ "details",
469
+ "[data-footnote-id]",
470
+ "[data-list-marker]",
471
+ "[data-anchor]",
472
+ "[id]",
473
+ "[contenteditable]",
474
+ ].join(", ");
475
+ if (paragraph.querySelector(unsupportedDescendants)) {
476
+ return false;
477
+ }
478
+ const paragraphStyle = window.getComputedStyle(paragraph);
479
+ if (paragraphStyle.display !== "block" ||
480
+ paragraphStyle.position !== "static" ||
481
+ paragraphStyle.float !== "none" ||
482
+ paragraphStyle.whiteSpace !== "normal" ||
483
+ paragraphStyle.breakBefore !== "auto" ||
484
+ paragraphStyle.breakAfter !== "auto" ||
485
+ paragraphStyle.breakInside === "avoid" ||
486
+ paragraphStyle.pageBreakBefore !== "auto" ||
487
+ paragraphStyle.pageBreakAfter !== "auto" ||
488
+ paragraphStyle.pageBreakInside === "avoid") {
489
+ return false;
490
+ }
491
+ // A range clone preserves nested inline formatting exactly. Anything that
492
+ // establishes its own box/layout context is intentionally deferred until a
493
+ // future fragmenter can model it accurately.
494
+ for (const descendant of Array.from(paragraph.querySelectorAll("*"))) {
495
+ const style = window.getComputedStyle(descendant);
496
+ if (style.display !== "inline" ||
497
+ style.position !== "static" ||
498
+ style.float !== "none" ||
499
+ style.whiteSpace !== "normal") {
500
+ return false;
501
+ }
502
+ }
503
+ return true;
504
+ }
505
+ /**
506
+ * Builds one range-cloned paragraph fragment. Only the leading fragment keeps
507
+ * the source paragraph's addressability; continuations must not duplicate an
508
+ * id/data-anchor in the rendered document.
509
+ */
510
+ createParagraphFragment(paragraph, range, retainSourceIdentity, isFinalFragment) {
511
+ const fragment = paragraph.cloneNode(false);
512
+ const contents = range.cloneContents();
513
+ fragment.appendChild(contents);
514
+ if (!retainSourceIdentity) {
515
+ fragment.removeAttribute("id");
516
+ fragment.removeAttribute("data-anchor");
517
+ // A continuation starts with a normal line rather than repeating a first-
518
+ // line/hanging indent. Keep the side margin so paragraph alignment remains.
519
+ fragment.style.setProperty("margin-top", "0", "important");
520
+ fragment.style.setProperty("text-indent", "0", "important");
521
+ }
522
+ if (!isFinalFragment) {
523
+ // The original bottom spacing belongs after the complete paragraph, not
524
+ // between synthetic page fragments.
525
+ fragment.style.setProperty("margin-bottom", "0", "important");
526
+ }
527
+ return fragment;
528
+ }
529
+ /**
530
+ * Splits a simple paragraph at the largest DOM Range endpoint that fits the
531
+ * currently available body space. The caller then processes the tail normally,
532
+ * allowing it to fragment again on later pages when necessary.
533
+ */
534
+ tryFragmentParagraph(block, dims, availableHeightPt, effectiveMarginTopPt) {
535
+ if (!this.canFragmentParagraph(block) || availableHeightPt <= effectiveMarginTopPt) {
536
+ return null;
537
+ }
538
+ const paragraph = block.element;
539
+ const endpoints = this.paragraphFragmentEndpoints(paragraph);
540
+ // The final endpoint is the full paragraph and cannot leave a tail. A
541
+ // single text run with no earlier whitespace remains an overflow fallback.
542
+ if (endpoints.length < 2) {
543
+ return null;
544
+ }
545
+ let low = 0;
546
+ let high = endpoints.length - 2;
547
+ let best = null;
548
+ // Fragment height is monotonic for the deliberately narrow eligible subset,
549
+ // so binary search avoids measuring every word in long body paragraphs.
550
+ while (low <= high) {
551
+ const middle = Math.floor((low + high) / 2);
552
+ const endpoint = endpoints[middle];
553
+ const headRange = document.createRange();
554
+ headRange.setStart(paragraph, 0);
555
+ headRange.setEnd(endpoint.node, endpoint.offset);
556
+ const headContents = headRange.cloneContents();
557
+ if (!this.hasVisibleFragmentText(headContents)) {
558
+ low = middle + 1;
559
+ continue;
560
+ }
561
+ const head = this.createParagraphFragment(paragraph, headRange, true, false);
562
+ const measured = this.measureElement(head, dims);
563
+ if (effectiveMarginTopPt + measured.heightPt <= availableHeightPt) {
564
+ best = { endpoint, element: head, measured };
565
+ low = middle + 1;
566
+ }
567
+ else {
568
+ high = middle - 1;
569
+ }
570
+ }
571
+ if (!best) {
572
+ return null;
573
+ }
574
+ const tailRange = document.createRange();
575
+ tailRange.setStart(best.endpoint.node, best.endpoint.offset);
576
+ tailRange.setEnd(paragraph, paragraph.childNodes.length);
577
+ const tailContents = tailRange.cloneContents();
578
+ if (!this.hasVisibleFragmentText(tailContents)) {
579
+ return null;
580
+ }
581
+ const tail = this.createParagraphFragment(paragraph, tailRange, false, true);
582
+ const tailMeasured = this.measureElement(tail, dims);
583
+ return [
584
+ {
585
+ ...best.measured,
586
+ element: best.element,
587
+ keepWithNext: false,
588
+ keepLines: false,
589
+ pageBreakBefore: false,
590
+ isPageBreak: false,
591
+ },
592
+ {
593
+ ...tailMeasured,
594
+ element: tail,
595
+ keepWithNext: false,
596
+ keepLines: false,
597
+ pageBreakBefore: false,
598
+ isPageBreak: false,
599
+ },
600
+ ];
601
+ }
164
602
  /**
165
603
  * Parses the header/footer registry from the staging element.
166
604
  * Also measures heights during parsing for lazy-loading compatibility.
@@ -625,7 +1063,8 @@ export class PaginationEngine {
625
1063
  currentFootnoteHeight = this.measureContinuationHeight(currentContinuation, dims.contentWidth);
626
1064
  }
627
1065
  const finishPage = () => {
628
- if (currentContent.length === 0 && !currentContinuation)
1066
+ const hasCurrentContinuation = (currentContinuation?.remainingElements.length ?? 0) > 0;
1067
+ if (currentContent.length === 0 && !hasCurrentContinuation)
629
1068
  return;
630
1069
  const page = this.createPage(dims, pageNumber, sectionIndex, currentContent, pageInSection, currentFootnoteIds, currentFootnoteHeight, currentContinuation, currentPartialFootnotes.length > 0 ? currentPartialFootnotes : undefined);
631
1070
  pages.push(page);
@@ -652,7 +1091,6 @@ export class PaginationEngine {
652
1091
  };
653
1092
  for (let i = 0; i < blocks.length; i++) {
654
1093
  const block = blocks[i];
655
- const nextBlock = blocks[i + 1];
656
1094
  // Handle explicit page breaks
657
1095
  if (block.isPageBreak) {
658
1096
  finishPage();
@@ -662,10 +1100,50 @@ export class PaginationEngine {
662
1100
  if (block.pageBreakBefore && currentContent.length > 0) {
663
1101
  finishPage();
664
1102
  }
1103
+ // A series of keep-with-next blocks is one indivisible placement unit
1104
+ // when it can fit a new page. The former one-block lookahead was never
1105
+ // applied to the placement decision, and it could not preserve a chain of
1106
+ // headings/paragraphs. Do not force an oversized chain to a fresh page:
1107
+ // its members retain the established greedy/overflow behavior instead.
1108
+ const previousBlock = blocks[i - 1];
1109
+ const startsKeepChain = !previousBlock ||
1110
+ !previousBlock.keepWithNext ||
1111
+ previousBlock.isPageBreak ||
1112
+ block.pageBreakBefore;
1113
+ // A footnote continuation also occupies this page even when its body has
1114
+ // no blocks yet. In that case a feasible chain may need to move past the
1115
+ // continuation as a unit.
1116
+ const pageHasOccupiedSpace = currentContent.length > 0 || currentFootnoteHeight > 0;
1117
+ if (pageHasOccupiedSpace && block.keepWithNext && startsKeepChain) {
1118
+ const keepChain = this.getKeepWithNextChain(blocks, i);
1119
+ if (keepChain.length > 1) {
1120
+ const newChainFootnoteIds = this.collectNewFootnoteIds(keepChain, currentFootnoteIds);
1121
+ let additionalChainFootnoteHeight = 0;
1122
+ if (newChainFootnoteIds.length > 0 && this.footnoteRegistry.size > 0) {
1123
+ const totalChainFootnoteHeight = this.measureFootnotesHeight([...currentFootnoteIds, ...newChainFootnoteIds], dims.contentWidth, currentContinuation);
1124
+ additionalChainFootnoteHeight = Math.max(0, totalChainFootnoteHeight - currentFootnoteHeight);
1125
+ }
1126
+ const currentChainHeight = this.measureKeepWithNextChainBodyHeight(keepChain, prevMarginBottomPt, currentContent.length === 0) +
1127
+ additionalChainFootnoteHeight;
1128
+ const currentAvailableHeight = remainingHeight - currentFootnoteHeight;
1129
+ if (currentChainHeight > currentAvailableHeight) {
1130
+ const nextPageHeights = this.getEffectiveHeights(dims, sectionIndex, pageInSection + 1, pageNumber + 1);
1131
+ const freshChainBodyHeight = this.measureKeepWithNextChainBodyHeight(keepChain, 0, true);
1132
+ // finishPage transfers this continuation to the new page's
1133
+ // currentContinuation state, so include it in the destination
1134
+ // page's footnote reservation before deciding to move the chain.
1135
+ const freshChainFootnoteHeight = this.measureFootnotesHeight(newChainFootnoteIds, dims.contentWidth, nextPageContinuation);
1136
+ if (freshChainBodyHeight + freshChainFootnoteHeight <=
1137
+ nextPageHeights.contentHeight) {
1138
+ finishPage();
1139
+ }
1140
+ }
1141
+ }
1142
+ }
665
1143
  // Extract footnote references from this block
666
- const blockFootnoteIds = this.extractFootnoteRefs(block.element);
1144
+ const allBlockFootnoteIds = this.extractFootnoteRefs(block.element);
667
1145
  // Only count new footnotes (not already on this page)
668
- const newFootnoteIds = blockFootnoteIds.filter(id => !currentFootnoteIds.includes(id));
1146
+ const newFootnoteIds = this.collectNewFootnoteIds([block], currentFootnoteIds);
669
1147
  // Calculate additional footnote height if this block is added
670
1148
  let additionalFootnoteHeight = 0;
671
1149
  if (newFootnoteIds.length > 0 && this.footnoteRegistry.size > 0) {
@@ -688,20 +1166,24 @@ export class PaginationEngine {
688
1166
  // bottom margin extends beyond the content area and is clipped by overflow:hidden.
689
1167
  // It is still tracked in remainingHeight for correct margin collapsing with the next block.
690
1168
  const blockSpace = effectiveMarginTop + block.heightPt + additionalFootnoteHeight;
691
- // Calculate needed height (including keepWithNext)
692
- let neededHeight = blockSpace;
693
- if (block.keepWithNext && nextBlock && !nextBlock.isPageBreak) {
694
- // For keepWithNext, include the next block with collapsed margins
695
- const collapsedMargin = Math.max(block.marginBottomPt, nextBlock.marginTopPt);
696
- neededHeight = effectiveMarginTop + block.heightPt + collapsedMargin +
697
- nextBlock.heightPt + additionalFootnoteHeight;
698
- }
699
1169
  // Effective remaining height (content area minus footnotes already on page)
700
1170
  const effectiveRemainingHeight = remainingHeight - currentFootnoteHeight;
701
1171
  // Calculate maximum footnote area for this page (can expand into body content space)
702
1172
  const bodyContentUsed = effectiveContentHeight - remainingHeight;
703
1173
  const maxFootnoteArea = effectiveContentHeight * MAX_FOOTNOTE_AREA_RATIO;
704
1174
  const maxFootnoteExpansion = Math.max(0, maxFootnoteArea - currentFootnoteHeight);
1175
+ // A paragraph that cannot fit as a whole may still have a simple text-only
1176
+ // prefix that fits this page. Fragment before the ordinary next-page or
1177
+ // oversized fallback so the cloned head participates in the same margin
1178
+ // and footnote accounting as every other block.
1179
+ if (blockSpace > effectiveRemainingHeight) {
1180
+ const paragraphFragments = this.tryFragmentParagraph(block, dims, effectiveRemainingHeight, effectiveMarginTop);
1181
+ if (paragraphFragments) {
1182
+ blocks.splice(i, 1, ...paragraphFragments);
1183
+ i--;
1184
+ continue;
1185
+ }
1186
+ }
705
1187
  // Check if block fits on current page (including its footnotes)
706
1188
  if (blockSpace <= effectiveRemainingHeight) {
707
1189
  // Block fits with current footnote allocation
@@ -752,10 +1234,12 @@ export class PaginationEngine {
752
1234
  footnoteId,
753
1235
  fittingElements: fits
754
1236
  });
755
- nextPageContinuation = {
756
- footnoteId,
757
- remainingElements: overflow
758
- };
1237
+ if (overflow.length > 0) {
1238
+ nextPageContinuation = {
1239
+ footnoteId,
1240
+ remainingElements: overflow
1241
+ };
1242
+ }
759
1243
  currentFootnoteHeight = availableForFootnotes;
760
1244
  }
761
1245
  else {
@@ -802,14 +1286,14 @@ export class PaginationEngine {
802
1286
  else {
803
1287
  // Still doesn't fit - start new page
804
1288
  finishPage();
805
- const newPageFootnoteHeight = blockFootnoteIds.length > 0
806
- ? this.measureFootnotesHeight(blockFootnoteIds, dims.contentWidth, currentContinuation)
1289
+ const newPageFootnoteHeight = allBlockFootnoteIds.length > 0
1290
+ ? this.measureFootnotesHeight(allBlockFootnoteIds, dims.contentWidth, currentContinuation)
807
1291
  : (currentContinuation ? this.measureContinuationHeight(currentContinuation, dims.contentWidth) : 0);
808
1292
  const newPageSpace = block.marginTopPt + block.heightPt + block.marginBottomPt;
809
1293
  currentContent.push(block.element.cloneNode(true));
810
1294
  remainingHeight = effectiveContentHeight - newPageSpace;
811
1295
  prevMarginBottomPt = block.marginBottomPt;
812
- currentFootnoteIds = [...blockFootnoteIds];
1296
+ currentFootnoteIds = [...allBlockFootnoteIds];
813
1297
  currentFootnoteHeight = newPageFootnoteHeight;
814
1298
  }
815
1299
  }
@@ -818,26 +1302,38 @@ export class PaginationEngine {
818
1302
  finishPage();
819
1303
  // On new page, recalculate footnote height for just this block's footnotes
820
1304
  // (plus any continuation from previous page)
821
- const newPageFootnoteHeight = blockFootnoteIds.length > 0
822
- ? this.measureFootnotesHeight(blockFootnoteIds, dims.contentWidth, currentContinuation)
1305
+ const newPageFootnoteHeight = allBlockFootnoteIds.length > 0
1306
+ ? this.measureFootnotesHeight(allBlockFootnoteIds, dims.contentWidth, currentContinuation)
823
1307
  : (currentContinuation ? this.measureContinuationHeight(currentContinuation, dims.contentWidth) : 0);
824
1308
  // Include full top margin
825
1309
  const newPageSpace = block.marginTopPt + block.heightPt + block.marginBottomPt;
826
1310
  currentContent.push(block.element.cloneNode(true));
827
1311
  remainingHeight = effectiveContentHeight - newPageSpace;
828
1312
  prevMarginBottomPt = block.marginBottomPt;
829
- currentFootnoteIds = [...blockFootnoteIds];
1313
+ currentFootnoteIds = [...allBlockFootnoteIds];
830
1314
  currentFootnoteHeight = newPageFootnoteHeight;
831
1315
  }
832
1316
  }
833
1317
  else {
834
- // Block is taller than a page - add it and let it overflow
835
- // (In a more sophisticated implementation, we would split the block)
1318
+ // Block is taller than a page. Ordinary tables can be split at complete
1319
+ // row boundaries; every other block retains the established overflow path.
1320
+ const tableFragments = this.trySplitSimpleOversizedTable(block, dims, sectionIndex);
1321
+ if (tableFragments) {
1322
+ if (currentContent.length > 0 || currentContinuation) {
1323
+ finishPage();
1324
+ }
1325
+ blocks.splice(i, 1, ...tableFragments);
1326
+ i--;
1327
+ continue;
1328
+ }
1329
+ // Unsupported oversized blocks are intentionally left intact. Splitting
1330
+ // arbitrary HTML, merged tables, or footnote-bearing tables would be less
1331
+ // correct than the prior clipped fallback.
836
1332
  if (currentContent.length > 0) {
837
1333
  finishPage();
838
1334
  }
839
1335
  currentContent.push(block.element.cloneNode(true));
840
- currentFootnoteIds = [...blockFootnoteIds];
1336
+ currentFootnoteIds = [...allBlockFootnoteIds];
841
1337
  finishPage();
842
1338
  }
843
1339
  }
@@ -1014,7 +1510,13 @@ export function paginateHtml(html, container, options = {}) {
1014
1510
  if (!pageContainer) {
1015
1511
  throw new Error("Pagination container element not found");
1016
1512
  }
1017
- const engine = new PaginationEngine(staging, pageContainer, options);
1513
+ // This convenience function is the read-only HTML viewer path. Keep the
1514
+ // lower-level engine conservative by default, while enabling paragraph
1515
+ // fragmentation here unless a caller deliberately opts out.
1516
+ const engine = new PaginationEngine(staging, pageContainer, {
1517
+ ...options,
1518
+ fragmentParagraphs: options.fragmentParagraphs ?? true,
1519
+ });
1018
1520
  return engine.paginate();
1019
1521
  }
1020
1522
  //# sourceMappingURL=pagination.js.map