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

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
@@ -10,7 +10,7 @@ import {
10
10
  wasmModelToDocumentXml,
11
11
  wasmSerializeDocx,
12
12
  withPart
13
- } from "./chunk-BGCGPO6Q.js";
13
+ } from "./chunk-XLGR633U.js";
14
14
 
15
15
  // src/index.tsx
16
16
  import * as React2 from "react";
@@ -638,7 +638,7 @@ async function buildDocModel(pkg) {
638
638
  return normalizeDocModel(model);
639
639
  }
640
640
  async function buildDocModelFromBytes(bytes) {
641
- const { parseDocx: parseDocx2 } = await import("./src-XA2NEWYM.js");
641
+ const { parseDocx: parseDocx2 } = await import("./src-IT7QNDVM.js");
642
642
  const payload = bytes instanceof Uint8Array ? bytes : new Uint8Array(bytes);
643
643
  const buffer = payload.buffer.slice(payload.byteOffset, payload.byteOffset + payload.byteLength);
644
644
  const pkg = await parseDocx2(buffer);
@@ -2009,7 +2009,7 @@ async function serializeDocModel(model, basePackage) {
2009
2009
  model,
2010
2010
  basePackage ? mapsToWasmPackage(basePackage) : void 0
2011
2011
  );
2012
- const { parseDocx: parseDocx2 } = await import("./src-XA2NEWYM.js");
2012
+ const { parseDocx: parseDocx2 } = await import("./src-IT7QNDVM.js");
2013
2013
  return parseDocx2(bytes);
2014
2014
  }
