@extend-ai/react-docx 0.7.0-alpha.2 → 0.7.0-alpha.3

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/index.js CHANGED
@@ -2178,16 +2178,17 @@ function shouldAllowStoredPageCountReduction(options) {
2178
2178
  if (targetPageCount >= estimatedPageCount) {
2179
2179
  return true;
2180
2180
  }
2181
+ const renderedBreakHintPageCount = Number.isFinite(
2182
+ options.renderedBreakHintPageCount
2183
+ ) ? Math.max(1, Math.round(options.renderedBreakHintPageCount)) : void 0;
2184
+ const renderedBreakHintsSupportTarget = options.hasLastRenderedPageBreakHints === true && renderedBreakHintPageCount !== void 0 && targetPageCount >= renderedBreakHintPageCount;
2181
2185
  if (options.hasMeasuredBodyFooterOverlap === true) {
2182
- return false;
2186
+ return renderedBreakHintsSupportTarget;
2183
2187
  }
2184
2188
  if (options.hasLastRenderedPageBreakHints !== true) {
2185
2189
  return true;
2186
2190
  }
2187
- const renderedBreakHintPageCount = Number.isFinite(
2188
- options.renderedBreakHintPageCount
2189
- ) ? Math.max(1, Math.round(options.renderedBreakHintPageCount)) : void 0;
2190
- return renderedBreakHintPageCount !== void 0 && targetPageCount >= renderedBreakHintPageCount;
2191
+ return renderedBreakHintsSupportTarget;
2191
2192
  }
