@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.cjs CHANGED
@@ -4403,6 +4403,14 @@ var THUMBNAIL_EXCLUDED_CLONE_SELECTOR = [
4403
4403
  var THUMBNAIL_IMAGE_DOWNSCALE_MIN_DATA_URI_LENGTH = 32768;
4404
4404
  var THUMBNAIL_IMAGE_DOWNSCALE_MAX_DIMENSION_PX = 512;
4405
4405
  var THUMBNAIL_IMAGE_JPEG_QUALITY = 0.78;
4406
+ var THUMBNAIL_DIRECT_DEFAULT_FONT_FAMILY = "Calibri, Arial, sans-serif";
4407
+ var THUMBNAIL_DIRECT_DEFAULT_TEXT_COLOR = "#111827";
4408
+ var THUMBNAIL_DIRECT_TABLE_BORDER_COLOR = "#d1d5db";
4409
+ var THUMBNAIL_DIRECT_IMAGE_BACKGROUND = "#f3f4f6";
4410
+ var THUMBNAIL_DIRECT_MAX_ELEMENTS = 320;
4411
+ var THUMBNAIL_DIRECT_MAX_TEXT_CHARS = 640;
4412
+ var THUMBNAIL_DIRECT_MAX_LINES = 14;
4413
+ var THUMBNAIL_DIRECT_MAX_LAYOUT_LINES = 80;
4406
4414
  function thumbnailSvgDataUri(svg) {
4407
4415
  return `data:image/svg+xml;charset=utf-8,${encodeURIComponent(svg)}`;
4408
4416
  }
@@ -4466,6 +4474,346 @@ function getDownscaledThumbnailImageDataUri(src) {
4466
4474
  downscaledThumbnailImageCache.set(src, pending);
4467
4475
  return pending;
4468
4476
  }
4477
+ function directThumbnailPositivePx(value, fallback = 1) {
4478
+ return Number.isFinite(value) && value > 0 ? Math.max(1, Number(value)) : fallback;
4479
+ }
4480
+ function setCanvasFillStyle(context, color, fallback) {
4481
+ try {
4482
+ context.fillStyle = color || fallback;
4483
+ } catch {
4484
+ context.fillStyle = fallback;
4485
+ }
4486
+ }
4487
+ function setCanvasStrokeStyle(context, color, fallback) {
4488
+ try {
4489
+ context.strokeStyle = color || fallback;
4490
+ } catch {
4491
+ context.strokeStyle = fallback;
4492
+ }
4493
+ }
4494
+ function directThumbnailFont(run, fallbackFontSizePx) {
4495
+ const fontSizePx = Math.max(
4496
+ 6,
4497
+ Math.min(
4498
+ 36,
4499
+ Math.round(
4500
+ directThumbnailPositivePx(run?.fontSizePx, fallbackFontSizePx)
4501
+ )
4502
+ )
4503
+ );
4504
+ const fontStyle = run?.italic ? "italic " : "";
4505
+ const fontWeight = run?.bold ? "700 " : "";
4506
+ return `${fontStyle}${fontWeight}${fontSizePx}px ${run?.fontFamily || THUMBNAIL_DIRECT_DEFAULT_FONT_FAMILY}`;
4507
+ }
4508
+ var THUMBNAIL_DIRECT_TOKEN_REGEX = /(\r\n|\n|\t|[^\S\r\n\t]+|[^\s\r\n\t]+)/g;
4509
+ var THUMBNAIL_DIRECT_LEADING_WHITESPACE_REGEX = /^\s/;
4510
+ var THUMBNAIL_DIRECT_TEXT_MEASURE_CACHE_MAX_ENTRIES = 4096;
4511
+ var directThumbnailTextMeasureCache = /* @__PURE__ */ new Map();
4512
+ function measureDirectThumbnailToken(context, font, text) {
4513
+ if (!text) {
4514
+ return 0;
4515
+ }
4516
+ const cacheKey = `${font}\0${text}`;
4517
+ const cached = directThumbnailTextMeasureCache.get(cacheKey);
4518
+ if (cached !== void 0) {
4519
+ return cached;
4520
+ }
4521
+ const width = context.measureText(text).width;
4522
+ if (directThumbnailTextMeasureCache.size >= THUMBNAIL_DIRECT_TEXT_MEASURE_CACHE_MAX_ENTRIES) {
4523
+ const oldestKey = directThumbnailTextMeasureCache.keys().next().value;
4524
+ if (oldestKey !== void 0) {
4525
+ directThumbnailTextMeasureCache.delete(oldestKey);
4526
+ }
4527
+ }
4528
+ directThumbnailTextMeasureCache.set(cacheKey, width);
4529
+ return width;
4530
+ }
4531
+ function appendDirectThumbnailTextLine(lines, currentSegments, currentWidthPx) {
4532
+ const line = {
4533
+ segments: currentSegments,
4534
+ widthPx: currentWidthPx
4535
+ };
4536
+ lines.push(line);
4537
+ return line;
4538
+ }
4539
+ function layoutDirectThumbnailTextRuns(params) {
4540
+ const { context, runs, fallbackFontSizePx } = params;
4541
+ const widthPx = Math.max(1, params.widthPx);
4542
+ const maxLineCount = Math.max(
4543
+ 1,
4544
+ Math.min(THUMBNAIL_DIRECT_MAX_LAYOUT_LINES, params.maxLineCount)
4545
+ );
4546
+ const lines = [];
4547
+ let currentSegments = [];
4548
+ let currentWidthPx = 0;
4549
+ let remainingChars = THUMBNAIL_DIRECT_MAX_TEXT_CHARS;
4550
+ const flushLine = () => {
4551
+ appendDirectThumbnailTextLine(lines, currentSegments, currentWidthPx);
4552
+ currentSegments = [];
4553
+ currentWidthPx = 0;
4554
+ };
4555
+ for (const run of runs) {
4556
+ if (lines.length >= maxLineCount || remainingChars <= 0) {
4557
+ break;
4558
+ }
4559
+ const text = run.text.slice(0, remainingChars);
4560
+ remainingChars -= text.length;
4561
+ const runFont = directThumbnailFont(run, fallbackFontSizePx);
4562
+ context.font = runFont;
4563
+ const tokens = text.match(THUMBNAIL_DIRECT_TOKEN_REGEX) ?? [];
4564
+ for (const token of tokens) {
4565
+ if (lines.length >= maxLineCount) {
4566
+ break;
4567
+ }
4568
+ if (token === "\n" || token === "\r\n") {
4569
+ flushLine();
4570
+ continue;
4571
+ }
4572
+ const drawableToken = token === " " ? " " : token;
4573
+ const tokenWidthPx = measureDirectThumbnailToken(
4574
+ context,
4575
+ runFont,
4576
+ drawableToken
4577
+ );
4578
+ const tokenIsWhitespace = THUMBNAIL_DIRECT_LEADING_WHITESPACE_REGEX.test(drawableToken);
4579
+ if (currentSegments.length > 0 && currentWidthPx + tokenWidthPx > widthPx && !tokenIsWhitespace) {
4580
+ flushLine();
4581
+ }
4582
+ if (currentSegments.length === 0 && tokenIsWhitespace) {
4583
+ continue;
4584
+ }
4585
+ currentSegments.push({
4586
+ run,
4587
+ text: drawableToken,
4588
+ widthPx: tokenWidthPx,
4589
+ font: runFont
4590
+ });
4591
+ currentWidthPx += tokenWidthPx;
4592
+ }
4593
+ }
4594
+ if (currentSegments.length > 0 || lines.length === 0) {
4595
+ flushLine();
4596
+ }
4597
+ return lines;
4598
+ }
4599
+ function directThumbnailAlignedX(params) {
4600
+ const { xPx, widthPx, lineWidthPx, align } = params;
4601
+ if (align === "center") {
4602
+ return xPx + Math.max(0, (widthPx - lineWidthPx) / 2);
4603
+ }
4604
+ if (align === "right") {
4605
+ return xPx + Math.max(0, widthPx - lineWidthPx);
4606
+ }
4607
+ return xPx;
4608
+ }
4609
+ function drawDirectThumbnailTextRuns(params) {
4610
+ const {
4611
+ context,
4612
+ runs,
4613
+ xPx,
4614
+ yPx,
4615
+ widthPx,
4616
+ heightPx,
4617
+ align,
4618
+ startLineIndex
4619
+ } = params;
4620
+ const safeWidthPx = Math.max(1, widthPx);
4621
+ const safeHeightPx = Math.max(1, heightPx);
4622
+ const fallbackFontSizePx = Math.max(
4623
+ 7,
4624
+ Math.min(
4625
+ 20,
4626
+ Math.round(
4627
+ runs.find((run) => Number.isFinite(run.fontSizePx))?.fontSizePx ?? 12
4628
+ )
4629
+ )
4630
+ );
4631
+ const lineHeightPx = Math.max(
4632
+ fallbackFontSizePx + 1,
4633
+ Math.round(params.lineHeightPx ?? fallbackFontSizePx * 1.25)
4634
+ );
4635
+ const skippedLineCount = Math.max(
4636
+ 0,
4637
+ Math.min(THUMBNAIL_DIRECT_MAX_LAYOUT_LINES - 1, Math.trunc(startLineIndex ?? 0))
4638
+ );
4639
+ const visibleLineCount = Math.max(
4640
+ 1,
4641
+ Math.min(
4642
+ THUMBNAIL_DIRECT_MAX_LINES,
4643
+ Math.ceil(safeHeightPx / Math.max(1, lineHeightPx)) + 1
4644
+ )
4645
+ );
4646
+ const lines = layoutDirectThumbnailTextRuns({
4647
+ context,
4648
+ runs,
4649
+ widthPx: safeWidthPx,
4650
+ fallbackFontSizePx,
4651
+ maxLineCount: skippedLineCount + visibleLineCount
4652
+ }).slice(skippedLineCount, skippedLineCount + visibleLineCount);
4653
+ context.save();
4654
+ context.beginPath();
4655
+ context.rect(xPx, yPx, safeWidthPx, safeHeightPx);
4656
+ context.clip();
4657
+ context.textBaseline = "alphabetic";
4658
+ let lastAppliedFont;
4659
+ lines.forEach((line, lineIndex) => {
4660
+ const lineTopPx = yPx + lineIndex * lineHeightPx;
4661
+ if (lineTopPx > yPx + safeHeightPx) {
4662
+ return;
4663
+ }
4664
+ let cursorXPx = directThumbnailAlignedX({
4665
+ xPx,
4666
+ widthPx: safeWidthPx,
4667
+ lineWidthPx: line.widthPx,
4668
+ align
4669
+ });
4670
+ const baselineYPx = lineTopPx + Math.max(1, Math.round(lineHeightPx * 0.78));
4671
+ line.segments.forEach((segment) => {
4672
+ const segmentWidthPx = segment.widthPx;
4673
+ if (segment.run.backgroundColor) {
4674
+ setCanvasFillStyle(context, segment.run.backgroundColor, "transparent");
4675
+ context.fillRect(cursorXPx, lineTopPx + 1, segmentWidthPx, lineHeightPx);
4676
+ }
4677
+ if (segment.font !== lastAppliedFont) {
4678
+ context.font = segment.font;
4679
+ lastAppliedFont = segment.font;
4680
+ }
4681
+ setCanvasFillStyle(
4682
+ context,
4683
+ segment.run.color,
4684
+ THUMBNAIL_DIRECT_DEFAULT_TEXT_COLOR
4685
+ );
4686
+ context.fillText(segment.text, cursorXPx, baselineYPx);
4687
+ cursorXPx += segmentWidthPx;
4688
+ });
4689
+ });
4690
+ context.restore();
4691
+ }
4692
+ function drawDirectThumbnailParagraph(context, paragraph) {
4693
+ const xPx = Math.round(paragraph.xPx);
4694
+ const yPx = Math.round(paragraph.yPx);
4695
+ const widthPx = Math.max(1, Math.round(paragraph.widthPx));
4696
+ const heightPx = Math.max(1, Math.round(paragraph.heightPx));
4697
+ if (paragraph.backgroundColor) {
4698
+ setCanvasFillStyle(context, paragraph.backgroundColor, "transparent");
4699
+ context.fillRect(xPx, yPx, widthPx, heightPx);
4700
+ }
4701
+ drawDirectThumbnailTextRuns({
4702
+ context,
4703
+ runs: paragraph.runs,
4704
+ xPx: xPx + 1,
4705
+ yPx,
4706
+ widthPx: Math.max(1, widthPx - 2),
4707
+ heightPx,
4708
+ align: paragraph.align,
4709
+ lineHeightPx: paragraph.lineHeightPx,
4710
+ startLineIndex: paragraph.startLineIndex
4711
+ });
4712
+ }
4713
+ function drawDirectThumbnailImagePlaceholder(context, image, hairlineSourcePx) {
4714
+ const xPx = Math.round(image.xPx);
4715
+ const yPx = Math.round(image.yPx);
4716
+ const widthPx = Math.max(1, Math.round(image.widthPx));
4717
+ const heightPx = Math.max(1, Math.round(image.heightPx));
4718
+ setCanvasFillStyle(
4719
+ context,
4720
+ image.backgroundColor,
4721
+ THUMBNAIL_DIRECT_IMAGE_BACKGROUND
4722
+ );
4723
+ context.fillRect(xPx, yPx, widthPx, heightPx);
4724
+ setCanvasStrokeStyle(
4725
+ context,
4726
+ image.borderColor,
4727
+ THUMBNAIL_DIRECT_TABLE_BORDER_COLOR
4728
+ );
4729
+ context.lineWidth = hairlineSourcePx;
4730
+ context.strokeRect(xPx, yPx, widthPx, heightPx);
4731
+ }
4732
+ function drawDirectThumbnailTable(context, table, hairlineSourcePx) {
4733
+ const tableXPx = Math.round(table.xPx);
4734
+ const tableYPx = Math.round(table.yPx);
4735
+ const tableWidthPx = Math.max(1, Math.round(table.widthPx));
4736
+ const tableHeightPx = Math.max(1, Math.round(table.heightPx));
4737
+ context.save();
4738
+ context.beginPath();
4739
+ context.rect(tableXPx, tableYPx, tableWidthPx, tableHeightPx);
4740
+ context.clip();
4741
+ setCanvasStrokeStyle(
4742
+ context,
4743
+ table.borderColor,
4744
+ THUMBNAIL_DIRECT_TABLE_BORDER_COLOR
4745
+ );
4746
+ context.lineWidth = hairlineSourcePx;
4747
+ table.cells.forEach((cell) => {
4748
+ const xPx = tableXPx + Math.round(cell.xPx);
4749
+ const yPx = tableYPx + Math.round(cell.yPx);
4750
+ const widthPx = Math.max(1, Math.round(cell.widthPx));
4751
+ const heightPx = Math.max(1, Math.round(cell.heightPx));
4752
+ if (cell.backgroundColor) {
4753
+ setCanvasFillStyle(context, cell.backgroundColor, "transparent");
4754
+ context.fillRect(xPx, yPx, widthPx, heightPx);
4755
+ }
4756
+ context.strokeRect(xPx, yPx, widthPx, heightPx);
4757
+ if (cell.runs?.length) {
4758
+ drawDirectThumbnailTextRuns({
4759
+ context,
4760
+ runs: cell.runs,
4761
+ xPx: xPx + 3,
4762
+ yPx: yPx + 2,
4763
+ widthPx: Math.max(1, widthPx - 6),
4764
+ heightPx: Math.max(1, heightPx - 4),
4765
+ lineHeightPx: 13
4766
+ });
4767
+ }
4768
+ });
4769
+ context.restore();
4770
+ }
4771
+ function renderDocxThumbnailSnapshotSurface(params) {
4772
+ if (typeof document === "undefined") {
4773
+ throw new Error("DOCX thumbnails require a browser environment.");
4774
+ }
4775
+ const sourceWidthPx = directThumbnailPositivePx(params.snapshot.sourceWidthPx);
4776
+ const sourceHeightPx = directThumbnailPositivePx(params.snapshot.sourceHeightPx);
4777
+ const pixelWidthPx = Math.max(1, Math.round(params.pixelWidthPx));
4778
+ const pixelHeightPx = Math.max(1, Math.round(params.pixelHeightPx));
4779
+ const surface = document.createElement("canvas");
4780
+ surface.width = pixelWidthPx;
4781
+ surface.height = pixelHeightPx;
4782
+ const context = surface.getContext("2d");
4783
+ if (!context) {
4784
+ throw new Error("2D canvas context is unavailable for DOCX thumbnails.");
4785
+ }
4786
+ const scaleX = pixelWidthPx / sourceWidthPx;
4787
+ const scaleY = pixelHeightPx / sourceHeightPx;
4788
+ const hairlineSourcePx = Math.max(0.75, 1 / Math.max(scaleX, scaleY));
4789
+ context.setTransform(scaleX, 0, 0, scaleY, 0, 0);
4790
+ context.imageSmoothingEnabled = true;
4791
+ context.imageSmoothingQuality = "high";
4792
+ setCanvasFillStyle(
4793
+ context,
4794
+ params.snapshot.pageBackgroundColor,
4795
+ "#ffffff"
4796
+ );
4797
+ context.fillRect(0, 0, sourceWidthPx, sourceHeightPx);
4798
+ params.snapshot.elements.slice(0, THUMBNAIL_DIRECT_MAX_ELEMENTS).forEach((element) => {
4799
+ switch (element.kind) {
4800
+ case "paragraph":
4801
+ drawDirectThumbnailParagraph(context, element);
4802
+ break;
4803
+ case "image-placeholder":
4804
+ drawDirectThumbnailImagePlaceholder(
4805
+ context,
4806
+ element,
4807
+ hairlineSourcePx
4808
+ );
4809
+ break;
4810
+ case "table":
4811
+ drawDirectThumbnailTable(context, element, hairlineSourcePx);
4812
+ break;
4813
+ }
4814
+ });
4815
+ return surface;
4816
+ }
4469
4817
  async function buildDocxThumbnailSvgMarkup(params) {
4470
4818
  const { pageElement, sourceWidthPx, sourceHeightPx, widthPx, heightPx } = params;
4471
4819
  const clone = pageElement.cloneNode(true);
@@ -4527,16 +4875,33 @@ async function rasterizeDocxThumbnailSurface(params) {
4527
4875
  return surface;
4528
4876
  }
4529
4877
  function blitDocxThumbnailSurface(surface, canvas, resolution) {
4530
- canvas.width = Math.max(1, Math.round(resolution.pixelWidthPx));
4531
- canvas.height = Math.max(1, Math.round(resolution.pixelHeightPx));
4532
- canvas.style.width = `${Math.max(1, Math.round(resolution.widthPx))}px`;
4533
- canvas.style.height = `${Math.max(1, Math.round(resolution.heightPx))}px`;
4878
+ const pixelWidth = Math.max(1, Math.round(resolution.pixelWidthPx));
4879
+ const pixelHeight = Math.max(1, Math.round(resolution.pixelHeightPx));
4880
+ const cssWidth = `${Math.max(1, Math.round(resolution.widthPx))}px`;
4881
+ const cssHeight = `${Math.max(1, Math.round(resolution.heightPx))}px`;
4882
+ let bufferResized = false;
4883
+ if (canvas.width !== pixelWidth) {
4884
+ canvas.width = pixelWidth;
4885
+ bufferResized = true;
4886
+ }
4887
+ if (canvas.height !== pixelHeight) {
4888
+ canvas.height = pixelHeight;
4889
+ bufferResized = true;
4890
+ }
4891
+ if (canvas.style.width !== cssWidth) {
4892
+ canvas.style.width = cssWidth;
4893
+ }
4894
+ if (canvas.style.height !== cssHeight) {
4895
+ canvas.style.height = cssHeight;
4896
+ }
4534
4897
  const context = canvas.getContext("2d");
4535
4898
  if (!context) {
4536
4899
  throw new Error("2D canvas context is unavailable for DOCX thumbnails.");
4537
4900
  }
4538
4901
  context.setTransform(1, 0, 0, 1, 0, 0);
4539
- context.clearRect(0, 0, canvas.width, canvas.height);
4902
+ if (!bufferResized) {
4903
+ context.clearRect(0, 0, canvas.width, canvas.height);
4904
+ }
4540
4905
  context.drawImage(surface, 0, 0, canvas.width, canvas.height);
4541
4906
  }
4542
4907
  var DocxThumbnailSurfaceCache = class {
@@ -4611,6 +4976,7 @@ var SerialIdleTaskQueue = class {
4611
4976
  now;
4612
4977
  pumpScheduled = false;
4613
4978
  running = false;
4979
+ nextSequence = 0;
4614
4980
  constructor(options) {
4615
4981
  this.scheduleTask = options?.scheduleTask ?? defaultScheduleTask;
4616
4982
  this.scheduleDelayed = options?.scheduleDelayed ?? defaultScheduleDelayed;
@@ -4620,14 +4986,23 @@ var SerialIdleTaskQueue = class {
4620
4986
  get pendingCount() {
4621
4987
  return this.pending.length;
4622
4988
  }
4623
- enqueue(key, run) {
4989
+ enqueue(key, run, options) {
4990
+ const priority = Number.isFinite(options?.priority) ? Number(options?.priority) : 0;
4624
4991
  return new Promise((resolve) => {
4625
4992
  const existing = this.pending.find((entry) => entry.key === key);
4626
4993
  if (existing) {
4627
4994
  existing.run = run;
4628
4995
  existing.resolvers.push(resolve);
4996
+ existing.priority = Math.min(existing.priority, priority);
4629
4997
  } else {
4630
- this.pending.push({ key, run, resolvers: [resolve] });
4998
+ this.pending.push({
4999
+ key,
5000
+ run,
5001
+ resolvers: [resolve],
5002
+ priority,
5003
+ sequence: this.nextSequence
5004
+ });
5005
+ this.nextSequence += 1;
4631
5006
  }
4632
5007
  this.schedulePump();
4633
5008
  });
@@ -4672,6 +5047,8 @@ var SerialIdleTaskQueue = class {
4672
5047
  }
4673
5048
  const now = this.now();
4674
5049
  let earliestWaitMs;
5050
+ let bestIndex = -1;
5051
+ let bestEntry;
4675
5052
  for (let index = 0; index < this.pending.length; index += 1) {
4676
5053
  const candidate = this.pending[index];
4677
5054
  if (!candidate) {
@@ -4680,11 +5057,18 @@ var SerialIdleTaskQueue = class {
4680
5057
  const lastRunAt = this.lastRunAtByKey.get(candidate.key);
4681
5058
  const waitMs = lastRunAt === void 0 ? 0 : lastRunAt + this.minTaskIntervalMs - now;
4682
5059
  if (waitMs <= 0) {
4683
- this.pending.splice(index, 1);
4684
- return { entry: candidate };
5060
+ if (!bestEntry || candidate.priority < bestEntry.priority || candidate.priority === bestEntry.priority && candidate.sequence < bestEntry.sequence) {
5061
+ bestEntry = candidate;
5062
+ bestIndex = index;
5063
+ }
5064
+ continue;
4685
5065
  }
4686
5066
  earliestWaitMs = earliestWaitMs === void 0 ? waitMs : Math.min(earliestWaitMs, waitMs);
4687
5067
  }
5068
+ if (bestEntry && bestIndex >= 0) {
5069
+ this.pending.splice(bestIndex, 1);
5070
+ return { entry: bestEntry };
5071
+ }
4688
5072
  return earliestWaitMs === void 0 ? void 0 : { retryDelayMs: earliestWaitMs };
4689
5073
  }
4690
5074
  async runNext() {
@@ -23682,13 +24066,15 @@ function ensureDocxViewerPageSurfaceRegistry(editor) {
23682
24066
  registry = {
23683
24067
  pageElements: /* @__PURE__ */ new Map(),
23684
24068
  pageContentKeys: /* @__PURE__ */ new Map(),
24069
+ pageSizes: /* @__PURE__ */ new Map(),
24070
+ pageThumbnailSnapshots: /* @__PURE__ */ new Map(),
23685
24071
  listeners: /* @__PURE__ */ new Set()
23686
24072
  };
23687
24073
  docxViewerPageSurfaceRegistryByEditor.set(owner, registry);
23688
24074
  }
23689
24075
  return registry;
23690
24076
  }
23691
- function syncDocxViewerPageSurfaceContentKeys(editor, contentKeysByPage) {
24077
+ function syncDocxViewerPageSurfaceContentKeys(editor, contentKeysByPage, pageSizesByPage = [], thumbnailSnapshotsByPage = []) {
23692
24078
  const registry = ensureDocxViewerPageSurfaceRegistry(editor);
23693
24079
  let changed = false;
23694
24080
  contentKeysByPage.forEach((contentKey, pageIndex) => {
@@ -23697,12 +24083,47 @@ function syncDocxViewerPageSurfaceContentKeys(editor, contentKeysByPage) {
23697
24083
  changed = true;
23698
24084
  }
23699
24085
  });
24086
+ pageSizesByPage.forEach((pageSize, pageIndex) => {
24087
+ const widthPx = Math.max(1, Math.round(pageSize.widthPx));
24088
+ const heightPx = Math.max(1, Math.round(pageSize.heightPx));
24089
+ const previous = registry.pageSizes.get(pageIndex);
24090
+ if (previous?.widthPx !== widthPx || previous?.heightPx !== heightPx) {
24091
+ registry.pageSizes.set(pageIndex, { widthPx, heightPx });
24092
+ changed = true;
24093
+ }
24094
+ });
24095
+ thumbnailSnapshotsByPage.forEach((snapshot, pageIndex) => {
24096
+ const previous = registry.pageThumbnailSnapshots.get(pageIndex);
24097
+ if (!snapshot) {
24098
+ if (previous) {
24099
+ registry.pageThumbnailSnapshots.delete(pageIndex);
24100
+ changed = true;
24101
+ }
24102
+ return;
24103
+ }
24104
+ if (previous?.key !== snapshot.key) {
24105
+ registry.pageThumbnailSnapshots.set(pageIndex, snapshot);
24106
+ changed = true;
24107
+ }
24108
+ });
23700
24109
  registry.pageContentKeys.forEach((_, pageIndex) => {
23701
24110
  if (pageIndex >= contentKeysByPage.length) {
23702
24111
  registry.pageContentKeys.delete(pageIndex);
23703
24112
  changed = true;
23704
24113
  }
23705
24114
  });
24115
+ registry.pageSizes.forEach((_, pageIndex) => {
24116
+ if (pageIndex >= pageSizesByPage.length) {
24117
+ registry.pageSizes.delete(pageIndex);
24118
+ changed = true;
24119
+ }
24120
+ });
24121
+ registry.pageThumbnailSnapshots.forEach((_, pageIndex) => {
24122
+ if (pageIndex >= thumbnailSnapshotsByPage.length) {
24123
+ registry.pageThumbnailSnapshots.delete(pageIndex);
24124
+ changed = true;
24125
+ }
24126
+ });
23706
24127
  if (changed) {
23707
24128
  notifyDocxViewerPageSurfaceSubscribers(registry);
23708
24129
  }
@@ -23719,7 +24140,7 @@ function notifyDocxViewerPageSurfaceSubscribers(registry) {
23719
24140
  listener();
23720
24141
  });
23721
24142
  }
23722
- function registerDocxViewerPageSurface(editor, pageIndex, element) {
24143
+ function registerDocxViewerPageSurface(editor, pageIndex, element, previousElement) {
23723
24144
  const registry = ensureDocxViewerPageSurfaceRegistry(editor);
23724
24145
  const normalizedPageIndex = Math.max(0, Math.round(pageIndex));
23725
24146
  const currentElement = registry.pageElements.get(normalizedPageIndex);
@@ -23734,9 +24155,22 @@ function registerDocxViewerPageSurface(editor, pageIndex, element) {
23734
24155
  if (!currentElement) {
23735
24156
  return;
23736
24157
  }
24158
+ if (previousElement && currentElement !== previousElement) {
24159
+ return;
24160
+ }
23737
24161
  registry.pageElements.delete(normalizedPageIndex);
23738
24162
  notifyDocxViewerPageSurfaceSubscribers(registry);
23739
24163
  }
24164
+ function resolveDocxViewerRegisteredPageSurfaceSize(registry, pageIndex, fallbackWidthPx, fallbackHeightPx) {
24165
+ const registeredSize = registry.pageSizes.get(pageIndex);
24166
+ if (registeredSize) {
24167
+ return registeredSize;
24168
+ }
24169
+ return {
24170
+ widthPx: Math.max(1, Math.round(fallbackWidthPx)),
24171
+ heightPx: Math.max(1, Math.round(fallbackHeightPx))
24172
+ };
24173
+ }
23740
24174
  function resolveDocxViewerPageSurfaceSize(element, fallbackWidthPx, fallbackHeightPx) {
23741
24175
  if (element) {
23742
24176
  const rect = element.getBoundingClientRect();
@@ -23770,58 +24204,546 @@ function resolveDocxViewerPageSurfaceSize(element, fallbackWidthPx, fallbackHeig
23770
24204
  heightPx: Math.max(1, Math.round(fallbackHeightPx))
23771
24205
  };
23772
24206
  }
23773
- var DOCX_THUMBNAIL_SURFACE_CACHE_MAX_ENTRIES = 32;
23774
- var DOCX_THUMBNAIL_MIN_RASTER_INTERVAL_MS = 200;
23775
- function resolveDocxPageThumbnailResolution(options) {
23776
- const safeSourceWidthPx = Math.max(1, Math.round(options.sourceWidthPx));
23777
- const safeSourceHeightPx = Math.max(1, Math.round(options.sourceHeightPx));
23778
- const resolutionBounds = typeof options.resolution === "number" && Number.isFinite(options.resolution) && options.resolution > 0 ? {
23779
- maxWidthPx: Number(options.resolution),
23780
- maxHeightPx: Number(options.resolution)
23781
- } : typeof options.resolution === "object" && options.resolution ? {
23782
- maxWidthPx: Number.isFinite(options.resolution.maxWidth) && Number(options.resolution.maxWidth) > 0 ? Number(options.resolution.maxWidth) : void 0,
23783
- maxHeightPx: Number.isFinite(options.resolution.maxHeight) && Number(options.resolution.maxHeight) > 0 ? Number(options.resolution.maxHeight) : void 0
23784
- } : void 0;
23785
- 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;
23786
- 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;
23787
- const scale = Math.min(
23788
- 1,
23789
- widthBoundPx / safeSourceWidthPx,
23790
- heightBoundPx / safeSourceHeightPx
23791
- );
23792
- const pixelRatio = Number.isFinite(options.pixelRatio) ? Math.max(1, Number(options.pixelRatio)) : 1;
23793
- const widthPx = Math.max(1, Math.round(safeSourceWidthPx * scale));
23794
- const heightPx = Math.max(1, Math.round(safeSourceHeightPx * scale));
23795
- return {
23796
- widthPx,
23797
- heightPx,
23798
- pixelWidthPx: Math.max(1, Math.round(widthPx * pixelRatio)),
23799
- pixelHeightPx: Math.max(1, Math.round(heightPx * pixelRatio)),
23800
- scale
23801
- };
24207
+ var DOCX_DIRECT_THUMBNAIL_MAX_ELEMENTS_PER_PAGE = 260;
24208
+ var DOCX_DIRECT_THUMBNAIL_MAX_TEXT_RUNS = 28;
24209
+ var DOCX_DIRECT_THUMBNAIL_MAX_TEXT_CHARS = 900;
24210
+ var DOCX_DIRECT_THUMBNAIL_MAX_TABLE_CELLS = 220;
24211
+ var DOCX_DIRECT_THUMBNAIL_MAX_CELL_TEXT_CHARS = 120;
24212
+ function docxThumbnailCssNumber(value) {
24213
+ if (typeof value === "number" && Number.isFinite(value)) {
24214
+ return value;
24215
+ }
24216
+ if (typeof value === "string") {
24217
+ const parsed = Number.parseFloat(value);
24218
+ return Number.isFinite(parsed) ? parsed : 0;
24219
+ }
24220
+ return 0;
23802
24221
  }
23803
- function useDocxPageThumbnails(editor, options = {}) {
23804
- const pageSurfaceRegistryOwner = docxViewerPageSurfaceRegistryOwner(editor);
23805
- const pageSurfaceRegistryEditor = React.useMemo(
23806
- () => ({ syncPaginationInfo: editor.syncPaginationInfo }),
23807
- [pageSurfaceRegistryOwner]
24222
+ function normalizeDocxThumbnailColor(value) {
24223
+ const normalized = value?.trim();
24224
+ if (!normalized || normalized.toLowerCase() === "auto") {
24225
+ return void 0;
24226
+ }
24227
+ if (/^[0-9a-fA-F]{6}$/.test(normalized)) {
24228
+ return `#${normalized}`;
24229
+ }
24230
+ return normalized;
24231
+ }
24232
+ function docxThumbnailTextRunStylesMatch(left, right) {
24233
+ 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;
24234
+ }
24235
+ function appendDocxThumbnailTextRun(runs, run, remaining) {
24236
+ if (remaining.chars <= 0 || runs.length >= DOCX_DIRECT_THUMBNAIL_MAX_TEXT_RUNS) {
24237
+ return;
24238
+ }
24239
+ const text = run.text.slice(0, remaining.chars);
24240
+ if (!text) {
24241
+ return;
24242
+ }
24243
+ remaining.chars -= text.length;
24244
+ const nextRun = { ...run, text };
24245
+ const previous = runs[runs.length - 1];
24246
+ if (previous && docxThumbnailTextRunStylesMatch(previous, nextRun)) {
24247
+ previous.text += nextRun.text;
24248
+ return;
24249
+ }
24250
+ runs.push(nextRun);
24251
+ }
24252
+ function docxThumbnailFallbackHeadingRunStyle(paragraph) {
24253
+ const headingLevel = paragraph.style?.headingLevel;
24254
+ return headingLevel && headingLevel >= 1 && headingLevel <= 6 ? DEFAULT_WORD_HEADING_RUN_STYLES[headingLevel] : void 0;
24255
+ }
24256
+ function docxThumbnailTextRunsFromParagraph(paragraph, documentTheme, maxChars = DOCX_DIRECT_THUMBNAIL_MAX_TEXT_CHARS) {
24257
+ const runs = [];
24258
+ const remaining = { chars: maxChars };
24259
+ const fallbackHeadingStyle = docxThumbnailFallbackHeadingRunStyle(paragraph);
24260
+ const fallbackFontFamily = cssFontFamily(fallbackHeadingStyle?.fontFamily) ?? cssFontFamily(paragraphDominantFontFamily(paragraph));
24261
+ paragraph.children.forEach((child) => {
24262
+ if (remaining.chars <= 0) {
24263
+ return;
24264
+ }
24265
+ const style = child.type === "text" || child.type === "form-field" ? child.style : void 0;
24266
+ const text = child.type === "text" ? child.text : child.type === "form-field" ? formFieldDisplayValue2(child) : child.type === "image" ? child.alt || "[image]" : "";
24267
+ appendDocxThumbnailTextRun(
24268
+ runs,
24269
+ {
24270
+ text,
24271
+ bold: style?.bold ?? fallbackHeadingStyle?.bold,
24272
+ italic: style?.italic ?? fallbackHeadingStyle?.italic,
24273
+ color: normalizeDocxThumbnailColor(
24274
+ themedRunColor(style?.color ?? fallbackHeadingStyle?.color, documentTheme)
24275
+ ),
24276
+ backgroundColor: normalizeDocxThumbnailColor(
24277
+ style?.backgroundColor ?? resolveHighlightColor(style?.highlight)
24278
+ ),
24279
+ fontSizePx: style?.fontSizePt && style.fontSizePt > 0 ? Math.max(6, style.fontSizePt * 96 / 72) : fallbackHeadingStyle?.fontSizePt ? Math.max(6, fallbackHeadingStyle.fontSizePt * 96 / 72) : paragraphBaseFontSizePx(paragraph),
24280
+ fontFamily: cssFontFamily(style?.fontFamily) ?? fallbackFontFamily
24281
+ },
24282
+ remaining
24283
+ );
24284
+ });
24285
+ return runs;
24286
+ }
24287
+ function docxThumbnailCellTextRuns(cell, documentTheme) {
24288
+ const paragraph = tableCellParagraphsRecursively(cell.nodes).find(
24289
+ (candidate) => paragraphText(candidate).trim().length > 0
23808
24290
  );
23809
- const [pageSurfaceEpoch, setPageSurfaceEpoch] = React.useState(0);
23810
- const [pageThumbnailStates, setPageThumbnailStates] = React.useState(() => /* @__PURE__ */ new Map());
23811
- const attachedCanvasByPageRef = React.useRef(
23812
- /* @__PURE__ */ new Map()
24291
+ if (!paragraph) {
24292
+ return void 0;
24293
+ }
24294
+ const runs = docxThumbnailTextRunsFromParagraph(
24295
+ paragraph,
24296
+ documentTheme,
24297
+ DOCX_DIRECT_THUMBNAIL_MAX_CELL_TEXT_CHARS
23813
24298
  );
23814
- const canvasRefCallbacksRef = React.useRef(/* @__PURE__ */ new Map());
23815
- const renderToCanvasCallbacksRef = React.useRef(/* @__PURE__ */ new Map());
23816
- const fallbackLayout = React.useMemo(
23817
- () => resolveDocumentLayout(editor.model),
23818
- [editor.model]
24299
+ return runs.length > 0 ? runs : void 0;
24300
+ }
24301
+ function buildDocxThumbnailParagraphElements(params) {
24302
+ const {
24303
+ paragraph,
24304
+ segment,
24305
+ contentLeftPx,
24306
+ contentWidthPx,
24307
+ yPx,
24308
+ heightPx,
24309
+ numberingDefinitions,
24310
+ docGridLinePitchPx,
24311
+ documentTheme
24312
+ } = params;
24313
+ const blockStyle = paragraphBlockStyle(
24314
+ paragraph,
24315
+ numberingDefinitions,
24316
+ void 0,
24317
+ docGridLinePitchPx
23819
24318
  );
23820
- const pageSurfaceRegistry = React.useMemo(
23821
- () => ensureDocxViewerPageSurfaceRegistry(pageSurfaceRegistryEditor),
23822
- [pageSurfaceRegistryEditor]
24319
+ const paragraphLineRange = segment.paragraphLineRange;
24320
+ const marginTopPx = paragraphLineRange && paragraphLineRange.startLineIndex > 0 ? 0 : Math.max(0, docxThumbnailCssNumber(blockStyle.marginTop));
24321
+ const marginBottomPx = paragraphLineRange && paragraphLineRange.endLineIndex < paragraphLineRange.totalLineCount ? 0 : Math.max(0, docxThumbnailCssNumber(blockStyle.marginBottom));
24322
+ const marginLeftPx = docxThumbnailCssNumber(blockStyle.marginLeft);
24323
+ const marginRightPx = Math.max(0, docxThumbnailCssNumber(blockStyle.marginRight));
24324
+ const xPx = contentLeftPx + marginLeftPx;
24325
+ const widthPx = Math.max(8, contentWidthPx - marginLeftPx - marginRightPx);
24326
+ const bodyYPx = yPx + marginTopPx;
24327
+ const bodyHeightPx = Math.max(1, heightPx - marginTopPx - marginBottomPx);
24328
+ const runs = docxThumbnailTextRunsFromParagraph(paragraph, documentTheme);
24329
+ const elements = [];
24330
+ elements.push({
24331
+ kind: "paragraph",
24332
+ xPx,
24333
+ yPx: bodyYPx,
24334
+ widthPx,
24335
+ heightPx: bodyHeightPx,
24336
+ align: paragraph.style?.align,
24337
+ backgroundColor: normalizeDocxThumbnailColor(
24338
+ paragraph.style?.backgroundColor
24339
+ ),
24340
+ lineHeightPx: paragraphLineRange?.lineHeightPx ?? estimateParagraphLineHeightPx(paragraph, docGridLinePitchPx),
24341
+ startLineIndex: paragraphLineRange?.startLineIndex,
24342
+ runs
24343
+ });
24344
+ const hasVisibleText = runs.some((run) => run.text.trim().length > 0);
24345
+ if (!hasVisibleText) {
24346
+ const imageRuns = paragraph.children.filter(
24347
+ (child) => child.type === "image"
24348
+ );
24349
+ imageRuns.slice(0, 4).forEach((imageRun) => {
24350
+ const imageWidthPx = Math.max(18, imageRun.widthPx ?? widthPx * 0.5);
24351
+ const imageHeightPx = Math.max(18, imageRun.heightPx ?? bodyHeightPx * 0.5);
24352
+ const scale = Math.min(
24353
+ 1,
24354
+ (widthPx - 4) / imageWidthPx,
24355
+ (bodyHeightPx - 4) / imageHeightPx
24356
+ );
24357
+ elements.push({
24358
+ kind: "image-placeholder",
24359
+ xPx: xPx + 2,
24360
+ yPx: bodyYPx + 2,
24361
+ widthPx: Math.max(12, imageWidthPx * scale),
24362
+ heightPx: Math.max(12, imageHeightPx * scale)
24363
+ });
24364
+ });
24365
+ }
24366
+ return elements;
24367
+ }
24368
+ function buildDocxThumbnailTableElement(params) {
24369
+ const {
24370
+ table,
24371
+ segment,
24372
+ contentLeftPx,
24373
+ contentWidthPx,
24374
+ contentHeightPx,
24375
+ yPx,
24376
+ heightPx,
24377
+ numberingDefinitions,
24378
+ docGridLinePitchPx,
24379
+ documentTheme
24380
+ } = params;
24381
+ const columnCount = tableColumnCount(table);
24382
+ const tableIndentPx = twipsToSignedPixels(table.style?.indentTwips) ?? 0;
24383
+ const tableWidthPx = twipsToPixels(table.style?.widthTwips);
24384
+ const definedWidthsTwips = columnWidthsFromTableDefinition(table, columnCount);
24385
+ const rawTableColumnWidthsPx = definedWidthsTwips && definedWidthsTwips.length > 0 ? normalizeColumnWidthsPx(
24386
+ definedWidthsTwips.map((widthTwips) => twipsToPixels(widthTwips) ?? 0),
24387
+ columnCount,
24388
+ tableWidthPx,
24389
+ 1
24390
+ ) : defaultColumnWidthsPx(columnCount, tableWidthPx);
24391
+ const rawResolvedTableWidthPx = tableWidthPx ?? rawTableColumnWidthsPx.reduce((sum, widthPx) => sum + widthPx, 0);
24392
+ const maxTableWidthPx = Math.max(
24393
+ 24,
24394
+ contentWidthPx - tableIndentPx - resolveCollapsedTableHorizontalOuterBleedPx(table, columnCount)
23823
24395
  );
23824
- React.useEffect(
24396
+ const resolvedTableWidthPx = clampTableWidthPx(
24397
+ rawResolvedTableWidthPx,
24398
+ maxTableWidthPx
24399
+ );
24400
+ const { columnWidthsPx } = resolveFittedTableColumnWidths(
24401
+ table,
24402
+ rawTableColumnWidthsPx,
24403
+ resolvedTableWidthPx
24404
+ );
24405
+ const rowHeightsPx = estimateTableRowHeightsPx(
24406
+ table,
24407
+ contentWidthPx,
24408
+ numberingDefinitions,
24409
+ docGridLinePitchPx,
24410
+ contentHeightPx
24411
+ );
24412
+ const startRowIndex = Math.max(
24413
+ 0,
24414
+ segment.tableRowSlice?.rowIndex ?? segment.tableRowRange?.startRowIndex ?? 0
24415
+ );
24416
+ const endRowIndex = Math.min(
24417
+ table.rows.length,
24418
+ segment.tableRowSlice ? startRowIndex + 1 : segment.tableRowRange?.endRowIndex ?? table.rows.length
24419
+ );
24420
+ const cells = [];
24421
+ let rowYPx = segment.tableRowSlice ? -Math.max(0, segment.tableRowSlice.startOffsetPx) : 0;
24422
+ for (let rowIndex = startRowIndex; rowIndex < endRowIndex && cells.length < DOCX_DIRECT_THUMBNAIL_MAX_TABLE_CELLS; rowIndex += 1) {
24423
+ const row = table.rows[rowIndex];
24424
+ if (!row) {
24425
+ continue;
24426
+ }
24427
+ const rowHeightPx = segment.tableRowSlice && rowIndex === segment.tableRowSlice.rowIndex ? Math.max(1, segment.tableRowSlice.totalRowHeightPx) : Math.max(1, rowHeightsPx[rowIndex] ?? MIN_PARAGRAPH_LINE_HEIGHT_PX);
24428
+ let columnCursor = 0;
24429
+ row.cells.forEach((cell) => {
24430
+ if (cells.length >= DOCX_DIRECT_THUMBNAIL_MAX_TABLE_CELLS) {
24431
+ return;
24432
+ }
24433
+ const span = cell.style?.gridSpan && cell.style.gridSpan > 1 ? cell.style.gridSpan : 1;
24434
+ const xPx = columnWidthsPx.slice(0, columnCursor).reduce((sum, widthPx2) => sum + widthPx2, 0);
24435
+ const widthPx = columnWidthsPx.slice(columnCursor, columnCursor + span).reduce((sum, widthPx2) => sum + widthPx2, 0);
24436
+ columnCursor += span;
24437
+ cells.push({
24438
+ xPx,
24439
+ yPx: rowYPx,
24440
+ widthPx: Math.max(1, widthPx),
24441
+ heightPx: rowHeightPx,
24442
+ backgroundColor: normalizeDocxThumbnailColor(
24443
+ cell.style?.backgroundColor ?? row.style?.backgroundColor
24444
+ ),
24445
+ runs: docxThumbnailCellTextRuns(cell, documentTheme)
24446
+ });
24447
+ });
24448
+ rowYPx += rowHeightPx;
24449
+ }
24450
+ if (cells.length === 0) {
24451
+ return void 0;
24452
+ }
24453
+ return {
24454
+ kind: "table",
24455
+ xPx: contentLeftPx + tableIndentPx,
24456
+ yPx,
24457
+ widthPx: Math.max(1, resolvedTableWidthPx),
24458
+ heightPx: Math.max(1, heightPx),
24459
+ cells
24460
+ };
24461
+ }
24462
+ function buildDocxPageThumbnailRenderSnapshotEntries(params) {
24463
+ const {
24464
+ model,
24465
+ pageNodeSegmentsByPage,
24466
+ pageSectionInfoByIndex,
24467
+ contentKeysByPage,
24468
+ fallbackLayout,
24469
+ documentTheme,
24470
+ docGridLinePitchPxByNodeIndex,
24471
+ numberingDefinitions
24472
+ } = params;
24473
+ return pageNodeSegmentsByPage.map((pageSegments, pageIndex) => {
24474
+ const key = `${contentKeysByPage[pageIndex] ?? ""}|theme:${documentTheme}`;
24475
+ let cachedSnapshot;
24476
+ return {
24477
+ key,
24478
+ getSnapshot: () => {
24479
+ if (cachedSnapshot) {
24480
+ return cachedSnapshot;
24481
+ }
24482
+ const pageLayout = pageSectionInfoByIndex[pageIndex]?.layout ?? fallbackLayout;
24483
+ const contentLeftPx = pageLayout.marginsPx.left;
24484
+ const contentTopPx = pageLayout.marginsPx.top;
24485
+ const contentWidthPx = Math.max(
24486
+ 1,
24487
+ pageLayout.pageWidthPx - pageLayout.marginsPx.left - pageLayout.marginsPx.right
24488
+ );
24489
+ const contentHeightPx = Math.max(
24490
+ 1,
24491
+ pageLayout.pageHeightPx - pageLayout.marginsPx.top - pageLayout.marginsPx.bottom
24492
+ );
24493
+ const elements = [];
24494
+ let yPx = contentTopPx;
24495
+ for (const segment of pageSegments) {
24496
+ if (elements.length >= DOCX_DIRECT_THUMBNAIL_MAX_ELEMENTS_PER_PAGE) {
24497
+ break;
24498
+ }
24499
+ const node = model.nodes[segment.nodeIndex];
24500
+ if (!node) {
24501
+ continue;
24502
+ }
24503
+ const docGridLinePitchPx = docGridLinePitchPxByNodeIndex.get(
24504
+ segment.nodeIndex
24505
+ );
24506
+ const segmentHeightPx = estimateRenderedPageSegmentHeightPx(
24507
+ node,
24508
+ segment,
24509
+ model,
24510
+ contentWidthPx,
24511
+ numberingDefinitions,
24512
+ docGridLinePitchPx
24513
+ );
24514
+ if (node.type === "paragraph" && !segment.tableRowRange) {
24515
+ elements.push(
24516
+ ...buildDocxThumbnailParagraphElements({
24517
+ paragraph: node,
24518
+ segment,
24519
+ contentLeftPx,
24520
+ contentTopPx,
24521
+ contentWidthPx,
24522
+ yPx,
24523
+ heightPx: segmentHeightPx,
24524
+ numberingDefinitions,
24525
+ docGridLinePitchPx,
24526
+ documentTheme
24527
+ })
24528
+ );
24529
+ } else if (node.type === "table") {
24530
+ const tableElement = buildDocxThumbnailTableElement({
24531
+ table: node,
24532
+ segment,
24533
+ contentLeftPx,
24534
+ contentWidthPx,
24535
+ contentHeightPx,
24536
+ yPx,
24537
+ heightPx: segmentHeightPx,
24538
+ numberingDefinitions,
24539
+ docGridLinePitchPx,
24540
+ documentTheme
24541
+ });
24542
+ if (tableElement) {
24543
+ elements.push(tableElement);
24544
+ }
24545
+ }
24546
+ yPx += Math.max(1, segmentHeightPx);
24547
+ }
24548
+ cachedSnapshot = {
24549
+ key,
24550
+ sourceWidthPx: pageLayout.pageWidthPx,
24551
+ sourceHeightPx: pageLayout.pageHeightPx,
24552
+ pageBackgroundColor: documentTheme === "dark" ? "#111827" : "#ffffff",
24553
+ elements
24554
+ };
24555
+ return cachedSnapshot;
24556
+ }
24557
+ };
24558
+ });
24559
+ }
24560
+ function DocxDetachedThumbnailPageSurface({
24561
+ editor,
24562
+ pageIndex
24563
+ }) {
24564
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
24565
+ DocxEditorViewer,
24566
+ {
24567
+ editor,
24568
+ mode: "read-only",
24569
+ visiblePageRange: { startPageIndex: pageIndex, endPageIndex: pageIndex },
24570
+ pageVirtualization: { enabled: false },
24571
+ showTrackedChanges: editor.showTrackedChanges,
24572
+ showComments: editor.showComments,
24573
+ style: {
24574
+ background: "transparent",
24575
+ padding: 0,
24576
+ margin: 0
24577
+ }
24578
+ }
24579
+ );
24580
+ }
24581
+ var DocxDetachedThumbnailSurfaceRenderer = class {
24582
+ host;
24583
+ root;
24584
+ activePageIndex;
24585
+ activeRenderKey;
24586
+ async renderPageSurface(params) {
24587
+ if (typeof document === "undefined" || typeof window === "undefined") {
24588
+ return void 0;
24589
+ }
24590
+ const { editor, registry, pageIndex, renderKey } = params;
24591
+ await this.ensureRoot();
24592
+ if (!this.root) {
24593
+ return void 0;
24594
+ }
24595
+ if (this.activePageIndex !== pageIndex || this.activeRenderKey !== renderKey) {
24596
+ this.activePageIndex = pageIndex;
24597
+ this.activeRenderKey = renderKey;
24598
+ this.root.render(
24599
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
24600
+ DocxDetachedThumbnailPageSurface,
24601
+ {
24602
+ editor,
24603
+ pageIndex
24604
+ }
24605
+ )
24606
+ );
24607
+ }
24608
+ return this.waitForPageSurface(registry, pageIndex);
24609
+ }
24610
+ clear() {
24611
+ if (this.root) {
24612
+ this.root.unmount();
24613
+ this.root = void 0;
24614
+ }
24615
+ if (this.host?.parentNode) {
24616
+ this.host.parentNode.removeChild(this.host);
24617
+ }
24618
+ this.host = void 0;
24619
+ this.activePageIndex = void 0;
24620
+ this.activeRenderKey = void 0;
24621
+ }
24622
+ async ensureRoot() {
24623
+ if (this.root) {
24624
+ return;
24625
+ }
24626
+ if (typeof document === "undefined") {
24627
+ return;
24628
+ }
24629
+ const host = document.createElement("div");
24630
+ host.setAttribute("data-docx-thumbnail-detached-renderer", "true");
24631
+ Object.assign(host.style, {
24632
+ position: "fixed",
24633
+ left: "-100000px",
24634
+ top: "0",
24635
+ width: "1px",
24636
+ height: "1px",
24637
+ overflow: "visible",
24638
+ opacity: "0",
24639
+ pointerEvents: "none",
24640
+ zIndex: "-1"
24641
+ });
24642
+ document.body.appendChild(host);
24643
+ this.host = host;
24644
+ const { createRoot } = await import("react-dom/client");
24645
+ this.root = createRoot(host);
24646
+ }
24647
+ async waitForPageSurface(registry, pageIndex) {
24648
+ for (let attempt = 0; attempt < 8; attempt += 1) {
24649
+ const pageElement2 = registry.pageElements.get(pageIndex);
24650
+ if (pageElement2?.isConnected) {
24651
+ return pageElement2;
24652
+ }
24653
+ await new Promise((resolve) => {
24654
+ window.requestAnimationFrame(() => resolve());
24655
+ });
24656
+ }
24657
+ const pageElement = registry.pageElements.get(pageIndex);
24658
+ return pageElement?.isConnected ? pageElement : void 0;
24659
+ }
24660
+ };
24661
+ var DOCX_THUMBNAIL_SURFACE_CACHE_MAX_ENTRIES = 64;
24662
+ var DOCX_THUMBNAIL_MIN_RASTER_INTERVAL_MS = 200;
24663
+ var DOCX_THUMBNAIL_RENDER_PRIORITY_VISIBLE = 0;
24664
+ var DOCX_THUMBNAIL_RENDER_PRIORITY_ATTACHED = 1;
24665
+ var DOCX_THUMBNAIL_RENDER_PRIORITY_PREFETCH = 2;
24666
+ function normalizeDocxThumbnailMinRasterIntervalMs(value) {
24667
+ return Number.isFinite(value) && Number(value) >= 0 ? Number(value) : DOCX_THUMBNAIL_MIN_RASTER_INTERVAL_MS;
24668
+ }
24669
+ function normalizeDocxThumbnailPageIndexes(indexes, totalPages) {
24670
+ if (!indexes?.length || totalPages <= 0) {
24671
+ return [];
24672
+ }
24673
+ const seen = /* @__PURE__ */ new Set();
24674
+ const normalized = [];
24675
+ indexes.forEach((pageIndex) => {
24676
+ if (!Number.isFinite(pageIndex)) {
24677
+ return;
24678
+ }
24679
+ const roundedPageIndex = Math.trunc(pageIndex);
24680
+ if (roundedPageIndex < 0 || roundedPageIndex >= totalPages || seen.has(roundedPageIndex)) {
24681
+ return;
24682
+ }
24683
+ seen.add(roundedPageIndex);
24684
+ normalized.push(roundedPageIndex);
24685
+ });
24686
+ return normalized;
24687
+ }
24688
+ function docxThumbnailPageIndexesKey(pageIndexes) {
24689
+ return pageIndexes.join(",");
24690
+ }
24691
+ function docxThumbnailCanvasQueueKey(canvasId) {
24692
+ return `canvas:${canvasId}`;
24693
+ }
24694
+ function docxThumbnailPrefetchQueueKey(pageIndex) {
24695
+ return `prefetch:${pageIndex}`;
24696
+ }
24697
+ function resolveDocxPageThumbnailResolution(options) {
24698
+ const safeSourceWidthPx = Math.max(1, Math.round(options.sourceWidthPx));
24699
+ const safeSourceHeightPx = Math.max(1, Math.round(options.sourceHeightPx));
24700
+ const resolutionBounds = typeof options.resolution === "number" && Number.isFinite(options.resolution) && options.resolution > 0 ? {
24701
+ maxWidthPx: Number(options.resolution),
24702
+ maxHeightPx: Number(options.resolution)
24703
+ } : typeof options.resolution === "object" && options.resolution ? {
24704
+ maxWidthPx: Number.isFinite(options.resolution.maxWidth) && Number(options.resolution.maxWidth) > 0 ? Number(options.resolution.maxWidth) : void 0,
24705
+ maxHeightPx: Number.isFinite(options.resolution.maxHeight) && Number(options.resolution.maxHeight) > 0 ? Number(options.resolution.maxHeight) : void 0
24706
+ } : void 0;
24707
+ 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;
24708
+ 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;
24709
+ const scale = Math.min(
24710
+ 1,
24711
+ widthBoundPx / safeSourceWidthPx,
24712
+ heightBoundPx / safeSourceHeightPx
24713
+ );
24714
+ const pixelRatio = Number.isFinite(options.pixelRatio) ? Math.max(1, Number(options.pixelRatio)) : 1;
24715
+ const widthPx = Math.max(1, Math.round(safeSourceWidthPx * scale));
24716
+ const heightPx = Math.max(1, Math.round(safeSourceHeightPx * scale));
24717
+ return {
24718
+ widthPx,
24719
+ heightPx,
24720
+ pixelWidthPx: Math.max(1, Math.round(widthPx * pixelRatio)),
24721
+ pixelHeightPx: Math.max(1, Math.round(heightPx * pixelRatio)),
24722
+ scale
24723
+ };
24724
+ }
24725
+ function useDocxPageThumbnails(editor, options = {}) {
24726
+ const pageSurfaceRegistryOwner = docxViewerPageSurfaceRegistryOwner(editor);
24727
+ const pageSurfaceRegistryEditor = React.useMemo(
24728
+ () => ({ syncPaginationInfo: editor.syncPaginationInfo }),
24729
+ [pageSurfaceRegistryOwner]
24730
+ );
24731
+ const [pageSurfaceEpoch, setPageSurfaceEpoch] = React.useState(0);
24732
+ const [pageThumbnailStates, setPageThumbnailStates] = React.useState(() => /* @__PURE__ */ new Map());
24733
+ const attachedCanvasByPageRef = React.useRef(
24734
+ /* @__PURE__ */ new Map()
24735
+ );
24736
+ const canvasRefCallbacksRef = React.useRef(/* @__PURE__ */ new Map());
24737
+ const renderToCanvasCallbacksRef = React.useRef(/* @__PURE__ */ new Map());
24738
+ const fallbackLayout = React.useMemo(
24739
+ () => resolveDocumentLayout(editor.model),
24740
+ [editor.model]
24741
+ );
24742
+ const pageSurfaceRegistry = React.useMemo(
24743
+ () => ensureDocxViewerPageSurfaceRegistry(pageSurfaceRegistryEditor),
24744
+ [pageSurfaceRegistryEditor]
24745
+ );
24746
+ React.useEffect(
23825
24747
  () => subscribeDocxViewerPageSurfaces(pageSurfaceRegistryEditor, () => {
23826
24748
  setPageSurfaceEpoch((current) => current + 1);
23827
24749
  }),
@@ -23847,9 +24769,16 @@ function useDocxPageThumbnails(editor, options = {}) {
23847
24769
  );
23848
24770
  const thumbnailSurfaceCacheRef = React.useRef(void 0);
23849
24771
  const thumbnailRasterQueueRef = React.useRef(void 0);
24772
+ const detachedThumbnailSurfaceRendererRef = React.useRef(void 0);
23850
24773
  const lastPaintedThumbnailKeyByCanvasRef = React.useRef(
23851
24774
  /* @__PURE__ */ new WeakMap()
23852
24775
  );
24776
+ const thumbnailQueueKeyByCanvasRef = React.useRef(
24777
+ /* @__PURE__ */ new WeakMap()
24778
+ );
24779
+ const nextThumbnailCanvasQueueIdRef = React.useRef(0);
24780
+ const queuedPrefetchThumbnailKeysRef = React.useRef(/* @__PURE__ */ new Set());
24781
+ const thumbnailMinRasterIntervalMs = normalizeDocxThumbnailMinRasterIntervalMs(options.minRasterIntervalMs);
23853
24782
  const ensureThumbnailSurfaceCache = React.useCallback(() => {
23854
24783
  if (!thumbnailSurfaceCacheRef.current) {
23855
24784
  thumbnailSurfaceCacheRef.current = new DocxThumbnailSurfaceCache(
@@ -23861,16 +24790,57 @@ function useDocxPageThumbnails(editor, options = {}) {
23861
24790
  const ensureThumbnailRasterQueue = React.useCallback(() => {
23862
24791
  if (!thumbnailRasterQueueRef.current) {
23863
24792
  thumbnailRasterQueueRef.current = new SerialIdleTaskQueue({
23864
- minTaskIntervalMs: DOCX_THUMBNAIL_MIN_RASTER_INTERVAL_MS
24793
+ minTaskIntervalMs: thumbnailMinRasterIntervalMs
23865
24794
  });
23866
24795
  }
23867
24796
  return thumbnailRasterQueueRef.current;
23868
- }, []);
24797
+ }, [thumbnailMinRasterIntervalMs]);
24798
+ const thumbnailQueueKeyForCanvas = React.useCallback(
24799
+ (canvas) => {
24800
+ const existing = thumbnailQueueKeyByCanvasRef.current.get(canvas);
24801
+ if (existing) {
24802
+ return existing;
24803
+ }
24804
+ const nextKey = docxThumbnailCanvasQueueKey(
24805
+ nextThumbnailCanvasQueueIdRef.current
24806
+ );
24807
+ nextThumbnailCanvasQueueIdRef.current += 1;
24808
+ thumbnailQueueKeyByCanvasRef.current.set(canvas, nextKey);
24809
+ return nextKey;
24810
+ },
24811
+ []
24812
+ );
24813
+ React.useEffect(() => {
24814
+ thumbnailRasterQueueRef.current?.clear();
24815
+ thumbnailRasterQueueRef.current = void 0;
24816
+ queuedPrefetchThumbnailKeysRef.current.clear();
24817
+ }, [thumbnailMinRasterIntervalMs]);
23869
24818
  React.useEffect(() => {
23870
24819
  thumbnailSurfaceCacheRef.current?.clear();
23871
24820
  thumbnailRasterQueueRef.current?.clear();
24821
+ detachedThumbnailSurfaceRendererRef.current?.clear();
24822
+ detachedThumbnailSurfaceRendererRef.current = void 0;
23872
24823
  lastPaintedThumbnailKeyByCanvasRef.current = /* @__PURE__ */ new WeakMap();
24824
+ thumbnailQueueKeyByCanvasRef.current = /* @__PURE__ */ new WeakMap();
24825
+ nextThumbnailCanvasQueueIdRef.current = 0;
24826
+ queuedPrefetchThumbnailKeysRef.current.clear();
23873
24827
  }, [editor.documentLoadNonce, pageSurfaceRegistryOwner]);
24828
+ React.useEffect(() => {
24829
+ detachedThumbnailSurfaceRendererRef.current?.clear();
24830
+ detachedThumbnailSurfaceRendererRef.current = void 0;
24831
+ }, [
24832
+ editor.documentTheme,
24833
+ editor.model,
24834
+ editor.showComments,
24835
+ editor.showTrackedChanges
24836
+ ]);
24837
+ React.useEffect(
24838
+ () => () => {
24839
+ detachedThumbnailSurfaceRendererRef.current?.clear();
24840
+ detachedThumbnailSurfaceRendererRef.current = void 0;
24841
+ },
24842
+ []
24843
+ );
23874
24844
  const thumbnailResolutionOptionsKey = React.useMemo(() => {
23875
24845
  const bounds = options.resolution;
23876
24846
  const boundsKey = typeof bounds === "number" ? `n${bounds}` : bounds ? `b${bounds.maxWidth ?? ""}x${bounds.maxHeight ?? ""}` : "";
@@ -23888,6 +24858,125 @@ function useDocxPageThumbnails(editor, options = {}) {
23888
24858
  },
23889
24859
  [editor.documentTheme, pageSurfaceRegistry, thumbnailResolutionOptionsKey]
23890
24860
  );
24861
+ const totalThumbnailPages = Math.max(1, editor.totalPages);
24862
+ const visibleThumbnailPageIndexes = React.useMemo(
24863
+ () => normalizeDocxThumbnailPageIndexes(
24864
+ options.renderWindow?.visiblePageIndexes,
24865
+ totalThumbnailPages
24866
+ ),
24867
+ [options.renderWindow?.visiblePageIndexes, totalThumbnailPages]
24868
+ );
24869
+ const prefetchThumbnailPageIndexes = React.useMemo(
24870
+ () => normalizeDocxThumbnailPageIndexes(
24871
+ options.renderWindow?.prefetchPageIndexes,
24872
+ totalThumbnailPages
24873
+ ),
24874
+ [options.renderWindow?.prefetchPageIndexes, totalThumbnailPages]
24875
+ );
24876
+ const visibleThumbnailPageIndexesKey = docxThumbnailPageIndexesKey(
24877
+ visibleThumbnailPageIndexes
24878
+ );
24879
+ const prefetchThumbnailPageIndexesKey = docxThumbnailPageIndexesKey(
24880
+ prefetchThumbnailPageIndexes
24881
+ );
24882
+ const visibleThumbnailPageIndexSet = React.useMemo(
24883
+ () => new Set(visibleThumbnailPageIndexes),
24884
+ [visibleThumbnailPageIndexesKey]
24885
+ );
24886
+ const thumbnailRenderPriorityForPage = React.useCallback(
24887
+ (pageIndex, fallbackPriority = DOCX_THUMBNAIL_RENDER_PRIORITY_ATTACHED) => visibleThumbnailPageIndexSet.has(pageIndex) ? DOCX_THUMBNAIL_RENDER_PRIORITY_VISIBLE : fallbackPriority,
24888
+ [visibleThumbnailPageIndexSet]
24889
+ );
24890
+ const renderPageThumbnailSurface = React.useCallback(
24891
+ async (pageIndex, renderOptions) => {
24892
+ const force = renderOptions?.force === true;
24893
+ const runSkipKey = thumbnailSkipKeyForPage(pageIndex);
24894
+ const detachedRenderKey = [
24895
+ runSkipKey ?? `load:${editor.documentLoadNonce}`,
24896
+ editor.showComments ? "comments:1" : "comments:0",
24897
+ editor.showTrackedChanges ? "tracked:1" : "tracked:0"
24898
+ ].join("|");
24899
+ const thumbnailSnapshotEntry = pageSurfaceRegistry.pageThumbnailSnapshots.get(pageIndex);
24900
+ const fallbackSourceSize = resolveDocxViewerRegisteredPageSurfaceSize(
24901
+ pageSurfaceRegistry,
24902
+ pageIndex,
24903
+ fallbackLayout.pageWidthPx,
24904
+ fallbackLayout.pageHeightPx
24905
+ );
24906
+ const resolution = resolveDocxPageThumbnailResolution({
24907
+ sourceWidthPx: fallbackSourceSize.widthPx,
24908
+ sourceHeightPx: fallbackSourceSize.heightPx,
24909
+ resolution: options.resolution,
24910
+ maxWidthPx: options.maxWidthPx,
24911
+ maxHeightPx: options.maxHeightPx,
24912
+ pixelRatio: options.pixelRatio
24913
+ });
24914
+ const surfaceKey = runSkipKey === void 0 ? void 0 : `${runSkipKey}|${fallbackSourceSize.widthPx}x${fallbackSourceSize.heightPx}|${resolution.pixelWidthPx}x${resolution.pixelHeightPx}`;
24915
+ const surfaceCache = ensureThumbnailSurfaceCache();
24916
+ let surface = !force && surfaceKey !== void 0 ? surfaceCache.get(surfaceKey) : void 0;
24917
+ if (!surface) {
24918
+ const thumbnailSnapshot = thumbnailSnapshotEntry?.getSnapshot();
24919
+ if (thumbnailSnapshot) {
24920
+ surface = renderDocxThumbnailSnapshotSurface({
24921
+ snapshot: thumbnailSnapshot,
24922
+ widthPx: resolution.widthPx,
24923
+ heightPx: resolution.heightPx,
24924
+ pixelWidthPx: resolution.pixelWidthPx,
24925
+ pixelHeightPx: resolution.pixelHeightPx
24926
+ });
24927
+ if (surfaceKey !== void 0) {
24928
+ surfaceCache.set(surfaceKey, surface);
24929
+ }
24930
+ return { surface, resolution, runSkipKey };
24931
+ }
24932
+ let livePageElement = pageSurfaceRegistry.pageElements.get(pageIndex);
24933
+ if (!livePageElement || !livePageElement.isConnected) {
24934
+ if (!detachedThumbnailSurfaceRendererRef.current) {
24935
+ detachedThumbnailSurfaceRendererRef.current = new DocxDetachedThumbnailSurfaceRenderer();
24936
+ }
24937
+ livePageElement = await detachedThumbnailSurfaceRendererRef.current.renderPageSurface({
24938
+ editor,
24939
+ registry: pageSurfaceRegistry,
24940
+ pageIndex,
24941
+ renderKey: detachedRenderKey
24942
+ });
24943
+ }
24944
+ if (!livePageElement || !livePageElement.isConnected) {
24945
+ return void 0;
24946
+ }
24947
+ const sourceSize = resolveDocxViewerPageSurfaceSize(
24948
+ livePageElement,
24949
+ fallbackSourceSize.widthPx,
24950
+ fallbackSourceSize.heightPx
24951
+ );
24952
+ surface = await rasterizeDocxThumbnailSurface({
24953
+ pageElement: livePageElement,
24954
+ sourceWidthPx: sourceSize.widthPx,
24955
+ sourceHeightPx: sourceSize.heightPx,
24956
+ widthPx: resolution.widthPx,
24957
+ heightPx: resolution.heightPx,
24958
+ pixelWidthPx: resolution.pixelWidthPx,
24959
+ pixelHeightPx: resolution.pixelHeightPx
24960
+ });
24961
+ if (surfaceKey !== void 0) {
24962
+ surfaceCache.set(surfaceKey, surface);
24963
+ }
24964
+ }
24965
+ return { surface, resolution, runSkipKey };
24966
+ },
24967
+ [
24968
+ ensureThumbnailSurfaceCache,
24969
+ fallbackLayout.pageHeightPx,
24970
+ fallbackLayout.pageWidthPx,
24971
+ options.resolution,
24972
+ options.maxHeightPx,
24973
+ options.maxWidthPx,
24974
+ options.pixelRatio,
24975
+ pageSurfaceRegistry,
24976
+ thumbnailSkipKeyForPage,
24977
+ editor
24978
+ ]
24979
+ );
23891
24980
  const renderPageThumbnailToCanvas = React.useCallback(
23892
24981
  async (pageIndex, canvas, renderOptions) => {
23893
24982
  if (options.disabled) {
@@ -23898,59 +24987,25 @@ function useDocxPageThumbnails(editor, options = {}) {
23898
24987
  return;
23899
24988
  }
23900
24989
  const requiresAttachedTarget = canvas === void 0;
23901
- const pageElement = pageSurfaceRegistry.pageElements.get(pageIndex);
23902
- if (!pageElement || !pageElement.isConnected) {
23903
- updatePageThumbnailState(pageIndex, "unavailable");
23904
- return;
23905
- }
23906
24990
  const force = renderOptions?.force === true;
23907
24991
  const lastPaintedKey = lastPaintedThumbnailKeyByCanvasRef.current.get(targetCanvas);
23908
24992
  if (!force && lastPaintedKey !== void 0 && lastPaintedKey === thumbnailSkipKeyForPage(pageIndex)) {
23909
24993
  updatePageThumbnailState(pageIndex, "ready");
23910
24994
  return;
23911
24995
  }
23912
- updatePageThumbnailState(pageIndex, "rendering");
23913
- await ensureThumbnailRasterQueue().enqueue(targetCanvas, async () => {
24996
+ const paintIntoTarget = async () => {
23914
24997
  if (requiresAttachedTarget && attachedCanvasByPageRef.current.get(pageIndex) !== targetCanvas) {
23915
24998
  return;
23916
24999
  }
23917
- const livePageElement = pageSurfaceRegistry.pageElements.get(pageIndex);
23918
- if (!livePageElement || !livePageElement.isConnected) {
23919
- updatePageThumbnailState(pageIndex, "unavailable");
23920
- return;
23921
- }
23922
- const runSkipKey = thumbnailSkipKeyForPage(pageIndex);
23923
- const sourceSize = resolveDocxViewerPageSurfaceSize(
23924
- livePageElement,
23925
- fallbackLayout.pageWidthPx,
23926
- fallbackLayout.pageHeightPx
23927
- );
23928
- const resolution = resolveDocxPageThumbnailResolution({
23929
- sourceWidthPx: sourceSize.widthPx,
23930
- sourceHeightPx: sourceSize.heightPx,
23931
- resolution: options.resolution,
23932
- maxWidthPx: options.maxWidthPx,
23933
- maxHeightPx: options.maxHeightPx,
23934
- pixelRatio: options.pixelRatio
23935
- });
23936
- const surfaceKey = runSkipKey === void 0 ? void 0 : `${runSkipKey}|${sourceSize.widthPx}x${sourceSize.heightPx}|${resolution.pixelWidthPx}x${resolution.pixelHeightPx}`;
23937
- const surfaceCache = ensureThumbnailSurfaceCache();
23938
25000
  try {
23939
- let surface = !force && surfaceKey !== void 0 ? surfaceCache.get(surfaceKey) : void 0;
23940
- if (!surface) {
23941
- surface = await rasterizeDocxThumbnailSurface({
23942
- pageElement: livePageElement,
23943
- sourceWidthPx: sourceSize.widthPx,
23944
- sourceHeightPx: sourceSize.heightPx,
23945
- widthPx: resolution.widthPx,
23946
- heightPx: resolution.heightPx,
23947
- pixelWidthPx: resolution.pixelWidthPx,
23948
- pixelHeightPx: resolution.pixelHeightPx
23949
- });
23950
- if (surfaceKey !== void 0) {
23951
- surfaceCache.set(surfaceKey, surface);
23952
- }
25001
+ const rendered = await renderPageThumbnailSurface(pageIndex, {
25002
+ force
25003
+ });
25004
+ if (!rendered) {
25005
+ updatePageThumbnailState(pageIndex, "unavailable");
25006
+ return;
23953
25007
  }
25008
+ const { surface, resolution, runSkipKey } = rendered;
23954
25009
  blitDocxThumbnailSurface(surface, targetCanvas, resolution);
23955
25010
  if (runSkipKey !== void 0) {
23956
25011
  lastPaintedThumbnailKeyByCanvasRef.current.set(
@@ -23966,32 +25021,116 @@ function useDocxPageThumbnails(editor, options = {}) {
23966
25021
  error instanceof Error ? error : new Error("Failed to render DOCX page thumbnail.")
23967
25022
  );
23968
25023
  }
23969
- });
25024
+ };
25025
+ updatePageThumbnailState(pageIndex, "rendering");
25026
+ if (pageSurfaceRegistry.pageThumbnailSnapshots.has(pageIndex)) {
25027
+ await paintIntoTarget();
25028
+ return;
25029
+ }
25030
+ await ensureThumbnailRasterQueue().enqueue(
25031
+ thumbnailQueueKeyForCanvas(targetCanvas),
25032
+ paintIntoTarget,
25033
+ {
25034
+ priority: renderOptions?.priority ?? thumbnailRenderPriorityForPage(
25035
+ pageIndex,
25036
+ DOCX_THUMBNAIL_RENDER_PRIORITY_ATTACHED
25037
+ )
25038
+ }
25039
+ );
23970
25040
  },
23971
25041
  [
23972
25042
  ensureThumbnailRasterQueue,
23973
- ensureThumbnailSurfaceCache,
23974
- fallbackLayout.pageHeightPx,
23975
- fallbackLayout.pageWidthPx,
23976
25043
  options.disabled,
23977
- options.resolution,
23978
- options.maxHeightPx,
23979
- options.maxWidthPx,
23980
- options.pixelRatio,
23981
25044
  pageSurfaceRegistry,
25045
+ renderPageThumbnailSurface,
23982
25046
  thumbnailSkipKeyForPage,
25047
+ thumbnailQueueKeyForCanvas,
25048
+ thumbnailRenderPriorityForPage,
23983
25049
  updatePageThumbnailState
23984
25050
  ]
23985
25051
  );
25052
+ const prefetchPageThumbnailSurface = React.useCallback(
25053
+ async (pageIndex) => {
25054
+ if (options.disabled) {
25055
+ return;
25056
+ }
25057
+ const queueKey = docxThumbnailPrefetchQueueKey(pageIndex);
25058
+ queuedPrefetchThumbnailKeysRef.current.add(queueKey);
25059
+ await ensureThumbnailRasterQueue().enqueue(
25060
+ queueKey,
25061
+ async () => {
25062
+ try {
25063
+ await renderPageThumbnailSurface(pageIndex);
25064
+ } catch {
25065
+ }
25066
+ },
25067
+ { priority: DOCX_THUMBNAIL_RENDER_PRIORITY_PREFETCH }
25068
+ );
25069
+ queuedPrefetchThumbnailKeysRef.current.delete(queueKey);
25070
+ },
25071
+ [ensureThumbnailRasterQueue, options.disabled, renderPageThumbnailSurface]
25072
+ );
23986
25073
  const requestAttachedThumbnailRenders = React.useCallback(
23987
25074
  async (renderOptions) => {
23988
- const tasks = [...attachedCanvasByPageRef.current.keys()].map(
23989
- (pageIndex) => renderPageThumbnailToCanvas(pageIndex, void 0, renderOptions)
25075
+ const attachedPageIndexes = [
25076
+ ...attachedCanvasByPageRef.current.keys()
25077
+ ].sort((leftPageIndex, rightPageIndex) => {
25078
+ const leftPriority = thumbnailRenderPriorityForPage(leftPageIndex);
25079
+ const rightPriority = thumbnailRenderPriorityForPage(rightPageIndex);
25080
+ return leftPriority - rightPriority || leftPageIndex - rightPageIndex;
25081
+ });
25082
+ const tasks = attachedPageIndexes.map(
25083
+ (pageIndex) => renderPageThumbnailToCanvas(pageIndex, void 0, {
25084
+ ...renderOptions,
25085
+ priority: thumbnailRenderPriorityForPage(pageIndex)
25086
+ })
23990
25087
  );
23991
25088
  await Promise.all(tasks);
23992
25089
  },
23993
- [renderPageThumbnailToCanvas]
25090
+ [renderPageThumbnailToCanvas, thumbnailRenderPriorityForPage]
23994
25091
  );
25092
+ const requestPrefetchThumbnailRenders = React.useCallback(async () => {
25093
+ if (options.disabled) {
25094
+ queuedPrefetchThumbnailKeysRef.current.forEach((queueKey) => {
25095
+ thumbnailRasterQueueRef.current?.cancel(queueKey);
25096
+ });
25097
+ queuedPrefetchThumbnailKeysRef.current.clear();
25098
+ return;
25099
+ }
25100
+ const requestedPrefetchKeys = new Set(
25101
+ prefetchThumbnailPageIndexes.map(
25102
+ (pageIndex) => docxThumbnailPrefetchQueueKey(pageIndex)
25103
+ )
25104
+ );
25105
+ queuedPrefetchThumbnailKeysRef.current.forEach((queueKey) => {
25106
+ if (!requestedPrefetchKeys.has(queueKey)) {
25107
+ thumbnailRasterQueueRef.current?.cancel(queueKey);
25108
+ queuedPrefetchThumbnailKeysRef.current.delete(queueKey);
25109
+ }
25110
+ });
25111
+ const tasks = prefetchThumbnailPageIndexes.map(
25112
+ (pageIndex) => prefetchPageThumbnailSurface(pageIndex)
25113
+ );
25114
+ await Promise.all(tasks);
25115
+ }, [
25116
+ options.disabled,
25117
+ prefetchPageThumbnailSurface,
25118
+ prefetchThumbnailPageIndexes
25119
+ ]);
25120
+ React.useEffect(() => {
25121
+ void requestPrefetchThumbnailRenders();
25122
+ }, [
25123
+ editor.documentLoadNonce,
25124
+ editor.documentTheme,
25125
+ editor.model,
25126
+ mountedPageElements,
25127
+ options.maxHeightPx,
25128
+ options.maxWidthPx,
25129
+ options.pixelRatio,
25130
+ options.resolution,
25131
+ prefetchThumbnailPageIndexesKey,
25132
+ requestPrefetchThumbnailRenders
25133
+ ]);
23995
25134
  const rerenderAttachedThumbnails = React.useCallback(
23996
25135
  async () => requestAttachedThumbnailRenders({ force: true }),
23997
25136
  [requestAttachedThumbnailRenders]
@@ -24020,14 +25159,19 @@ function useDocxPageThumbnails(editor, options = {}) {
24020
25159
  options.pixelRatio,
24021
25160
  requestAttachedThumbnailRenders
24022
25161
  ]);
24023
- const thumbnails = React.useMemo(() => {
25162
+ const thumbnailGeometryItems = React.useMemo(() => {
24024
25163
  const totalPages = Math.max(1, editor.totalPages);
24025
25164
  return Array.from({ length: totalPages }, (_, pageIndex) => {
24026
25165
  const pageElement = mountedPageElements.get(pageIndex);
24027
- const sourceSize = resolveDocxViewerPageSurfaceSize(
25166
+ const sourceSize = pageElement && pageElement.isConnected ? resolveDocxViewerPageSurfaceSize(
24028
25167
  pageElement,
24029
25168
  fallbackLayout.pageWidthPx,
24030
25169
  fallbackLayout.pageHeightPx
25170
+ ) : resolveDocxViewerRegisteredPageSurfaceSize(
25171
+ pageSurfaceRegistry,
25172
+ pageIndex,
25173
+ fallbackLayout.pageWidthPx,
25174
+ fallbackLayout.pageHeightPx
24031
25175
  );
24032
25176
  const resolution = resolveDocxPageThumbnailResolution({
24033
25177
  sourceWidthPx: sourceSize.widthPx,
@@ -24037,15 +25181,14 @@ function useDocxPageThumbnails(editor, options = {}) {
24037
25181
  maxHeightPx: options.maxHeightPx,
24038
25182
  pixelRatio: options.pixelRatio
24039
25183
  });
24040
- const state = pageThumbnailStates.get(pageIndex);
24041
25184
  return {
25185
+ // Internal: drives the default status below; stripped before exposure.
25186
+ hasElement: Boolean(pageElement),
24042
25187
  pageIndex,
24043
25188
  pageNumber: pageIndex + 1,
24044
25189
  sourceWidthPx: sourceSize.widthPx,
24045
25190
  sourceHeightPx: sourceSize.heightPx,
24046
25191
  isMounted: Boolean(pageElement && pageElement.isConnected),
24047
- status: state?.status ?? (pageElement ? "idle" : "unavailable"),
24048
- error: state?.error,
24049
25192
  paint: (canvas) => {
24050
25193
  if (!canvas || options.disabled) {
24051
25194
  return false;
@@ -24062,7 +25205,9 @@ function useDocxPageThumbnails(editor, options = {}) {
24062
25205
  }
24063
25206
  const previousCanvas = attachedCanvasByPageRef.current.get(pageIndex);
24064
25207
  if (previousCanvas) {
24065
- thumbnailRasterQueueRef.current?.cancel(previousCanvas);
25208
+ thumbnailRasterQueueRef.current?.cancel(
25209
+ thumbnailQueueKeyForCanvas(previousCanvas)
25210
+ );
24066
25211
  }
24067
25212
  attachedCanvasByPageRef.current.delete(pageIndex);
24068
25213
  };
@@ -24097,8 +25242,21 @@ function useDocxPageThumbnails(editor, options = {}) {
24097
25242
  options.maxHeightPx,
24098
25243
  options.maxWidthPx,
24099
25244
  options.pixelRatio,
24100
- pageThumbnailStates
25245
+ pageSurfaceRegistry,
25246
+ thumbnailQueueKeyForCanvas
24101
25247
  ]);
25248
+ const thumbnails = React.useMemo(
25249
+ () => thumbnailGeometryItems.map((geometryItem) => {
25250
+ const { hasElement, ...item } = geometryItem;
25251
+ const state = pageThumbnailStates.get(item.pageIndex);
25252
+ return {
25253
+ ...item,
25254
+ status: state?.status ?? (hasElement ? "idle" : "unavailable"),
25255
+ error: state?.error
25256
+ };
25257
+ }),
25258
+ [thumbnailGeometryItems, pageThumbnailStates]
25259
+ );
24102
25260
  const paintThumbnail = React.useCallback(
24103
25261
  (pageIndex, canvas) => {
24104
25262
  if (!canvas || options.disabled) {
@@ -25393,13 +26551,23 @@ function DocxEditorViewer({
25393
26551
  if (cached) {
25394
26552
  return cached;
25395
26553
  }
26554
+ let registeredElement = null;
25396
26555
  const nextRef = (element) => {
25397
26556
  if (element) {
26557
+ registeredElement = element;
25398
26558
  pageElementsRef.current.set(normalizedPageIndex, element);
25399
26559
  } else {
25400
26560
  pageElementsRef.current.delete(normalizedPageIndex);
25401
26561
  }
25402
- registerDocxViewerPageSurface(editor, normalizedPageIndex, element);
26562
+ registerDocxViewerPageSurface(
26563
+ editor,
26564
+ normalizedPageIndex,
26565
+ element,
26566
+ registeredElement
26567
+ );
26568
+ if (!element) {
26569
+ registeredElement = null;
26570
+ }
25403
26571
  };
25404
26572
  pageSurfaceRefCallbacksRef.current.set(normalizedPageIndex, nextRef);
25405
26573
  return nextRef;
@@ -26462,12 +27630,51 @@ function DocxEditorViewer({
26462
27630
  pageSectionInfoByIndex,
26463
27631
  trackedChangesEnabled
26464
27632
  ]);
27633
+ const pageThumbnailSurfaceSizesByPage = React.useMemo(
27634
+ () => pageNodeSegmentsByPage.map((_, pageIndex) => {
27635
+ const pageLayout = pageSectionInfoByIndex[pageIndex]?.layout ?? documentLayout;
27636
+ return {
27637
+ widthPx: pageLayout.pageWidthPx,
27638
+ heightPx: pageLayout.pageHeightPx
27639
+ };
27640
+ }),
27641
+ [documentLayout, pageNodeSegmentsByPage, pageSectionInfoByIndex]
27642
+ );
27643
+ const pageThumbnailSnapshotEntriesByPage = React.useMemo(
27644
+ () => buildDocxPageThumbnailRenderSnapshotEntries({
27645
+ model: editor.model,
27646
+ pageNodeSegmentsByPage,
27647
+ pageSectionInfoByIndex,
27648
+ contentKeysByPage: pageThumbnailContentKeysByPage,
27649
+ fallbackLayout: documentLayout,
27650
+ documentTheme: editor.documentTheme,
27651
+ docGridLinePitchPxByNodeIndex,
27652
+ numberingDefinitions: editor.model.metadata.numberingDefinitions
27653
+ }),
27654
+ [
27655
+ docGridLinePitchPxByNodeIndex,
27656
+ documentLayout,
27657
+ editor.documentTheme,
27658
+ editor.model,
27659
+ editor.model.metadata.numberingDefinitions,
27660
+ pageNodeSegmentsByPage,
27661
+ pageSectionInfoByIndex,
27662
+ pageThumbnailContentKeysByPage
27663
+ ]
27664
+ );
26465
27665
  React.useEffect(() => {
26466
27666
  syncDocxViewerPageSurfaceContentKeys(
26467
27667
  editor,
26468
- pageThumbnailContentKeysByPage
27668
+ pageThumbnailContentKeysByPage,
27669
+ pageThumbnailSurfaceSizesByPage,
27670
+ pageThumbnailSnapshotEntriesByPage
26469
27671
  );
26470
- }, [pageSurfaceRegistryOwner, pageThumbnailContentKeysByPage]);
27672
+ }, [
27673
+ pageSurfaceRegistryOwner,
27674
+ pageThumbnailContentKeysByPage,
27675
+ pageThumbnailSnapshotEntriesByPage,
27676
+ pageThumbnailSurfaceSizesByPage
27677
+ ]);
26471
27678
  const resolveStyleRefFieldValueForPage = React.useMemo(() => {
26472
27679
  const valueCache = /* @__PURE__ */ new Map();
26473
27680
  const nodes = editor.model.nodes;