@extend-ai/react-docx 0.7.0-alpha.6 → 0.7.0-alpha.8

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
@@ -3160,6 +3160,14 @@ var THUMBNAIL_EXCLUDED_CLONE_SELECTOR = [
3160
3160
  var THUMBNAIL_IMAGE_DOWNSCALE_MIN_DATA_URI_LENGTH = 32768;
3161
3161
  var THUMBNAIL_IMAGE_DOWNSCALE_MAX_DIMENSION_PX = 512;
3162
3162
  var THUMBNAIL_IMAGE_JPEG_QUALITY = 0.78;
3163
+ var THUMBNAIL_DIRECT_DEFAULT_FONT_FAMILY = "Calibri, Arial, sans-serif";
3164
+ var THUMBNAIL_DIRECT_DEFAULT_TEXT_COLOR = "#111827";
3165
+ var THUMBNAIL_DIRECT_TABLE_BORDER_COLOR = "#d1d5db";
3166
+ var THUMBNAIL_DIRECT_IMAGE_BACKGROUND = "#f3f4f6";
3167
+ var THUMBNAIL_DIRECT_MAX_ELEMENTS = 320;
3168
+ var THUMBNAIL_DIRECT_MAX_TEXT_CHARS = 640;
3169
+ var THUMBNAIL_DIRECT_MAX_LINES = 14;
3170
+ var THUMBNAIL_DIRECT_MAX_LAYOUT_LINES = 80;
3163
3171
  function thumbnailSvgDataUri(svg) {
3164
3172
  return `data:image/svg+xml;charset=utf-8,${encodeURIComponent(svg)}`;
3165
3173
  }
@@ -3223,6 +3231,346 @@ function getDownscaledThumbnailImageDataUri(src) {
3223
3231
  downscaledThumbnailImageCache.set(src, pending);
3224
3232
  return pending;
3225
3233
  }
3234
+ function directThumbnailPositivePx(value, fallback = 1) {
3235
+ return Number.isFinite(value) && value > 0 ? Math.max(1, Number(value)) : fallback;
3236
+ }
3237
+ function setCanvasFillStyle(context, color, fallback) {
3238
+ try {
3239
+ context.fillStyle = color || fallback;
3240
+ } catch {
3241
+ context.fillStyle = fallback;
3242
+ }
3243
+ }
3244
+ function setCanvasStrokeStyle(context, color, fallback) {
3245
+ try {
3246
+ context.strokeStyle = color || fallback;
3247
+ } catch {
3248
+ context.strokeStyle = fallback;
3249
+ }
3250
+ }
3251
+ function directThumbnailFont(run, fallbackFontSizePx) {
3252
+ const fontSizePx = Math.max(
3253
+ 6,
3254
+ Math.min(
3255
+ 36,
3256
+ Math.round(
3257
+ directThumbnailPositivePx(run?.fontSizePx, fallbackFontSizePx)
3258
+ )
3259
+ )
3260
+ );
3261
+ const fontStyle = run?.italic ? "italic " : "";
3262
+ const fontWeight = run?.bold ? "700 " : "";
3263
+ return `${fontStyle}${fontWeight}${fontSizePx}px ${run?.fontFamily || THUMBNAIL_DIRECT_DEFAULT_FONT_FAMILY}`;
3264
+ }
3265
+ var THUMBNAIL_DIRECT_TOKEN_REGEX = /(\r\n|\n|\t|[^\S\r\n\t]+|[^\s\r\n\t]+)/g;
3266
+ var THUMBNAIL_DIRECT_LEADING_WHITESPACE_REGEX = /^\s/;
3267
+ var THUMBNAIL_DIRECT_TEXT_MEASURE_CACHE_MAX_ENTRIES = 4096;
3268
+ var directThumbnailTextMeasureCache = /* @__PURE__ */ new Map();
3269
+ function measureDirectThumbnailToken(context, font, text) {
3270
+ if (!text) {
3271
+ return 0;
3272
+ }
3273
+ const cacheKey = `${font}\0${text}`;
3274
+ const cached = directThumbnailTextMeasureCache.get(cacheKey);
3275
+ if (cached !== void 0) {
3276
+ return cached;
3277
+ }
3278
+ const width = context.measureText(text).width;
3279
+ if (directThumbnailTextMeasureCache.size >= THUMBNAIL_DIRECT_TEXT_MEASURE_CACHE_MAX_ENTRIES) {
3280
+ const oldestKey = directThumbnailTextMeasureCache.keys().next().value;
3281
+ if (oldestKey !== void 0) {
3282
+ directThumbnailTextMeasureCache.delete(oldestKey);
3283
+ }
3284
+ }
3285
+ directThumbnailTextMeasureCache.set(cacheKey, width);
3286
+ return width;
3287
+ }
3288
+ function appendDirectThumbnailTextLine(lines, currentSegments, currentWidthPx) {
3289
+ const line = {
3290
+ segments: currentSegments,
3291
+ widthPx: currentWidthPx
3292
+ };
3293
+ lines.push(line);
3294
+ return line;
3295
+ }
3296
+ function layoutDirectThumbnailTextRuns(params) {
3297
+ const { context, runs, fallbackFontSizePx } = params;
3298
+ const widthPx = Math.max(1, params.widthPx);
3299
+ const maxLineCount = Math.max(
3300
+ 1,
3301
+ Math.min(THUMBNAIL_DIRECT_MAX_LAYOUT_LINES, params.maxLineCount)
3302
+ );
3303
+ const lines = [];
3304
+ let currentSegments = [];
3305
+ let currentWidthPx = 0;
3306
+ let remainingChars = THUMBNAIL_DIRECT_MAX_TEXT_CHARS;
3307
+ const flushLine = () => {
3308
+ appendDirectThumbnailTextLine(lines, currentSegments, currentWidthPx);
3309
+ currentSegments = [];
3310
+ currentWidthPx = 0;
3311
+ };
3312
+ for (const run of runs) {
3313
+ if (lines.length >= maxLineCount || remainingChars <= 0) {
3314
+ break;
3315
+ }
3316
+ const text = run.text.slice(0, remainingChars);
3317
+ remainingChars -= text.length;
3318
+ const runFont = directThumbnailFont(run, fallbackFontSizePx);
3319
+ context.font = runFont;
3320
+ const tokens = text.match(THUMBNAIL_DIRECT_TOKEN_REGEX) ?? [];
3321
+ for (const token of tokens) {
3322
+ if (lines.length >= maxLineCount) {
3323
+ break;
3324
+ }
3325
+ if (token === "\n" || token === "\r\n") {
3326
+ flushLine();
3327
+ continue;
3328
+ }
3329
+ const drawableToken = token === " " ? " " : token;
3330
+ const tokenWidthPx = measureDirectThumbnailToken(
3331
+ context,
3332
+ runFont,
3333
+ drawableToken
3334
+ );
3335
+ const tokenIsWhitespace = THUMBNAIL_DIRECT_LEADING_WHITESPACE_REGEX.test(drawableToken);
3336
+ if (currentSegments.length > 0 && currentWidthPx + tokenWidthPx > widthPx && !tokenIsWhitespace) {
3337
+ flushLine();
3338
+ }
3339
+ if (currentSegments.length === 0 && tokenIsWhitespace) {
3340
+ continue;
3341
+ }
3342
+ currentSegments.push({
3343
+ run,
3344
+ text: drawableToken,
3345
+ widthPx: tokenWidthPx,
3346
+ font: runFont
3347
+ });
3348
+ currentWidthPx += tokenWidthPx;
3349
+ }
3350
+ }
3351
+ if (currentSegments.length > 0 || lines.length === 0) {
3352
+ flushLine();
3353
+ }
3354
+ return lines;
3355
+ }
3356
+ function directThumbnailAlignedX(params) {
3357
+ const { xPx, widthPx, lineWidthPx, align } = params;
3358
+ if (align === "center") {
3359
+ return xPx + Math.max(0, (widthPx - lineWidthPx) / 2);
3360
+ }
3361
+ if (align === "right") {
3362
+ return xPx + Math.max(0, widthPx - lineWidthPx);
3363
+ }
3364
+ return xPx;
3365
+ }
3366
+ function drawDirectThumbnailTextRuns(params) {
3367
+ const {
3368
+ context,
3369
+ runs,
3370
+ xPx,
3371
+ yPx,
3372
+ widthPx,
3373
+ heightPx,
3374
+ align,
3375
+ startLineIndex
3376
+ } = params;
3377
+ const safeWidthPx = Math.max(1, widthPx);
3378
+ const safeHeightPx = Math.max(1, heightPx);
3379
+ const fallbackFontSizePx = Math.max(
3380
+ 7,
3381
+ Math.min(
3382
+ 20,
3383
+ Math.round(
3384
+ runs.find((run) => Number.isFinite(run.fontSizePx))?.fontSizePx ?? 12
3385
+ )
3386
+ )
3387
+ );
3388
+ const lineHeightPx = Math.max(
3389
+ fallbackFontSizePx + 1,
3390
+ Math.round(params.lineHeightPx ?? fallbackFontSizePx * 1.25)
3391
+ );
3392
+ const skippedLineCount = Math.max(
3393
+ 0,
3394
+ Math.min(THUMBNAIL_DIRECT_MAX_LAYOUT_LINES - 1, Math.trunc(startLineIndex ?? 0))
3395
+ );
3396
+ const visibleLineCount = Math.max(
3397
+ 1,
3398
+ Math.min(
3399
+ THUMBNAIL_DIRECT_MAX_LINES,
3400
+ Math.ceil(safeHeightPx / Math.max(1, lineHeightPx)) + 1
3401
+ )
3402
+ );
3403
+ const lines = layoutDirectThumbnailTextRuns({
3404
+ context,
3405
+ runs,
3406
+ widthPx: safeWidthPx,
3407
+ fallbackFontSizePx,
3408
+ maxLineCount: skippedLineCount + visibleLineCount
3409
+ }).slice(skippedLineCount, skippedLineCount + visibleLineCount);
3410
+ context.save();
3411
+ context.beginPath();
3412
+ context.rect(xPx, yPx, safeWidthPx, safeHeightPx);
3413
+ context.clip();
3414
+ context.textBaseline = "alphabetic";
3415
+ let lastAppliedFont;
3416
+ lines.forEach((line, lineIndex) => {
3417
+ const lineTopPx = yPx + lineIndex * lineHeightPx;
3418
+ if (lineTopPx > yPx + safeHeightPx) {
3419
+ return;
3420
+ }
3421
+ let cursorXPx = directThumbnailAlignedX({
3422
+ xPx,
3423
+ widthPx: safeWidthPx,
3424
+ lineWidthPx: line.widthPx,
3425
+ align
3426
+ });
3427
+ const baselineYPx = lineTopPx + Math.max(1, Math.round(lineHeightPx * 0.78));
3428
+ line.segments.forEach((segment) => {
3429
+ const segmentWidthPx = segment.widthPx;
3430
+ if (segment.run.backgroundColor) {
3431
+ setCanvasFillStyle(context, segment.run.backgroundColor, "transparent");
3432
+ context.fillRect(cursorXPx, lineTopPx + 1, segmentWidthPx, lineHeightPx);
3433
+ }
3434
+ if (segment.font !== lastAppliedFont) {
3435
+ context.font = segment.font;
3436
+ lastAppliedFont = segment.font;
3437
+ }
3438
+ setCanvasFillStyle(
3439
+ context,
3440
+ segment.run.color,
3441
+ THUMBNAIL_DIRECT_DEFAULT_TEXT_COLOR
3442
+ );
3443
+ context.fillText(segment.text, cursorXPx, baselineYPx);
3444
+ cursorXPx += segmentWidthPx;
3445
+ });
3446
+ });
3447
+ context.restore();
3448
+ }
3449
+ function drawDirectThumbnailParagraph(context, paragraph) {
3450
+ const xPx = Math.round(paragraph.xPx);
3451
+ const yPx = Math.round(paragraph.yPx);
3452
+ const widthPx = Math.max(1, Math.round(paragraph.widthPx));
3453
+ const heightPx = Math.max(1, Math.round(paragraph.heightPx));
3454
+ if (paragraph.backgroundColor) {
3455
+ setCanvasFillStyle(context, paragraph.backgroundColor, "transparent");
3456
+ context.fillRect(xPx, yPx, widthPx, heightPx);
3457
+ }
3458
+ drawDirectThumbnailTextRuns({
3459
+ context,
3460
+ runs: paragraph.runs,
3461
+ xPx: xPx + 1,
3462
+ yPx,
3463
+ widthPx: Math.max(1, widthPx - 2),
3464
+ heightPx,
3465
+ align: paragraph.align,
3466
+ lineHeightPx: paragraph.lineHeightPx,
3467
+ startLineIndex: paragraph.startLineIndex
3468
+ });
3469
+ }
3470
+ function drawDirectThumbnailImagePlaceholder(context, image, hairlineSourcePx) {
3471
+ const xPx = Math.round(image.xPx);
3472
+ const yPx = Math.round(image.yPx);
3473
+ const widthPx = Math.max(1, Math.round(image.widthPx));
3474
+ const heightPx = Math.max(1, Math.round(image.heightPx));
3475
+ setCanvasFillStyle(
3476
+ context,
3477
+ image.backgroundColor,
3478
+ THUMBNAIL_DIRECT_IMAGE_BACKGROUND
3479
+ );
3480
+ context.fillRect(xPx, yPx, widthPx, heightPx);
3481
+ setCanvasStrokeStyle(
3482
+ context,
3483
+ image.borderColor,
3484
+ THUMBNAIL_DIRECT_TABLE_BORDER_COLOR
3485
+ );
3486
+ context.lineWidth = hairlineSourcePx;
3487
+ context.strokeRect(xPx, yPx, widthPx, heightPx);
3488
+ }
3489
+ function drawDirectThumbnailTable(context, table, hairlineSourcePx) {
3490
+ const tableXPx = Math.round(table.xPx);
3491
+ const tableYPx = Math.round(table.yPx);
3492
+ const tableWidthPx = Math.max(1, Math.round(table.widthPx));
3493
+ const tableHeightPx = Math.max(1, Math.round(table.heightPx));
3494
+ context.save();
3495
+ context.beginPath();
3496
+ context.rect(tableXPx, tableYPx, tableWidthPx, tableHeightPx);
3497
+ context.clip();
3498
+ setCanvasStrokeStyle(
3499
+ context,
3500
+ table.borderColor,
3501
+ THUMBNAIL_DIRECT_TABLE_BORDER_COLOR
3502
+ );
3503
+ context.lineWidth = hairlineSourcePx;
3504
+ table.cells.forEach((cell) => {
3505
+ const xPx = tableXPx + Math.round(cell.xPx);
3506
+ const yPx = tableYPx + Math.round(cell.yPx);
3507
+ const widthPx = Math.max(1, Math.round(cell.widthPx));
3508
+ const heightPx = Math.max(1, Math.round(cell.heightPx));
3509
+ if (cell.backgroundColor) {
3510
+ setCanvasFillStyle(context, cell.backgroundColor, "transparent");
3511
+ context.fillRect(xPx, yPx, widthPx, heightPx);
3512
+ }
3513
+ context.strokeRect(xPx, yPx, widthPx, heightPx);
3514
+ if (cell.runs?.length) {
3515
+ drawDirectThumbnailTextRuns({
3516
+ context,
3517
+ runs: cell.runs,
3518
+ xPx: xPx + 3,
3519
+ yPx: yPx + 2,
3520
+ widthPx: Math.max(1, widthPx - 6),
3521
+ heightPx: Math.max(1, heightPx - 4),
3522
+ lineHeightPx: 13
3523
+ });
3524
+ }
3525
+ });
3526
+ context.restore();
3527
+ }
3528
+ function renderDocxThumbnailSnapshotSurface(params) {
3529
+ if (typeof document === "undefined") {
3530
+ throw new Error("DOCX thumbnails require a browser environment.");
3531
+ }
3532
+ const sourceWidthPx = directThumbnailPositivePx(params.snapshot.sourceWidthPx);
3533
+ const sourceHeightPx = directThumbnailPositivePx(params.snapshot.sourceHeightPx);
3534
+ const pixelWidthPx = Math.max(1, Math.round(params.pixelWidthPx));
3535
+ const pixelHeightPx = Math.max(1, Math.round(params.pixelHeightPx));
3536
+ const surface = document.createElement("canvas");
3537
+ surface.width = pixelWidthPx;
3538
+ surface.height = pixelHeightPx;
3539
+ const context = surface.getContext("2d");
3540
+ if (!context) {
3541
+ throw new Error("2D canvas context is unavailable for DOCX thumbnails.");
3542
+ }
3543
+ const scaleX = pixelWidthPx / sourceWidthPx;
3544
+ const scaleY = pixelHeightPx / sourceHeightPx;
3545
+ const hairlineSourcePx = Math.max(0.75, 1 / Math.max(scaleX, scaleY));
3546
+ context.setTransform(scaleX, 0, 0, scaleY, 0, 0);
3547
+ context.imageSmoothingEnabled = true;
3548
+ context.imageSmoothingQuality = "high";
3549
+ setCanvasFillStyle(
3550
+ context,
3551
+ params.snapshot.pageBackgroundColor,
3552
+ "#ffffff"
3553
+ );
3554
+ context.fillRect(0, 0, sourceWidthPx, sourceHeightPx);
3555
+ params.snapshot.elements.slice(0, THUMBNAIL_DIRECT_MAX_ELEMENTS).forEach((element) => {
3556
+ switch (element.kind) {
3557
+ case "paragraph":
3558
+ drawDirectThumbnailParagraph(context, element);
3559
+ break;
3560
+ case "image-placeholder":
3561
+ drawDirectThumbnailImagePlaceholder(
3562
+ context,
3563
+ element,
3564
+ hairlineSourcePx
3565
+ );
3566
+ break;
3567
+ case "table":
3568
+ drawDirectThumbnailTable(context, element, hairlineSourcePx);
3569
+ break;
3570
+ }
3571
+ });
3572
+ return surface;
3573
+ }
3226
3574
  async function buildDocxThumbnailSvgMarkup(params) {
3227
3575
  const { pageElement, sourceWidthPx, sourceHeightPx, widthPx, heightPx } = params;
3228
3576
  const clone = pageElement.cloneNode(true);
@@ -3284,16 +3632,33 @@ async function rasterizeDocxThumbnailSurface(params) {
3284
3632
  return surface;
3285
3633
  }
3286
3634
  function blitDocxThumbnailSurface(surface, canvas, resolution) {
3287
- canvas.width = Math.max(1, Math.round(resolution.pixelWidthPx));
3288
- canvas.height = Math.max(1, Math.round(resolution.pixelHeightPx));
3289
- canvas.style.width = `${Math.max(1, Math.round(resolution.widthPx))}px`;
3290
- canvas.style.height = `${Math.max(1, Math.round(resolution.heightPx))}px`;
3635
+ const pixelWidth = Math.max(1, Math.round(resolution.pixelWidthPx));
3636
+ const pixelHeight = Math.max(1, Math.round(resolution.pixelHeightPx));
3637
+ const cssWidth = `${Math.max(1, Math.round(resolution.widthPx))}px`;
3638
+ const cssHeight = `${Math.max(1, Math.round(resolution.heightPx))}px`;
3639
+ let bufferResized = false;
3640
+ if (canvas.width !== pixelWidth) {
3641
+ canvas.width = pixelWidth;
3642
+ bufferResized = true;
3643
+ }
3644
+ if (canvas.height !== pixelHeight) {
3645
+ canvas.height = pixelHeight;
3646
+ bufferResized = true;
3647
+ }
3648
+ if (canvas.style.width !== cssWidth) {
3649
+ canvas.style.width = cssWidth;
3650
+ }
3651
+ if (canvas.style.height !== cssHeight) {
3652
+ canvas.style.height = cssHeight;
3653
+ }
3291
3654
  const context = canvas.getContext("2d");
3292
3655
  if (!context) {
3293
3656
  throw new Error("2D canvas context is unavailable for DOCX thumbnails.");
3294
3657
  }
3295
3658
  context.setTransform(1, 0, 0, 1, 0, 0);
3296
- context.clearRect(0, 0, canvas.width, canvas.height);
3659
+ if (!bufferResized) {
3660
+ context.clearRect(0, 0, canvas.width, canvas.height);
3661
+ }
3297
3662
  context.drawImage(surface, 0, 0, canvas.width, canvas.height);
3298
3663
  }
3299
3664
  var DocxThumbnailSurfaceCache = class {
@@ -3368,6 +3733,7 @@ var SerialIdleTaskQueue = class {
3368
3733
  now;
3369
3734
  pumpScheduled = false;
3370
3735
  running = false;
3736
+ nextSequence = 0;
3371
3737
  constructor(options) {
3372
3738
  this.scheduleTask = options?.scheduleTask ?? defaultScheduleTask;
3373
3739
  this.scheduleDelayed = options?.scheduleDelayed ?? defaultScheduleDelayed;
@@ -3377,14 +3743,23 @@ var SerialIdleTaskQueue = class {
3377
3743
  get pendingCount() {
3378
3744
  return this.pending.length;
3379
3745
  }
3380
- enqueue(key, run) {
3746
+ enqueue(key, run, options) {
3747
+ const priority = Number.isFinite(options?.priority) ? Number(options?.priority) : 0;
3381
3748
  return new Promise((resolve) => {
3382
3749
  const existing = this.pending.find((entry) => entry.key === key);
3383
3750
  if (existing) {
3384
3751
  existing.run = run;
3385
3752
  existing.resolvers.push(resolve);
3753
+ existing.priority = Math.min(existing.priority, priority);
3386
3754
  } else {
3387
- this.pending.push({ key, run, resolvers: [resolve] });
3755
+ this.pending.push({
3756
+ key,
3757
+ run,
3758
+ resolvers: [resolve],
3759
+ priority,
3760
+ sequence: this.nextSequence
3761
+ });
3762
+ this.nextSequence += 1;
3388
3763
  }
3389
3764
  this.schedulePump();
3390
3765
  });
@@ -3429,6 +3804,8 @@ var SerialIdleTaskQueue = class {
3429
3804
  }
3430
3805
  const now = this.now();
3431
3806
  let earliestWaitMs;
3807
+ let bestIndex = -1;
3808
+ let bestEntry;
3432
3809
  for (let index = 0; index < this.pending.length; index += 1) {
3433
3810
  const candidate = this.pending[index];
3434
3811
  if (!candidate) {
@@ -3437,11 +3814,18 @@ var SerialIdleTaskQueue = class {
3437
3814
  const lastRunAt = this.lastRunAtByKey.get(candidate.key);
3438
3815
  const waitMs = lastRunAt === void 0 ? 0 : lastRunAt + this.minTaskIntervalMs - now;
3439
3816
  if (waitMs <= 0) {
3440
- this.pending.splice(index, 1);
3441
- return { entry: candidate };
3817
+ if (!bestEntry || candidate.priority < bestEntry.priority || candidate.priority === bestEntry.priority && candidate.sequence < bestEntry.sequence) {
3818
+ bestEntry = candidate;
3819
+ bestIndex = index;
3820
+ }
3821
+ continue;
3442
3822
  }
3443
3823
  earliestWaitMs = earliestWaitMs === void 0 ? waitMs : Math.min(earliestWaitMs, waitMs);
3444
3824
  }
3825
+ if (bestEntry && bestIndex >= 0) {
3826
+ this.pending.splice(bestIndex, 1);
3827
+ return { entry: bestEntry };
3828
+ }
3445
3829
  return earliestWaitMs === void 0 ? void 0 : { retryDelayMs: earliestWaitMs };
3446
3830
  }
3447
3831
  async runNext() {
@@ -22439,13 +22823,15 @@ function ensureDocxViewerPageSurfaceRegistry(editor) {
22439
22823
  registry = {
22440
22824
  pageElements: /* @__PURE__ */ new Map(),
22441
22825
  pageContentKeys: /* @__PURE__ */ new Map(),
22826
+ pageSizes: /* @__PURE__ */ new Map(),
22827
+ pageThumbnailSnapshots: /* @__PURE__ */ new Map(),
22442
22828
  listeners: /* @__PURE__ */ new Set()
22443
22829
  };
22444
22830
  docxViewerPageSurfaceRegistryByEditor.set(owner, registry);
22445
22831
  }
22446
22832
  return registry;
22447
22833
  }
22448
- function syncDocxViewerPageSurfaceContentKeys(editor, contentKeysByPage) {
22834
+ function syncDocxViewerPageSurfaceContentKeys(editor, contentKeysByPage, pageSizesByPage = [], thumbnailSnapshotsByPage = []) {
22449
22835
  const registry = ensureDocxViewerPageSurfaceRegistry(editor);
22450
22836
  let changed = false;
22451
22837
  contentKeysByPage.forEach((contentKey, pageIndex) => {
@@ -22454,12 +22840,47 @@ function syncDocxViewerPageSurfaceContentKeys(editor, contentKeysByPage) {
22454
22840
  changed = true;
22455
22841
  }
22456
22842
  });
22843
+ pageSizesByPage.forEach((pageSize, pageIndex) => {
22844
+ const widthPx = Math.max(1, Math.round(pageSize.widthPx));
22845
+ const heightPx = Math.max(1, Math.round(pageSize.heightPx));
22846
+ const previous = registry.pageSizes.get(pageIndex);
22847
+ if (previous?.widthPx !== widthPx || previous?.heightPx !== heightPx) {
22848
+ registry.pageSizes.set(pageIndex, { widthPx, heightPx });
22849
+ changed = true;
22850
+ }
22851
+ });
22852
+ thumbnailSnapshotsByPage.forEach((snapshot, pageIndex) => {
22853
+ const previous = registry.pageThumbnailSnapshots.get(pageIndex);
22854
+ if (!snapshot) {
22855
+ if (previous) {
22856
+ registry.pageThumbnailSnapshots.delete(pageIndex);
22857
+ changed = true;
22858
+ }
22859
+ return;
22860
+ }
22861
+ if (previous?.key !== snapshot.key) {
22862
+ registry.pageThumbnailSnapshots.set(pageIndex, snapshot);
22863
+ changed = true;
22864
+ }
22865
+ });
22457
22866
  registry.pageContentKeys.forEach((_, pageIndex) => {
22458
22867
  if (pageIndex >= contentKeysByPage.length) {
22459
22868
  registry.pageContentKeys.delete(pageIndex);
22460
22869
  changed = true;
22461
22870
  }
22462
22871
  });
22872
+ registry.pageSizes.forEach((_, pageIndex) => {
22873
+ if (pageIndex >= pageSizesByPage.length) {
22874
+ registry.pageSizes.delete(pageIndex);
22875
+ changed = true;
22876
+ }
22877
+ });
22878
+ registry.pageThumbnailSnapshots.forEach((_, pageIndex) => {
22879
+ if (pageIndex >= thumbnailSnapshotsByPage.length) {
22880
+ registry.pageThumbnailSnapshots.delete(pageIndex);
22881
+ changed = true;
22882
+ }
22883
+ });
22463
22884
  if (changed) {
22464
22885
  notifyDocxViewerPageSurfaceSubscribers(registry);
22465
22886
  }
@@ -22476,7 +22897,7 @@ function notifyDocxViewerPageSurfaceSubscribers(registry) {
22476
22897
  listener();
22477
22898
  });
22478
22899
  }
22479
- function registerDocxViewerPageSurface(editor, pageIndex, element) {
22900
+ function registerDocxViewerPageSurface(editor, pageIndex, element, previousElement) {
22480
22901
  const registry = ensureDocxViewerPageSurfaceRegistry(editor);
22481
22902
  const normalizedPageIndex = Math.max(0, Math.round(pageIndex));
22482
22903
  const currentElement = registry.pageElements.get(normalizedPageIndex);
@@ -22491,9 +22912,22 @@ function registerDocxViewerPageSurface(editor, pageIndex, element) {
22491
22912
  if (!currentElement) {
22492
22913
  return;
22493
22914
  }
22915
+ if (previousElement && currentElement !== previousElement) {
22916
+ return;
22917
+ }
22494
22918
  registry.pageElements.delete(normalizedPageIndex);
22495
22919
  notifyDocxViewerPageSurfaceSubscribers(registry);
22496
22920
  }
22921
+ function resolveDocxViewerRegisteredPageSurfaceSize(registry, pageIndex, fallbackWidthPx, fallbackHeightPx) {
22922
+ const registeredSize = registry.pageSizes.get(pageIndex);
22923
+ if (registeredSize) {
22924
+ return registeredSize;
22925
+ }
22926
+ return {
22927
+ widthPx: Math.max(1, Math.round(fallbackWidthPx)),
22928
+ heightPx: Math.max(1, Math.round(fallbackHeightPx))
22929
+ };
22930
+ }
22497
22931
  function resolveDocxViewerPageSurfaceSize(element, fallbackWidthPx, fallbackHeightPx) {
22498
22932
  if (element) {
22499
22933
  const rect = element.getBoundingClientRect();
@@ -22527,58 +22961,546 @@ function resolveDocxViewerPageSurfaceSize(element, fallbackWidthPx, fallbackHeig
22527
22961
  heightPx: Math.max(1, Math.round(fallbackHeightPx))
22528
22962
  };
22529
22963
  }
22530
- var DOCX_THUMBNAIL_SURFACE_CACHE_MAX_ENTRIES = 32;
22531
- var DOCX_THUMBNAIL_MIN_RASTER_INTERVAL_MS = 200;
22532
- function resolveDocxPageThumbnailResolution(options) {
22533
- const safeSourceWidthPx = Math.max(1, Math.round(options.sourceWidthPx));
22534
- const safeSourceHeightPx = Math.max(1, Math.round(options.sourceHeightPx));
22535
- const resolutionBounds = typeof options.resolution === "number" && Number.isFinite(options.resolution) && options.resolution > 0 ? {
22536
- maxWidthPx: Number(options.resolution),
22537
- maxHeightPx: Number(options.resolution)
22538
- } : typeof options.resolution === "object" && options.resolution ? {
22539
- maxWidthPx: Number.isFinite(options.resolution.maxWidth) && Number(options.resolution.maxWidth) > 0 ? Number(options.resolution.maxWidth) : void 0,
22540
- maxHeightPx: Number.isFinite(options.resolution.maxHeight) && Number(options.resolution.maxHeight) > 0 ? Number(options.resolution.maxHeight) : void 0
22541
- } : void 0;
22542
- const widthBoundPx = Number.isFinite(options.maxWidthPx) ? Math.max(1, Math.round(options.maxWidthPx)) : Number.isFinite(resolutionBounds?.maxWidthPx) ? Math.max(1, Math.round(resolutionBounds?.maxWidthPx)) : 180;
22543
- const heightBoundPx = Number.isFinite(options.maxHeightPx) ? Math.max(1, Math.round(options.maxHeightPx)) : Number.isFinite(resolutionBounds?.maxHeightPx) ? Math.max(1, Math.round(resolutionBounds?.maxHeightPx)) : Number.POSITIVE_INFINITY;
22544
- const scale = Math.min(
22545
- 1,
22546
- widthBoundPx / safeSourceWidthPx,
22547
- heightBoundPx / safeSourceHeightPx
22548
- );
22549
- const pixelRatio = Number.isFinite(options.pixelRatio) ? Math.max(1, Number(options.pixelRatio)) : 1;
22550
- const widthPx = Math.max(1, Math.round(safeSourceWidthPx * scale));
22551
- const heightPx = Math.max(1, Math.round(safeSourceHeightPx * scale));
22552
- return {
22553
- widthPx,
22554
- heightPx,
22555
- pixelWidthPx: Math.max(1, Math.round(widthPx * pixelRatio)),
22556
- pixelHeightPx: Math.max(1, Math.round(heightPx * pixelRatio)),
22557
- scale
22558
- };
22964
+ var DOCX_DIRECT_THUMBNAIL_MAX_ELEMENTS_PER_PAGE = 260;
22965
+ var DOCX_DIRECT_THUMBNAIL_MAX_TEXT_RUNS = 28;
22966
+ var DOCX_DIRECT_THUMBNAIL_MAX_TEXT_CHARS = 900;
22967
+ var DOCX_DIRECT_THUMBNAIL_MAX_TABLE_CELLS = 220;
22968
+ var DOCX_DIRECT_THUMBNAIL_MAX_CELL_TEXT_CHARS = 120;
22969
+ function docxThumbnailCssNumber(value) {
22970
+ if (typeof value === "number" && Number.isFinite(value)) {
22971
+ return value;
22972
+ }
22973
+ if (typeof value === "string") {
22974
+ const parsed = Number.parseFloat(value);
22975
+ return Number.isFinite(parsed) ? parsed : 0;
22976
+ }
22977
+ return 0;
22559
22978
  }
22560
- function useDocxPageThumbnails(editor, options = {}) {
22561
- const pageSurfaceRegistryOwner = docxViewerPageSurfaceRegistryOwner(editor);
22562
- const pageSurfaceRegistryEditor = React.useMemo(
22563
- () => ({ syncPaginationInfo: editor.syncPaginationInfo }),
22564
- [pageSurfaceRegistryOwner]
22979
+ function normalizeDocxThumbnailColor(value) {
22980
+ const normalized = value?.trim();
22981
+ if (!normalized || normalized.toLowerCase() === "auto") {
22982
+ return void 0;
22983
+ }
22984
+ if (/^[0-9a-fA-F]{6}$/.test(normalized)) {
22985
+ return `#${normalized}`;
22986
+ }
22987
+ return normalized;
22988
+ }
22989
+ function docxThumbnailTextRunStylesMatch(left, right) {
22990
+ return left.bold === right.bold && left.italic === right.italic && left.color === right.color && left.backgroundColor === right.backgroundColor && left.fontSizePx === right.fontSizePx && left.fontFamily === right.fontFamily;
22991
+ }
22992
+ function appendDocxThumbnailTextRun(runs, run, remaining) {
22993
+ if (remaining.chars <= 0 || runs.length >= DOCX_DIRECT_THUMBNAIL_MAX_TEXT_RUNS) {
22994
+ return;
22995
+ }
22996
+ const text = run.text.slice(0, remaining.chars);
22997
+ if (!text) {
22998
+ return;
22999
+ }
23000
+ remaining.chars -= text.length;
23001
+ const nextRun = { ...run, text };
23002
+ const previous = runs[runs.length - 1];
23003
+ if (previous && docxThumbnailTextRunStylesMatch(previous, nextRun)) {
23004
+ previous.text += nextRun.text;
23005
+ return;
23006
+ }
23007
+ runs.push(nextRun);
23008
+ }
23009
+ function docxThumbnailFallbackHeadingRunStyle(paragraph) {
23010
+ const headingLevel = paragraph.style?.headingLevel;
23011
+ return headingLevel && headingLevel >= 1 && headingLevel <= 6 ? DEFAULT_WORD_HEADING_RUN_STYLES[headingLevel] : void 0;
23012
+ }
23013
+ function docxThumbnailTextRunsFromParagraph(paragraph, documentTheme, maxChars = DOCX_DIRECT_THUMBNAIL_MAX_TEXT_CHARS) {
23014
+ const runs = [];
23015
+ const remaining = { chars: maxChars };
23016
+ const fallbackHeadingStyle = docxThumbnailFallbackHeadingRunStyle(paragraph);
23017
+ const fallbackFontFamily = cssFontFamily(fallbackHeadingStyle?.fontFamily) ?? cssFontFamily(paragraphDominantFontFamily(paragraph));
23018
+ paragraph.children.forEach((child) => {
23019
+ if (remaining.chars <= 0) {
23020
+ return;
23021
+ }
23022
+ const style = child.type === "text" || child.type === "form-field" ? child.style : void 0;
23023
+ const text = child.type === "text" ? child.text : child.type === "form-field" ? formFieldDisplayValue2(child) : child.type === "image" ? child.alt || "[image]" : "";
23024
+ appendDocxThumbnailTextRun(
23025
+ runs,
23026
+ {
23027
+ text,
23028
+ bold: style?.bold ?? fallbackHeadingStyle?.bold,
23029
+ italic: style?.italic ?? fallbackHeadingStyle?.italic,
23030
+ color: normalizeDocxThumbnailColor(
23031
+ themedRunColor(style?.color ?? fallbackHeadingStyle?.color, documentTheme)
23032
+ ),
23033
+ backgroundColor: normalizeDocxThumbnailColor(
23034
+ style?.backgroundColor ?? resolveHighlightColor(style?.highlight)
23035
+ ),
23036
+ fontSizePx: style?.fontSizePt && style.fontSizePt > 0 ? Math.max(6, style.fontSizePt * 96 / 72) : fallbackHeadingStyle?.fontSizePt ? Math.max(6, fallbackHeadingStyle.fontSizePt * 96 / 72) : paragraphBaseFontSizePx(paragraph),
23037
+ fontFamily: cssFontFamily(style?.fontFamily) ?? fallbackFontFamily
23038
+ },
23039
+ remaining
23040
+ );
23041
+ });
23042
+ return runs;
23043
+ }
23044
+ function docxThumbnailCellTextRuns(cell, documentTheme) {
23045
+ const paragraph = tableCellParagraphsRecursively(cell.nodes).find(
23046
+ (candidate) => paragraphText(candidate).trim().length > 0
22565
23047
  );
22566
- const [pageSurfaceEpoch, setPageSurfaceEpoch] = React.useState(0);
22567
- const [pageThumbnailStates, setPageThumbnailStates] = React.useState(() => /* @__PURE__ */ new Map());
22568
- const attachedCanvasByPageRef = React.useRef(
22569
- /* @__PURE__ */ new Map()
23048
+ if (!paragraph) {
23049
+ return void 0;
23050
+ }
23051
+ const runs = docxThumbnailTextRunsFromParagraph(
23052
+ paragraph,
23053
+ documentTheme,
23054
+ DOCX_DIRECT_THUMBNAIL_MAX_CELL_TEXT_CHARS
22570
23055
  );
22571
- const canvasRefCallbacksRef = React.useRef(/* @__PURE__ */ new Map());
22572
- const renderToCanvasCallbacksRef = React.useRef(/* @__PURE__ */ new Map());
22573
- const fallbackLayout = React.useMemo(
22574
- () => resolveDocumentLayout(editor.model),
22575
- [editor.model]
23056
+ return runs.length > 0 ? runs : void 0;
23057
+ }
23058
+ function buildDocxThumbnailParagraphElements(params) {
23059
+ const {
23060
+ paragraph,
23061
+ segment,
23062
+ contentLeftPx,
23063
+ contentWidthPx,
23064
+ yPx,
23065
+ heightPx,
23066
+ numberingDefinitions,
23067
+ docGridLinePitchPx,
23068
+ documentTheme
23069
+ } = params;
23070
+ const blockStyle = paragraphBlockStyle(
23071
+ paragraph,
23072
+ numberingDefinitions,
23073
+ void 0,
23074
+ docGridLinePitchPx
22576
23075
  );
22577
- const pageSurfaceRegistry = React.useMemo(
22578
- () => ensureDocxViewerPageSurfaceRegistry(pageSurfaceRegistryEditor),
22579
- [pageSurfaceRegistryEditor]
23076
+ const paragraphLineRange = segment.paragraphLineRange;
23077
+ const marginTopPx = paragraphLineRange && paragraphLineRange.startLineIndex > 0 ? 0 : Math.max(0, docxThumbnailCssNumber(blockStyle.marginTop));
23078
+ const marginBottomPx = paragraphLineRange && paragraphLineRange.endLineIndex < paragraphLineRange.totalLineCount ? 0 : Math.max(0, docxThumbnailCssNumber(blockStyle.marginBottom));
23079
+ const marginLeftPx = docxThumbnailCssNumber(blockStyle.marginLeft);
23080
+ const marginRightPx = Math.max(0, docxThumbnailCssNumber(blockStyle.marginRight));
23081
+ const xPx = contentLeftPx + marginLeftPx;
23082
+ const widthPx = Math.max(8, contentWidthPx - marginLeftPx - marginRightPx);
23083
+ const bodyYPx = yPx + marginTopPx;
23084
+ const bodyHeightPx = Math.max(1, heightPx - marginTopPx - marginBottomPx);
23085
+ const runs = docxThumbnailTextRunsFromParagraph(paragraph, documentTheme);
23086
+ const elements = [];
23087
+ elements.push({
23088
+ kind: "paragraph",
23089
+ xPx,
23090
+ yPx: bodyYPx,
23091
+ widthPx,
23092
+ heightPx: bodyHeightPx,
23093
+ align: paragraph.style?.align,
23094
+ backgroundColor: normalizeDocxThumbnailColor(
23095
+ paragraph.style?.backgroundColor
23096
+ ),
23097
+ lineHeightPx: paragraphLineRange?.lineHeightPx ?? estimateParagraphLineHeightPx(paragraph, docGridLinePitchPx),
23098
+ startLineIndex: paragraphLineRange?.startLineIndex,
23099
+ runs
23100
+ });
23101
+ const hasVisibleText = runs.some((run) => run.text.trim().length > 0);
23102
+ if (!hasVisibleText) {
23103
+ const imageRuns = paragraph.children.filter(
23104
+ (child) => child.type === "image"
23105
+ );
23106
+ imageRuns.slice(0, 4).forEach((imageRun) => {
23107
+ const imageWidthPx = Math.max(18, imageRun.widthPx ?? widthPx * 0.5);
23108
+ const imageHeightPx = Math.max(18, imageRun.heightPx ?? bodyHeightPx * 0.5);
23109
+ const scale = Math.min(
23110
+ 1,
23111
+ (widthPx - 4) / imageWidthPx,
23112
+ (bodyHeightPx - 4) / imageHeightPx
23113
+ );
23114
+ elements.push({
23115
+ kind: "image-placeholder",
23116
+ xPx: xPx + 2,
23117
+ yPx: bodyYPx + 2,
23118
+ widthPx: Math.max(12, imageWidthPx * scale),
23119
+ heightPx: Math.max(12, imageHeightPx * scale)
23120
+ });
23121
+ });
23122
+ }
23123
+ return elements;
23124
+ }
23125
+ function buildDocxThumbnailTableElement(params) {
23126
+ const {
23127
+ table,
23128
+ segment,
23129
+ contentLeftPx,
23130
+ contentWidthPx,
23131
+ contentHeightPx,
23132
+ yPx,
23133
+ heightPx,
23134
+ numberingDefinitions,
23135
+ docGridLinePitchPx,
23136
+ documentTheme
23137
+ } = params;
23138
+ const columnCount = tableColumnCount(table);
23139
+ const tableIndentPx = twipsToSignedPixels(table.style?.indentTwips) ?? 0;
23140
+ const tableWidthPx = twipsToPixels(table.style?.widthTwips);
23141
+ const definedWidthsTwips = columnWidthsFromTableDefinition(table, columnCount);
23142
+ const rawTableColumnWidthsPx = definedWidthsTwips && definedWidthsTwips.length > 0 ? normalizeColumnWidthsPx(
23143
+ definedWidthsTwips.map((widthTwips) => twipsToPixels(widthTwips) ?? 0),
23144
+ columnCount,
23145
+ tableWidthPx,
23146
+ 1
23147
+ ) : defaultColumnWidthsPx(columnCount, tableWidthPx);
23148
+ const rawResolvedTableWidthPx = tableWidthPx ?? rawTableColumnWidthsPx.reduce((sum, widthPx) => sum + widthPx, 0);
23149
+ const maxTableWidthPx = Math.max(
23150
+ 24,
23151
+ contentWidthPx - tableIndentPx - resolveCollapsedTableHorizontalOuterBleedPx(table, columnCount)
22580
23152
  );
22581
- React.useEffect(
23153
+ const resolvedTableWidthPx = clampTableWidthPx(
23154
+ rawResolvedTableWidthPx,
23155
+ maxTableWidthPx
23156
+ );
23157
+ const { columnWidthsPx } = resolveFittedTableColumnWidths(
23158
+ table,
23159
+ rawTableColumnWidthsPx,
23160
+ resolvedTableWidthPx
23161
+ );
23162
+ const rowHeightsPx = estimateTableRowHeightsPx(
23163
+ table,
23164
+ contentWidthPx,
23165
+ numberingDefinitions,
23166
+ docGridLinePitchPx,
23167
+ contentHeightPx
23168
+ );
23169
+ const startRowIndex = Math.max(
23170
+ 0,
23171
+ segment.tableRowSlice?.rowIndex ?? segment.tableRowRange?.startRowIndex ?? 0
23172
+ );
23173
+ const endRowIndex = Math.min(
23174
+ table.rows.length,
23175
+ segment.tableRowSlice ? startRowIndex + 1 : segment.tableRowRange?.endRowIndex ?? table.rows.length
23176
+ );
23177
+ const cells = [];
23178
+ let rowYPx = segment.tableRowSlice ? -Math.max(0, segment.tableRowSlice.startOffsetPx) : 0;
23179
+ for (let rowIndex = startRowIndex; rowIndex < endRowIndex && cells.length < DOCX_DIRECT_THUMBNAIL_MAX_TABLE_CELLS; rowIndex += 1) {
23180
+ const row = table.rows[rowIndex];
23181
+ if (!row) {
23182
+ continue;
23183
+ }
23184
+ const rowHeightPx = segment.tableRowSlice && rowIndex === segment.tableRowSlice.rowIndex ? Math.max(1, segment.tableRowSlice.totalRowHeightPx) : Math.max(1, rowHeightsPx[rowIndex] ?? MIN_PARAGRAPH_LINE_HEIGHT_PX);
23185
+ let columnCursor = 0;
23186
+ row.cells.forEach((cell) => {
23187
+ if (cells.length >= DOCX_DIRECT_THUMBNAIL_MAX_TABLE_CELLS) {
23188
+ return;
23189
+ }
23190
+ const span = cell.style?.gridSpan && cell.style.gridSpan > 1 ? cell.style.gridSpan : 1;
23191
+ const xPx = columnWidthsPx.slice(0, columnCursor).reduce((sum, widthPx2) => sum + widthPx2, 0);
23192
+ const widthPx = columnWidthsPx.slice(columnCursor, columnCursor + span).reduce((sum, widthPx2) => sum + widthPx2, 0);
23193
+ columnCursor += span;
23194
+ cells.push({
23195
+ xPx,
23196
+ yPx: rowYPx,
23197
+ widthPx: Math.max(1, widthPx),
23198
+ heightPx: rowHeightPx,
23199
+ backgroundColor: normalizeDocxThumbnailColor(
23200
+ cell.style?.backgroundColor ?? row.style?.backgroundColor
23201
+ ),
23202
+ runs: docxThumbnailCellTextRuns(cell, documentTheme)
23203
+ });
23204
+ });
23205
+ rowYPx += rowHeightPx;
23206
+ }
23207
+ if (cells.length === 0) {
23208
+ return void 0;
23209
+ }
23210
+ return {
23211
+ kind: "table",
23212
+ xPx: contentLeftPx + tableIndentPx,
23213
+ yPx,
23214
+ widthPx: Math.max(1, resolvedTableWidthPx),
23215
+ heightPx: Math.max(1, heightPx),
23216
+ cells
23217
+ };
23218
+ }
23219
+ function buildDocxPageThumbnailRenderSnapshotEntries(params) {
23220
+ const {
23221
+ model,
23222
+ pageNodeSegmentsByPage,
23223
+ pageSectionInfoByIndex,
23224
+ contentKeysByPage,
23225
+ fallbackLayout,
23226
+ documentTheme,
23227
+ docGridLinePitchPxByNodeIndex,
23228
+ numberingDefinitions
23229
+ } = params;
23230
+ return pageNodeSegmentsByPage.map((pageSegments, pageIndex) => {
23231
+ const key = `${contentKeysByPage[pageIndex] ?? ""}|theme:${documentTheme}`;
23232
+ let cachedSnapshot;
23233
+ return {
23234
+ key,
23235
+ getSnapshot: () => {
23236
+ if (cachedSnapshot) {
23237
+ return cachedSnapshot;
23238
+ }
23239
+ const pageLayout = pageSectionInfoByIndex[pageIndex]?.layout ?? fallbackLayout;
23240
+ const contentLeftPx = pageLayout.marginsPx.left;
23241
+ const contentTopPx = pageLayout.marginsPx.top;
23242
+ const contentWidthPx = Math.max(
23243
+ 1,
23244
+ pageLayout.pageWidthPx - pageLayout.marginsPx.left - pageLayout.marginsPx.right
23245
+ );
23246
+ const contentHeightPx = Math.max(
23247
+ 1,
23248
+ pageLayout.pageHeightPx - pageLayout.marginsPx.top - pageLayout.marginsPx.bottom
23249
+ );
23250
+ const elements = [];
23251
+ let yPx = contentTopPx;
23252
+ for (const segment of pageSegments) {
23253
+ if (elements.length >= DOCX_DIRECT_THUMBNAIL_MAX_ELEMENTS_PER_PAGE) {
23254
+ break;
23255
+ }
23256
+ const node = model.nodes[segment.nodeIndex];
23257
+ if (!node) {
23258
+ continue;
23259
+ }
23260
+ const docGridLinePitchPx = docGridLinePitchPxByNodeIndex.get(
23261
+ segment.nodeIndex
23262
+ );
23263
+ const segmentHeightPx = estimateRenderedPageSegmentHeightPx(
23264
+ node,
23265
+ segment,
23266
+ model,
23267
+ contentWidthPx,
23268
+ numberingDefinitions,
23269
+ docGridLinePitchPx
23270
+ );
23271
+ if (node.type === "paragraph" && !segment.tableRowRange) {
23272
+ elements.push(
23273
+ ...buildDocxThumbnailParagraphElements({
23274
+ paragraph: node,
23275
+ segment,
23276
+ contentLeftPx,
23277
+ contentTopPx,
23278
+ contentWidthPx,
23279
+ yPx,
23280
+ heightPx: segmentHeightPx,
23281
+ numberingDefinitions,
23282
+ docGridLinePitchPx,
23283
+ documentTheme
23284
+ })
23285
+ );
23286
+ } else if (node.type === "table") {
23287
+ const tableElement = buildDocxThumbnailTableElement({
23288
+ table: node,
23289
+ segment,
23290
+ contentLeftPx,
23291
+ contentWidthPx,
23292
+ contentHeightPx,
23293
+ yPx,
23294
+ heightPx: segmentHeightPx,
23295
+ numberingDefinitions,
23296
+ docGridLinePitchPx,
23297
+ documentTheme
23298
+ });
23299
+ if (tableElement) {
23300
+ elements.push(tableElement);
23301
+ }
23302
+ }
23303
+ yPx += Math.max(1, segmentHeightPx);
23304
+ }
23305
+ cachedSnapshot = {
23306
+ key,
23307
+ sourceWidthPx: pageLayout.pageWidthPx,
23308
+ sourceHeightPx: pageLayout.pageHeightPx,
23309
+ pageBackgroundColor: documentTheme === "dark" ? "#111827" : "#ffffff",
23310
+ elements
23311
+ };
23312
+ return cachedSnapshot;
23313
+ }
23314
+ };
23315
+ });
23316
+ }
23317
+ function DocxDetachedThumbnailPageSurface({
23318
+ editor,
23319
+ pageIndex
23320
+ }) {
23321
+ return /* @__PURE__ */ jsx(
23322
+ DocxEditorViewer,
23323
+ {
23324
+ editor,
23325
+ mode: "read-only",
23326
+ visiblePageRange: { startPageIndex: pageIndex, endPageIndex: pageIndex },
23327
+ pageVirtualization: { enabled: false },
23328
+ showTrackedChanges: editor.showTrackedChanges,
23329
+ showComments: editor.showComments,
23330
+ style: {
23331
+ background: "transparent",
23332
+ padding: 0,
23333
+ margin: 0
23334
+ }
23335
+ }
23336
+ );
23337
+ }
23338
+ var DocxDetachedThumbnailSurfaceRenderer = class {
23339
+ host;
23340
+ root;
23341
+ activePageIndex;
23342
+ activeRenderKey;
23343
+ async renderPageSurface(params) {
23344
+ if (typeof document === "undefined" || typeof window === "undefined") {
23345
+ return void 0;
23346
+ }
23347
+ const { editor, registry, pageIndex, renderKey } = params;
23348
+ await this.ensureRoot();
23349
+ if (!this.root) {
23350
+ return void 0;
23351
+ }
23352
+ if (this.activePageIndex !== pageIndex || this.activeRenderKey !== renderKey) {
23353
+ this.activePageIndex = pageIndex;
23354
+ this.activeRenderKey = renderKey;
23355
+ this.root.render(
23356
+ /* @__PURE__ */ jsx(
23357
+ DocxDetachedThumbnailPageSurface,
23358
+ {
23359
+ editor,
23360
+ pageIndex
23361
+ }
23362
+ )
23363
+ );
23364
+ }
23365
+ return this.waitForPageSurface(registry, pageIndex);
23366
+ }
23367
+ clear() {
23368
+ if (this.root) {
23369
+ this.root.unmount();
23370
+ this.root = void 0;
23371
+ }
23372
+ if (this.host?.parentNode) {
23373
+ this.host.parentNode.removeChild(this.host);
23374
+ }
23375
+ this.host = void 0;
23376
+ this.activePageIndex = void 0;
23377
+ this.activeRenderKey = void 0;
23378
+ }
23379
+ async ensureRoot() {
23380
+ if (this.root) {
23381
+ return;
23382
+ }
23383
+ if (typeof document === "undefined") {
23384
+ return;
23385
+ }
23386
+ const host = document.createElement("div");
23387
+ host.setAttribute("data-docx-thumbnail-detached-renderer", "true");
23388
+ Object.assign(host.style, {
23389
+ position: "fixed",
23390
+ left: "-100000px",
23391
+ top: "0",
23392
+ width: "1px",
23393
+ height: "1px",
23394
+ overflow: "visible",
23395
+ opacity: "0",
23396
+ pointerEvents: "none",
23397
+ zIndex: "-1"
23398
+ });
23399
+ document.body.appendChild(host);
23400
+ this.host = host;
23401
+ const { createRoot } = await import("react-dom/client");
23402
+ this.root = createRoot(host);
23403
+ }
23404
+ async waitForPageSurface(registry, pageIndex) {
23405
+ for (let attempt = 0; attempt < 8; attempt += 1) {
23406
+ const pageElement2 = registry.pageElements.get(pageIndex);
23407
+ if (pageElement2?.isConnected) {
23408
+ return pageElement2;
23409
+ }
23410
+ await new Promise((resolve) => {
23411
+ window.requestAnimationFrame(() => resolve());
23412
+ });
23413
+ }
23414
+ const pageElement = registry.pageElements.get(pageIndex);
23415
+ return pageElement?.isConnected ? pageElement : void 0;
23416
+ }
23417
+ };
23418
+ var DOCX_THUMBNAIL_SURFACE_CACHE_MAX_ENTRIES = 64;
23419
+ var DOCX_THUMBNAIL_MIN_RASTER_INTERVAL_MS = 200;
23420
+ var DOCX_THUMBNAIL_RENDER_PRIORITY_VISIBLE = 0;
23421
+ var DOCX_THUMBNAIL_RENDER_PRIORITY_ATTACHED = 1;
23422
+ var DOCX_THUMBNAIL_RENDER_PRIORITY_PREFETCH = 2;
23423
+ function normalizeDocxThumbnailMinRasterIntervalMs(value) {
23424
+ return Number.isFinite(value) && Number(value) >= 0 ? Number(value) : DOCX_THUMBNAIL_MIN_RASTER_INTERVAL_MS;
23425
+ }
23426
+ function normalizeDocxThumbnailPageIndexes(indexes, totalPages) {
23427
+ if (!indexes?.length || totalPages <= 0) {
23428
+ return [];
23429
+ }
23430
+ const seen = /* @__PURE__ */ new Set();
23431
+ const normalized = [];
23432
+ indexes.forEach((pageIndex) => {
23433
+ if (!Number.isFinite(pageIndex)) {
23434
+ return;
23435
+ }
23436
+ const roundedPageIndex = Math.trunc(pageIndex);
23437
+ if (roundedPageIndex < 0 || roundedPageIndex >= totalPages || seen.has(roundedPageIndex)) {
23438
+ return;
23439
+ }
23440
+ seen.add(roundedPageIndex);
23441
+ normalized.push(roundedPageIndex);
23442
+ });
23443
+ return normalized;
23444
+ }
23445
+ function docxThumbnailPageIndexesKey(pageIndexes) {
23446
+ return pageIndexes.join(",");
23447
+ }
23448
+ function docxThumbnailCanvasQueueKey(canvasId) {
23449
+ return `canvas:${canvasId}`;
23450
+ }
23451
+ function docxThumbnailPrefetchQueueKey(pageIndex) {
23452
+ return `prefetch:${pageIndex}`;
23453
+ }
23454
+ function resolveDocxPageThumbnailResolution(options) {
23455
+ const safeSourceWidthPx = Math.max(1, Math.round(options.sourceWidthPx));
23456
+ const safeSourceHeightPx = Math.max(1, Math.round(options.sourceHeightPx));
23457
+ const resolutionBounds = typeof options.resolution === "number" && Number.isFinite(options.resolution) && options.resolution > 0 ? {
23458
+ maxWidthPx: Number(options.resolution),
23459
+ maxHeightPx: Number(options.resolution)
23460
+ } : typeof options.resolution === "object" && options.resolution ? {
23461
+ maxWidthPx: Number.isFinite(options.resolution.maxWidth) && Number(options.resolution.maxWidth) > 0 ? Number(options.resolution.maxWidth) : void 0,
23462
+ maxHeightPx: Number.isFinite(options.resolution.maxHeight) && Number(options.resolution.maxHeight) > 0 ? Number(options.resolution.maxHeight) : void 0
23463
+ } : void 0;
23464
+ const widthBoundPx = Number.isFinite(options.maxWidthPx) ? Math.max(1, Math.round(options.maxWidthPx)) : Number.isFinite(resolutionBounds?.maxWidthPx) ? Math.max(1, Math.round(resolutionBounds?.maxWidthPx)) : 180;
23465
+ const heightBoundPx = Number.isFinite(options.maxHeightPx) ? Math.max(1, Math.round(options.maxHeightPx)) : Number.isFinite(resolutionBounds?.maxHeightPx) ? Math.max(1, Math.round(resolutionBounds?.maxHeightPx)) : Number.POSITIVE_INFINITY;
23466
+ const scale = Math.min(
23467
+ 1,
23468
+ widthBoundPx / safeSourceWidthPx,
23469
+ heightBoundPx / safeSourceHeightPx
23470
+ );
23471
+ const pixelRatio = Number.isFinite(options.pixelRatio) ? Math.max(1, Number(options.pixelRatio)) : 1;
23472
+ const widthPx = Math.max(1, Math.round(safeSourceWidthPx * scale));
23473
+ const heightPx = Math.max(1, Math.round(safeSourceHeightPx * scale));
23474
+ return {
23475
+ widthPx,
23476
+ heightPx,
23477
+ pixelWidthPx: Math.max(1, Math.round(widthPx * pixelRatio)),
23478
+ pixelHeightPx: Math.max(1, Math.round(heightPx * pixelRatio)),
23479
+ scale
23480
+ };
23481
+ }
23482
+ function useDocxPageThumbnails(editor, options = {}) {
23483
+ const pageSurfaceRegistryOwner = docxViewerPageSurfaceRegistryOwner(editor);
23484
+ const pageSurfaceRegistryEditor = React.useMemo(
23485
+ () => ({ syncPaginationInfo: editor.syncPaginationInfo }),
23486
+ [pageSurfaceRegistryOwner]
23487
+ );
23488
+ const [pageSurfaceEpoch, setPageSurfaceEpoch] = React.useState(0);
23489
+ const [pageThumbnailStates, setPageThumbnailStates] = React.useState(() => /* @__PURE__ */ new Map());
23490
+ const attachedCanvasByPageRef = React.useRef(
23491
+ /* @__PURE__ */ new Map()
23492
+ );
23493
+ const canvasRefCallbacksRef = React.useRef(/* @__PURE__ */ new Map());
23494
+ const renderToCanvasCallbacksRef = React.useRef(/* @__PURE__ */ new Map());
23495
+ const fallbackLayout = React.useMemo(
23496
+ () => resolveDocumentLayout(editor.model),
23497
+ [editor.model]
23498
+ );
23499
+ const pageSurfaceRegistry = React.useMemo(
23500
+ () => ensureDocxViewerPageSurfaceRegistry(pageSurfaceRegistryEditor),
23501
+ [pageSurfaceRegistryEditor]
23502
+ );
23503
+ React.useEffect(
22582
23504
  () => subscribeDocxViewerPageSurfaces(pageSurfaceRegistryEditor, () => {
22583
23505
  setPageSurfaceEpoch((current) => current + 1);
22584
23506
  }),
@@ -22604,9 +23526,16 @@ function useDocxPageThumbnails(editor, options = {}) {
22604
23526
  );
22605
23527
  const thumbnailSurfaceCacheRef = React.useRef(void 0);
22606
23528
  const thumbnailRasterQueueRef = React.useRef(void 0);
23529
+ const detachedThumbnailSurfaceRendererRef = React.useRef(void 0);
22607
23530
  const lastPaintedThumbnailKeyByCanvasRef = React.useRef(
22608
23531
  /* @__PURE__ */ new WeakMap()
22609
23532
  );
23533
+ const thumbnailQueueKeyByCanvasRef = React.useRef(
23534
+ /* @__PURE__ */ new WeakMap()
23535
+ );
23536
+ const nextThumbnailCanvasQueueIdRef = React.useRef(0);
23537
+ const queuedPrefetchThumbnailKeysRef = React.useRef(/* @__PURE__ */ new Set());
23538
+ const thumbnailMinRasterIntervalMs = normalizeDocxThumbnailMinRasterIntervalMs(options.minRasterIntervalMs);
22610
23539
  const ensureThumbnailSurfaceCache = React.useCallback(() => {
22611
23540
  if (!thumbnailSurfaceCacheRef.current) {
22612
23541
  thumbnailSurfaceCacheRef.current = new DocxThumbnailSurfaceCache(
@@ -22618,16 +23547,57 @@ function useDocxPageThumbnails(editor, options = {}) {
22618
23547
  const ensureThumbnailRasterQueue = React.useCallback(() => {
22619
23548
  if (!thumbnailRasterQueueRef.current) {
22620
23549
  thumbnailRasterQueueRef.current = new SerialIdleTaskQueue({
22621
- minTaskIntervalMs: DOCX_THUMBNAIL_MIN_RASTER_INTERVAL_MS
23550
+ minTaskIntervalMs: thumbnailMinRasterIntervalMs
22622
23551
  });
22623
23552
  }
22624
23553
  return thumbnailRasterQueueRef.current;
22625
- }, []);
23554
+ }, [thumbnailMinRasterIntervalMs]);
23555
+ const thumbnailQueueKeyForCanvas = React.useCallback(
23556
+ (canvas) => {
23557
+ const existing = thumbnailQueueKeyByCanvasRef.current.get(canvas);
23558
+ if (existing) {
23559
+ return existing;
23560
+ }
23561
+ const nextKey = docxThumbnailCanvasQueueKey(
23562
+ nextThumbnailCanvasQueueIdRef.current
23563
+ );
23564
+ nextThumbnailCanvasQueueIdRef.current += 1;
23565
+ thumbnailQueueKeyByCanvasRef.current.set(canvas, nextKey);
23566
+ return nextKey;
23567
+ },
23568
+ []
23569
+ );
23570
+ React.useEffect(() => {
23571
+ thumbnailRasterQueueRef.current?.clear();
23572
+ thumbnailRasterQueueRef.current = void 0;
23573
+ queuedPrefetchThumbnailKeysRef.current.clear();
23574
+ }, [thumbnailMinRasterIntervalMs]);
22626
23575
  React.useEffect(() => {
22627
23576
  thumbnailSurfaceCacheRef.current?.clear();
22628
23577
  thumbnailRasterQueueRef.current?.clear();
23578
+ detachedThumbnailSurfaceRendererRef.current?.clear();
23579
+ detachedThumbnailSurfaceRendererRef.current = void 0;
22629
23580
  lastPaintedThumbnailKeyByCanvasRef.current = /* @__PURE__ */ new WeakMap();
23581
+ thumbnailQueueKeyByCanvasRef.current = /* @__PURE__ */ new WeakMap();
23582
+ nextThumbnailCanvasQueueIdRef.current = 0;
23583
+ queuedPrefetchThumbnailKeysRef.current.clear();
22630
23584
  }, [editor.documentLoadNonce, pageSurfaceRegistryOwner]);
23585
+ React.useEffect(() => {
23586
+ detachedThumbnailSurfaceRendererRef.current?.clear();
23587
+ detachedThumbnailSurfaceRendererRef.current = void 0;
23588
+ }, [
23589
+ editor.documentTheme,
23590
+ editor.model,
23591
+ editor.showComments,
23592
+ editor.showTrackedChanges
23593
+ ]);
23594
+ React.useEffect(
23595
+ () => () => {
23596
+ detachedThumbnailSurfaceRendererRef.current?.clear();
23597
+ detachedThumbnailSurfaceRendererRef.current = void 0;
23598
+ },
23599
+ []
23600
+ );
22631
23601
  const thumbnailResolutionOptionsKey = React.useMemo(() => {
22632
23602
  const bounds = options.resolution;
22633
23603
  const boundsKey = typeof bounds === "number" ? `n${bounds}` : bounds ? `b${bounds.maxWidth ?? ""}x${bounds.maxHeight ?? ""}` : "";
@@ -22645,6 +23615,125 @@ function useDocxPageThumbnails(editor, options = {}) {
22645
23615
  },
22646
23616
  [editor.documentTheme, pageSurfaceRegistry, thumbnailResolutionOptionsKey]
22647
23617
  );
23618
+ const totalThumbnailPages = Math.max(1, editor.totalPages);
23619
+ const visibleThumbnailPageIndexes = React.useMemo(
23620
+ () => normalizeDocxThumbnailPageIndexes(
23621
+ options.renderWindow?.visiblePageIndexes,
23622
+ totalThumbnailPages
23623
+ ),
23624
+ [options.renderWindow?.visiblePageIndexes, totalThumbnailPages]
23625
+ );
23626
+ const prefetchThumbnailPageIndexes = React.useMemo(
23627
+ () => normalizeDocxThumbnailPageIndexes(
23628
+ options.renderWindow?.prefetchPageIndexes,
23629
+ totalThumbnailPages
23630
+ ),
23631
+ [options.renderWindow?.prefetchPageIndexes, totalThumbnailPages]
23632
+ );
23633
+ const visibleThumbnailPageIndexesKey = docxThumbnailPageIndexesKey(
23634
+ visibleThumbnailPageIndexes
23635
+ );
23636
+ const prefetchThumbnailPageIndexesKey = docxThumbnailPageIndexesKey(
23637
+ prefetchThumbnailPageIndexes
23638
+ );
23639
+ const visibleThumbnailPageIndexSet = React.useMemo(
23640
+ () => new Set(visibleThumbnailPageIndexes),
23641
+ [visibleThumbnailPageIndexesKey]
23642
+ );
23643
+ const thumbnailRenderPriorityForPage = React.useCallback(
23644
+ (pageIndex, fallbackPriority = DOCX_THUMBNAIL_RENDER_PRIORITY_ATTACHED) => visibleThumbnailPageIndexSet.has(pageIndex) ? DOCX_THUMBNAIL_RENDER_PRIORITY_VISIBLE : fallbackPriority,
23645
+ [visibleThumbnailPageIndexSet]
23646
+ );
23647
+ const renderPageThumbnailSurface = React.useCallback(
23648
+ async (pageIndex, renderOptions) => {
23649
+ const force = renderOptions?.force === true;
23650
+ const runSkipKey = thumbnailSkipKeyForPage(pageIndex);
23651
+ const detachedRenderKey = [
23652
+ runSkipKey ?? `load:${editor.documentLoadNonce}`,
23653
+ editor.showComments ? "comments:1" : "comments:0",
23654
+ editor.showTrackedChanges ? "tracked:1" : "tracked:0"
23655
+ ].join("|");
23656
+ const thumbnailSnapshotEntry = pageSurfaceRegistry.pageThumbnailSnapshots.get(pageIndex);
23657
+ const fallbackSourceSize = resolveDocxViewerRegisteredPageSurfaceSize(
23658
+ pageSurfaceRegistry,
23659
+ pageIndex,
23660
+ fallbackLayout.pageWidthPx,
23661
+ fallbackLayout.pageHeightPx
23662
+ );
23663
+ const resolution = resolveDocxPageThumbnailResolution({
23664
+ sourceWidthPx: fallbackSourceSize.widthPx,
23665
+ sourceHeightPx: fallbackSourceSize.heightPx,
23666
+ resolution: options.resolution,
23667
+ maxWidthPx: options.maxWidthPx,
23668
+ maxHeightPx: options.maxHeightPx,
23669
+ pixelRatio: options.pixelRatio
23670
+ });
23671
+ const surfaceKey = runSkipKey === void 0 ? void 0 : `${runSkipKey}|${fallbackSourceSize.widthPx}x${fallbackSourceSize.heightPx}|${resolution.pixelWidthPx}x${resolution.pixelHeightPx}`;
23672
+ const surfaceCache = ensureThumbnailSurfaceCache();
23673
+ let surface = !force && surfaceKey !== void 0 ? surfaceCache.get(surfaceKey) : void 0;
23674
+ if (!surface) {
23675
+ const thumbnailSnapshot = thumbnailSnapshotEntry?.getSnapshot();
23676
+ if (thumbnailSnapshot) {
23677
+ surface = renderDocxThumbnailSnapshotSurface({
23678
+ snapshot: thumbnailSnapshot,
23679
+ widthPx: resolution.widthPx,
23680
+ heightPx: resolution.heightPx,
23681
+ pixelWidthPx: resolution.pixelWidthPx,
23682
+ pixelHeightPx: resolution.pixelHeightPx
23683
+ });
23684
+ if (surfaceKey !== void 0) {
23685
+ surfaceCache.set(surfaceKey, surface);
23686
+ }
23687
+ return { surface, resolution, runSkipKey };
23688
+ }
23689
+ let livePageElement = pageSurfaceRegistry.pageElements.get(pageIndex);
23690
+ if (!livePageElement || !livePageElement.isConnected) {
23691
+ if (!detachedThumbnailSurfaceRendererRef.current) {
23692
+ detachedThumbnailSurfaceRendererRef.current = new DocxDetachedThumbnailSurfaceRenderer();
23693
+ }
23694
+ livePageElement = await detachedThumbnailSurfaceRendererRef.current.renderPageSurface({
23695
+ editor,
23696
+ registry: pageSurfaceRegistry,
23697
+ pageIndex,
23698
+ renderKey: detachedRenderKey
23699
+ });
23700
+ }
23701
+ if (!livePageElement || !livePageElement.isConnected) {
23702
+ return void 0;
23703
+ }
23704
+ const sourceSize = resolveDocxViewerPageSurfaceSize(
23705
+ livePageElement,
23706
+ fallbackSourceSize.widthPx,
23707
+ fallbackSourceSize.heightPx
23708
+ );
23709
+ surface = await rasterizeDocxThumbnailSurface({
23710
+ pageElement: livePageElement,
23711
+ sourceWidthPx: sourceSize.widthPx,
23712
+ sourceHeightPx: sourceSize.heightPx,
23713
+ widthPx: resolution.widthPx,
23714
+ heightPx: resolution.heightPx,
23715
+ pixelWidthPx: resolution.pixelWidthPx,
23716
+ pixelHeightPx: resolution.pixelHeightPx
23717
+ });
23718
+ if (surfaceKey !== void 0) {
23719
+ surfaceCache.set(surfaceKey, surface);
23720
+ }
23721
+ }
23722
+ return { surface, resolution, runSkipKey };
23723
+ },
23724
+ [
23725
+ ensureThumbnailSurfaceCache,
23726
+ fallbackLayout.pageHeightPx,
23727
+ fallbackLayout.pageWidthPx,
23728
+ options.resolution,
23729
+ options.maxHeightPx,
23730
+ options.maxWidthPx,
23731
+ options.pixelRatio,
23732
+ pageSurfaceRegistry,
23733
+ thumbnailSkipKeyForPage,
23734
+ editor
23735
+ ]
23736
+ );
22648
23737
  const renderPageThumbnailToCanvas = React.useCallback(
22649
23738
  async (pageIndex, canvas, renderOptions) => {
22650
23739
  if (options.disabled) {
@@ -22655,59 +23744,25 @@ function useDocxPageThumbnails(editor, options = {}) {
22655
23744
  return;
22656
23745
  }
22657
23746
  const requiresAttachedTarget = canvas === void 0;
22658
- const pageElement = pageSurfaceRegistry.pageElements.get(pageIndex);
22659
- if (!pageElement || !pageElement.isConnected) {
22660
- updatePageThumbnailState(pageIndex, "unavailable");
22661
- return;
22662
- }
22663
23747
  const force = renderOptions?.force === true;
22664
23748
  const lastPaintedKey = lastPaintedThumbnailKeyByCanvasRef.current.get(targetCanvas);
22665
23749
  if (!force && lastPaintedKey !== void 0 && lastPaintedKey === thumbnailSkipKeyForPage(pageIndex)) {
22666
23750
  updatePageThumbnailState(pageIndex, "ready");
22667
23751
  return;
22668
23752
  }
22669
- updatePageThumbnailState(pageIndex, "rendering");
22670
- await ensureThumbnailRasterQueue().enqueue(targetCanvas, async () => {
23753
+ const paintIntoTarget = async () => {
22671
23754
  if (requiresAttachedTarget && attachedCanvasByPageRef.current.get(pageIndex) !== targetCanvas) {
22672
23755
  return;
22673
23756
  }
22674
- const livePageElement = pageSurfaceRegistry.pageElements.get(pageIndex);
22675
- if (!livePageElement || !livePageElement.isConnected) {
22676
- updatePageThumbnailState(pageIndex, "unavailable");
22677
- return;
22678
- }
22679
- const runSkipKey = thumbnailSkipKeyForPage(pageIndex);
22680
- const sourceSize = resolveDocxViewerPageSurfaceSize(
22681
- livePageElement,
22682
- fallbackLayout.pageWidthPx,
22683
- fallbackLayout.pageHeightPx
22684
- );
22685
- const resolution = resolveDocxPageThumbnailResolution({
22686
- sourceWidthPx: sourceSize.widthPx,
22687
- sourceHeightPx: sourceSize.heightPx,
22688
- resolution: options.resolution,
22689
- maxWidthPx: options.maxWidthPx,
22690
- maxHeightPx: options.maxHeightPx,
22691
- pixelRatio: options.pixelRatio
22692
- });
22693
- const surfaceKey = runSkipKey === void 0 ? void 0 : `${runSkipKey}|${sourceSize.widthPx}x${sourceSize.heightPx}|${resolution.pixelWidthPx}x${resolution.pixelHeightPx}`;
22694
- const surfaceCache = ensureThumbnailSurfaceCache();
22695
23757
  try {
22696
- let surface = !force && surfaceKey !== void 0 ? surfaceCache.get(surfaceKey) : void 0;
22697
- if (!surface) {
22698
- surface = await rasterizeDocxThumbnailSurface({
22699
- pageElement: livePageElement,
22700
- sourceWidthPx: sourceSize.widthPx,
22701
- sourceHeightPx: sourceSize.heightPx,
22702
- widthPx: resolution.widthPx,
22703
- heightPx: resolution.heightPx,
22704
- pixelWidthPx: resolution.pixelWidthPx,
22705
- pixelHeightPx: resolution.pixelHeightPx
22706
- });
22707
- if (surfaceKey !== void 0) {
22708
- surfaceCache.set(surfaceKey, surface);
22709
- }
23758
+ const rendered = await renderPageThumbnailSurface(pageIndex, {
23759
+ force
23760
+ });
23761
+ if (!rendered) {
23762
+ updatePageThumbnailState(pageIndex, "unavailable");
23763
+ return;
22710
23764
  }
23765
+ const { surface, resolution, runSkipKey } = rendered;
22711
23766
  blitDocxThumbnailSurface(surface, targetCanvas, resolution);
22712
23767
  if (runSkipKey !== void 0) {
22713
23768
  lastPaintedThumbnailKeyByCanvasRef.current.set(
@@ -22723,32 +23778,116 @@ function useDocxPageThumbnails(editor, options = {}) {
22723
23778
  error instanceof Error ? error : new Error("Failed to render DOCX page thumbnail.")
22724
23779
  );
22725
23780
  }
22726
- });
23781
+ };
23782
+ updatePageThumbnailState(pageIndex, "rendering");
23783
+ if (pageSurfaceRegistry.pageThumbnailSnapshots.has(pageIndex)) {
23784
+ await paintIntoTarget();
23785
+ return;
23786
+ }
23787
+ await ensureThumbnailRasterQueue().enqueue(
23788
+ thumbnailQueueKeyForCanvas(targetCanvas),
23789
+ paintIntoTarget,
23790
+ {
23791
+ priority: renderOptions?.priority ?? thumbnailRenderPriorityForPage(
23792
+ pageIndex,
23793
+ DOCX_THUMBNAIL_RENDER_PRIORITY_ATTACHED
23794
+ )
23795
+ }
23796
+ );
22727
23797
  },
22728
23798
  [
22729
23799
  ensureThumbnailRasterQueue,
22730
- ensureThumbnailSurfaceCache,
22731
- fallbackLayout.pageHeightPx,
22732
- fallbackLayout.pageWidthPx,
22733
23800
  options.disabled,
22734
- options.resolution,
22735
- options.maxHeightPx,
22736
- options.maxWidthPx,
22737
- options.pixelRatio,
22738
23801
  pageSurfaceRegistry,
23802
+ renderPageThumbnailSurface,
22739
23803
  thumbnailSkipKeyForPage,
23804
+ thumbnailQueueKeyForCanvas,
23805
+ thumbnailRenderPriorityForPage,
22740
23806
  updatePageThumbnailState
22741
23807
  ]
22742
23808
  );
23809
+ const prefetchPageThumbnailSurface = React.useCallback(
23810
+ async (pageIndex) => {
23811
+ if (options.disabled) {
23812
+ return;
23813
+ }
23814
+ const queueKey = docxThumbnailPrefetchQueueKey(pageIndex);
23815
+ queuedPrefetchThumbnailKeysRef.current.add(queueKey);
23816
+ await ensureThumbnailRasterQueue().enqueue(
23817
+ queueKey,
23818
+ async () => {
23819
+ try {
23820
+ await renderPageThumbnailSurface(pageIndex);
23821
+ } catch {
23822
+ }
23823
+ },
23824
+ { priority: DOCX_THUMBNAIL_RENDER_PRIORITY_PREFETCH }
23825
+ );
23826
+ queuedPrefetchThumbnailKeysRef.current.delete(queueKey);
23827
+ },
23828
+ [ensureThumbnailRasterQueue, options.disabled, renderPageThumbnailSurface]
23829
+ );
22743
23830
  const requestAttachedThumbnailRenders = React.useCallback(
22744
23831
  async (renderOptions) => {
22745
- const tasks = [...attachedCanvasByPageRef.current.keys()].map(
22746
- (pageIndex) => renderPageThumbnailToCanvas(pageIndex, void 0, renderOptions)
23832
+ const attachedPageIndexes = [
23833
+ ...attachedCanvasByPageRef.current.keys()
23834
+ ].sort((leftPageIndex, rightPageIndex) => {
23835
+ const leftPriority = thumbnailRenderPriorityForPage(leftPageIndex);
23836
+ const rightPriority = thumbnailRenderPriorityForPage(rightPageIndex);
23837
+ return leftPriority - rightPriority || leftPageIndex - rightPageIndex;
23838
+ });
23839
+ const tasks = attachedPageIndexes.map(
23840
+ (pageIndex) => renderPageThumbnailToCanvas(pageIndex, void 0, {
23841
+ ...renderOptions,
23842
+ priority: thumbnailRenderPriorityForPage(pageIndex)
23843
+ })
22747
23844
  );
22748
23845
  await Promise.all(tasks);
22749
23846
  },
22750
- [renderPageThumbnailToCanvas]
23847
+ [renderPageThumbnailToCanvas, thumbnailRenderPriorityForPage]
22751
23848
  );
23849
+ const requestPrefetchThumbnailRenders = React.useCallback(async () => {
23850
+ if (options.disabled) {
23851
+ queuedPrefetchThumbnailKeysRef.current.forEach((queueKey) => {
23852
+ thumbnailRasterQueueRef.current?.cancel(queueKey);
23853
+ });
23854
+ queuedPrefetchThumbnailKeysRef.current.clear();
23855
+ return;
23856
+ }
23857
+ const requestedPrefetchKeys = new Set(
23858
+ prefetchThumbnailPageIndexes.map(
23859
+ (pageIndex) => docxThumbnailPrefetchQueueKey(pageIndex)
23860
+ )
23861
+ );
23862
+ queuedPrefetchThumbnailKeysRef.current.forEach((queueKey) => {
23863
+ if (!requestedPrefetchKeys.has(queueKey)) {
23864
+ thumbnailRasterQueueRef.current?.cancel(queueKey);
23865
+ queuedPrefetchThumbnailKeysRef.current.delete(queueKey);
23866
+ }
23867
+ });
23868
+ const tasks = prefetchThumbnailPageIndexes.map(
23869
+ (pageIndex) => prefetchPageThumbnailSurface(pageIndex)
23870
+ );
23871
+ await Promise.all(tasks);
23872
+ }, [
23873
+ options.disabled,
23874
+ prefetchPageThumbnailSurface,
23875
+ prefetchThumbnailPageIndexes
23876
+ ]);
23877
+ React.useEffect(() => {
23878
+ void requestPrefetchThumbnailRenders();
23879
+ }, [
23880
+ editor.documentLoadNonce,
23881
+ editor.documentTheme,
23882
+ editor.model,
23883
+ mountedPageElements,
23884
+ options.maxHeightPx,
23885
+ options.maxWidthPx,
23886
+ options.pixelRatio,
23887
+ options.resolution,
23888
+ prefetchThumbnailPageIndexesKey,
23889
+ requestPrefetchThumbnailRenders
23890
+ ]);
22752
23891
  const rerenderAttachedThumbnails = React.useCallback(
22753
23892
  async () => requestAttachedThumbnailRenders({ force: true }),
22754
23893
  [requestAttachedThumbnailRenders]
@@ -22777,14 +23916,19 @@ function useDocxPageThumbnails(editor, options = {}) {
22777
23916
  options.pixelRatio,
22778
23917
  requestAttachedThumbnailRenders
22779
23918
  ]);
22780
- const thumbnails = React.useMemo(() => {
23919
+ const thumbnailGeometryItems = React.useMemo(() => {
22781
23920
  const totalPages = Math.max(1, editor.totalPages);
22782
23921
  return Array.from({ length: totalPages }, (_, pageIndex) => {
22783
23922
  const pageElement = mountedPageElements.get(pageIndex);
22784
- const sourceSize = resolveDocxViewerPageSurfaceSize(
23923
+ const sourceSize = pageElement && pageElement.isConnected ? resolveDocxViewerPageSurfaceSize(
22785
23924
  pageElement,
22786
23925
  fallbackLayout.pageWidthPx,
22787
23926
  fallbackLayout.pageHeightPx
23927
+ ) : resolveDocxViewerRegisteredPageSurfaceSize(
23928
+ pageSurfaceRegistry,
23929
+ pageIndex,
23930
+ fallbackLayout.pageWidthPx,
23931
+ fallbackLayout.pageHeightPx
22788
23932
  );
22789
23933
  const resolution = resolveDocxPageThumbnailResolution({
22790
23934
  sourceWidthPx: sourceSize.widthPx,
@@ -22794,15 +23938,14 @@ function useDocxPageThumbnails(editor, options = {}) {
22794
23938
  maxHeightPx: options.maxHeightPx,
22795
23939
  pixelRatio: options.pixelRatio
22796
23940
  });
22797
- const state = pageThumbnailStates.get(pageIndex);
22798
23941
  return {
23942
+ // Internal: drives the default status below; stripped before exposure.
23943
+ hasElement: Boolean(pageElement),
22799
23944
  pageIndex,
22800
23945
  pageNumber: pageIndex + 1,
22801
23946
  sourceWidthPx: sourceSize.widthPx,
22802
23947
  sourceHeightPx: sourceSize.heightPx,
22803
23948
  isMounted: Boolean(pageElement && pageElement.isConnected),
22804
- status: state?.status ?? (pageElement ? "idle" : "unavailable"),
22805
- error: state?.error,
22806
23949
  paint: (canvas) => {
22807
23950
  if (!canvas || options.disabled) {
22808
23951
  return false;
@@ -22819,7 +23962,9 @@ function useDocxPageThumbnails(editor, options = {}) {
22819
23962
  }
22820
23963
  const previousCanvas = attachedCanvasByPageRef.current.get(pageIndex);
22821
23964
  if (previousCanvas) {
22822
- thumbnailRasterQueueRef.current?.cancel(previousCanvas);
23965
+ thumbnailRasterQueueRef.current?.cancel(
23966
+ thumbnailQueueKeyForCanvas(previousCanvas)
23967
+ );
22823
23968
  }
22824
23969
  attachedCanvasByPageRef.current.delete(pageIndex);
22825
23970
  };
@@ -22854,8 +23999,21 @@ function useDocxPageThumbnails(editor, options = {}) {
22854
23999
  options.maxHeightPx,
22855
24000
  options.maxWidthPx,
22856
24001
  options.pixelRatio,
22857
- pageThumbnailStates
24002
+ pageSurfaceRegistry,
24003
+ thumbnailQueueKeyForCanvas
22858
24004
  ]);
24005
+ const thumbnails = React.useMemo(
24006
+ () => thumbnailGeometryItems.map((geometryItem) => {
24007
+ const { hasElement, ...item } = geometryItem;
24008
+ const state = pageThumbnailStates.get(item.pageIndex);
24009
+ return {
24010
+ ...item,
24011
+ status: state?.status ?? (hasElement ? "idle" : "unavailable"),
24012
+ error: state?.error
24013
+ };
24014
+ }),
24015
+ [thumbnailGeometryItems, pageThumbnailStates]
24016
+ );
22859
24017
  const paintThumbnail = React.useCallback(
22860
24018
  (pageIndex, canvas) => {
22861
24019
  if (!canvas || options.disabled) {
@@ -24150,13 +25308,23 @@ function DocxEditorViewer({
24150
25308
  if (cached) {
24151
25309
  return cached;
24152
25310
  }
25311
+ let registeredElement = null;
24153
25312
  const nextRef = (element) => {
24154
25313
  if (element) {
25314
+ registeredElement = element;
24155
25315
  pageElementsRef.current.set(normalizedPageIndex, element);
24156
25316
  } else {
24157
25317
  pageElementsRef.current.delete(normalizedPageIndex);
24158
25318
  }
24159
- registerDocxViewerPageSurface(editor, normalizedPageIndex, element);
25319
+ registerDocxViewerPageSurface(
25320
+ editor,
25321
+ normalizedPageIndex,
25322
+ element,
25323
+ registeredElement
25324
+ );
25325
+ if (!element) {
25326
+ registeredElement = null;
25327
+ }
24160
25328
  };
24161
25329
  pageSurfaceRefCallbacksRef.current.set(normalizedPageIndex, nextRef);
24162
25330
  return nextRef;
@@ -25219,12 +26387,51 @@ function DocxEditorViewer({
25219
26387
  pageSectionInfoByIndex,
25220
26388
  trackedChangesEnabled
25221
26389
  ]);
26390
+ const pageThumbnailSurfaceSizesByPage = React.useMemo(
26391
+ () => pageNodeSegmentsByPage.map((_, pageIndex) => {
26392
+ const pageLayout = pageSectionInfoByIndex[pageIndex]?.layout ?? documentLayout;
26393
+ return {
26394
+ widthPx: pageLayout.pageWidthPx,
26395
+ heightPx: pageLayout.pageHeightPx
26396
+ };
26397
+ }),
26398
+ [documentLayout, pageNodeSegmentsByPage, pageSectionInfoByIndex]
26399
+ );
26400
+ const pageThumbnailSnapshotEntriesByPage = React.useMemo(
26401
+ () => buildDocxPageThumbnailRenderSnapshotEntries({
26402
+ model: editor.model,
26403
+ pageNodeSegmentsByPage,
26404
+ pageSectionInfoByIndex,
26405
+ contentKeysByPage: pageThumbnailContentKeysByPage,
26406
+ fallbackLayout: documentLayout,
26407
+ documentTheme: editor.documentTheme,
26408
+ docGridLinePitchPxByNodeIndex,
26409
+ numberingDefinitions: editor.model.metadata.numberingDefinitions
26410
+ }),
26411
+ [
26412
+ docGridLinePitchPxByNodeIndex,
26413
+ documentLayout,
26414
+ editor.documentTheme,
26415
+ editor.model,
26416
+ editor.model.metadata.numberingDefinitions,
26417
+ pageNodeSegmentsByPage,
26418
+ pageSectionInfoByIndex,
26419
+ pageThumbnailContentKeysByPage
26420
+ ]
26421
+ );
25222
26422
  React.useEffect(() => {
25223
26423
  syncDocxViewerPageSurfaceContentKeys(
25224
26424
  editor,
25225
- pageThumbnailContentKeysByPage
26425
+ pageThumbnailContentKeysByPage,
26426
+ pageThumbnailSurfaceSizesByPage,
26427
+ pageThumbnailSnapshotEntriesByPage
25226
26428
  );
25227
- }, [pageSurfaceRegistryOwner, pageThumbnailContentKeysByPage]);
26429
+ }, [
26430
+ pageSurfaceRegistryOwner,
26431
+ pageThumbnailContentKeysByPage,
26432
+ pageThumbnailSnapshotEntriesByPage,
26433
+ pageThumbnailSurfaceSizesByPage
26434
+ ]);
25228
26435
  const resolveStyleRefFieldValueForPage = React.useMemo(() => {
25229
26436
  const valueCache = /* @__PURE__ */ new Map();
25230
26437
  const nodes = editor.model.nodes;