2192
2193
  function shouldLatchMeasuredBodyFooterOverlap(options) {
2193
2194
  if (options.measuredBodyFooterOverlap !== true) {
@@ -3297,6 +3298,423 @@ function sliceLayoutToLineRange(layout, startLineIndex, endLineIndex) {
3297
3298
  };
3298
3299
  }
3299
3300
 
3301
+ // src/content-signature.ts
3302
+ var FNV_OFFSET_BASIS = 2166136261;
3303
+ var FNV_PRIME = 16777619;
3304
+ var LONG_STRING_SAMPLE_LENGTH = 64;
3305
+ var LONG_STRING_THRESHOLD = 256;
3306
+ function fnv1aAppend(hash, text) {
3307
+ let next = hash;
3308
+ for (let index = 0; index < text.length; index += 1) {
3309
+ next ^= text.charCodeAt(index);
3310
+ next = Math.imul(next, FNV_PRIME);
3311
+ }
3312
+ return next >>> 0;
3313
+ }
3314
+ function fnv1aAppendString(hash, value) {
3315
+ if (value.length <= LONG_STRING_THRESHOLD) {
3316
+ return fnv1aAppend(fnv1aAppend(hash, `${value.length}:`), value);
3317
+ }
3318
+ const head = value.slice(0, LONG_STRING_SAMPLE_LENGTH);
3319
+ const middleIndex = Math.floor(value.length / 2);
3320
+ const middle = value.slice(
3321
+ middleIndex,
3322
+ middleIndex + LONG_STRING_SAMPLE_LENGTH
3323
+ );
3324
+ const tail = value.slice(value.length - LONG_STRING_SAMPLE_LENGTH);
3325
+ let next = fnv1aAppend(hash, `${value.length}:`);
3326
+ next = fnv1aAppend(next, head);
3327
+ next = fnv1aAppend(next, middle);
3328
+ return fnv1aAppend(next, tail);
3329
+ }
3330
+ function fnv1aAppendValue(hash, value) {
3331
+ if (value === null) {
3332
+ return fnv1aAppend(hash, "~n");
3333
+ }
3334
+ switch (typeof value) {
3335
+ case "undefined":
3336
+ return fnv1aAppend(hash, "~u");
3337
+ case "string":
3338
+ return fnv1aAppendString(fnv1aAppend(hash, "~s"), value);
3339
+ case "number":
3340
+ return fnv1aAppend(hash, `~#${value}`);
3341
+ case "boolean":
3342
+ return fnv1aAppend(hash, value ? "~t" : "~f");
3343
+ case "object":
3344
+ break;
3345
+ default:
3346
+ return fnv1aAppend(hash, "~x");
3347
+ }
3348
+ if (Array.isArray(value)) {
3349
+ let next2 = fnv1aAppend(hash, `~a${value.length}`);
3350
+ for (const entry of value) {
3351
+ next2 = fnv1aAppendValue(next2, entry);
3352
+ }
3353
+ return next2;
3354
+ }
3355
+ if (ArrayBuffer.isView(value) || value instanceof ArrayBuffer) {
3356
+ const byteLength = value instanceof ArrayBuffer ? value.byteLength : value.byteLength;
3357
+ return fnv1aAppend(hash, `~b${byteLength}`);
3358
+ }
3359
+ let next = fnv1aAppend(hash, "~o");
3360
+ for (const key of Object.keys(value)) {
3361
+ const entry = value[key];
3362
+ if (entry === void 0) {
3363
+ continue;
3364
+ }
3365
+ next = fnv1aAppend(next, key);
3366
+ next = fnv1aAppendValue(next, entry);
3367
+ }
3368
+ return next;
3369
+ }
3370
+ function contentHash(value) {
3371
+ return fnv1aAppendValue(FNV_OFFSET_BASIS, value).toString(36);
3372
+ }
3373
+ var nodeSignatureCache = /* @__PURE__ */ new WeakMap();
3374
+ function docNodeContentSignature(node) {
3375
+ if (typeof node !== "object" || node === null) {
3376
+ return contentHash(node);
3377
+ }
3378
+ const cached = nodeSignatureCache.get(node);
3379
+ if (cached !== void 0) {
3380
+ return cached;
3381
+ }
3382
+ const signature = contentHash(node);
3383
+ nodeSignatureCache.set(node, signature);
3384
+ return signature;
3385
+ }
3386
+ var metadataSignatureCache = /* @__PURE__ */ new WeakMap();
3387
+ function docModelThumbnailMetadataSignature(metadata) {
3388
+ const cached = metadataSignatureCache.get(metadata);
3389
+ if (cached !== void 0) {
3390
+ return cached;
3391
+ }
3392
+ const relevant = metadata;
3393
+ let hash = FNV_OFFSET_BASIS;
3394
+ hash = fnv1aAppendValue(hash, relevant.sections);
3395
+ hash = fnv1aAppendValue(hash, relevant.headerSections);
3396
+ hash = fnv1aAppendValue(hash, relevant.footerSections);
3397
+ hash = fnv1aAppendValue(hash, relevant.numberingDefinitions);
3398
+ hash = fnv1aAppendValue(hash, relevant.footnotes);
3399
+ hash = fnv1aAppendValue(hash, relevant.endnotes);
3400
+ hash = fnv1aAppendValue(hash, relevant.documentBackgroundColor);
3401
+ hash = fnv1aAppendValue(hash, relevant.compatibility);
3402
+ const signature = (hash >>> 0).toString(36);
3403
+ metadataSignatureCache.set(metadata, signature);
3404
+ return signature;
3405
+ }
3406
+
3407
+ // src/thumbnail-raster.ts
3408
+ var DOCX_THUMBNAIL_EXCLUDE_ATTRIBUTE = "data-docx-thumbnail-exclude";
3409
+ var THUMBNAIL_EXCLUDED_CLONE_SELECTOR = [
3410
+ `[${DOCX_THUMBNAIL_EXCLUDE_ATTRIBUTE}="true"]`,
3411
+ "textarea",
3412
+ '[data-image-resize-handle="true"]',
3413
+ '[data-docx-table-move-handle="true"]'
3414
+ ].join(",");
3415
+ var THUMBNAIL_IMAGE_DOWNSCALE_MIN_DATA_URI_LENGTH = 32768;
3416
+ var THUMBNAIL_IMAGE_DOWNSCALE_MAX_DIMENSION_PX = 512;
3417
+ var THUMBNAIL_IMAGE_JPEG_QUALITY = 0.78;
3418
+ function thumbnailSvgDataUri(svg) {
3419
+ return `data:image/svg+xml;charset=utf-8,${encodeURIComponent(svg)}`;
3420
+ }
3421
+ function thumbnailImageSourceQualifiesForDownscale(src) {
3422
+ return src.length >= THUMBNAIL_IMAGE_DOWNSCALE_MIN_DATA_URI_LENGTH && src.startsWith("data:image/") && !src.startsWith("data:image/svg");
3423
+ }
3424
+ async function loadThumbnailImage(src) {
3425
+ const image = new Image();
3426
+ image.decoding = "async";
3427
+ const loaded = new Promise((resolve, reject) => {
3428
+ image.onload = () => resolve(image);
3429
+ image.onerror = () => {
3430
+ reject(new Error("Failed to decode DOCX thumbnail image."));
3431
+ };
3432
+ });
3433
+ image.src = src;
3434
+ if (typeof image.decode === "function") {
3435
+ try {
3436
+ await image.decode();
3437
+ return image;
3438
+ } catch {
3439
+ }
3440
+ }
3441
+ return loaded;
3442
+ }
3443
+ var downscaledThumbnailImageCache = /* @__PURE__ */ new Map();
3444
+ async function downscaleThumbnailImageDataUri(src) {
3445
+ if (typeof document === "undefined") {
3446
+ return void 0;
3447
+ }
3448
+ const image = await loadThumbnailImage(src);
3449
+ const naturalWidth = image.naturalWidth || image.width;
3450
+ const naturalHeight = image.naturalHeight || image.height;
3451
+ if (!naturalWidth || !naturalHeight) {
3452
+ return void 0;
3453
+ }
3454
+ const scale = THUMBNAIL_IMAGE_DOWNSCALE_MAX_DIMENSION_PX / Math.max(naturalWidth, naturalHeight);
3455
+ if (scale >= 1) {
3456
+ return void 0;
3457
+ }
3458
+ const canvas = document.createElement("canvas");
3459
+ canvas.width = Math.max(1, Math.round(naturalWidth * scale));
3460
+ canvas.height = Math.max(1, Math.round(naturalHeight * scale));
3461
+ const context = canvas.getContext("2d");
3462
+ if (!context) {
3463
+ return void 0;
3464
+ }
3465
+ context.imageSmoothingEnabled = true;
3466
+ context.imageSmoothingQuality = "high";
3467
+ context.drawImage(image, 0, 0, canvas.width, canvas.height);
3468
+ const sourceIsJpeg = src.startsWith("data:image/jpeg") || src.startsWith("data:image/jpg");
3469
+ const downscaled = sourceIsJpeg ? canvas.toDataURL("image/jpeg", THUMBNAIL_IMAGE_JPEG_QUALITY) : canvas.toDataURL("image/png");
3470
+ return downscaled.length < src.length ? downscaled : void 0;
3471
+ }
3472
+ function getDownscaledThumbnailImageDataUri(src) {
3473
+ const cached = downscaledThumbnailImageCache.get(src);
3474
+ if (cached) {
3475
+ return cached;
3476
+ }
3477
+ const pending = downscaleThumbnailImageDataUri(src).catch(() => void 0);
3478
+ downscaledThumbnailImageCache.set(src, pending);
3479
+ return pending;
3480
+ }
3481
+ async function buildDocxThumbnailSvgMarkup(params) {
3482
+ const { pageElement, sourceWidthPx, sourceHeightPx, widthPx, heightPx } = params;
3483
+ const clone = pageElement.cloneNode(true);
3484
+ clone.querySelectorAll(THUMBNAIL_EXCLUDED_CLONE_SELECTOR).forEach((excluded) => {
3485
+ excluded.remove();
3486
+ });
3487
+ const cloneImages = Array.from(clone.querySelectorAll("img"));
3488
+ await Promise.all(
3489
+ cloneImages.map(async (cloneImage) => {
3490
+ const src = cloneImage.getAttribute("src");
3491
+ if (!src || !thumbnailImageSourceQualifiesForDownscale(src)) {
3492
+ return;
3493
+ }
3494
+ const downscaled = await getDownscaledThumbnailImageDataUri(src);
3495
+ if (downscaled) {
3496
+ cloneImage.setAttribute("src", downscaled);
3497
+ }
3498
+ })
3499
+ );
3500
+ const scaleX = widthPx / sourceWidthPx;
3501
+ const scaleY = heightPx / sourceHeightPx;
3502
+ const serializedPage = new XMLSerializer().serializeToString(clone);
3503
+ return `
3504
+ <svg xmlns="http://www.w3.org/2000/svg" width="${widthPx}" height="${heightPx}" viewBox="0 0 ${widthPx} ${heightPx}">
3505
+ <foreignObject x="0" y="0" width="100%" height="100%">
3506
+ <div xmlns="http://www.w3.org/1999/xhtml" style="width:${widthPx}px;height:${heightPx}px;overflow:hidden;">
3507
+ <div style="width:${sourceWidthPx}px;height:${sourceHeightPx}px;transform-origin:top left;transform:scale(${scaleX}, ${scaleY});">
3508
+ ${serializedPage}
3509
+ </div>
3510
+ </div>
3511
+ </foreignObject>
3512
+ </svg>
3513
+ `;
3514
+ }
3515
+ async function rasterizeDocxThumbnailSurface(params) {
3516
+ if (typeof window === "undefined" || typeof XMLSerializer === "undefined") {
3517
+ throw new Error("DOCX thumbnails require a browser environment.");
3518
+ }
3519
+ const safeSourceWidthPx = Math.max(1, Math.round(params.sourceWidthPx));
3520
+ const safeSourceHeightPx = Math.max(1, Math.round(params.sourceHeightPx));
3521
+ const svgMarkup = await buildDocxThumbnailSvgMarkup({
3522
+ pageElement: params.pageElement,
3523
+ sourceWidthPx: safeSourceWidthPx,
3524
+ sourceHeightPx: safeSourceHeightPx,
3525
+ widthPx: params.widthPx,
3526
+ heightPx: params.heightPx
3527
+ });
3528
+ const image = await loadThumbnailImage(thumbnailSvgDataUri(svgMarkup));
3529
+ const surface = document.createElement("canvas");
3530
+ surface.width = Math.max(1, Math.round(params.pixelWidthPx));
3531
+ surface.height = Math.max(1, Math.round(params.pixelHeightPx));
3532
+ const context = surface.getContext("2d");
3533
+ if (!context) {
3534
+ throw new Error("2D canvas context is unavailable for DOCX thumbnails.");
3535
+ }
3536
+ context.imageSmoothingEnabled = true;
3537
+ context.imageSmoothingQuality = "high";
3538
+ context.drawImage(image, 0, 0, surface.width, surface.height);
3539
+ return surface;
3540
+ }
3541
+ function blitDocxThumbnailSurface(surface, canvas, resolution) {
3542
+ canvas.width = Math.max(1, Math.round(resolution.pixelWidthPx));
3543
+ canvas.height = Math.max(1, Math.round(resolution.pixelHeightPx));
3544
+ canvas.style.width = `${Math.max(1, Math.round(resolution.widthPx))}px`;
3545
+ canvas.style.height = `${Math.max(1, Math.round(resolution.heightPx))}px`;
3546
+ const context = canvas.getContext("2d");
3547
+ if (!context) {
3548
+ throw new Error("2D canvas context is unavailable for DOCX thumbnails.");
3549
+ }
3550
+ context.setTransform(1, 0, 0, 1, 0, 0);
3551
+ context.clearRect(0, 0, canvas.width, canvas.height);
3552
+ context.drawImage(surface, 0, 0, canvas.width, canvas.height);
3553
+ }
3554
+ var DocxThumbnailSurfaceCache = class {
3555
+ constructor(maxEntries) {
3556
+ this.maxEntries = maxEntries;
3557
+ }
3558
+ entries = /* @__PURE__ */ new Map();
3559
+ get size() {
3560
+ return this.entries.size;
3561
+ }
3562
+ get(key) {
3563
+ const value = this.entries.get(key);
3564
+ if (value === void 0) {
3565
+ return void 0;
3566
+ }
3567
+ this.entries.delete(key);
3568
+ this.entries.set(key, value);
3569
+ return value;
3570
+ }
3571
+ set(key, value) {
3572
+ this.entries.delete(key);
3573
+ this.entries.set(key, value);
3574
+ while (this.entries.size > this.maxEntries) {
3575
+ const oldestKey = this.entries.keys().next().value;
3576
+ if (oldestKey === void 0) {
3577
+ break;
3578
+ }
3579
+ this.entries.delete(oldestKey);
3580
+ }
3581
+ }
3582
+ clear() {
3583
+ this.entries.clear();
3584
+ }
3585
+ };
3586
+ var IDLE_TASK_TIMEOUT_MS = 300;
3587
+ function defaultScheduleTask(callback) {
3588
+ const idleWindow = typeof window === "undefined" ? void 0 : window;
3589
+ if (!idleWindow || typeof idleWindow.requestIdleCallback !== "function") {
3590
+ setTimeout(callback, 16);
3591
+ return;
3592
+ }
3593
+ let invoked = false;
3594
+ const runOnce = () => {
3595
+ if (invoked) {
3596
+ return;
3597
+ }
3598
+ invoked = true;
3599
+ callback();
3600
+ };
3601
+ const idleHandle = idleWindow.requestIdleCallback(runOnce, {
3602
+ timeout: IDLE_TASK_TIMEOUT_MS
3603
+ });
3604
+ setTimeout(() => {
3605
+ if (invoked) {
3606
+ return;
3607
+ }
3608
+ if (typeof idleWindow.cancelIdleCallback === "function") {
3609
+ idleWindow.cancelIdleCallback(idleHandle);
3610
+ }
3611
+ runOnce();
3612
+ }, IDLE_TASK_TIMEOUT_MS + 50);
3613
+ }
3614
+ function defaultScheduleDelayed(callback, delayMs) {
3615
+ setTimeout(callback, delayMs);
3616
+ }
3617
+ var SerialIdleTaskQueue = class {
3618
+ pending = [];
3619
+ lastRunAtByKey = /* @__PURE__ */ new Map();
3620
+ scheduleTask;
3621
+ scheduleDelayed;
3622
+ minTaskIntervalMs;
3623
+ now;
3624
+ pumpScheduled = false;
3625
+ running = false;
3626
+ constructor(options) {
3627
+ this.scheduleTask = options?.scheduleTask ?? defaultScheduleTask;
3628
+ this.scheduleDelayed = options?.scheduleDelayed ?? defaultScheduleDelayed;
3629
+ this.minTaskIntervalMs = Math.max(0, options?.minTaskIntervalMs ?? 0);
3630
+ this.now = options?.now ?? (() => Date.now());
3631
+ }
3632
+ get pendingCount() {
3633
+ return this.pending.length;
3634
+ }
3635
+ enqueue(key, run) {
3636
+ return new Promise((resolve) => {
3637
+ const existing = this.pending.find((entry) => entry.key === key);
3638
+ if (existing) {
3639
+ existing.run = run;
3640
+ existing.resolvers.push(resolve);
3641
+ } else {
3642
+ this.pending.push({ key, run, resolvers: [resolve] });
3643
+ }
3644
+ this.schedulePump();
3645
+ });
3646
+ }
3647
+ /** Drops all queued tasks, resolving their waiters without running them. */
3648
+ clear() {
3649
+ const dropped = this.pending.splice(0, this.pending.length);
3650
+ this.lastRunAtByKey.clear();
3651
+ dropped.forEach((entry) => {
3652
+ entry.resolvers.forEach((resolveEntry) => {
3653
+ resolveEntry();
3654
+ });
3655
+ });
3656
+ }
3657
+ schedulePump() {
3658
+ if (this.pumpScheduled || this.running || this.pending.length === 0) {
3659
+ return;
3660
+ }
3661
+ this.pumpScheduled = true;
3662
+ this.scheduleTask(() => {
3663
+ this.pumpScheduled = false;
3664
+ void this.runNext();
3665
+ });
3666
+ }
3667
+ takeNextEligibleEntry() {
3668
+ if (this.pending.length === 0) {
3669
+ return void 0;
3670
+ }
3671
+ const now = this.now();
3672
+ let earliestWaitMs;
3673
+ for (let index = 0; index < this.pending.length; index += 1) {
3674
+ const candidate = this.pending[index];
3675
+ if (!candidate) {
3676
+ continue;
3677
+ }
3678
+ const lastRunAt = this.lastRunAtByKey.get(candidate.key);
3679
+ const waitMs = lastRunAt === void 0 ? 0 : lastRunAt + this.minTaskIntervalMs - now;
3680
+ if (waitMs <= 0) {
3681
+ this.pending.splice(index, 1);
3682
+ return { entry: candidate };
3683
+ }
3684
+ earliestWaitMs = earliestWaitMs === void 0 ? waitMs : Math.min(earliestWaitMs, waitMs);
3685
+ }
3686
+ return earliestWaitMs === void 0 ? void 0 : { retryDelayMs: earliestWaitMs };
3687
+ }
3688
+ async runNext() {
3689
+ if (this.running) {
3690
+ return;
3691
+ }
3692
+ const next = this.takeNextEligibleEntry();
3693
+ if (!next) {
3694
+ return;
3695
+ }
3696
+ if (!("entry" in next)) {
3697
+ this.scheduleDelayed(() => {
3698
+ this.schedulePump();
3699
+ }, next.retryDelayMs);
3700
+ return;
3701
+ }
3702
+ this.running = true;
3703
+ const { entry } = next;
3704
+ try {
3705
+ await entry.run();
3706
+ } catch {
3707
+ } finally {
3708
+ this.lastRunAtByKey.set(entry.key, this.now());
3709
+ this.running = false;
3710
+ entry.resolvers.forEach((resolveEntry) => {
3711
+ resolveEntry();
3712
+ });
3713
+ this.schedulePump();
3714
+ }
3715
+ }
3716
+ };
3717
+
3300
3718
  // src/editor.tsx
3301
3719
  import { Fragment as Fragment2, jsx, jsxs } from "react/jsx-runtime";
3302
3720
  var HIGHLIGHT_TO_CSS = {
@@ -3393,6 +3811,9 @@ var DEFAULT_PARAGRAPH_LINE_MULTIPLE = 1;
3393
3811
  var WORD_SINGLE_LINE_AUTO_SCALE = 0.88;
3394
3812
  var WORD_SINGLE_LINE_AUTO_SCALE_SANS = 0.9;
3395
3813
  var WORD_SINGLE_LINE_AUTO_SCALE_SERIF = 1.08;
3814
+ var WORD_EMPTY_PARAGRAPH_LINE_SCALE = 1.21;
3815
+ var WORD_EMPTY_PARAGRAPH_LINE_SCALE_SERIF = 1.15;
3816
+ var WORD_EMPTY_PARAGRAPH_LINE_SCALE_SANS = 1.15;
3396
3817
  var WORD_AUTO_LINE_SCALE_BLEND_END_MULTIPLE = 1.08;
3397
3818
  var MIN_AUTO_LINE_MULTIPLE = 0.1;
3398
3819
  var MIN_PARAGRAPH_LINE_HEIGHT_PX = 14;
@@ -3437,7 +3858,6 @@ var TEXT_MEASURE_CACHE_MAX_ENTRIES = 12e3;
3437
3858
  var DEFAULT_TAB_STOP_PX = 48;
3438
3859
  var TAB_LEADER_ZONE_GAP_PX = 20;
3439
3860
  var EMPTY_PARAGRAPH_EXTRA_HEIGHT_PX = 0;
3440
- var LEADING_COVER_SPACER_EXTRA_HEIGHT_PX = 2;
3441
3861
  var PARAGRAPH_SEGMENT_TOP_BLEED_PX = 22;
3442
3862
  var PARAGRAPH_SEGMENT_DESCENDER_BLEED_PX = 6;
3443
3863
  var PARAGRAPH_SEGMENT_VISUAL_SAFETY_PX = 24;
@@ -5957,19 +6377,6 @@ function floatingTextBoxVisibleTextFromImage(image) {
5957
6377
  const normalized = normalizeFloatingTextBoxComparisonText(text);
5958
6378
  return normalized.length > 0 ? normalized : void 0;
5959
6379
  }
5960
- function absoluteFloatingTextBearingTextBoxFootprintPx(image) {
5961
- const floating = image.floating;
5962
- if (!shouldRenderAbsoluteFloatingImage(image) || !floating || image.syntheticTextBox !== true || syntheticTextBoxContainsPictureLayer(image) || !floatingTextBoxVisibleTextFromImage(image)) {
5963
- return 0;
5964
- }
5965
- const imageHeightPx = Number.isFinite(image.heightPx) && image.heightPx > 0 ? Math.round(image.heightPx) : Number.isFinite(image.widthPx) && image.widthPx > 0 ? Math.round(image.widthPx) : MIN_PARAGRAPH_LINE_HEIGHT_PX;
5966
- const distTPx = Math.max(0, Math.round(floating.distTPx ?? 0));
5967
- const distBPx = Math.max(0, Math.round(floating.distBPx ?? 0));
5968
- return Math.max(
5969
- MIN_PARAGRAPH_LINE_HEIGHT_PX,
5970
- imageHeightPx + distTPx + distBPx
5971
- );
5972
- }
5973
6380
  function paragraphVisibleTextIsOnlyAbsoluteFloatingTextBoxContent(paragraph) {
5974
6381
  if (!paragraphHasVisibleText2(paragraph) || paragraphHasFormField2(paragraph)) {
5975
6382
  return false;
@@ -6002,29 +6409,9 @@ function paragraphHasOnlyWhitespaceText(paragraph) {
6002
6409
  return child.text.replace(/[\s\u00a0]+/g, "").length === 0;
6003
6410
  });
6004
6411
  }
6005
- function paragraphHasActiveNumbering(paragraph) {
6006
- const numbering = paragraph.style?.numbering;
6007
- return Boolean(
6008
- numbering && Number.isFinite(numbering.numId) && Math.round(numbering.numId) > 0
6009
- );
6010
- }
6011
6412
  function paragraphContainsSectionBreakProperties(paragraph) {
6012
6413
  return /<w:sectPr\b/i.test(paragraph.sourceXml ?? "");
6013
6414
  }
6014
- function paragraphTextBearingAbsoluteFloatingTextBoxFootprintPx(paragraph) {
6015
- if (paragraphHasVisibleText2(paragraph) || paragraphHasFormField2(paragraph) || paragraphContainsSectionBreakProperties(paragraph)) {
6016
- return 0;
6017
- }
6018
- return paragraph.children.reduce((largest, child) => {
6019
- if (child.type !== "image") {
6020
- return largest;
6021
- }
6022
- return Math.max(
6023
- largest,
6024
- absoluteFloatingTextBearingTextBoxFootprintPx(child)
6025
- );
6026
- }, 0);
6027
- }
6028
6415
  function paragraphAbsoluteFloatingAnchorsDependOnParagraphFlow(paragraph) {
6029
6416
  return paragraph.children.some((child) => {
6030
6417
  if (child.type !== "image" || !shouldRenderAbsoluteFloatingImage(child) || child.syntheticTextBox !== true || !floatingTextBoxVisibleTextFromImage(child) || child.floating?.behindDocument !== true) {
@@ -6116,21 +6503,6 @@ function paragraphParticipatesInLeadingCoverLayout(model, nodeIndex, pageContent
6116
6503
  }
6117
6504
  return sawLikelyCoverArtAnchor;
6118
6505
  }
6119
- function paragraphLikelyFullPageCoverFootprintPx(model, nodeIndex, paragraph, pageContentWidthPx, pageContentHeightPx) {
6120
- if (!paragraphParticipatesInLeadingCoverLayout(
6121
- model,
6122
- nodeIndex,
6123
- pageContentWidthPx,
6124
- pageContentHeightPx
6125
- ) || !paragraphIsLikelyFullPageCoverArtAnchor(
6126
- paragraph,
6127
- pageContentWidthPx,
6128
- pageContentHeightPx
6129
- )) {
6130
- return 0;
6131
- }
6132
- return Math.max(1, Math.round(pageContentHeightPx));
6133
- }
6134
6506
  function fullPageCoverImageRenderKey(nodeIndex, childIndex) {
6135
6507
  return `${nodeIndex}:${childIndex}`;
6136
6508
  }
@@ -6169,71 +6541,6 @@ function fullPageCoverAbsoluteFloatingImageStyle(image, layout, options) {
6169
6541
  zIndex: floating?.behindDocument === true ? 0 : normalizedZIndex
6170
6542
  };
6171
6543
  }
6172
- function paragraphActsAsLeadingCoverLayoutSpacer(model, nodeIndex, paragraph, pageContentWidthPx, pageContentHeightPx) {
6173
- if (nodeIndex <= 0 || !paragraphHasOnlyWhitespaceText(paragraph) || paragraphHasExplicitPageBreak2(paragraph)) {
6174
- return false;
6175
- }
6176
- if (paragraphHasActiveNumbering(paragraph)) {
6177
- return false;
6178
- }
6179
- return paragraphParticipatesInLeadingCoverLayout(
6180
- model,
6181
- nodeIndex,
6182
- pageContentWidthPx,
6183
- pageContentHeightPx
6184
- );
6185
- }
6186
- function paragraphActsAsLeadingCoverLayoutPreambleSpacer(model, nodeIndex, paragraph, pageContentWidthPx, pageContentHeightPx) {
6187
- if (!paragraphHasOnlyWhitespaceText(paragraph) || paragraphHasExplicitPageBreak2(paragraph) || paragraphHasActiveNumbering(paragraph)) {
6188
- return false;
6189
- }
6190
- const nextNode = model.nodes[nodeIndex + 1];
6191
- if (!nextNode || nextNode.type !== "paragraph") {
6192
- return false;
6193
- }
6194
- if (!paragraphParticipatesInLeadingCoverLayout(
6195
- model,
6196
- nodeIndex + 1,
6197
- pageContentWidthPx,
6198
- pageContentHeightPx
6199
- )) {
6200
- return false;
6201
- }
6202
- for (let probeIndex = nodeIndex - 1; probeIndex >= 0; probeIndex -= 1) {
6203
- const previousNode = model.nodes[probeIndex];
6204
- if (!previousNode || previousNode.type !== "paragraph") {
6205
- return false;
6206
- }
6207
- if (paragraphHasOnlyWhitespaceText(previousNode)) {
6208
- continue;
6209
- }
6210
- if (paragraphHasAbsoluteFloatingImage(previousNode)) {
6211
- return false;
6212
- }
6213
- return !paragraphIsLikelyFullPageCoverArtAnchor(
6214
- previousNode,
6215
- pageContentWidthPx,
6216
- pageContentHeightPx
6217
- );
6218
- }
6219
- return true;
6220
- }
6221
- function paragraphStartsNewPageAfterLeadingCoverLayout(model, nodeIndex, paragraph, pageContentWidthPx, pageContentHeightPx) {
6222
- if (nodeIndex <= 0 || !paragraphStartsNormalFlowContent(paragraph) || paragraphHasExplicitPageBreak2(paragraph) || paragraphHasPageBreakBefore2(paragraph)) {
6223
- return false;
6224
- }
6225
- const previousNode = model.nodes[nodeIndex - 1];
6226
- if (!previousNode || previousNode.type !== "paragraph") {
6227
- return false;
6228
- }
6229
- return paragraphActsAsLeadingCoverLayoutSpacer(
6230
- model,
6231
- nodeIndex - 1,
6232
- previousNode,
6233
- pageContentWidthPx,
6234
- pageContentHeightPx
6235
- );
6236
- }
6237
6544
  function paragraphLooksLikeCheckboxChoiceRow(paragraph) {
6238
6545
  if (paragraph.children.some((child) => child.type === "image")) {
6239
6546
  return false;
@@ -6751,7 +7058,8 @@ function buildParagraphPretextLayoutSource(paragraph, options) {
6751
7058
  let combinedText = "";
6752
7059
  const paragraphBaseFontPx = paragraphBaseFontSizePx(paragraph);
6753
7060
  const tabStopPositionsPx = options?.expandTabsForLayout ? resolveParagraphTabStopsPx(paragraph) : [];
6754
- const fallbackTabWidthPx = paragraphLooksLikeCheckboxChoiceRow(paragraph) ? checkboxChoiceRowTabWidthPx(paragraph) : DEFAULT_TAB_STOP_PX;
7061
+ const usesCheckboxRowTabFallback = paragraphLooksLikeCheckboxChoiceRow(paragraph);
7062
+ const fallbackTabWidthPx = usesCheckboxRowTabFallback ? checkboxChoiceRowTabWidthPx(paragraph) : DEFAULT_TAB_STOP_PX;
6755
7063
  let approximateLineWidthPx = 0;
6756
7064
  let lastTextStyle = firstRunStyle(paragraph);
6757
7065
  for (let childIndex = 0; childIndex < paragraph.children.length; childIndex += 1) {
@@ -6817,7 +7125,8 @@ function buildParagraphPretextLayoutSource(paragraph, options) {
6817
7125
  const tabWidthPx = resolveTabSpacerWidthPx(
6818
7126
  tabStopPositionsPx,
6819
7127
  approximateLineWidthPx,
6820
- fallbackTabWidthPx
7128
+ fallbackTabWidthPx,
7129
+ usesCheckboxRowTabFallback
6821
7130
  );
6822
7131
  const spacerText = buildParagraphPretextTabSpacerText(
6823
7132
  tabWidthPx,
@@ -7566,10 +7875,14 @@ function updateEstimatedLineWidthPxForText(currentLineWidthPx, text, style) {
7566
7875
  const trailingSegment = segments[segments.length - 1] ?? "";
7567
7876
  return estimateTextAdvanceWidthPx(trailingSegment, style);
7568
7877
  }
7569
- function resolveTabSpacerWidthPx(tabStopPositionsPx, currentLineWidthPx, fallbackWidthPx) {
7878
+ function resolveTabSpacerWidthPx(tabStopPositionsPx, currentLineWidthPx, fallbackWidthPx, fixedFallback = false) {
7570
7879
  const safeFallback = Math.max(12, Math.round(fallbackWidthPx));
7571
7880
  if (tabStopPositionsPx.length === 0) {
7572
- return safeFallback;
7881
+ if (fixedFallback) {
7882
+ return safeFallback;
7883
+ }
7884
+ const nextStop2 = (Math.floor((currentLineWidthPx + 0.5) / safeFallback) + 1) * safeFallback;
7885
+ return Math.max(2, Math.round(nextStop2 - currentLineWidthPx));
7573
7886
  }
7574
7887
  const nextStop = tabStopPositionsPx.find(
7575
7888
  (stop) => stop > currentLineWidthPx + 0.5
@@ -8322,7 +8635,23 @@ function singleLineAutoScaleForFontFamily(fontFamily) {
8322
8635
  }
8323
8636
  return WORD_SINGLE_LINE_AUTO_SCALE;
8324
8637
  }
8638
+ function emptyParagraphLineScaleForFontFamily(fontFamily) {
8639
+ const normalized = normalizeFontFamilyToken(fontFamily) ?? fontFamily?.toLowerCase();
8640
+ if (!normalized) {
8641
+ return WORD_EMPTY_PARAGRAPH_LINE_SCALE;
8642
+ }
8643
+ if (normalized === "times roman" || normalized === "times new roman" || normalized === "cambria" || normalized === "garamond" || normalized === "georgia" || normalized === "book antiqua" || normalized === "palatino linotype") {
8644
+ return WORD_EMPTY_PARAGRAPH_LINE_SCALE_SERIF;
8645
+ }
8646
+ if (normalized === "arial") {
8647
+ return WORD_EMPTY_PARAGRAPH_LINE_SCALE_SANS;
8648
+ }
8649
+ return WORD_EMPTY_PARAGRAPH_LINE_SCALE;
8650
+ }
8325
8651
  function resolveParagraphSingleLineAutoScale(paragraph, fontFamily) {
8652
+ if (paragraphHasOnlyWhitespaceText(paragraph)) {
8653
+ return emptyParagraphLineScaleForFontFamily(fontFamily);
8654
+ }
8326
8655
  const baseScale = singleLineAutoScaleForFontFamily(fontFamily);
8327
8656
  return paragraphHasCheckboxFormField(paragraph) ? Math.max(1.08, baseScale) : baseScale;
8328
8657
  }
@@ -9037,36 +9366,6 @@ function resolveMaxPretextLineRangeEndIndexThatFits(layout, startLineIndex, maxE
9037
9366
  }
9038
9367
  return bestEnd;
9039
9368
  }
9040
- function estimateAbsoluteFloatingImageFootprintPx(paragraph, image) {
9041
- if (!shouldRenderAbsoluteFloatingImage(image)) {
9042
- return 0;
9043
- }
9044
- const floating = image.floating;
9045
- if (!floating) {
9046
- return 0;
9047
- }
9048
- const wrapType = (floating.wrapType ?? "none").trim().toLowerCase();
9049
- if (wrapType !== "none") {
9050
- return 0;
9051
- }
9052
- const behavesAsTextBearingFloatingTextBox = image.syntheticTextBox && !syntheticTextBoxContainsPictureLayer(image) && Boolean(floatingTextBoxVisibleTextFromImage(image));
9053
- if (behavesAsTextBearingFloatingTextBox) {
9054
- if (paragraphHasVisibleText2(paragraph) || paragraphHasFormField2(paragraph) || paragraphContainsSectionBreakProperties(paragraph)) {
9055
- return 0;
9056
- }
9057
- const imageHeightPx = Number.isFinite(image.heightPx) && image.heightPx > 0 ? Math.round(image.heightPx) : Number.isFinite(image.widthPx) && image.widthPx > 0 ? Math.round(image.widthPx) : MIN_PARAGRAPH_LINE_HEIGHT_PX;
9058
- const distTPx = Math.max(0, Math.round(floating.distTPx ?? 0));
9059
- const distBPx = Math.max(0, Math.round(floating.distBPx ?? 0));
9060
- return Math.max(
9061
- MIN_PARAGRAPH_LINE_HEIGHT_PX,
9062
- imageHeightPx + distTPx + distBPx
9063
- );
9064
- }
9065
- if (floating.behindDocument) {
9066
- return 0;
9067
- }
9068
- return 0;
9069
- }
9070
9369
  function resolveAutoLineSpacingMultiple(lineTwips, fallbackMultiple) {
9071
9370
  if (!Number.isFinite(lineTwips)) {
9072
9371
  return Math.max(MIN_AUTO_LINE_MULTIPLE, fallbackMultiple);
@@ -9164,8 +9463,15 @@ function estimateParagraphLineHeightPx(paragraph, docGridLinePitchPx, disableDoc
9164
9463
  docGridMinimumLineHeightPx ?? 0
9165
9464
  );
9166
9465
  }
9167
- const multiple = calibrateAutoLineSpacingMultiple(
9168
- resolveAutoLineSpacingMultiple(lineTwips, defaultLineMultiple),
9466
+ const resolvedAutoMultiple = resolveAutoLineSpacingMultiple(
9467
+ lineTwips,
9468
+ defaultLineMultiple
9469
+ );
9470
+ const multiple = paragraphHasOnlyWhitespaceText(paragraph) ? Math.max(
9471
+ MIN_AUTO_LINE_MULTIPLE,
9472
+ Number((resolvedAutoMultiple * singleLineScale).toFixed(3))
9473
+ ) : calibrateAutoLineSpacingMultiple(
9474
+ resolvedAutoMultiple,
9169
9475
  baseFontFamily,
9170
9476
  singleLineScale
9171
9477
  );
@@ -9242,19 +9548,6 @@ function estimateParagraphHeightPx(paragraph, availableWidthPx, numberingDefinit
9242
9548
  estimateWrappedFloatingImageFootprintPx(paragraph, child)
9243
9549
  );
9244
9550
  }, 0);
9245
- const absoluteFloatingImageHeightPx = paragraph.children.reduce(
9246
- (largest, child) => {
9247
- if (child.type !== "image") {
9248
- return largest;
9249
- }
9250
- return Math.max(
9251
- largest,
9252
- estimateAbsoluteFloatingImageFootprintPx(paragraph, child)
9253
- );
9254
- },
9255
- 0
9256
- );
9257
- const effectiveAbsoluteFloatingImageHeightPx = collapsibleAbsoluteFloatingAnchorOnlyParagraph || decorativeBehindTextAnchorOnlyParagraph ? 0 : absoluteFloatingImageHeightPx;
9258
9551
  const emptyParagraphHeightPx = decorativeBehindTextAnchorOnlyParagraph ? 0 : paragraphIsEffectivelyEmpty(paragraph) ? lineHeightPx + EMPTY_PARAGRAPH_EXTRA_HEIGHT_PX : 0;
9259
9552
  const topBorderInsetPx = paragraphBorderInsetPx(
9260
9553
  paragraph.style?.borders?.top
@@ -9274,7 +9567,6 @@ function estimateParagraphHeightPx(paragraph, availableWidthPx, numberingDefinit
9274
9567
  textFlowHeightPx,
9275
9568
  inlineImageHeightPx,
9276
9569
  wrappedFloatingImageHeightPx,
9277
- effectiveAbsoluteFloatingImageHeightPx,
9278
9570
  emptyParagraphHeightPx
9279
9571
  );
9280
9572
  if (excludeWrappedFloatingImageFootprint && paragraph.children.some((c) => c.type === "image")) {
@@ -10535,6 +10827,7 @@ function buildDocumentPageNodeSegments(model, pageContentHeightPx, pageContentWi
10535
10827
  let previousParagraphAfterPx = 0;
10536
10828
  let currentMetricsIndex = 0;
10537
10829
  let currentSectionPageFlowOriginPx = 0;
10830
+ let committedKeepNextChainEndNodeIndex = -1;
10538
10831
  let currentPageContentHeightPx = resolveMetricsPageContentHeightPx(
10539
10832
  0,
10540
10833
  metricsBySection[0]
@@ -10593,31 +10886,10 @@ function buildDocumentPageNodeSegments(model, pageContentHeightPx, pageContentWi
10593
10886
  previousParagraphAfterPx = 0;
10594
10887
  continue;
10595
10888
  }
10596
- if (node.type === "paragraph" && paragraphActsAsLeadingCoverLayoutPreambleSpacer(
10597
- model,
10598
- nodeIndex,
10599
- node,
10600
- nodeMetrics.pageContentWidthPx,
10601
- nodeMetrics.pageContentHeightPx
10602
- )) {
10603
- previousParagraphAfterPx = 0;
10604
- continue;
10605
- }
10606
10889
  if (node.type === "paragraph" && paragraphActsAsTrailingRenderedPageBreakSpacer(model, nodeIndex, node)) {
10607
10890
  previousParagraphAfterPx = 0;
10608
10891
  continue;
10609
10892
  }
10610
- if (node.type === "paragraph" && paragraphActsAsLeadingCoverLayoutOverlay(
10611
- model,
10612
- nodeIndex,
10613
- node,
10614
- nodeMetrics.pageContentWidthPx,
10615
- nodeMetrics.pageContentHeightPx
10616
- )) {
10617
- currentPageSegments.push({ nodeIndex });
10618
- previousParagraphAfterPx = 0;
10619
- continue;
10620
- }
10621
10893
  if (node.type === "paragraph" && paragraphActsAsDecorativeBehindTextBackgroundOverlay(node)) {
10622
10894
  currentPageSegments.push({ nodeIndex });
10623
10895
  previousParagraphAfterPx = 0;
@@ -10627,22 +10899,6 @@ function buildDocumentPageNodeSegments(model, pageContentHeightPx, pageContentWi
10627
10899
  continue;
10628
10900
  }
10629
10901
  if (node.type === "paragraph") {
10630
- if (paragraphStartsNewPageAfterLeadingCoverLayout(
10631
- model,
10632
- nodeIndex,
10633
- node,
10634
- nodeMetrics.pageContentWidthPx,
10635
- nodeMetrics.pageContentHeightPx
10636
- ) && currentPageSegments.length > 0) {
10637
- startNextPage();
10638
- pageConsumedHeightPx = 0;
10639
- previousParagraphAfterPx = 0;
10640
- currentSectionPageFlowOriginPx = 0;
10641
- currentPageContentHeightPx = resolveMetricsPageContentHeightPx(
10642
- currentPageIndex,
10643
- nodeMetrics
10644
- );
10645
- }
10646
10902
  if (paragraphHasPageBreakBefore2(node) && currentPageSegments.length > 0) {
10647
10903
  startNextPage();
10648
10904
  pageConsumedHeightPx = 0;
@@ -10726,18 +10982,12 @@ function buildDocumentPageNodeSegments(model, pageContentHeightPx, pageContentWi
10726
10982
  1,
10727
10983
  estimatedOrMeasuredHeightPx - directBeforeSpacingPx - directAfterSpacingPx + beforeSpacingPx + afterSpacingPx
10728
10984
  );
10729
- const coverFootprintPx = paragraphLikelyFullPageCoverFootprintPx(
10730
- model,
10731
- nodeIndex,
10732
- node,
10733
- nodeMetrics.pageContentWidthPx,
10734
- nodeMetrics.pageContentHeightPx
10735
- );
10736
- const paragraphTooTallForSinglePage = Math.max(rawNodeHeightPx, coverFootprintPx) > nodeMetrics.pageContentHeightPx + PAGE_OVERFLOW_TOLERANCE_PX;
10985
+ const paragraphTooTallForSinglePage = rawNodeHeightPx > nodeMetrics.pageContentHeightPx + PAGE_OVERFLOW_TOLERANCE_PX;
10737
10986
  const keepLinesOverflowSplit = node.style?.keepLines === true && paragraphTooTallForSinglePage;
10738
10987
  const keepNextOverflowSplit = node.style?.keepNext === true && paragraphTooTallForSinglePage;
10739
10988
  const forceOverflowSplit = keepLinesOverflowSplit || keepNextOverflowSplit;
10740
- if (forceOverflowSplit && pageConsumedHeightPx > 0 && currentPageSegments.length > 0) {
10989
+ const nodeIsWithinCommittedKeepNextChain = nodeIndex <= committedKeepNextChainEndNodeIndex;
10990
+ if (forceOverflowSplit && !nodeIsWithinCommittedKeepNextChain && pageConsumedHeightPx > 0 && currentPageSegments.length > 0) {
10741
10991
  startNextPage();
10742
10992
  pageConsumedHeightPx = 0;
10743
10993
  previousParagraphAfterPx = 0;
@@ -10750,7 +11000,7 @@ function buildDocumentPageNodeSegments(model, pageContentHeightPx, pageContentWi
10750
11000
  const collapsedMarginPx = pageConsumedHeightPx > 0 ? Math.min(previousParagraphAfterPx, beforeSpacingPx) : 0;
10751
11001
  const collapsedNodeHeightPx = Math.max(
10752
11002
  1,
10753
- Math.max(rawNodeHeightPx, coverFootprintPx) - collapsedMarginPx
11003
+ rawNodeHeightPx - collapsedMarginPx
10754
11004
  );
10755
11005
  const paragraphSupportsPretextSegmentRendering = Boolean(
10756
11006
  paragraphPretextSourceForSegmentRendering
@@ -10776,7 +11026,7 @@ function buildDocumentPageNodeSegments(model, pageContentHeightPx, pageContentWi
10776
11026
  }
10777
11027
  const collapsedNodeHeightPxAdjusted = Math.max(
10778
11028
  1,
10779
- Math.max(rawNodeHeightPx, coverFootprintPx) - collapsedMarginPx
11029
+ rawNodeHeightPx - collapsedMarginPx
10780
11030
  );
10781
11031
  const paragraphPretextLineCount = paragraphContainsExplicitLineBreakText(node) || paragraphContainsTabCharacter(node) ? resolveParagraphPretextLayoutForSegmentRendering()?.lineCount : void 0;
10782
11032
  const supportsImageParagraphLineSplit = paragraphHasImage2(node) && paragraphSupportsPretextSegmentRendering;
@@ -11000,7 +11250,8 @@ function buildDocumentPageNodeSegments(model, pageContentHeightPx, pageContentWi
11000
11250
  continue;
11001
11251
  }
11002
11252
  let requiredHeightPx = collapsedNodeHeightPxAdjusted;
11003
- if (node.style?.keepNext === true && paragraphHasVisibleText2(node)) {
11253
+ let keepNextChainEndNodeIndex = -1;
11254
+ if (node.style?.keepNext === true && !nodeIsWithinCommittedKeepNextChain && paragraphHasVisibleText2(node)) {
11004
11255
  let chainCursor = nodeIndex;
11005
11256
  let chainPreviousParagraphAfterPx = afterSpacingPx;
11006
11257
  while (chainCursor < model.nodes.length - 1) {
@@ -11060,6 +11311,7 @@ function buildDocumentPageNodeSegments(model, pageContentHeightPx, pageContentWi
11060
11311
  );
11061
11312
  chainPreviousParagraphAfterPx = nextAfterSpacingPx;
11062
11313
  }
11314
+ keepNextChainEndNodeIndex = chainCursor;
11063
11315
  }
11064
11316
  const remainingHeightPx = currentPageContentHeightPx - pageConsumedHeightPx;
11065
11317
  const canKeepTrailingSectionTailOnCurrentPage = shouldKeepTrailingSectionTailOnCurrentPage(
@@ -11101,11 +11353,11 @@ function buildDocumentPageNodeSegments(model, pageContentHeightPx, pageContentWi
11101
11353
  nodeMetrics
11102
11354
  );
11103
11355
  }
11356
+ if (pageConsumedHeightPx === 0 && keepNextChainEndNodeIndex > nodeIndex) {
11357
+ committedKeepNextChainEndNodeIndex = keepNextChainEndNodeIndex;
11358
+ }
11104
11359
  currentPageSegments.push({ nodeIndex });
11105
- const effectiveNodeHeightPx = Math.max(
11106
- pageConsumedHeightPx > 0 ? collapsedNodeHeightPx : rawNodeHeightPx,
11107
- coverFootprintPx
11108
- );
11360
+ const effectiveNodeHeightPx = pageConsumedHeightPx > 0 ? collapsedNodeHeightPx : rawNodeHeightPx;
11109
11361
  pageConsumedHeightPx += effectiveNodeHeightPx;
11110
11362
  previousParagraphAfterPx = afterSpacingPx;
11111
11363
  continue;
@@ -11912,11 +12164,15 @@ function paragraphLineHeight(paragraph, docGridLinePitchPx, disableDocGridSnap =
11912
12164
  disableDocGridSnap
11913
12165
  )}px`;
11914
12166
  }
11915
- const lineMultiple = calibrateAutoLineSpacingMultiple(
11916
- resolveAutoLineSpacingMultiple(
11917
- lineTwips,
11918
- DEFAULT_PARAGRAPH_LINE_MULTIPLE
11919
- ),
12167
+ const resolvedAutoMultiple = resolveAutoLineSpacingMultiple(
12168
+ lineTwips,
12169
+ DEFAULT_PARAGRAPH_LINE_MULTIPLE
12170
+ );
12171
+ const lineMultiple = paragraphHasOnlyWhitespaceText(paragraph) ? Math.max(
12172
+ MIN_AUTO_LINE_MULTIPLE,
12173
+ Number((resolvedAutoMultiple * singleLineScale).toFixed(3))
12174
+ ) : calibrateAutoLineSpacingMultiple(
12175
+ resolvedAutoMultiple,
11920
12176
  baseFontFamily,
11921
12177
  singleLineScale
11922
12178
  );
@@ -12260,8 +12516,7 @@ function paragraphBlockStyle(paragraph, numberingDefinitions, headingStyles, doc
12260
12516
  const suppressIndentForFloatingAnchorOnlyParagraph = paragraphIsFloatingImageAnchorOnly(paragraph);
12261
12517
  const suppressStackingContextForBehindTextAnchorOnlyParagraph = paragraphIsBehindTextAbsoluteFloatingImageAnchorOnly(paragraph);
12262
12518
  const suppressFlowFootprintForBehindTextAnchorOnlyParagraph = paragraphActsAsDecorativeBehindTextBackgroundOverlay(paragraph);
12263
- const textBearingAbsoluteFloatingTextBoxFootprintPx = paragraphTextBearingAbsoluteFloatingTextBoxFootprintPx(paragraph);
12264
- const reservedMinHeightPx = suppressFlowFootprintForBehindTextAnchorOnlyParagraph ? void 0 : textBearingAbsoluteFloatingTextBoxFootprintPx > 0 ? textBearingAbsoluteFloatingTextBoxFootprintPx : paragraphIsEffectivelyEmpty(paragraph) ? estimateParagraphLineHeightPx(
12519
+ const reservedMinHeightPx = suppressFlowFootprintForBehindTextAnchorOnlyParagraph ? void 0 : paragraphIsEffectivelyEmpty(paragraph) ? estimateParagraphLineHeightPx(
12265
12520
  paragraph,
12266
12521
  docGridLinePitchPx,
12267
12522
  disableDocGridSnap
@@ -14019,7 +14274,8 @@ function renderParagraphRuns(paragraph, keyPrefix, documentTheme = "light", numb
14019
14274
  const resolveNextTabWidthPx = () => resolveTabSpacerWidthPx(
14020
14275
  tabStopPositionsPx,
14021
14276
  approximateLineWidthPx,
14022
- fallbackTabWidthPx
14277
+ fallbackTabWidthPx,
14278
+ checkboxChoiceRow
14023
14279
  );
14024
14280
  const appendPlainTextWithSoftBreakControl = (target, keySeed, text, style, measureStyle) => {
14025
14281
  const shouldControlSoftBreakStretch = paragraph.style?.align === "justify" && text.includes("\n");
@@ -22097,12 +22353,32 @@ function ensureDocxViewerPageSurfaceRegistry(editor) {
22097
22353
  if (!registry) {
22098
22354
  registry = {
22099
22355
  pageElements: /* @__PURE__ */ new Map(),
22356
+ pageContentKeys: /* @__PURE__ */ new Map(),
22100
22357
  listeners: /* @__PURE__ */ new Set()
22101
22358
  };
22102
22359
  docxViewerPageSurfaceRegistryByEditor.set(owner, registry);
22103
22360
  }
22104
22361
  return registry;
22105
22362
  }
22363
+ function syncDocxViewerPageSurfaceContentKeys(editor, contentKeysByPage) {
22364
+ const registry = ensureDocxViewerPageSurfaceRegistry(editor);
22365
+ let changed = false;
22366
+ contentKeysByPage.forEach((contentKey, pageIndex) => {
22367
+ if (registry.pageContentKeys.get(pageIndex) !== contentKey) {
22368
+ registry.pageContentKeys.set(pageIndex, contentKey);
22369
+ changed = true;
22370
+ }
22371
+ });
22372
+ registry.pageContentKeys.forEach((_, pageIndex) => {
22373
+ if (pageIndex >= contentKeysByPage.length) {
22374
+ registry.pageContentKeys.delete(pageIndex);
22375
+ changed = true;
22376
+ }
22377
+ });
22378
+ if (changed) {
22379
+ notifyDocxViewerPageSurfaceSubscribers(registry);
22380
+ }
22381
+ }
22106
22382
  function subscribeDocxViewerPageSurfaces(editor, listener) {
22107
22383
  const registry = ensureDocxViewerPageSurfaceRegistry(editor);
22108
22384
  registry.listeners.add(listener);
@@ -22166,62 +22442,8 @@ function resolveDocxViewerPageSurfaceSize(element, fallbackWidthPx, fallbackHeig
22166
22442
  heightPx: Math.max(1, Math.round(fallbackHeightPx))
22167
22443
  };
22168
22444
  }
22169
- async function rasterizeDocxViewerPageSurfaceToCanvas(params) {
22170
- if (typeof window === "undefined" || typeof XMLSerializer === "undefined") {
22171
- throw new Error("DOCX thumbnails require a browser environment.");
22172
- }
22173
- const {
22174
- pageElement,
22175
- sourceWidthPx,
22176
- sourceHeightPx,
22177
- canvas,
22178
- widthPx,
22179
- heightPx,
22180
- pixelWidthPx,
22181
- pixelHeightPx
22182
- } = params;
22183
- const safeSourceWidthPx = Math.max(1, Math.round(sourceWidthPx));
22184
- const safeSourceHeightPx = Math.max(1, Math.round(sourceHeightPx));
22185
- const scaleX = widthPx / safeSourceWidthPx;
22186
- const scaleY = heightPx / safeSourceHeightPx;
22187
- const serializedPage = new XMLSerializer().serializeToString(
22188
- pageElement.cloneNode(true)
22189
- );
22190
- const svgMarkup = `
22191
- <svg xmlns="http://www.w3.org/2000/svg" width="${widthPx}" height="${heightPx}" viewBox="0 0 ${widthPx} ${heightPx}">
22192
- <foreignObject x="0" y="0" width="100%" height="100%">
22193
- <div xmlns="http://www.w3.org/1999/xhtml" style="width:${widthPx}px;height:${heightPx}px;overflow:hidden;">
22194
- <div style="width:${safeSourceWidthPx}px;height:${safeSourceHeightPx}px;transform-origin:top left;transform:scale(${scaleX}, ${scaleY});">
22195
- ${serializedPage}
22196
- </div>
22197
- </div>
22198
- </foreignObject>
22199
- </svg>
22200
- `;
22201
- const svgDataUrl = svgDataUri(svgMarkup);
22202
- const image = await new Promise((resolve, reject) => {
22203
- const nextImage = new Image();
22204
- nextImage.decoding = "async";
22205
- nextImage.onload = () => resolve(nextImage);
22206
- nextImage.onerror = () => {
22207
- reject(new Error("Failed to rasterize DOCX page thumbnail."));
22208
- };
22209
- nextImage.src = svgDataUrl;
22210
- });
22211
- canvas.width = Math.max(1, Math.round(pixelWidthPx));
22212
- canvas.height = Math.max(1, Math.round(pixelHeightPx));
22213
- canvas.style.width = `${Math.max(1, Math.round(widthPx))}px`;
22214
- canvas.style.height = `${Math.max(1, Math.round(heightPx))}px`;
22215
- const context = canvas.getContext("2d");
22216
- if (!context) {
22217
- throw new Error("2D canvas context is unavailable for DOCX thumbnails.");
22218
- }
22219
- context.setTransform(1, 0, 0, 1, 0, 0);
22220
- context.clearRect(0, 0, canvas.width, canvas.height);
22221
- context.imageSmoothingEnabled = true;
22222
- context.imageSmoothingQuality = "high";
22223
- context.drawImage(image, 0, 0, canvas.width, canvas.height);
22224
- }
22445
+ var DOCX_THUMBNAIL_SURFACE_CACHE_MAX_ENTRIES = 32;
22446
+ var DOCX_THUMBNAIL_MIN_RASTER_INTERVAL_MS = 200;
22225
22447
  function resolveDocxPageThumbnailResolution(options) {
22226
22448
  const safeSourceWidthPx = Math.max(1, Math.round(options.sourceWidthPx));
22227
22449
  const safeSourceHeightPx = Math.max(1, Math.round(options.sourceHeightPx));
@@ -22295,8 +22517,51 @@ function useDocxPageThumbnails(editor, options = {}) {
22295
22517
  },
22296
22518
  []
22297
22519
  );
22520
+ const thumbnailSurfaceCacheRef = React.useRef(void 0);
22521
+ const thumbnailRasterQueueRef = React.useRef(void 0);
22522
+ const lastPaintedThumbnailKeyByCanvasRef = React.useRef(
22523
+ /* @__PURE__ */ new WeakMap()
22524
+ );
22525
+ const ensureThumbnailSurfaceCache = React.useCallback(() => {
22526
+ if (!thumbnailSurfaceCacheRef.current) {
22527
+ thumbnailSurfaceCacheRef.current = new DocxThumbnailSurfaceCache(
22528
+ DOCX_THUMBNAIL_SURFACE_CACHE_MAX_ENTRIES
22529
+ );
22530
+ }
22531
+ return thumbnailSurfaceCacheRef.current;
22532
+ }, []);
22533
+ const ensureThumbnailRasterQueue = React.useCallback(() => {
22534
+ if (!thumbnailRasterQueueRef.current) {
22535
+ thumbnailRasterQueueRef.current = new SerialIdleTaskQueue({
22536
+ minTaskIntervalMs: DOCX_THUMBNAIL_MIN_RASTER_INTERVAL_MS
22537
+ });
22538
+ }
22539
+ return thumbnailRasterQueueRef.current;
22540
+ }, []);
22541
+ React.useEffect(() => {
22542
+ thumbnailSurfaceCacheRef.current?.clear();
22543
+ thumbnailRasterQueueRef.current?.clear();
22544
+ lastPaintedThumbnailKeyByCanvasRef.current = /* @__PURE__ */ new WeakMap();
22545
+ }, [editor.documentLoadNonce, pageSurfaceRegistryOwner]);
22546
+ const thumbnailResolutionOptionsKey = React.useMemo(() => {
22547
+ const bounds = options.resolution;
22548
+ const boundsKey = typeof bounds === "number" ? `n${bounds}` : bounds ? `b${bounds.maxWidth ?? ""}x${bounds.maxHeight ?? ""}` : "";
22549
+ return `${boundsKey}|${options.maxWidthPx ?? ""}|${options.maxHeightPx ?? ""}|${options.pixelRatio ?? ""}`;
22550
+ }, [
22551
+ options.maxHeightPx,
22552
+ options.maxWidthPx,
22553
+ options.pixelRatio,
22554
+ options.resolution
22555
+ ]);
22556
+ const thumbnailSkipKeyForPage = React.useCallback(
22557
+ (pageIndex) => {
22558
+ const contentKey = pageSurfaceRegistry.pageContentKeys.get(pageIndex);
22559
+ return contentKey === void 0 ? void 0 : `${contentKey}|${editor.documentTheme}|${thumbnailResolutionOptionsKey}`;
22560
+ },
22561
+ [editor.documentTheme, pageSurfaceRegistry, thumbnailResolutionOptionsKey]
22562
+ );
22298
22563
  const renderPageThumbnailToCanvas = React.useCallback(
22299
- async (pageIndex, canvas) => {
22564
+ async (pageIndex, canvas, renderOptions) => {
22300
22565
  if (options.disabled) {
22301
22566
  return;
22302
22567
  }
@@ -22304,63 +22569,101 @@ function useDocxPageThumbnails(editor, options = {}) {
22304
22569
  if (!targetCanvas) {
22305
22570
  return;
22306
22571
  }
22307
- const pageElement = mountedPageElements.get(pageIndex);
22572
+ const pageElement = pageSurfaceRegistry.pageElements.get(pageIndex);
22308
22573
  if (!pageElement || !pageElement.isConnected) {
22309
22574
  updatePageThumbnailState(pageIndex, "unavailable");
22310
22575
  return;
22311
22576
  }
22312
- const sourceSize = resolveDocxViewerPageSurfaceSize(
22313
- pageElement,
22314
- fallbackLayout.pageWidthPx,
22315
- fallbackLayout.pageHeightPx
22316
- );
22317
- const resolution = resolveDocxPageThumbnailResolution({
22318
- sourceWidthPx: sourceSize.widthPx,
22319
- sourceHeightPx: sourceSize.heightPx,
22320
- resolution: options.resolution,
22321
- maxWidthPx: options.maxWidthPx,
22322
- maxHeightPx: options.maxHeightPx,
22323
- pixelRatio: options.pixelRatio
22324
- });
22577
+ const force = renderOptions?.force === true;
22578
+ const lastPaintedKey = lastPaintedThumbnailKeyByCanvasRef.current.get(targetCanvas);
22579
+ if (!force && lastPaintedKey !== void 0 && lastPaintedKey === thumbnailSkipKeyForPage(pageIndex)) {
22580
+ updatePageThumbnailState(pageIndex, "ready");
22581
+ return;
22582
+ }
22325
22583
  updatePageThumbnailState(pageIndex, "rendering");
22326
- try {
22327
- await rasterizeDocxViewerPageSurfaceToCanvas({
22328
- pageElement,
22584
+ await ensureThumbnailRasterQueue().enqueue(targetCanvas, async () => {
22585
+ const livePageElement = pageSurfaceRegistry.pageElements.get(pageIndex);
22586
+ if (!livePageElement || !livePageElement.isConnected) {
22587
+ updatePageThumbnailState(pageIndex, "unavailable");
22588
+ return;
22589
+ }
22590
+ const runSkipKey = thumbnailSkipKeyForPage(pageIndex);
22591
+ const sourceSize = resolveDocxViewerPageSurfaceSize(
22592
+ livePageElement,
22593
+ fallbackLayout.pageWidthPx,
22594
+ fallbackLayout.pageHeightPx
22595
+ );
22596
+ const resolution = resolveDocxPageThumbnailResolution({
22329
22597
  sourceWidthPx: sourceSize.widthPx,
22330
22598
  sourceHeightPx: sourceSize.heightPx,
22331
- canvas: targetCanvas,
22332
- widthPx: resolution.widthPx,
22333
- heightPx: resolution.heightPx,
22334
- pixelWidthPx: resolution.pixelWidthPx,
22335
- pixelHeightPx: resolution.pixelHeightPx
22599
+ resolution: options.resolution,
22600
+ maxWidthPx: options.maxWidthPx,
22601
+ maxHeightPx: options.maxHeightPx,
22602
+ pixelRatio: options.pixelRatio
22336
22603
  });
22337
- updatePageThumbnailState(pageIndex, "ready");
22338
- } catch (error) {
22339
- updatePageThumbnailState(
22340
- pageIndex,
22341
- "error",
22342
- error instanceof Error ? error : new Error("Failed to render DOCX page thumbnail.")
22343
- );
22344
- }
22604
+ const surfaceKey = runSkipKey === void 0 ? void 0 : `${runSkipKey}|${sourceSize.widthPx}x${sourceSize.heightPx}|${resolution.pixelWidthPx}x${resolution.pixelHeightPx}`;
22605
+ const surfaceCache = ensureThumbnailSurfaceCache();
22606
+ try {
22607
+ let surface = !force && surfaceKey !== void 0 ? surfaceCache.get(surfaceKey) : void 0;
22608
+ if (!surface) {
22609
+ surface = await rasterizeDocxThumbnailSurface({
22610
+ pageElement: livePageElement,
22611
+ sourceWidthPx: sourceSize.widthPx,
22612
+ sourceHeightPx: sourceSize.heightPx,
22613
+ widthPx: resolution.widthPx,
22614
+ heightPx: resolution.heightPx,
22615
+ pixelWidthPx: resolution.pixelWidthPx,
22616
+ pixelHeightPx: resolution.pixelHeightPx
22617
+ });
22618
+ if (surfaceKey !== void 0) {
22619
+ surfaceCache.set(surfaceKey, surface);
22620
+ }
22621
+ }
22622
+ blitDocxThumbnailSurface(surface, targetCanvas, resolution);
22623
+ if (runSkipKey !== void 0) {
22624
+ lastPaintedThumbnailKeyByCanvasRef.current.set(
22625
+ targetCanvas,
22626
+ runSkipKey
22627
+ );
22628
+ }
22629
+ updatePageThumbnailState(pageIndex, "ready");
22630
+ } catch (error) {
22631
+ updatePageThumbnailState(
22632
+ pageIndex,
22633
+ "error",
22634
+ error instanceof Error ? error : new Error("Failed to render DOCX page thumbnail.")
22635
+ );
22636
+ }
22637
+ });
22345
22638
  },
22346
22639
  [
22640
+ ensureThumbnailRasterQueue,
22641
+ ensureThumbnailSurfaceCache,
22347
22642
  fallbackLayout.pageHeightPx,
22348
22643
  fallbackLayout.pageWidthPx,
22349
- mountedPageElements,
22350
22644
  options.disabled,
22351
22645
  options.resolution,
22352
22646
  options.maxHeightPx,
22353
22647
  options.maxWidthPx,
22354
22648
  options.pixelRatio,
22649
+ pageSurfaceRegistry,
22650
+ thumbnailSkipKeyForPage,
22355
22651
  updatePageThumbnailState
22356
22652
  ]
22357
22653
  );
22358
- const rerenderAttachedThumbnails = React.useCallback(async () => {
22359
- const tasks = [...attachedCanvasByPageRef.current.keys()].map(
22360
- (pageIndex) => renderPageThumbnailToCanvas(pageIndex)
22361
- );
22362
- await Promise.all(tasks);
22363
- }, [renderPageThumbnailToCanvas]);
22654
+ const requestAttachedThumbnailRenders = React.useCallback(
22655
+ async (renderOptions) => {
22656
+ const tasks = [...attachedCanvasByPageRef.current.keys()].map(
22657
+ (pageIndex) => renderPageThumbnailToCanvas(pageIndex, void 0, renderOptions)
22658
+ );
22659
+ await Promise.all(tasks);
22660
+ },
22661
+ [renderPageThumbnailToCanvas]
22662
+ );
22663
+ const rerenderAttachedThumbnails = React.useCallback(
22664
+ async () => requestAttachedThumbnailRenders({ force: true }),
22665
+ [requestAttachedThumbnailRenders]
22666
+ );
22364
22667
  const renderPageThumbnailToCanvasRef = React.useRef(
22365
22668
  renderPageThumbnailToCanvas
22366
22669
  );
@@ -22372,7 +22675,7 @@ function useDocxPageThumbnails(editor, options = {}) {
22372
22675
  renderToCanvasCallbacksRef.current.clear();
22373
22676
  }, [pageSurfaceRegistryOwner]);
22374
22677
  React.useEffect(() => {
22375
- void rerenderAttachedThumbnails();
22678
+ void requestAttachedThumbnailRenders();
22376
22679
  }, [
22377
22680
  editor.documentLoadNonce,
22378
22681
  editor.documentTheme,
@@ -22383,7 +22686,7 @@ function useDocxPageThumbnails(editor, options = {}) {
22383
22686
  options.maxHeightPx,
22384
22687
  options.maxWidthPx,
22385
22688
  options.pixelRatio,
22386
- rerenderAttachedThumbnails
22689
+ requestAttachedThumbnailRenders
22387
22690
  ]);
22388
22691
  const thumbnails = React.useMemo(() => {
22389
22692
  const totalPages = Math.max(1, editor.totalPages);
@@ -24773,6 +25076,42 @@ function DocxEditorViewer({
24773
25076
  pageNodeSegmentsByPage,
24774
25077
  primarySectionPropertiesXml
24775
25078
  ]);
25079
+ const pageThumbnailContentKeysByPage = React.useMemo(() => {
25080
+ const metadataSignature = docModelThumbnailMetadataSignature(
25081
+ editor.model.metadata
25082
+ );
25083
+ return pageNodeSegmentsByPage.map((pageSegments, pageIndex) => {
25084
+ const sectionInfo = pageSectionInfoByIndex[pageIndex];
25085
+ let nodeSignatures = "";
25086
+ for (const segment of pageSegments) {
25087
+ nodeSignatures += docNodeContentSignature(
25088
+ editor.model.nodes[segment.nodeIndex]
25089
+ );
25090
+ nodeSignatures += ",";
25091
+ }
25092
+ return [
25093
+ editor.documentLoadNonce,
25094
+ trackedChangesEnabled ? "tc1" : "tc0",
25095
+ metadataSignature,
25096
+ sectionInfo ? `${sectionInfo.sectionIndex}.${sectionInfo.pageNumber}` : "s?",
25097
+ pageNodeSegmentIdentityKeysByPage[pageIndex] ?? "",
25098
+ nodeSignatures
25099
+ ].join("|");
25100
+ });
25101
+ }, [
25102
+ editor.documentLoadNonce,
25103
+ editor.model,
25104
+ pageNodeSegmentIdentityKeysByPage,
25105
+ pageNodeSegmentsByPage,
25106
+ pageSectionInfoByIndex,
25107
+ trackedChangesEnabled
25108
+ ]);
25109
+ React.useEffect(() => {
25110
+ syncDocxViewerPageSurfaceContentKeys(
25111
+ editor,
25112
+ pageThumbnailContentKeysByPage
25113
+ );
25114
+ }, [pageSurfaceRegistryOwner, pageThumbnailContentKeysByPage]);
24776
25115
  const resolveStyleRefFieldValueForPage = React.useMemo(() => {
24777
25116
  const valueCache = /* @__PURE__ */ new Map();
24778
25117
  const nodes = editor.model.nodes;
@@ -32489,6 +32828,7 @@ function DocxEditorViewer({
32489
32828
  "span",
32490
32829
  {
32491
32830
  contentEditable: false,
32831
+ ...{ [DOCX_THUMBNAIL_EXCLUDE_ATTRIBUTE]: "true" },
32492
32832
  style: {
32493
32833
  position: "absolute",
32494
32834
  left: rect.left,
@@ -32511,6 +32851,7 @@ function DocxEditorViewer({
32511
32851
  "span",
32512
32852
  {
32513
32853
  contentEditable: false,
32854
+ ...{ [DOCX_THUMBNAIL_EXCLUDE_ATTRIBUTE]: "true" },
32514
32855
  style: {
32515
32856
  position: "absolute",
32516
32857
  left: caretRect.left,
@@ -35714,13 +36055,6 @@ function DocxEditorViewer({
35714
36055
  options?.pageFlowForeignExclusions ?? []
35715
36056
  )
35716
36057
  );
35717
- const leadingCoverLayoutSpacer = paragraphActsAsLeadingCoverLayoutSpacer(
35718
- editor.model,
35719
- nodeIndex,
35720
- node,
35721
- paragraphContentWidthPx,
35722
- resolvedPageLayout.pageHeightPx - resolvedPageLayout.marginsPx.top - resolvedPageLayout.marginsPx.bottom
35723
- );
35724
36058
  const beforeSpacingPx = effectiveParagraphBeforeSpacingPx(
35725
36059
  editor.model,
35726
36060
  nodeIndex,
@@ -35779,9 +36113,6 @@ function DocxEditorViewer({
35779
36113
  lineHeight: 0,
35780
36114
  overflow: "visible"
35781
36115
  } : requiresPageAbsoluteContext ? { position: "static" } : requiresLocalAbsoluteContext ? { position: "relative" } : hasDualWrappedFloatingImage ? { position: "relative" } : void 0,
35782
- ...leadingCoverLayoutSpacer ? {
35783
- minHeight: `${estimateParagraphLineHeightPx(node, nodeDocGridLinePitchPx) + EMPTY_PARAGRAPH_EXTRA_HEIGHT_PX + LEADING_COVER_SPACER_EXTRA_HEIGHT_PX}px`
35784
- } : void 0,
35785
36116
  // Carry the paragraph's run font on the editable host so text typed into
35786
36117
  // it (which is a bare, not-yet-committed text node, not a styled run
35787
36118
  // span) renders in the same font the committed run will use — i.e. the
@@ -36816,6 +37147,22 @@ ${currentText.slice(end)}`;
36816
37147
  const rowSpanValue = cell.style?.rowSpan && cell.style.rowSpan > 1 ? cell.style.rowSpan : 1;
36817
37148
  const colSpan = colSpanValue > 1 ? colSpanValue : void 0;
36818
37149
  const rowSpan = rowSpanValue > 1 ? rowSpanValue : void 0;
37150
+ const exactRowSpanRows = node.rows.slice(
37151
+ rowIndex,
37152
+ rowIndex + rowSpanValue
37153
+ );
37154
+ const exactRowSpanClipHeightPx = exactRowSpanRows.length > 0 && exactRowSpanRows.every((spannedRow, rowOffset) => {
37155
+ const spannedHeightPx = rowHeightsPx[rowIndex + rowOffset];
37156
+ return spannedRow.style?.heightRule === "exact" && Number.isFinite(spannedHeightPx) && spannedHeightPx > 0;
37157
+ }) ? exactRowSpanRows.reduce(
37158
+ (sum, _spannedRow, rowOffset) => sum + Math.max(
37159
+ MIN_PARAGRAPH_LINE_HEIGHT_PX,
37160
+ Math.round(
37161
+ rowHeightsPx[rowIndex + rowOffset]
37162
+ )
37163
+ ),
37164
+ 0
37165
+ ) : void 0;
36819
37166
  const startColumnIndex = columnCursor;
36820
37167
  const boundaryColumnIndex = startColumnIndex + colSpanValue - 1;
36821
37168
  columnCursor += colSpanValue;
@@ -37934,12 +38281,11 @@ ${currentText.slice(end)}`;
37934
38281
  style: {
37935
38282
  display: "grid",
37936
38283
  gap: 0,
37937
- // Word clips content of hRule="exact" rows
37938
- // at the declared height; without this the
37939
- // row balloons to fit content (height on a
37940
- // <tr>/<td> is only ever a minimum).
37941
- ...row.style?.heightRule === "exact" && !isSlicedRow && resolvedRowHeightStyle?.height ? {
37942
- maxHeight: resolvedRowHeightStyle.height,
38284
+ // Without the clip the row balloons to fit
38285
+ // content (height on a <tr>/<td> is only
38286
+ // ever a minimum).
38287
+ ...exactRowSpanClipHeightPx !== void 0 && !isSlicedRow ? {
38288
+ maxHeight: `${exactRowSpanClipHeightPx}px`,
37943
38289
  overflow: "hidden"
37944
38290
  } : void 0
37945
38291
  },
@@ -39726,11 +40072,27 @@ ${currentText.slice(end)}`;
39726
40072
  currentGroup.segments.push(segment);
39727
40073
  return;
39728
40074
  }
40075
+ if (currentGroup && parseSectionStartType(
40076
+ documentSections[currentSectionIndex]?.sectionPropertiesXml
40077
+ ) === "nextcolumn") {
40078
+ const previousColumns = sectionColumnsBySectionIndex[currentGroup.sectionIndex];
40079
+ const nextColumns = sectionColumnsBySectionIndex[currentSectionIndex];
40080
+ const sameGeometry = previousColumns && nextColumns && previousColumns.count === nextColumns.count && Math.abs(
40081
+ previousColumns.gapPx - nextColumns.gapPx
40082
+ ) <= 2 && JSON.stringify(previousColumns.widthsPx ?? null) === JSON.stringify(nextColumns.widthsPx ?? null);
40083
+ if (sameGeometry) {
40084
+ currentGroup.segments.push(segment);
40085
+ return;
40086
+ }
40087
+ }
39729
40088
  sectionGroups.push({
39730
40089
  sectionIndex: currentSectionIndex,
39731
40090
  segments: [segment]
39732
40091
  });
39733
40092
  });
40093
+ if (typeof window !== "undefined" && window.__docxDebugGroups) {
40094
+ console.log("[groups]", pageIndex, JSON.stringify(sectionGroups.map((g) => ({ s: g.sectionIndex, n: g.segments.map((x) => x.nodeIndex) }))));
40095
+ }
39734
40096
  return sectionGroups.map((group, groupIndex) => {
39735
40097
  const sectionColumns = sectionColumnsBySectionIndex[group.sectionIndex];
39736
40098
  const isLastGroupOnPage = groupIndex === sectionGroups.length - 1;
@@ -39899,7 +40261,7 @@ ${currentText.slice(end)}`;
39899
40261
  (editor.model.metadata.sections ?? []).filter(
39900
40262
  (candidate) => parseSectionStartType(
39901
40263
  candidate.sectionPropertiesXml
39902
- ) === "nextColumn"
40264
+ ) === "nextcolumn"
39903
40265
  ).map(
39904
40266
  (candidate) => Math.max(
39905
40267
  0,
@@ -41809,6 +42171,7 @@ function collectDocxEstimatedOverflowBreakStartNodeIndexes(model, hardBreakStart
41809
42171
  let pageConsumedHeightPx = 0;
41810
42172
  let previousParagraphAfterPx = 0;
41811
42173
  let currentMetricsIndex = 0;
42174
+ let committedKeepNextChainEndNodeIndex = -1;
41812
42175
  const suppressSpacingBeforeAfterPageBreak = options?.suppressSpacingBeforeAfterPageBreak ?? false;
41813
42176
  let currentPageContentHeightPx = metricsBySection[0]?.pageContentHeightPx ?? fallbackMetrics.pageContentHeightPx;
41814
42177
  for (let nodeIndex = 0; nodeIndex < model.nodes.length; nodeIndex += 1) {
@@ -41846,7 +42209,8 @@ function collectDocxEstimatedOverflowBreakStartNodeIndexes(model, hardBreakStart
41846
42209
  const collapsedMarginPx = node.type === "paragraph" && pageConsumedHeightPx > 0 ? Math.min(previousParagraphAfterPx, nodeBeforeSpacingPx) : 0;
41847
42210
  const collapsedNodeHeightPx = Math.max(1, rawNodeHeightPx - collapsedMarginPx);
41848
42211
  let requiredHeightPx = collapsedNodeHeightPx;
41849
- if (node.type === "paragraph" && node.style?.keepNext === true && callbacks.paragraphHasVisibleText(node)) {
42212
+ let keepNextChainEndNodeIndex = -1;
42213
+ if (node.type === "paragraph" && node.style?.keepNext === true && nodeIndex > committedKeepNextChainEndNodeIndex && callbacks.paragraphHasVisibleText(node)) {
41850
42214
  let chainCursor = nodeIndex;
41851
42215
  let chainPreviousParagraphAfterPx = paragraphAfterSpacingPx2(node);
41852
42216
  while (chainCursor < model.nodes.length - 1) {
@@ -41904,6 +42268,7 @@ function collectDocxEstimatedOverflowBreakStartNodeIndexes(model, hardBreakStart
41904
42268
  );
41905
42269
  chainPreviousParagraphAfterPx = paragraphAfterSpacingPx2(nextChainNode);
41906
42270
  }
42271
+ keepNextChainEndNodeIndex = chainCursor;
41907
42272
  }
41908
42273
  const remainingHeightPx = currentPageContentHeightPx - pageConsumedHeightPx;
41909
42274
  if (pageConsumedHeightPx > 0 && requiredHeightPx > remainingHeightPx + pageOverflowTolerancePx) {
@@ -41912,6 +42277,9 @@ function collectDocxEstimatedOverflowBreakStartNodeIndexes(model, hardBreakStart
41912
42277
  previousParagraphAfterPx = 0;
41913
42278
  currentPageContentHeightPx = nodeMetrics.pageContentHeightPx;
41914
42279
  }
42280
+ if (pageConsumedHeightPx === 0 && keepNextChainEndNodeIndex > nodeIndex) {
42281
+ committedKeepNextChainEndNodeIndex = keepNextChainEndNodeIndex;
42282
+ }
41915
42283
  const effectiveNodeHeightPx = pageConsumedHeightPx > 0 ? collapsedNodeHeightPx : rawNodeHeightPx;
41916
42284
  pageConsumedHeightPx += effectiveNodeHeightPx;
41917
42285
  previousParagraphAfterPx = node.type === "paragraph" ? paragraphAfterSpacingPx2(node) : 0;
@@ -41963,6 +42331,7 @@ function buildDocumentPageNodeSegments2(model, pageContentHeightPx, pageContentW
41963
42331
  let pageConsumedHeightPx = 0;
41964
42332
  let previousParagraphAfterPx = 0;
41965
42333
  let currentMetricsIndex = 0;
42334
+ let committedKeepNextChainEndNodeIndex = -1;
41966
42335
  let currentPageContentHeightPx = resolvePageContentHeightPx(
41967
42336
  0,
41968
42337
  metricsBySection[0]?.pageContentHeightPx ?? fallbackMetrics.pageContentHeightPx
@@ -42026,7 +42395,8 @@ function buildDocumentPageNodeSegments2(model, pageContentHeightPx, pageContentW
42026
42395
  const keepLinesOverflowSplit = node.style?.keepLines === true && paragraphTooTallForSinglePage;
42027
42396
  const keepNextOverflowSplit = node.style?.keepNext === true && paragraphTooTallForSinglePage;
42028
42397
  const forceOverflowSplit = keepLinesOverflowSplit || keepNextOverflowSplit;
42029
- if (forceOverflowSplit && pageConsumedHeightPx > 0 && currentPageSegments.length > 0) {
42398
+ const nodeIsWithinCommittedKeepNextChain = nodeIndex <= committedKeepNextChainEndNodeIndex;
42399
+ if (forceOverflowSplit && !nodeIsWithinCommittedKeepNextChain && pageConsumedHeightPx > 0 && currentPageSegments.length > 0) {
42030
42400
  startNextPage();
42031
42401
  pageConsumedHeightPx = 0;
42032
42402
  previousParagraphAfterPx = 0;
@@ -42176,7 +42546,8 @@ function buildDocumentPageNodeSegments2(model, pageContentHeightPx, pageContentW
42176
42546
  continue;
42177
42547
  }
42178
42548
  let requiredHeightPx = collapsedNodeHeightPx;
42179
- if (node.style?.keepNext === true && callbacks.paragraphHasVisibleText(node)) {
42549
+ let keepNextChainEndNodeIndex = -1;
42550
+ if (node.style?.keepNext === true && !nodeIsWithinCommittedKeepNextChain && callbacks.paragraphHasVisibleText(node)) {
42180
42551
  let chainCursor = nodeIndex;
42181
42552
  let chainPreviousParagraphAfterPx = afterSpacingPx;
42182
42553
  while (chainCursor < model.nodes.length - 1) {
@@ -42234,6 +42605,7 @@ function buildDocumentPageNodeSegments2(model, pageContentHeightPx, pageContentW
42234
42605
  );
42235
42606
  chainPreviousParagraphAfterPx = paragraphAfterSpacingPx2(nextChainNode);
42236
42607
  }
42608
+ keepNextChainEndNodeIndex = chainCursor;
42237
42609
  }
42238
42610
  const remainingHeightPx = currentPageContentHeightPx - pageConsumedHeightPx;
42239
42611
  if (pageConsumedHeightPx > 0 && requiredHeightPx > remainingHeightPx + pageOverflowTolerancePx) {
@@ -42245,6 +42617,9 @@ function buildDocumentPageNodeSegments2(model, pageContentHeightPx, pageContentW
42245
42617
  nodeMetrics.pageContentHeightPx
42246
42618
  );
42247
42619
  }
42620
+ if (pageConsumedHeightPx === 0 && keepNextChainEndNodeIndex > nodeIndex) {
42621
+ committedKeepNextChainEndNodeIndex = keepNextChainEndNodeIndex;
42622
+ }
42248
42623
  currentPageSegments.push({ nodeIndex });
42249
42624
  const effectiveNodeHeightPx = pageConsumedHeightPx > 0 ? collapsedNodeHeightPx : rawNodeHeightPx;
42250
42625
  pageConsumedHeightPx += effectiveNodeHeightPx;