2015
2015
  async function serializeDocx(model, basePackage) {
@@ -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,38 +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
- function paragraphAbsoluteFloatingAnchorsDependOnParagraphFlow(paragraph) {
6029
- return paragraph.children.some((child) => {
6030
- if (child.type !== "image" || !shouldRenderAbsoluteFloatingImage(child) || child.syntheticTextBox !== true || !floatingTextBoxVisibleTextFromImage(child) || child.floating?.behindDocument !== true) {
6031
- return false;
6032
- }
6033
- const verticalRelativeTo = child.floating?.verticalRelativeTo?.trim().toLowerCase();
6034
- return verticalRelativeTo === void 0 || verticalRelativeTo === "" || verticalRelativeTo === "paragraph" || verticalRelativeTo === "line";
6035
- });
6036
- }
6037
6415
  function likelyFullPageCoverImageRelativeToContentBox(image, pageContentWidthPx, pageContentHeightPx) {
6038
6416
  if (!shouldRenderAbsoluteFloatingImage(image) || !image.floating) {
6039
6417
  return false;
@@ -6116,21 +6494,6 @@ function paragraphParticipatesInLeadingCoverLayout(model, nodeIndex, pageContent
6116
6494
  }
6117
6495
  return sawLikelyCoverArtAnchor;
6118
6496
  }
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
6497
  function fullPageCoverImageRenderKey(nodeIndex, childIndex) {
6135
6498
  return `${nodeIndex}:${childIndex}`;
6136
6499
  }
@@ -6169,71 +6532,6 @@ function fullPageCoverAbsoluteFloatingImageStyle(image, layout, options) {
6169
6532
  zIndex: floating?.behindDocument === true ? 0 : normalizedZIndex
6170
6533
  };
6171
6534
  }
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
6535
  function paragraphLooksLikeCheckboxChoiceRow(paragraph) {
6238
6536
  if (paragraph.children.some((child) => child.type === "image")) {
6239
6537
  return false;
@@ -6751,7 +7049,8 @@ function buildParagraphPretextLayoutSource(paragraph, options) {
6751
7049
  let combinedText = "";
6752
7050
  const paragraphBaseFontPx = paragraphBaseFontSizePx(paragraph);
6753
7051
  const tabStopPositionsPx = options?.expandTabsForLayout ? resolveParagraphTabStopsPx(paragraph) : [];
6754
- const fallbackTabWidthPx = paragraphLooksLikeCheckboxChoiceRow(paragraph) ? checkboxChoiceRowTabWidthPx(paragraph) : DEFAULT_TAB_STOP_PX;
7052
+ const usesCheckboxRowTabFallback = paragraphLooksLikeCheckboxChoiceRow(paragraph);
7053
+ const fallbackTabWidthPx = usesCheckboxRowTabFallback ? checkboxChoiceRowTabWidthPx(paragraph) : DEFAULT_TAB_STOP_PX;
6755
7054
  let approximateLineWidthPx = 0;
6756
7055
  let lastTextStyle = firstRunStyle(paragraph);
6757
7056
  for (let childIndex = 0; childIndex < paragraph.children.length; childIndex += 1) {
@@ -6817,7 +7116,8 @@ function buildParagraphPretextLayoutSource(paragraph, options) {
6817
7116
  const tabWidthPx = resolveTabSpacerWidthPx(
6818
7117
  tabStopPositionsPx,
6819
7118
  approximateLineWidthPx,
6820
- fallbackTabWidthPx
7119
+ fallbackTabWidthPx,
7120
+ usesCheckboxRowTabFallback
6821
7121
  );
6822
7122
  const spacerText = buildParagraphPretextTabSpacerText(
6823
7123
  tabWidthPx,
@@ -7566,10 +7866,14 @@ function updateEstimatedLineWidthPxForText(currentLineWidthPx, text, style) {
7566
7866
  const trailingSegment = segments[segments.length - 1] ?? "";
7567
7867
  return estimateTextAdvanceWidthPx(trailingSegment, style);
7568
7868
  }
7569
- function resolveTabSpacerWidthPx(tabStopPositionsPx, currentLineWidthPx, fallbackWidthPx) {
7869
+ function resolveTabSpacerWidthPx(tabStopPositionsPx, currentLineWidthPx, fallbackWidthPx, fixedFallback = false) {
7570
7870
  const safeFallback = Math.max(12, Math.round(fallbackWidthPx));
7571
7871
  if (tabStopPositionsPx.length === 0) {
7572
- return safeFallback;
7872
+ if (fixedFallback) {
7873
+ return safeFallback;
7874
+ }
7875
+ const nextStop2 = (Math.floor((currentLineWidthPx + 0.5) / safeFallback) + 1) * safeFallback;
7876
+ return Math.max(2, Math.round(nextStop2 - currentLineWidthPx));
7573
7877
  }
7574
7878
  const nextStop = tabStopPositionsPx.find(
7575
7879
  (stop) => stop > currentLineWidthPx + 0.5
@@ -8322,7 +8626,26 @@ function singleLineAutoScaleForFontFamily(fontFamily) {
8322
8626
  }
8323
8627
  return WORD_SINGLE_LINE_AUTO_SCALE;
8324
8628
  }
8629
+ function emptyParagraphLineScaleForFontFamily(fontFamily) {
8630
+ const normalized = normalizeFontFamilyToken(fontFamily) ?? fontFamily?.toLowerCase();
8631
+ if (!normalized) {
8632
+ return WORD_EMPTY_PARAGRAPH_LINE_SCALE;
8633
+ }
8634
+ if (normalized === "times roman" || normalized === "times new roman" || normalized === "cambria" || normalized === "garamond" || normalized === "georgia" || normalized === "book antiqua" || normalized === "palatino linotype") {
8635
+ return WORD_EMPTY_PARAGRAPH_LINE_SCALE_SERIF;
8636
+ }
8637
+ if (normalized === "arial") {
8638
+ return WORD_EMPTY_PARAGRAPH_LINE_SCALE_SANS;
8639
+ }
8640
+ return WORD_EMPTY_PARAGRAPH_LINE_SCALE;
8641
+ }
8642
+ function paragraphRendersTextFreeLine(paragraph) {
8643
+ return paragraphHasOnlyWhitespaceText(paragraph) || paragraphIsFloatingImageAnchorOnly(paragraph);
8644
+ }
8325
8645
  function resolveParagraphSingleLineAutoScale(paragraph, fontFamily) {
8646
+ if (paragraphRendersTextFreeLine(paragraph)) {
8647
+ return emptyParagraphLineScaleForFontFamily(fontFamily);
8648
+ }
8326
8649
  const baseScale = singleLineAutoScaleForFontFamily(fontFamily);
8327
8650
  return paragraphHasCheckboxFormField(paragraph) ? Math.max(1.08, baseScale) : baseScale;
8328
8651
  }
@@ -9037,36 +9360,6 @@ function resolveMaxPretextLineRangeEndIndexThatFits(layout, startLineIndex, maxE
9037
9360
  }
9038
9361
  return bestEnd;
9039
9362
  }
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
9363
  function resolveAutoLineSpacingMultiple(lineTwips, fallbackMultiple) {
9071
9364
  if (!Number.isFinite(lineTwips)) {
9072
9365
  return Math.max(MIN_AUTO_LINE_MULTIPLE, fallbackMultiple);
@@ -9164,8 +9457,15 @@ function estimateParagraphLineHeightPx(paragraph, docGridLinePitchPx, disableDoc
9164
9457
  docGridMinimumLineHeightPx ?? 0
9165
9458
  );
9166
9459
  }
9167
- const multiple = calibrateAutoLineSpacingMultiple(
9168
- resolveAutoLineSpacingMultiple(lineTwips, defaultLineMultiple),
9460
+ const resolvedAutoMultiple = resolveAutoLineSpacingMultiple(
9461
+ lineTwips,
9462
+ defaultLineMultiple
9463
+ );
9464
+ const multiple = paragraphRendersTextFreeLine(paragraph) ? Math.max(
9465
+ MIN_AUTO_LINE_MULTIPLE,
9466
+ Number((resolvedAutoMultiple * singleLineScale).toFixed(3))
9467
+ ) : calibrateAutoLineSpacingMultiple(
9468
+ resolvedAutoMultiple,
9169
9469
  baseFontFamily,
9170
9470
  singleLineScale
9171
9471
  );
@@ -9220,10 +9520,7 @@ function estimateParagraphHeightPx(paragraph, availableWidthPx, numberingDefinit
9220
9520
  numberingLabel
9221
9521
  );
9222
9522
  const absoluteFloatingAnchorOnlyParagraph = paragraphIsAbsoluteFloatingImageAnchorOnly(paragraph);
9223
- const sectionBreakAnchorCarryoverParagraph = paragraphIsSectionBreakAnchorCarryover(paragraph);
9224
- const collapsibleAbsoluteFloatingAnchorOnlyParagraph = absoluteFloatingAnchorOnlyParagraph && (!paragraphAbsoluteFloatingAnchorsDependOnParagraphFlow(paragraph) || sectionBreakAnchorCarryoverParagraph);
9225
- const paragraphFlowAnchoredAbsoluteFloatingAnchorOnlyParagraph = absoluteFloatingAnchorOnlyParagraph && !collapsibleAbsoluteFloatingAnchorOnlyParagraph;
9226
- const decorativeBehindTextAnchorOnlyParagraph = paragraphActsAsDecorativeBehindTextBackgroundOverlay(paragraph);
9523
+ const collapsibleAbsoluteFloatingAnchorOnlyParagraph = absoluteFloatingAnchorOnlyParagraph && paragraphIsSectionBreakAnchorCarryover(paragraph);
9227
9524
  const inlineImageHeightPx = paragraph.children.reduce((largest, child) => {
9228
9525
  if (child.type !== "image") {
9229
9526
  return largest;
@@ -9242,27 +9539,14 @@ function estimateParagraphHeightPx(paragraph, availableWidthPx, numberingDefinit
9242
9539
  estimateWrappedFloatingImageFootprintPx(paragraph, child)
9243
9540
  );
9244
9541
  }, 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
- const emptyParagraphHeightPx = decorativeBehindTextAnchorOnlyParagraph ? 0 : paragraphIsEffectivelyEmpty(paragraph) ? lineHeightPx + EMPTY_PARAGRAPH_EXTRA_HEIGHT_PX : 0;
9542
+ const emptyParagraphHeightPx = paragraphIsEffectivelyEmpty(paragraph) ? lineHeightPx + EMPTY_PARAGRAPH_EXTRA_HEIGHT_PX : 0;
9259
9543
  const topBorderInsetPx = paragraphBorderInsetPx(
9260
9544
  paragraph.style?.borders?.top
9261
9545
  );
9262
9546
  const bottomBorderInsetPx = paragraphBorderInsetPx(
9263
9547
  paragraph.style?.borders?.bottom
9264
9548
  );
9265
- const textFlowHeightPx = collapsibleAbsoluteFloatingAnchorOnlyParagraph ? 0 : paragraphFlowAnchoredAbsoluteFloatingAnchorOnlyParagraph ? MIN_PARAGRAPH_LINE_HEIGHT_PX : (
9549
+ const textFlowHeightPx = collapsibleAbsoluteFloatingAnchorOnlyParagraph ? 0 : (
9266
9550
  // When excluding the wrapped-float footprint, the dual-wrapped block
9267
9551
  // height spans the image; but the rendered paragraph only occupies its
9268
9552
  // text lines while the float overhangs. Use the text-line height so the
@@ -9270,11 +9554,10 @@ function estimateParagraphHeightPx(paragraph, availableWidthPx, numberingDefinit
9270
9554
  dualWrappedLayout && !excludeWrappedFloatingImageFootprint ? wrappedPretextParagraphBlockHeightPx(dualWrappedLayout.layout) : lineHeightPx * lineCount
9271
9555
  );
9272
9556
  const contentHeightPx = Math.max(
9273
- collapsibleAbsoluteFloatingAnchorOnlyParagraph || decorativeBehindTextAnchorOnlyParagraph ? 0 : paragraphFlowAnchoredAbsoluteFloatingAnchorOnlyParagraph ? MIN_PARAGRAPH_LINE_HEIGHT_PX : lineHeightPx,
9557
+ collapsibleAbsoluteFloatingAnchorOnlyParagraph ? 0 : lineHeightPx,
9274
9558
  textFlowHeightPx,
9275
9559
  inlineImageHeightPx,
9276
9560
  wrappedFloatingImageHeightPx,
9277
- effectiveAbsoluteFloatingImageHeightPx,
9278
9561
  emptyParagraphHeightPx
9279
9562
  );
9280
9563
  if (excludeWrappedFloatingImageFootprint && paragraph.children.some((c) => c.type === "image")) {
@@ -10535,6 +10818,7 @@ function buildDocumentPageNodeSegments(model, pageContentHeightPx, pageContentWi
10535
10818
  let previousParagraphAfterPx = 0;
10536
10819
  let currentMetricsIndex = 0;
10537
10820
  let currentSectionPageFlowOriginPx = 0;
10821
+ let committedKeepNextChainEndNodeIndex = -1;
10538
10822
  let currentPageContentHeightPx = resolveMetricsPageContentHeightPx(
10539
10823
  0,
10540
10824
  metricsBySection[0]
@@ -10593,56 +10877,14 @@ function buildDocumentPageNodeSegments(model, pageContentHeightPx, pageContentWi
10593
10877
  previousParagraphAfterPx = 0;
10594
10878
  continue;
10595
10879
  }
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
10880
  if (node.type === "paragraph" && paragraphActsAsTrailingRenderedPageBreakSpacer(model, nodeIndex, node)) {
10607
10881
  previousParagraphAfterPx = 0;
10608
10882
  continue;
10609
10883
  }
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
- if (node.type === "paragraph" && paragraphActsAsDecorativeBehindTextBackgroundOverlay(node)) {
10622
- currentPageSegments.push({ nodeIndex });
10623
- previousParagraphAfterPx = 0;
10624
- continue;
10625
- }
10626
10884
  if (node.type === "paragraph" && paragraphCollapsesIntoPreviousParagraph(node, model.nodes[nodeIndex - 1])) {
10627
10885
  continue;
10628
10886
  }
10629
10887
  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
10888
  if (paragraphHasPageBreakBefore2(node) && currentPageSegments.length > 0) {
10647
10889
  startNextPage();
10648
10890
  pageConsumedHeightPx = 0;
@@ -10726,18 +10968,12 @@ function buildDocumentPageNodeSegments(model, pageContentHeightPx, pageContentWi
10726
10968
  1,
10727
10969
  estimatedOrMeasuredHeightPx - directBeforeSpacingPx - directAfterSpacingPx + beforeSpacingPx + afterSpacingPx
10728
10970
  );
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;
10971
+ const paragraphTooTallForSinglePage = rawNodeHeightPx > nodeMetrics.pageContentHeightPx + PAGE_OVERFLOW_TOLERANCE_PX;
10737
10972
  const keepLinesOverflowSplit = node.style?.keepLines === true && paragraphTooTallForSinglePage;
10738
10973
  const keepNextOverflowSplit = node.style?.keepNext === true && paragraphTooTallForSinglePage;
10739
10974
  const forceOverflowSplit = keepLinesOverflowSplit || keepNextOverflowSplit;
10740
- if (forceOverflowSplit && pageConsumedHeightPx > 0 && currentPageSegments.length > 0) {
10975
+ const nodeIsWithinCommittedKeepNextChain = nodeIndex <= committedKeepNextChainEndNodeIndex;
10976
+ if (forceOverflowSplit && !nodeIsWithinCommittedKeepNextChain && pageConsumedHeightPx > 0 && currentPageSegments.length > 0) {
10741
10977
  startNextPage();
10742
10978
  pageConsumedHeightPx = 0;
10743
10979
  previousParagraphAfterPx = 0;
@@ -10750,7 +10986,7 @@ function buildDocumentPageNodeSegments(model, pageContentHeightPx, pageContentWi
10750
10986
  const collapsedMarginPx = pageConsumedHeightPx > 0 ? Math.min(previousParagraphAfterPx, beforeSpacingPx) : 0;
10751
10987
  const collapsedNodeHeightPx = Math.max(
10752
10988
  1,
10753
- Math.max(rawNodeHeightPx, coverFootprintPx) - collapsedMarginPx
10989
+ rawNodeHeightPx - collapsedMarginPx
10754
10990
  );
10755
10991
  const paragraphSupportsPretextSegmentRendering = Boolean(
10756
10992
  paragraphPretextSourceForSegmentRendering
@@ -10776,7 +11012,7 @@ function buildDocumentPageNodeSegments(model, pageContentHeightPx, pageContentWi
10776
11012
  }
10777
11013
  const collapsedNodeHeightPxAdjusted = Math.max(
10778
11014
  1,
10779
- Math.max(rawNodeHeightPx, coverFootprintPx) - collapsedMarginPx
11015
+ rawNodeHeightPx - collapsedMarginPx
10780
11016
  );
10781
11017
  const paragraphPretextLineCount = paragraphContainsExplicitLineBreakText(node) || paragraphContainsTabCharacter(node) ? resolveParagraphPretextLayoutForSegmentRendering()?.lineCount : void 0;
10782
11018
  const supportsImageParagraphLineSplit = paragraphHasImage2(node) && paragraphSupportsPretextSegmentRendering;
@@ -11000,7 +11236,8 @@ function buildDocumentPageNodeSegments(model, pageContentHeightPx, pageContentWi
11000
11236
  continue;
11001
11237
  }
11002
11238
  let requiredHeightPx = collapsedNodeHeightPxAdjusted;
11003
- if (node.style?.keepNext === true && paragraphHasVisibleText2(node)) {
11239
+ let keepNextChainEndNodeIndex = -1;
11240
+ if (node.style?.keepNext === true && !nodeIsWithinCommittedKeepNextChain && paragraphHasVisibleText2(node)) {
11004
11241
  let chainCursor = nodeIndex;
11005
11242
  let chainPreviousParagraphAfterPx = afterSpacingPx;
11006
11243
  while (chainCursor < model.nodes.length - 1) {
@@ -11060,6 +11297,7 @@ function buildDocumentPageNodeSegments(model, pageContentHeightPx, pageContentWi
11060
11297
  );
11061
11298
  chainPreviousParagraphAfterPx = nextAfterSpacingPx;
11062
11299
  }
11300
+ keepNextChainEndNodeIndex = chainCursor;
11063
11301
  }
11064
11302
  const remainingHeightPx = currentPageContentHeightPx - pageConsumedHeightPx;
11065
11303
  const canKeepTrailingSectionTailOnCurrentPage = shouldKeepTrailingSectionTailOnCurrentPage(
@@ -11101,11 +11339,11 @@ function buildDocumentPageNodeSegments(model, pageContentHeightPx, pageContentWi
11101
11339
  nodeMetrics
11102
11340
  );
11103
11341
  }
11342
+ if (pageConsumedHeightPx === 0 && keepNextChainEndNodeIndex > nodeIndex) {
11343
+ committedKeepNextChainEndNodeIndex = keepNextChainEndNodeIndex;
11344
+ }
11104
11345
  currentPageSegments.push({ nodeIndex });
11105
- const effectiveNodeHeightPx = Math.max(
11106
- pageConsumedHeightPx > 0 ? collapsedNodeHeightPx : rawNodeHeightPx,
11107
- coverFootprintPx
11108
- );
11346
+ const effectiveNodeHeightPx = pageConsumedHeightPx > 0 ? collapsedNodeHeightPx : rawNodeHeightPx;
11109
11347
  pageConsumedHeightPx += effectiveNodeHeightPx;
11110
11348
  previousParagraphAfterPx = afterSpacingPx;
11111
11349
  continue;
@@ -11912,11 +12150,15 @@ function paragraphLineHeight(paragraph, docGridLinePitchPx, disableDocGridSnap =
11912
12150
  disableDocGridSnap
11913
12151
  )}px`;
11914
12152
  }
11915
- const lineMultiple = calibrateAutoLineSpacingMultiple(
11916
- resolveAutoLineSpacingMultiple(
11917
- lineTwips,
11918
- DEFAULT_PARAGRAPH_LINE_MULTIPLE
11919
- ),
12153
+ const resolvedAutoMultiple = resolveAutoLineSpacingMultiple(
12154
+ lineTwips,
12155
+ DEFAULT_PARAGRAPH_LINE_MULTIPLE
12156
+ );
12157
+ const lineMultiple = paragraphRendersTextFreeLine(paragraph) ? Math.max(
12158
+ MIN_AUTO_LINE_MULTIPLE,
12159
+ Number((resolvedAutoMultiple * singleLineScale).toFixed(3))
12160
+ ) : calibrateAutoLineSpacingMultiple(
12161
+ resolvedAutoMultiple,
11920
12162
  baseFontFamily,
11921
12163
  singleLineScale
11922
12164
  );
@@ -12259,9 +12501,7 @@ function paragraphBlockStyle(paragraph, numberingDefinitions, headingStyles, doc
12259
12501
  const suppressTocNumberingTextIndent = isTableOfContentsParagraph(paragraph) && paragraphHasNumbering(paragraph);
12260
12502
  const suppressIndentForFloatingAnchorOnlyParagraph = paragraphIsFloatingImageAnchorOnly(paragraph);
12261
12503
  const suppressStackingContextForBehindTextAnchorOnlyParagraph = paragraphIsBehindTextAbsoluteFloatingImageAnchorOnly(paragraph);
12262
- const suppressFlowFootprintForBehindTextAnchorOnlyParagraph = paragraphActsAsDecorativeBehindTextBackgroundOverlay(paragraph);
12263
- const textBearingAbsoluteFloatingTextBoxFootprintPx = paragraphTextBearingAbsoluteFloatingTextBoxFootprintPx(paragraph);
12264
- const reservedMinHeightPx = suppressFlowFootprintForBehindTextAnchorOnlyParagraph ? void 0 : textBearingAbsoluteFloatingTextBoxFootprintPx > 0 ? textBearingAbsoluteFloatingTextBoxFootprintPx : paragraphIsEffectivelyEmpty(paragraph) ? estimateParagraphLineHeightPx(
12504
+ const reservedMinHeightPx = paragraphIsEffectivelyEmpty(paragraph) ? estimateParagraphLineHeightPx(
12265
12505
  paragraph,
12266
12506
  docGridLinePitchPx,
12267
12507
  disableDocGridSnap
@@ -12280,15 +12520,13 @@ function paragraphBlockStyle(paragraph, numberingDefinitions, headingStyles, doc
12280
12520
  // line-box strut tracks the actual content instead of the browser's
12281
12521
  // 16px default, which inflates lines for sub-12pt paragraphs.
12282
12522
  fontSize: `${paragraphBaseFontSizePx(paragraph)}px`,
12283
- lineHeight: suppressFlowFootprintForBehindTextAnchorOnlyParagraph ? 0 : paragraphLineHeight(paragraph, docGridLinePitchPx, disableDocGridSnap),
12284
- ...suppressFlowFootprintForBehindTextAnchorOnlyParagraph ? {
12285
- height: 0,
12286
- marginTop: 0,
12287
- marginBottom: 0
12288
- } : {
12289
- marginTop: beforeSpacing,
12290
- marginBottom: afterSpacing
12291
- },
12523
+ lineHeight: paragraphLineHeight(
12524
+ paragraph,
12525
+ docGridLinePitchPx,
12526
+ disableDocGridSnap
12527
+ ),
12528
+ marginTop: beforeSpacing,
12529
+ marginBottom: afterSpacing,
12292
12530
  marginLeft: suppressIndentForFloatingAnchorOnlyParagraph ? 0 : leftIndent,
12293
12531
  marginRight: suppressIndentForFloatingAnchorOnlyParagraph ? 0 : rightIndent,
12294
12532
  backgroundColor: paragraph.style?.backgroundColor,
@@ -14019,7 +14257,8 @@ function renderParagraphRuns(paragraph, keyPrefix, documentTheme = "light", numb
14019
14257
  const resolveNextTabWidthPx = () => resolveTabSpacerWidthPx(
14020
14258
  tabStopPositionsPx,
14021
14259
  approximateLineWidthPx,
14022
- fallbackTabWidthPx
14260
+ fallbackTabWidthPx,
14261
+ checkboxChoiceRow
14023
14262
  );
14024
14263
  const appendPlainTextWithSoftBreakControl = (target, keySeed, text, style, measureStyle) => {
14025
14264
  const shouldControlSoftBreakStretch = paragraph.style?.align === "justify" && text.includes("\n");
@@ -22097,12 +22336,32 @@ function ensureDocxViewerPageSurfaceRegistry(editor) {
22097
22336
  if (!registry) {
22098
22337
  registry = {
22099
22338
  pageElements: /* @__PURE__ */ new Map(),
22339
+ pageContentKeys: /* @__PURE__ */ new Map(),
22100
22340
  listeners: /* @__PURE__ */ new Set()
22101
22341
  };
22102
22342
  docxViewerPageSurfaceRegistryByEditor.set(owner, registry);
22103
22343
  }
22104
22344
  return registry;
22105
22345
  }
22346
+ function syncDocxViewerPageSurfaceContentKeys(editor, contentKeysByPage) {
22347
+ const registry = ensureDocxViewerPageSurfaceRegistry(editor);
22348
+ let changed = false;
22349
+ contentKeysByPage.forEach((contentKey, pageIndex) => {
22350
+ if (registry.pageContentKeys.get(pageIndex) !== contentKey) {
22351
+ registry.pageContentKeys.set(pageIndex, contentKey);
22352
+ changed = true;
22353
+ }
22354
+ });
22355
+ registry.pageContentKeys.forEach((_, pageIndex) => {
22356
+ if (pageIndex >= contentKeysByPage.length) {
22357
+ registry.pageContentKeys.delete(pageIndex);
22358
+ changed = true;
22359
+ }
22360
+ });
22361
+ if (changed) {
22362
+ notifyDocxViewerPageSurfaceSubscribers(registry);
22363
+ }
22364
+ }
22106
22365
  function subscribeDocxViewerPageSurfaces(editor, listener) {
22107
22366
  const registry = ensureDocxViewerPageSurfaceRegistry(editor);
22108
22367
  registry.listeners.add(listener);
@@ -22166,62 +22425,8 @@ function resolveDocxViewerPageSurfaceSize(element, fallbackWidthPx, fallbackHeig
22166
22425
  heightPx: Math.max(1, Math.round(fallbackHeightPx))
22167
22426
  };
22168
22427
  }
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
- }
22428
+ var DOCX_THUMBNAIL_SURFACE_CACHE_MAX_ENTRIES = 32;
22429
+ var DOCX_THUMBNAIL_MIN_RASTER_INTERVAL_MS = 200;
22225
22430
  function resolveDocxPageThumbnailResolution(options) {
22226
22431
  const safeSourceWidthPx = Math.max(1, Math.round(options.sourceWidthPx));
22227
22432
  const safeSourceHeightPx = Math.max(1, Math.round(options.sourceHeightPx));
@@ -22295,8 +22500,51 @@ function useDocxPageThumbnails(editor, options = {}) {
22295
22500
  },
22296
22501
  []
22297
22502
  );
22503
+ const thumbnailSurfaceCacheRef = React.useRef(void 0);
22504
+ const thumbnailRasterQueueRef = React.useRef(void 0);
22505
+ const lastPaintedThumbnailKeyByCanvasRef = React.useRef(
22506
+ /* @__PURE__ */ new WeakMap()
22507
+ );
22508
+ const ensureThumbnailSurfaceCache = React.useCallback(() => {
22509
+ if (!thumbnailSurfaceCacheRef.current) {
22510
+ thumbnailSurfaceCacheRef.current = new DocxThumbnailSurfaceCache(
22511
+ DOCX_THUMBNAIL_SURFACE_CACHE_MAX_ENTRIES
22512
+ );
22513
+ }
22514
+ return thumbnailSurfaceCacheRef.current;
22515
+ }, []);
22516
+ const ensureThumbnailRasterQueue = React.useCallback(() => {
22517
+ if (!thumbnailRasterQueueRef.current) {
22518
+ thumbnailRasterQueueRef.current = new SerialIdleTaskQueue({
22519
+ minTaskIntervalMs: DOCX_THUMBNAIL_MIN_RASTER_INTERVAL_MS
22520
+ });
22521
+ }
22522
+ return thumbnailRasterQueueRef.current;
22523
+ }, []);
22524
+ React.useEffect(() => {
22525
+ thumbnailSurfaceCacheRef.current?.clear();
22526
+ thumbnailRasterQueueRef.current?.clear();
22527
+ lastPaintedThumbnailKeyByCanvasRef.current = /* @__PURE__ */ new WeakMap();
22528
+ }, [editor.documentLoadNonce, pageSurfaceRegistryOwner]);
22529
+ const thumbnailResolutionOptionsKey = React.useMemo(() => {
22530
+ const bounds = options.resolution;
22531
+ const boundsKey = typeof bounds === "number" ? `n${bounds}` : bounds ? `b${bounds.maxWidth ?? ""}x${bounds.maxHeight ?? ""}` : "";
22532
+ return `${boundsKey}|${options.maxWidthPx ?? ""}|${options.maxHeightPx ?? ""}|${options.pixelRatio ?? ""}`;
22533
+ }, [
22534
+ options.maxHeightPx,
22535
+ options.maxWidthPx,
22536
+ options.pixelRatio,
22537
+ options.resolution
22538
+ ]);
22539
+ const thumbnailSkipKeyForPage = React.useCallback(
22540
+ (pageIndex) => {
22541
+ const contentKey = pageSurfaceRegistry.pageContentKeys.get(pageIndex);
22542
+ return contentKey === void 0 ? void 0 : `${contentKey}|${editor.documentTheme}|${thumbnailResolutionOptionsKey}`;
22543
+ },
22544
+ [editor.documentTheme, pageSurfaceRegistry, thumbnailResolutionOptionsKey]
22545
+ );
22298
22546
  const renderPageThumbnailToCanvas = React.useCallback(
22299
- async (pageIndex, canvas) => {
22547
+ async (pageIndex, canvas, renderOptions) => {
22300
22548
  if (options.disabled) {
22301
22549
  return;
22302
22550
  }
@@ -22304,63 +22552,101 @@ function useDocxPageThumbnails(editor, options = {}) {
22304
22552
  if (!targetCanvas) {
22305
22553
  return;
22306
22554
  }
22307
- const pageElement = mountedPageElements.get(pageIndex);
22555
+ const pageElement = pageSurfaceRegistry.pageElements.get(pageIndex);
22308
22556
  if (!pageElement || !pageElement.isConnected) {
22309
22557
  updatePageThumbnailState(pageIndex, "unavailable");
22310
22558
  return;
22311
22559
  }
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
- });
22560
+ const force = renderOptions?.force === true;
22561
+ const lastPaintedKey = lastPaintedThumbnailKeyByCanvasRef.current.get(targetCanvas);
22562
+ if (!force && lastPaintedKey !== void 0 && lastPaintedKey === thumbnailSkipKeyForPage(pageIndex)) {
22563
+ updatePageThumbnailState(pageIndex, "ready");
22564
+ return;
22565
+ }
22325
22566
  updatePageThumbnailState(pageIndex, "rendering");
22326
- try {
22327
- await rasterizeDocxViewerPageSurfaceToCanvas({
22328
- pageElement,
22567
+ await ensureThumbnailRasterQueue().enqueue(targetCanvas, async () => {
22568
+ const livePageElement = pageSurfaceRegistry.pageElements.get(pageIndex);
22569
+ if (!livePageElement || !livePageElement.isConnected) {
22570
+ updatePageThumbnailState(pageIndex, "unavailable");
22571
+ return;
22572
+ }
22573
+ const runSkipKey = thumbnailSkipKeyForPage(pageIndex);
22574
+ const sourceSize = resolveDocxViewerPageSurfaceSize(
22575
+ livePageElement,
22576
+ fallbackLayout.pageWidthPx,
22577
+ fallbackLayout.pageHeightPx
22578
+ );
22579
+ const resolution = resolveDocxPageThumbnailResolution({
22329
22580
  sourceWidthPx: sourceSize.widthPx,
22330
22581
  sourceHeightPx: sourceSize.heightPx,
22331
- canvas: targetCanvas,
22332
- widthPx: resolution.widthPx,
22333
- heightPx: resolution.heightPx,
22334
- pixelWidthPx: resolution.pixelWidthPx,
22335
- pixelHeightPx: resolution.pixelHeightPx
22582
+ resolution: options.resolution,
22583
+ maxWidthPx: options.maxWidthPx,
22584
+ maxHeightPx: options.maxHeightPx,
22585
+ pixelRatio: options.pixelRatio
22336
22586
  });
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
- }
22587
+ const surfaceKey = runSkipKey === void 0 ? void 0 : `${runSkipKey}|${sourceSize.widthPx}x${sourceSize.heightPx}|${resolution.pixelWidthPx}x${resolution.pixelHeightPx}`;
22588
+ const surfaceCache = ensureThumbnailSurfaceCache();
22589
+ try {
22590
+ let surface = !force && surfaceKey !== void 0 ? surfaceCache.get(surfaceKey) : void 0;
22591
+ if (!surface) {
22592
+ surface = await rasterizeDocxThumbnailSurface({
22593
+ pageElement: livePageElement,
22594
+ sourceWidthPx: sourceSize.widthPx,
22595
+ sourceHeightPx: sourceSize.heightPx,
22596
+ widthPx: resolution.widthPx,
22597
+ heightPx: resolution.heightPx,
22598
+ pixelWidthPx: resolution.pixelWidthPx,
22599
+ pixelHeightPx: resolution.pixelHeightPx
22600
+ });
22601
+ if (surfaceKey !== void 0) {
22602
+ surfaceCache.set(surfaceKey, surface);
22603
+ }
22604
+ }
22605
+ blitDocxThumbnailSurface(surface, targetCanvas, resolution);
22606
+ if (runSkipKey !== void 0) {
22607
+ lastPaintedThumbnailKeyByCanvasRef.current.set(
22608
+ targetCanvas,
22609
+ runSkipKey
22610
+ );
22611
+ }
22612
+ updatePageThumbnailState(pageIndex, "ready");
22613
+ } catch (error) {
22614
+ updatePageThumbnailState(
22615
+ pageIndex,
22616
+ "error",
22617
+ error instanceof Error ? error : new Error("Failed to render DOCX page thumbnail.")
22618
+ );
22619
+ }
22620
+ });
22345
22621
  },
22346
22622
  [
22623
+ ensureThumbnailRasterQueue,
22624
+ ensureThumbnailSurfaceCache,
22347
22625
  fallbackLayout.pageHeightPx,
22348
22626
  fallbackLayout.pageWidthPx,
22349
- mountedPageElements,
22350
22627
  options.disabled,
22351
22628
  options.resolution,
22352
22629
  options.maxHeightPx,
22353
22630
  options.maxWidthPx,
22354
22631
  options.pixelRatio,
22632
+ pageSurfaceRegistry,
22633
+ thumbnailSkipKeyForPage,
22355
22634
  updatePageThumbnailState
22356
22635
  ]
22357
22636
  );
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]);
22637
+ const requestAttachedThumbnailRenders = React.useCallback(
22638
+ async (renderOptions) => {
22639
+ const tasks = [...attachedCanvasByPageRef.current.keys()].map(
22640
+ (pageIndex) => renderPageThumbnailToCanvas(pageIndex, void 0, renderOptions)
22641
+ );
22642
+ await Promise.all(tasks);
22643
+ },
22644
+ [renderPageThumbnailToCanvas]
22645
+ );
22646
+ const rerenderAttachedThumbnails = React.useCallback(
22647
+ async () => requestAttachedThumbnailRenders({ force: true }),
22648
+ [requestAttachedThumbnailRenders]
22649
+ );
22364
22650
  const renderPageThumbnailToCanvasRef = React.useRef(
22365
22651
  renderPageThumbnailToCanvas
22366
22652
  );
@@ -22372,7 +22658,7 @@ function useDocxPageThumbnails(editor, options = {}) {
22372
22658
  renderToCanvasCallbacksRef.current.clear();
22373
22659
  }, [pageSurfaceRegistryOwner]);
22374
22660
  React.useEffect(() => {
22375
- void rerenderAttachedThumbnails();
22661
+ void requestAttachedThumbnailRenders();
22376
22662
  }, [
22377
22663
  editor.documentLoadNonce,
22378
22664
  editor.documentTheme,
@@ -22383,7 +22669,7 @@ function useDocxPageThumbnails(editor, options = {}) {
22383
22669
  options.maxHeightPx,
22384
22670
  options.maxWidthPx,
22385
22671
  options.pixelRatio,
22386
- rerenderAttachedThumbnails
22672
+ requestAttachedThumbnailRenders
22387
22673
  ]);
22388
22674
  const thumbnails = React.useMemo(() => {
22389
22675
  const totalPages = Math.max(1, editor.totalPages);
@@ -24773,6 +25059,42 @@ function DocxEditorViewer({
24773
25059
  pageNodeSegmentsByPage,
24774
25060
  primarySectionPropertiesXml
24775
25061
  ]);
25062
+ const pageThumbnailContentKeysByPage = React.useMemo(() => {
25063
+ const metadataSignature = docModelThumbnailMetadataSignature(
25064
+ editor.model.metadata
25065
+ );
25066
+ return pageNodeSegmentsByPage.map((pageSegments, pageIndex) => {
25067
+ const sectionInfo = pageSectionInfoByIndex[pageIndex];
25068
+ let nodeSignatures = "";
25069
+ for (const segment of pageSegments) {
25070
+ nodeSignatures += docNodeContentSignature(
25071
+ editor.model.nodes[segment.nodeIndex]
25072
+ );
25073
+ nodeSignatures += ",";
25074
+ }
25075
+ return [
25076
+ editor.documentLoadNonce,
25077
+ trackedChangesEnabled ? "tc1" : "tc0",
25078
+ metadataSignature,
25079
+ sectionInfo ? `${sectionInfo.sectionIndex}.${sectionInfo.pageNumber}` : "s?",
25080
+ pageNodeSegmentIdentityKeysByPage[pageIndex] ?? "",
25081
+ nodeSignatures
25082
+ ].join("|");
25083
+ });
25084
+ }, [
25085
+ editor.documentLoadNonce,
25086
+ editor.model,
25087
+ pageNodeSegmentIdentityKeysByPage,
25088
+ pageNodeSegmentsByPage,
25089
+ pageSectionInfoByIndex,
25090
+ trackedChangesEnabled
25091
+ ]);
25092
+ React.useEffect(() => {
25093
+ syncDocxViewerPageSurfaceContentKeys(
25094
+ editor,
25095
+ pageThumbnailContentKeysByPage
25096
+ );
25097
+ }, [pageSurfaceRegistryOwner, pageThumbnailContentKeysByPage]);
24776
25098
  const resolveStyleRefFieldValueForPage = React.useMemo(() => {
24777
25099
  const valueCache = /* @__PURE__ */ new Map();
24778
25100
  const nodes = editor.model.nodes;
@@ -25031,6 +25353,14 @@ function DocxEditorViewer({
25031
25353
  overscan: pageVirtualizationOverscan
25032
25354
  });
25033
25355
  const internalPageVirtualizer = internalVirtualScrollUsesWindow ? internalWindowPageVirtualizer : internalElementPageVirtualizer;
25356
+ React.useLayoutEffect(() => {
25357
+ internalElementPageVirtualizer.measure();
25358
+ internalWindowPageVirtualizer.measure();
25359
+ }, [
25360
+ estimateVirtualPageSize,
25361
+ internalElementPageVirtualizer,
25362
+ internalWindowPageVirtualizer
25363
+ ]);
25034
25364
  const internalVirtualItems = internalPageVirtualizer.getVirtualItems();
25035
25365
  const internalVisiblePageRange = React.useMemo(() => {
25036
25366
  if (!internalPageVirtualizationEnabled) {
@@ -25073,8 +25403,8 @@ function DocxEditorViewer({
25073
25403
  return internalVisiblePageRange;
25074
25404
  }
25075
25405
  const scrollDirection = internalPageVirtualizer.scrollDirection;
25076
- const renderPreviousPage = scrollDirection !== "backward";
25077
- const renderNextPage = scrollDirection === "backward";
25406
+ const renderPreviousPage = scrollDirection !== "forward";
25407
+ const renderNextPage = scrollDirection !== "backward";
25078
25408
  const startPageIndex = clampNumber(
25079
25409
  internalVisiblePageRange.startPageIndex - (renderPreviousPage ? LARGE_TABLE_PAGE_ADJACENT_RENDER_COUNT : 0),
25080
25410
  0,
@@ -32489,6 +32819,7 @@ function DocxEditorViewer({
32489
32819
  "span",
32490
32820
  {
32491
32821
  contentEditable: false,
32822
+ ...{ [DOCX_THUMBNAIL_EXCLUDE_ATTRIBUTE]: "true" },
32492
32823
  style: {
32493
32824
  position: "absolute",
32494
32825
  left: rect.left,
@@ -32511,6 +32842,7 @@ function DocxEditorViewer({
32511
32842
  "span",
32512
32843
  {
32513
32844
  contentEditable: false,
32845
+ ...{ [DOCX_THUMBNAIL_EXCLUDE_ATTRIBUTE]: "true" },
32514
32846
  style: {
32515
32847
  position: "absolute",
32516
32848
  left: caretRect.left,
@@ -32880,7 +33212,10 @@ function DocxEditorViewer({
32880
33212
  Math.round(options?.pageFlowTopPx ?? 0)
32881
33213
  );
32882
33214
  const bodyParagraphOriginLeftPx = interactiveBodyFloatingPageOriginPx ? paragraphPageLayout.marginsPx.left : void 0;
32883
- const bodyParagraphOriginTopPx = interactiveBodyFloatingPageOriginPx ? paragraphPageLayout.marginsPx.top + paragraphPageFlowTopPx : void 0;
33215
+ const bodyParagraphOriginTopPx = interactiveBodyFloatingPageOriginPx ? paragraphActsAsPageAnchoredCoverOverlayHost(
33216
+ paragraph,
33217
+ paragraphPageLayout
33218
+ ) ? paragraphPageLayout.marginsPx.top + paragraphPageFlowTopPx : 0 : void 0;
32884
33219
  const resizedWidthPxByImageIndex = /* @__PURE__ */ new Map();
32885
33220
  const resizedHeightPxByImageIndex = /* @__PURE__ */ new Map();
32886
33221
  const movePreviewByImageIndex = /* @__PURE__ */ new Map();
@@ -35714,13 +36049,6 @@ function DocxEditorViewer({
35714
36049
  options?.pageFlowForeignExclusions ?? []
35715
36050
  )
35716
36051
  );
35717
- const leadingCoverLayoutSpacer = paragraphActsAsLeadingCoverLayoutSpacer(
35718
- editor.model,
35719
- nodeIndex,
35720
- node,
35721
- paragraphContentWidthPx,
35722
- resolvedPageLayout.pageHeightPx - resolvedPageLayout.marginsPx.top - resolvedPageLayout.marginsPx.bottom
35723
- );
35724
36052
  const beforeSpacingPx = effectiveParagraphBeforeSpacingPx(
35725
36053
  editor.model,
35726
36054
  nodeIndex,
@@ -35774,14 +36102,12 @@ function DocxEditorViewer({
35774
36102
  marginTop: (typeof baseParagraphStyle.marginTop === "number" ? baseParagraphStyle.marginTop : 0) - (pageAnchoredCoverOverlayParagraph ? resolvedPageLayout.marginsPx.top : 0),
35775
36103
  marginLeft: -resolvedPageLayout.marginsPx.left,
35776
36104
  marginRight: -resolvedPageLayout.marginsPx.right,
35777
- minHeight: 0,
35778
- height: 0,
35779
- lineHeight: 0,
36105
+ // A cover-overlay host is remapped to the page surface and must
36106
+ // not displace flow; an ordinary anchor host still occupies its
36107
+ // one-line paragraph box (Word semantics).
36108
+ ...pageAnchoredCoverOverlayParagraph ? { minHeight: 0, height: 0, lineHeight: 0 } : void 0,
35780
36109
  overflow: "visible"
35781
36110
  } : 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
36111
  // Carry the paragraph's run font on the editable host so text typed into
35786
36112
  // it (which is a bare, not-yet-committed text node, not a styled run
35787
36113
  // span) renders in the same font the committed run will use — i.e. the
@@ -36816,6 +37142,22 @@ ${currentText.slice(end)}`;
36816
37142
  const rowSpanValue = cell.style?.rowSpan && cell.style.rowSpan > 1 ? cell.style.rowSpan : 1;
36817
37143
  const colSpan = colSpanValue > 1 ? colSpanValue : void 0;
36818
37144
  const rowSpan = rowSpanValue > 1 ? rowSpanValue : void 0;
37145
+ const exactRowSpanRows = node.rows.slice(
37146
+ rowIndex,
37147
+ rowIndex + rowSpanValue
37148
+ );
37149
+ const exactRowSpanClipHeightPx = exactRowSpanRows.length > 0 && exactRowSpanRows.every((spannedRow, rowOffset) => {
37150
+ const spannedHeightPx = rowHeightsPx[rowIndex + rowOffset];
37151
+ return spannedRow.style?.heightRule === "exact" && Number.isFinite(spannedHeightPx) && spannedHeightPx > 0;
37152
+ }) ? exactRowSpanRows.reduce(
37153
+ (sum, _spannedRow, rowOffset) => sum + Math.max(
37154
+ MIN_PARAGRAPH_LINE_HEIGHT_PX,
37155
+ Math.round(
37156
+ rowHeightsPx[rowIndex + rowOffset]
37157
+ )
37158
+ ),
37159
+ 0
37160
+ ) : void 0;
36819
37161
  const startColumnIndex = columnCursor;
36820
37162
  const boundaryColumnIndex = startColumnIndex + colSpanValue - 1;
36821
37163
  columnCursor += colSpanValue;
@@ -37934,12 +38276,11 @@ ${currentText.slice(end)}`;
37934
38276
  style: {
37935
38277
  display: "grid",
37936
38278
  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,
38279
+ // Without the clip the row balloons to fit
38280
+ // content (height on a <tr>/<td> is only
38281
+ // ever a minimum).
38282
+ ...exactRowSpanClipHeightPx !== void 0 && !isSlicedRow ? {
38283
+ maxHeight: `${exactRowSpanClipHeightPx}px`,
37943
38284
  overflow: "hidden"
37944
38285
  } : void 0
37945
38286
  },
@@ -39726,11 +40067,27 @@ ${currentText.slice(end)}`;
39726
40067
  currentGroup.segments.push(segment);
39727
40068
  return;
39728
40069
  }
40070
+ if (currentGroup && parseSectionStartType(
40071
+ documentSections[currentSectionIndex]?.sectionPropertiesXml
40072
+ ) === "nextcolumn") {
40073
+ const previousColumns = sectionColumnsBySectionIndex[currentGroup.sectionIndex];
40074
+ const nextColumns = sectionColumnsBySectionIndex[currentSectionIndex];
40075
+ const sameGeometry = previousColumns && nextColumns && previousColumns.count === nextColumns.count && Math.abs(
40076
+ previousColumns.gapPx - nextColumns.gapPx
40077
+ ) <= 2 && JSON.stringify(previousColumns.widthsPx ?? null) === JSON.stringify(nextColumns.widthsPx ?? null);
40078
+ if (sameGeometry) {
40079
+ currentGroup.segments.push(segment);
40080
+ return;
40081
+ }
40082
+ }
39729
40083
  sectionGroups.push({
39730
40084
  sectionIndex: currentSectionIndex,
39731
40085
  segments: [segment]
39732
40086
  });
39733
40087
  });
40088
+ if (typeof window !== "undefined" && window.__docxDebugGroups) {
40089
+ console.log("[groups]", pageIndex, JSON.stringify(sectionGroups.map((g) => ({ s: g.sectionIndex, n: g.segments.map((x) => x.nodeIndex) }))));
40090
+ }
39734
40091
  return sectionGroups.map((group, groupIndex) => {
39735
40092
  const sectionColumns = sectionColumnsBySectionIndex[group.sectionIndex];
39736
40093
  const isLastGroupOnPage = groupIndex === sectionGroups.length - 1;
@@ -39899,7 +40256,7 @@ ${currentText.slice(end)}`;
39899
40256
  (editor.model.metadata.sections ?? []).filter(
39900
40257
  (candidate) => parseSectionStartType(
39901
40258
  candidate.sectionPropertiesXml
39902
- ) === "nextColumn"
40259
+ ) === "nextcolumn"
39903
40260
  ).map(
39904
40261
  (candidate) => Math.max(
39905
40262
  0,
@@ -41809,6 +42166,7 @@ function collectDocxEstimatedOverflowBreakStartNodeIndexes(model, hardBreakStart
41809
42166
  let pageConsumedHeightPx = 0;
41810
42167
  let previousParagraphAfterPx = 0;
41811
42168
  let currentMetricsIndex = 0;
42169
+ let committedKeepNextChainEndNodeIndex = -1;
41812
42170
  const suppressSpacingBeforeAfterPageBreak = options?.suppressSpacingBeforeAfterPageBreak ?? false;
41813
42171
  let currentPageContentHeightPx = metricsBySection[0]?.pageContentHeightPx ?? fallbackMetrics.pageContentHeightPx;
41814
42172
  for (let nodeIndex = 0; nodeIndex < model.nodes.length; nodeIndex += 1) {
@@ -41846,7 +42204,8 @@ function collectDocxEstimatedOverflowBreakStartNodeIndexes(model, hardBreakStart
41846
42204
  const collapsedMarginPx = node.type === "paragraph" && pageConsumedHeightPx > 0 ? Math.min(previousParagraphAfterPx, nodeBeforeSpacingPx) : 0;
41847
42205
  const collapsedNodeHeightPx = Math.max(1, rawNodeHeightPx - collapsedMarginPx);
41848
42206
  let requiredHeightPx = collapsedNodeHeightPx;
41849
- if (node.type === "paragraph" && node.style?.keepNext === true && callbacks.paragraphHasVisibleText(node)) {
42207
+ let keepNextChainEndNodeIndex = -1;
42208
+ if (node.type === "paragraph" && node.style?.keepNext === true && nodeIndex > committedKeepNextChainEndNodeIndex && callbacks.paragraphHasVisibleText(node)) {
41850
42209
  let chainCursor = nodeIndex;
41851
42210
  let chainPreviousParagraphAfterPx = paragraphAfterSpacingPx2(node);
41852
42211
  while (chainCursor < model.nodes.length - 1) {
@@ -41904,6 +42263,7 @@ function collectDocxEstimatedOverflowBreakStartNodeIndexes(model, hardBreakStart
41904
42263
  );
41905
42264
  chainPreviousParagraphAfterPx = paragraphAfterSpacingPx2(nextChainNode);
41906
42265
  }
42266
+ keepNextChainEndNodeIndex = chainCursor;
41907
42267
  }
41908
42268
  const remainingHeightPx = currentPageContentHeightPx - pageConsumedHeightPx;
41909
42269
  if (pageConsumedHeightPx > 0 && requiredHeightPx > remainingHeightPx + pageOverflowTolerancePx) {
@@ -41912,6 +42272,9 @@ function collectDocxEstimatedOverflowBreakStartNodeIndexes(model, hardBreakStart
41912
42272
  previousParagraphAfterPx = 0;
41913
42273
  currentPageContentHeightPx = nodeMetrics.pageContentHeightPx;
41914
42274
  }
42275
+ if (pageConsumedHeightPx === 0 && keepNextChainEndNodeIndex > nodeIndex) {
42276
+ committedKeepNextChainEndNodeIndex = keepNextChainEndNodeIndex;
42277
+ }
41915
42278
  const effectiveNodeHeightPx = pageConsumedHeightPx > 0 ? collapsedNodeHeightPx : rawNodeHeightPx;
41916
42279
  pageConsumedHeightPx += effectiveNodeHeightPx;
41917
42280
  previousParagraphAfterPx = node.type === "paragraph" ? paragraphAfterSpacingPx2(node) : 0;
@@ -41963,6 +42326,7 @@ function buildDocumentPageNodeSegments2(model, pageContentHeightPx, pageContentW
41963
42326
  let pageConsumedHeightPx = 0;
41964
42327
  let previousParagraphAfterPx = 0;
41965
42328
  let currentMetricsIndex = 0;
42329
+ let committedKeepNextChainEndNodeIndex = -1;
41966
42330
  let currentPageContentHeightPx = resolvePageContentHeightPx(
41967
42331
  0,
41968
42332
  metricsBySection[0]?.pageContentHeightPx ?? fallbackMetrics.pageContentHeightPx
@@ -42026,7 +42390,8 @@ function buildDocumentPageNodeSegments2(model, pageContentHeightPx, pageContentW
42026
42390
  const keepLinesOverflowSplit = node.style?.keepLines === true && paragraphTooTallForSinglePage;
42027
42391
  const keepNextOverflowSplit = node.style?.keepNext === true && paragraphTooTallForSinglePage;
42028
42392
  const forceOverflowSplit = keepLinesOverflowSplit || keepNextOverflowSplit;
42029
- if (forceOverflowSplit && pageConsumedHeightPx > 0 && currentPageSegments.length > 0) {
42393
+ const nodeIsWithinCommittedKeepNextChain = nodeIndex <= committedKeepNextChainEndNodeIndex;
42394
+ if (forceOverflowSplit && !nodeIsWithinCommittedKeepNextChain && pageConsumedHeightPx > 0 && currentPageSegments.length > 0) {
42030
42395
  startNextPage();
42031
42396
  pageConsumedHeightPx = 0;
42032
42397
  previousParagraphAfterPx = 0;
@@ -42176,7 +42541,8 @@ function buildDocumentPageNodeSegments2(model, pageContentHeightPx, pageContentW
42176
42541
  continue;
42177
42542
  }
42178
42543
  let requiredHeightPx = collapsedNodeHeightPx;
42179
- if (node.style?.keepNext === true && callbacks.paragraphHasVisibleText(node)) {
42544
+ let keepNextChainEndNodeIndex = -1;
42545
+ if (node.style?.keepNext === true && !nodeIsWithinCommittedKeepNextChain && callbacks.paragraphHasVisibleText(node)) {
42180
42546
  let chainCursor = nodeIndex;
42181
42547
  let chainPreviousParagraphAfterPx = afterSpacingPx;
42182
42548
  while (chainCursor < model.nodes.length - 1) {
@@ -42234,6 +42600,7 @@ function buildDocumentPageNodeSegments2(model, pageContentHeightPx, pageContentW
42234
42600
  );
42235
42601
  chainPreviousParagraphAfterPx = paragraphAfterSpacingPx2(nextChainNode);
42236
42602
  }
42603
+ keepNextChainEndNodeIndex = chainCursor;
42237
42604
  }
42238
42605
  const remainingHeightPx = currentPageContentHeightPx - pageConsumedHeightPx;
42239
42606
  if (pageConsumedHeightPx > 0 && requiredHeightPx > remainingHeightPx + pageOverflowTolerancePx) {
@@ -42245,6 +42612,9 @@ function buildDocumentPageNodeSegments2(model, pageContentHeightPx, pageContentW
42245
42612
  nodeMetrics.pageContentHeightPx
42246
42613
  );
42247
42614
  }
42615
+ if (pageConsumedHeightPx === 0 && keepNextChainEndNodeIndex > nodeIndex) {
42616
+ committedKeepNextChainEndNodeIndex = keepNextChainEndNodeIndex;
42617
+ }
42248
42618
  currentPageSegments.push({ nodeIndex });
42249
42619
  const effectiveNodeHeightPx = pageConsumedHeightPx > 0 ? collapsedNodeHeightPx : rawNodeHeightPx;
42250
42620
  pageConsumedHeightPx += effectiveNodeHeightPx;