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

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.cjs CHANGED
@@ -2906,16 +2906,17 @@ function shouldAllowStoredPageCountReduction(options) {
2906
2906
  if (targetPageCount >= estimatedPageCount) {
2907
2907
  return true;
2908
2908
  }
2909
+ const renderedBreakHintPageCount = Number.isFinite(
2910
+ options.renderedBreakHintPageCount
2911
+ ) ? Math.max(1, Math.round(options.renderedBreakHintPageCount)) : void 0;
2912
+ const renderedBreakHintsSupportTarget = options.hasLastRenderedPageBreakHints === true && renderedBreakHintPageCount !== void 0 && targetPageCount >= renderedBreakHintPageCount;
2909
2913
  if (options.hasMeasuredBodyFooterOverlap === true) {
2910
- return false;
2914
+ return renderedBreakHintsSupportTarget;
2911
2915
  }
2912
2916
  if (options.hasLastRenderedPageBreakHints !== true) {
2913
2917
  return true;
2914
2918
  }
2915
- const renderedBreakHintPageCount = Number.isFinite(
2916
- options.renderedBreakHintPageCount
2917
- ) ? Math.max(1, Math.round(options.renderedBreakHintPageCount)) : void 0;
2918
- return renderedBreakHintPageCount !== void 0 && targetPageCount >= renderedBreakHintPageCount;
2919
+ return renderedBreakHintsSupportTarget;
2919
2920
  }
2920
2921
  function shouldLatchMeasuredBodyFooterOverlap(options) {
2921
2922
  if (options.measuredBodyFooterOverlap !== true) {
@@ -4022,6 +4023,425 @@ function sliceLayoutToLineRange(layout, startLineIndex, endLineIndex) {
4022
4023
  };
4023
4024
  }
4024
4025
 
4026
+ // src/content-signature.ts
4027
+ init_cjs_shims();
4028
+ var FNV_OFFSET_BASIS = 2166136261;
4029
+ var FNV_PRIME = 16777619;
4030
+ var LONG_STRING_SAMPLE_LENGTH = 64;
4031
+ var LONG_STRING_THRESHOLD = 256;
4032
+ function fnv1aAppend(hash, text) {
4033
+ let next = hash;
4034
+ for (let index = 0; index < text.length; index += 1) {
4035
+ next ^= text.charCodeAt(index);
4036
+ next = Math.imul(next, FNV_PRIME);
4037
+ }
4038
+ return next >>> 0;
4039
+ }
4040
+ function fnv1aAppendString(hash, value) {
4041
+ if (value.length <= LONG_STRING_THRESHOLD) {
4042
+ return fnv1aAppend(fnv1aAppend(hash, `${value.length}:`), value);
4043
+ }
4044
+ const head = value.slice(0, LONG_STRING_SAMPLE_LENGTH);
4045
+ const middleIndex = Math.floor(value.length / 2);
4046
+ const middle = value.slice(
4047
+ middleIndex,
4048
+ middleIndex + LONG_STRING_SAMPLE_LENGTH
4049
+ );
4050
+ const tail = value.slice(value.length - LONG_STRING_SAMPLE_LENGTH);
4051
+ let next = fnv1aAppend(hash, `${value.length}:`);
4052
+ next = fnv1aAppend(next, head);
4053
+ next = fnv1aAppend(next, middle);
4054
+ return fnv1aAppend(next, tail);
4055
+ }
4056
+ function fnv1aAppendValue(hash, value) {
4057
+ if (value === null) {
4058
+ return fnv1aAppend(hash, "~n");
4059
+ }
4060
+ switch (typeof value) {
4061
+ case "undefined":
4062
+ return fnv1aAppend(hash, "~u");
4063
+ case "string":
4064
+ return fnv1aAppendString(fnv1aAppend(hash, "~s"), value);
4065
+ case "number":
4066
+ return fnv1aAppend(hash, `~#${value}`);
4067
+ case "boolean":
4068
+ return fnv1aAppend(hash, value ? "~t" : "~f");
4069
+ case "object":
4070
+ break;
4071
+ default:
4072
+ return fnv1aAppend(hash, "~x");
4073
+ }
4074
+ if (Array.isArray(value)) {
4075
+ let next2 = fnv1aAppend(hash, `~a${value.length}`);
4076
+ for (const entry of value) {
4077
+ next2 = fnv1aAppendValue(next2, entry);
4078
+ }
4079
+ return next2;
4080
+ }
4081
+ if (ArrayBuffer.isView(value) || value instanceof ArrayBuffer) {
4082
+ const byteLength = value instanceof ArrayBuffer ? value.byteLength : value.byteLength;
4083
+ return fnv1aAppend(hash, `~b${byteLength}`);
4084
+ }
4085
+ let next = fnv1aAppend(hash, "~o");
4086
+ for (const key of Object.keys(value)) {
4087
+ const entry = value[key];
4088
+ if (entry === void 0) {
4089
+ continue;
4090
+ }
4091
+ next = fnv1aAppend(next, key);
4092
+ next = fnv1aAppendValue(next, entry);
4093
+ }
4094
+ return next;
4095
+ }
4096
+ function contentHash(value) {
4097
+ return fnv1aAppendValue(FNV_OFFSET_BASIS, value).toString(36);
4098
+ }
4099
+ var nodeSignatureCache = /* @__PURE__ */ new WeakMap();
4100
+ function docNodeContentSignature(node) {
4101
+ if (typeof node !== "object" || node === null) {
4102
+ return contentHash(node);
4103
+ }
4104
+ const cached = nodeSignatureCache.get(node);
4105
+ if (cached !== void 0) {
4106
+ return cached;
4107
+ }
4108
+ const signature = contentHash(node);
4109
+ nodeSignatureCache.set(node, signature);
4110
+ return signature;
4111
+ }
4112
+ var metadataSignatureCache = /* @__PURE__ */ new WeakMap();
4113
+ function docModelThumbnailMetadataSignature(metadata) {
4114
+ const cached = metadataSignatureCache.get(metadata);
4115
+ if (cached !== void 0) {
4116
+ return cached;
4117
+ }
4118
+ const relevant = metadata;
4119
+ let hash = FNV_OFFSET_BASIS;
4120
+ hash = fnv1aAppendValue(hash, relevant.sections);
4121
+ hash = fnv1aAppendValue(hash, relevant.headerSections);
4122
+ hash = fnv1aAppendValue(hash, relevant.footerSections);
4123
+ hash = fnv1aAppendValue(hash, relevant.numberingDefinitions);
4124
+ hash = fnv1aAppendValue(hash, relevant.footnotes);
4125
+ hash = fnv1aAppendValue(hash, relevant.endnotes);
4126
+ hash = fnv1aAppendValue(hash, relevant.documentBackgroundColor);
4127
+ hash = fnv1aAppendValue(hash, relevant.compatibility);
4128
+ const signature = (hash >>> 0).toString(36);
4129
+ metadataSignatureCache.set(metadata, signature);
4130
+ return signature;
4131
+ }
4132
+
4133
+ // src/thumbnail-raster.ts
4134
+ init_cjs_shims();
4135
+ var DOCX_THUMBNAIL_EXCLUDE_ATTRIBUTE = "data-docx-thumbnail-exclude";
4136
+ var THUMBNAIL_EXCLUDED_CLONE_SELECTOR = [
4137
+ `[${DOCX_THUMBNAIL_EXCLUDE_ATTRIBUTE}="true"]`,
4138
+ "textarea",
4139
+ '[data-image-resize-handle="true"]',
4140
+ '[data-docx-table-move-handle="true"]'
4141
+ ].join(",");
4142
+ var THUMBNAIL_IMAGE_DOWNSCALE_MIN_DATA_URI_LENGTH = 32768;
4143
+ var THUMBNAIL_IMAGE_DOWNSCALE_MAX_DIMENSION_PX = 512;
4144
+ var THUMBNAIL_IMAGE_JPEG_QUALITY = 0.78;
4145
+ function thumbnailSvgDataUri(svg) {
4146
+ return `data:image/svg+xml;charset=utf-8,${encodeURIComponent(svg)}`;
4147
+ }
4148
+ function thumbnailImageSourceQualifiesForDownscale(src) {
4149
+ return src.length >= THUMBNAIL_IMAGE_DOWNSCALE_MIN_DATA_URI_LENGTH && src.startsWith("data:image/") && !src.startsWith("data:image/svg");
4150
+ }
4151
+ async function loadThumbnailImage(src) {
4152
+ const image = new Image();
4153
+ image.decoding = "async";
4154
+ const loaded = new Promise((resolve, reject) => {
4155
+ image.onload = () => resolve(image);
4156
+ image.onerror = () => {
4157
+ reject(new Error("Failed to decode DOCX thumbnail image."));
4158
+ };
4159
+ });
4160
+ image.src = src;
4161
+ if (typeof image.decode === "function") {
4162
+ try {
4163
+ await image.decode();
4164
+ return image;
4165
+ } catch {
4166
+ }
4167
+ }
4168
+ return loaded;
4169
+ }
4170
+ var downscaledThumbnailImageCache = /* @__PURE__ */ new Map();
4171
+ async function downscaleThumbnailImageDataUri(src) {
4172
+ if (typeof document === "undefined") {
4173
+ return void 0;
4174
+ }
4175
+ const image = await loadThumbnailImage(src);
4176
+ const naturalWidth = image.naturalWidth || image.width;
4177
+ const naturalHeight = image.naturalHeight || image.height;
4178
+ if (!naturalWidth || !naturalHeight) {
4179
+ return void 0;
4180
+ }
4181
+ const scale = THUMBNAIL_IMAGE_DOWNSCALE_MAX_DIMENSION_PX / Math.max(naturalWidth, naturalHeight);
4182
+ if (scale >= 1) {
4183
+ return void 0;
4184
+ }
4185
+ const canvas = document.createElement("canvas");
4186
+ canvas.width = Math.max(1, Math.round(naturalWidth * scale));
4187
+ canvas.height = Math.max(1, Math.round(naturalHeight * scale));
4188
+ const context = canvas.getContext("2d");
4189
+ if (!context) {
4190
+ return void 0;
4191
+ }
4192
+ context.imageSmoothingEnabled = true;
4193
+ context.imageSmoothingQuality = "high";
4194
+ context.drawImage(image, 0, 0, canvas.width, canvas.height);
4195
+ const sourceIsJpeg = src.startsWith("data:image/jpeg") || src.startsWith("data:image/jpg");
4196
+ const downscaled = sourceIsJpeg ? canvas.toDataURL("image/jpeg", THUMBNAIL_IMAGE_JPEG_QUALITY) : canvas.toDataURL("image/png");
4197
+ return downscaled.length < src.length ? downscaled : void 0;
4198
+ }
4199
+ function getDownscaledThumbnailImageDataUri(src) {
4200
+ const cached = downscaledThumbnailImageCache.get(src);
4201
+ if (cached) {
4202
+ return cached;
4203
+ }
4204
+ const pending = downscaleThumbnailImageDataUri(src).catch(() => void 0);
4205
+ downscaledThumbnailImageCache.set(src, pending);
4206
+ return pending;
4207
+ }
4208
+ async function buildDocxThumbnailSvgMarkup(params) {
4209
+ const { pageElement, sourceWidthPx, sourceHeightPx, widthPx, heightPx } = params;
4210
+ const clone = pageElement.cloneNode(true);
4211
+ clone.querySelectorAll(THUMBNAIL_EXCLUDED_CLONE_SELECTOR).forEach((excluded) => {
4212
+ excluded.remove();
4213
+ });
4214
+ const cloneImages = Array.from(clone.querySelectorAll("img"));
4215
+ await Promise.all(
4216
+ cloneImages.map(async (cloneImage) => {
4217
+ const src = cloneImage.getAttribute("src");
4218
+ if (!src || !thumbnailImageSourceQualifiesForDownscale(src)) {
4219
+ return;
4220
+ }
4221
+ const downscaled = await getDownscaledThumbnailImageDataUri(src);
4222
+ if (downscaled) {
4223
+ cloneImage.setAttribute("src", downscaled);
4224
+ }
4225
+ })
4226
+ );
4227
+ const scaleX = widthPx / sourceWidthPx;
4228
+ const scaleY = heightPx / sourceHeightPx;
4229
+ const serializedPage = new XMLSerializer().serializeToString(clone);
4230
+ return `
4231
+ <svg xmlns="http://www.w3.org/2000/svg" width="${widthPx}" height="${heightPx}" viewBox="0 0 ${widthPx} ${heightPx}">
4232
+ <foreignObject x="0" y="0" width="100%" height="100%">
4233
+ <div xmlns="http://www.w3.org/1999/xhtml" style="width:${widthPx}px;height:${heightPx}px;overflow:hidden;">
4234
+ <div style="width:${sourceWidthPx}px;height:${sourceHeightPx}px;transform-origin:top left;transform:scale(${scaleX}, ${scaleY});">
4235
+ ${serializedPage}
4236
+ </div>
4237
+ </div>
4238
+ </foreignObject>
4239
+ </svg>
4240
+ `;
4241
+ }
4242
+ async function rasterizeDocxThumbnailSurface(params) {
4243
+ if (typeof window === "undefined" || typeof XMLSerializer === "undefined") {
4244
+ throw new Error("DOCX thumbnails require a browser environment.");
4245
+ }
4246
+ const safeSourceWidthPx = Math.max(1, Math.round(params.sourceWidthPx));
4247
+ const safeSourceHeightPx = Math.max(1, Math.round(params.sourceHeightPx));
4248
+ const svgMarkup = await buildDocxThumbnailSvgMarkup({
4249
+ pageElement: params.pageElement,
4250
+ sourceWidthPx: safeSourceWidthPx,
4251
+ sourceHeightPx: safeSourceHeightPx,
4252
+ widthPx: params.widthPx,
4253
+ heightPx: params.heightPx
4254
+ });
4255
+ const image = await loadThumbnailImage(thumbnailSvgDataUri(svgMarkup));
4256
+ const surface = document.createElement("canvas");
4257
+ surface.width = Math.max(1, Math.round(params.pixelWidthPx));
4258
+ surface.height = Math.max(1, Math.round(params.pixelHeightPx));
4259
+ const context = surface.getContext("2d");
4260
+ if (!context) {
4261
+ throw new Error("2D canvas context is unavailable for DOCX thumbnails.");
4262
+ }
4263
+ context.imageSmoothingEnabled = true;
4264
+ context.imageSmoothingQuality = "high";
4265
+ context.drawImage(image, 0, 0, surface.width, surface.height);
4266
+ return surface;
4267
+ }
4268
+ function blitDocxThumbnailSurface(surface, canvas, resolution) {
4269
+ canvas.width = Math.max(1, Math.round(resolution.pixelWidthPx));
4270
+ canvas.height = Math.max(1, Math.round(resolution.pixelHeightPx));
4271
+ canvas.style.width = `${Math.max(1, Math.round(resolution.widthPx))}px`;
4272
+ canvas.style.height = `${Math.max(1, Math.round(resolution.heightPx))}px`;
4273
+ const context = canvas.getContext("2d");
4274
+ if (!context) {
4275
+ throw new Error("2D canvas context is unavailable for DOCX thumbnails.");
4276
+ }
4277
+ context.setTransform(1, 0, 0, 1, 0, 0);
4278
+ context.clearRect(0, 0, canvas.width, canvas.height);
4279
+ context.drawImage(surface, 0, 0, canvas.width, canvas.height);
4280
+ }
4281
+ var DocxThumbnailSurfaceCache = class {
4282
+ constructor(maxEntries) {
4283
+ this.maxEntries = maxEntries;
4284
+ }
4285
+ entries = /* @__PURE__ */ new Map();
4286
+ get size() {
4287
+ return this.entries.size;
4288
+ }
4289
+ get(key) {
4290
+ const value = this.entries.get(key);
4291
+ if (value === void 0) {
4292
+ return void 0;
4293
+ }
4294
+ this.entries.delete(key);
4295
+ this.entries.set(key, value);
4296
+ return value;
4297
+ }
4298
+ set(key, value) {
4299
+ this.entries.delete(key);
4300
+ this.entries.set(key, value);
4301
+ while (this.entries.size > this.maxEntries) {
4302
+ const oldestKey = this.entries.keys().next().value;
4303
+ if (oldestKey === void 0) {
4304
+ break;
4305
+ }
4306
+ this.entries.delete(oldestKey);
4307
+ }
4308
+ }
4309
+ clear() {
4310
+ this.entries.clear();
4311
+ }
4312
+ };
4313
+ var IDLE_TASK_TIMEOUT_MS = 300;
4314
+ function defaultScheduleTask(callback) {
4315
+ const idleWindow = typeof window === "undefined" ? void 0 : window;
4316
+ if (!idleWindow || typeof idleWindow.requestIdleCallback !== "function") {
4317
+ setTimeout(callback, 16);
4318
+ return;
4319
+ }
4320
+ let invoked = false;
4321
+ const runOnce = () => {
4322
+ if (invoked) {
4323
+ return;
4324
+ }
4325
+ invoked = true;
4326
+ callback();
4327
+ };
4328
+ const idleHandle = idleWindow.requestIdleCallback(runOnce, {
4329
+ timeout: IDLE_TASK_TIMEOUT_MS
4330
+ });
4331
+ setTimeout(() => {
4332
+ if (invoked) {
4333
+ return;
4334
+ }
4335
+ if (typeof idleWindow.cancelIdleCallback === "function") {
4336
+ idleWindow.cancelIdleCallback(idleHandle);
4337
+ }
4338
+ runOnce();
4339
+ }, IDLE_TASK_TIMEOUT_MS + 50);
4340
+ }
4341
+ function defaultScheduleDelayed(callback, delayMs) {
4342
+ setTimeout(callback, delayMs);
4343
+ }
4344
+ var SerialIdleTaskQueue = class {
4345
+ pending = [];
4346
+ lastRunAtByKey = /* @__PURE__ */ new Map();
4347
+ scheduleTask;
4348
+ scheduleDelayed;
4349
+ minTaskIntervalMs;
4350
+ now;
4351
+ pumpScheduled = false;
4352
+ running = false;
4353
+ constructor(options) {
4354
+ this.scheduleTask = options?.scheduleTask ?? defaultScheduleTask;
4355
+ this.scheduleDelayed = options?.scheduleDelayed ?? defaultScheduleDelayed;
4356
+ this.minTaskIntervalMs = Math.max(0, options?.minTaskIntervalMs ?? 0);
4357
+ this.now = options?.now ?? (() => Date.now());
4358
+ }
4359
+ get pendingCount() {
4360
+ return this.pending.length;
4361
+ }
4362
+ enqueue(key, run) {
4363
+ return new Promise((resolve) => {
4364
+ const existing = this.pending.find((entry) => entry.key === key);
4365
+ if (existing) {
4366
+ existing.run = run;
4367
+ existing.resolvers.push(resolve);
4368
+ } else {
4369
+ this.pending.push({ key, run, resolvers: [resolve] });
4370
+ }
4371
+ this.schedulePump();
4372
+ });
4373
+ }
4374
+ /** Drops all queued tasks, resolving their waiters without running them. */
4375
+ clear() {
4376
+ const dropped = this.pending.splice(0, this.pending.length);
4377
+ this.lastRunAtByKey.clear();
4378
+ dropped.forEach((entry) => {
4379
+ entry.resolvers.forEach((resolveEntry) => {
4380
+ resolveEntry();
4381
+ });
4382
+ });
4383
+ }
4384
+ schedulePump() {
4385
+ if (this.pumpScheduled || this.running || this.pending.length === 0) {
4386
+ return;
4387
+ }
4388
+ this.pumpScheduled = true;
4389
+ this.scheduleTask(() => {
4390
+ this.pumpScheduled = false;
4391
+ void this.runNext();
4392
+ });
4393
+ }
4394
+ takeNextEligibleEntry() {
4395
+ if (this.pending.length === 0) {
4396
+ return void 0;
4397
+ }
4398
+ const now = this.now();
4399
+ let earliestWaitMs;
4400
+ for (let index = 0; index < this.pending.length; index += 1) {
4401
+ const candidate = this.pending[index];
4402
+ if (!candidate) {
4403
+ continue;
4404
+ }
4405
+ const lastRunAt = this.lastRunAtByKey.get(candidate.key);
4406
+ const waitMs = lastRunAt === void 0 ? 0 : lastRunAt + this.minTaskIntervalMs - now;
4407
+ if (waitMs <= 0) {
4408
+ this.pending.splice(index, 1);
4409
+ return { entry: candidate };
4410
+ }
4411
+ earliestWaitMs = earliestWaitMs === void 0 ? waitMs : Math.min(earliestWaitMs, waitMs);
4412
+ }
4413
+ return earliestWaitMs === void 0 ? void 0 : { retryDelayMs: earliestWaitMs };
4414
+ }
4415
+ async runNext() {
4416
+ if (this.running) {
4417
+ return;
4418
+ }
4419
+ const next = this.takeNextEligibleEntry();
4420
+ if (!next) {
4421
+ return;
4422
+ }
4423
+ if (!("entry" in next)) {
4424
+ this.scheduleDelayed(() => {
4425
+ this.schedulePump();
4426
+ }, next.retryDelayMs);
4427
+ return;
4428
+ }
4429
+ this.running = true;
4430
+ const { entry } = next;
4431
+ try {
4432
+ await entry.run();
4433
+ } catch {
4434
+ } finally {
4435
+ this.lastRunAtByKey.set(entry.key, this.now());
4436
+ this.running = false;
4437
+ entry.resolvers.forEach((resolveEntry) => {
4438
+ resolveEntry();
4439
+ });
4440
+ this.schedulePump();
4441
+ }
4442
+ }
4443
+ };
4444
+
4025
4445
  // src/editor.tsx
4026
4446
  var import_jsx_runtime = require("react/jsx-runtime");
4027
4447
  var HIGHLIGHT_TO_CSS = {
@@ -4118,6 +4538,9 @@ var DEFAULT_PARAGRAPH_LINE_MULTIPLE = 1;
4118
4538
  var WORD_SINGLE_LINE_AUTO_SCALE = 0.88;
4119
4539
  var WORD_SINGLE_LINE_AUTO_SCALE_SANS = 0.9;
4120
4540
  var WORD_SINGLE_LINE_AUTO_SCALE_SERIF = 1.08;
4541
+ var WORD_EMPTY_PARAGRAPH_LINE_SCALE = 1.21;
4542
+ var WORD_EMPTY_PARAGRAPH_LINE_SCALE_SERIF = 1.15;
4543
+ var WORD_EMPTY_PARAGRAPH_LINE_SCALE_SANS = 1.15;
4121
4544
  var WORD_AUTO_LINE_SCALE_BLEND_END_MULTIPLE = 1.08;
4122
4545
  var MIN_AUTO_LINE_MULTIPLE = 0.1;
4123
4546
  var MIN_PARAGRAPH_LINE_HEIGHT_PX = 14;
@@ -4162,7 +4585,6 @@ var TEXT_MEASURE_CACHE_MAX_ENTRIES = 12e3;
4162
4585
  var DEFAULT_TAB_STOP_PX = 48;
4163
4586
  var TAB_LEADER_ZONE_GAP_PX = 20;
4164
4587
  var EMPTY_PARAGRAPH_EXTRA_HEIGHT_PX = 0;
4165
- var LEADING_COVER_SPACER_EXTRA_HEIGHT_PX = 2;
4166
4588
  var PARAGRAPH_SEGMENT_TOP_BLEED_PX = 22;
4167
4589
  var PARAGRAPH_SEGMENT_DESCENDER_BLEED_PX = 6;
4168
4590
  var PARAGRAPH_SEGMENT_VISUAL_SAFETY_PX = 24;
@@ -6682,19 +7104,6 @@ function floatingTextBoxVisibleTextFromImage(image) {
6682
7104
  const normalized = normalizeFloatingTextBoxComparisonText(text);
6683
7105
  return normalized.length > 0 ? normalized : void 0;
6684
7106
  }
6685
- function absoluteFloatingTextBearingTextBoxFootprintPx(image) {
6686
- const floating = image.floating;
6687
- if (!shouldRenderAbsoluteFloatingImage(image) || !floating || image.syntheticTextBox !== true || syntheticTextBoxContainsPictureLayer(image) || !floatingTextBoxVisibleTextFromImage(image)) {
6688
- return 0;
6689
- }
6690
- const imageHeightPx = Number.isFinite(image.heightPx) && image.heightPx > 0 ? Math.round(image.heightPx) : Number.isFinite(image.widthPx) && image.widthPx > 0 ? Math.round(image.widthPx) : MIN_PARAGRAPH_LINE_HEIGHT_PX;
6691
- const distTPx = Math.max(0, Math.round(floating.distTPx ?? 0));
6692
- const distBPx = Math.max(0, Math.round(floating.distBPx ?? 0));
6693
- return Math.max(
6694
- MIN_PARAGRAPH_LINE_HEIGHT_PX,
6695
- imageHeightPx + distTPx + distBPx
6696
- );
6697
- }
6698
7107
  function paragraphVisibleTextIsOnlyAbsoluteFloatingTextBoxContent(paragraph) {
6699
7108
  if (!paragraphHasVisibleText2(paragraph) || paragraphHasFormField2(paragraph)) {
6700
7109
  return false;
@@ -6727,29 +7136,9 @@ function paragraphHasOnlyWhitespaceText(paragraph) {
6727
7136
  return child.text.replace(/[\s\u00a0]+/g, "").length === 0;
6728
7137
  });
6729
7138
  }
6730
- function paragraphHasActiveNumbering(paragraph) {
6731
- const numbering = paragraph.style?.numbering;
6732
- return Boolean(
6733
- numbering && Number.isFinite(numbering.numId) && Math.round(numbering.numId) > 0
6734
- );
6735
- }
6736
7139
  function paragraphContainsSectionBreakProperties(paragraph) {
6737
7140
  return /<w:sectPr\b/i.test(paragraph.sourceXml ?? "");
6738
7141
  }
6739
- function paragraphTextBearingAbsoluteFloatingTextBoxFootprintPx(paragraph) {
6740
- if (paragraphHasVisibleText2(paragraph) || paragraphHasFormField2(paragraph) || paragraphContainsSectionBreakProperties(paragraph)) {
6741
- return 0;
6742
- }
6743
- return paragraph.children.reduce((largest, child) => {
6744
- if (child.type !== "image") {
6745
- return largest;
6746
- }
6747
- return Math.max(
6748
- largest,
6749
- absoluteFloatingTextBearingTextBoxFootprintPx(child)
6750
- );
6751
- }, 0);
6752
- }
6753
7142
  function paragraphAbsoluteFloatingAnchorsDependOnParagraphFlow(paragraph) {
6754
7143
  return paragraph.children.some((child) => {
6755
7144
  if (child.type !== "image" || !shouldRenderAbsoluteFloatingImage(child) || child.syntheticTextBox !== true || !floatingTextBoxVisibleTextFromImage(child) || child.floating?.behindDocument !== true) {
@@ -6841,21 +7230,6 @@ function paragraphParticipatesInLeadingCoverLayout(model, nodeIndex, pageContent
6841
7230
  }
6842
7231
  return sawLikelyCoverArtAnchor;
6843
7232
  }
6844
- function paragraphLikelyFullPageCoverFootprintPx(model, nodeIndex, paragraph, pageContentWidthPx, pageContentHeightPx) {
6845
- if (!paragraphParticipatesInLeadingCoverLayout(
6846
- model,
6847
- nodeIndex,
6848
- pageContentWidthPx,
6849
- pageContentHeightPx
6850
- ) || !paragraphIsLikelyFullPageCoverArtAnchor(
6851
- paragraph,
6852
- pageContentWidthPx,
6853
- pageContentHeightPx
6854
- )) {
6855
- return 0;
6856
- }
6857
- return Math.max(1, Math.round(pageContentHeightPx));
6858
- }
6859
7233
  function fullPageCoverImageRenderKey(nodeIndex, childIndex) {
6860
7234
  return `${nodeIndex}:${childIndex}`;
6861
7235
  }
@@ -6894,71 +7268,6 @@ function fullPageCoverAbsoluteFloatingImageStyle(image, layout, options) {
6894
7268
  zIndex: floating?.behindDocument === true ? 0 : normalizedZIndex
6895
7269
  };
6896
7270
  }
6897
- function paragraphActsAsLeadingCoverLayoutSpacer(model, nodeIndex, paragraph, pageContentWidthPx, pageContentHeightPx) {
6898
- if (nodeIndex <= 0 || !paragraphHasOnlyWhitespaceText(paragraph) || paragraphHasExplicitPageBreak2(paragraph)) {
6899
- return false;
6900
- }
6901
- if (paragraphHasActiveNumbering(paragraph)) {
6902
- return false;
6903
- }
6904
- return paragraphParticipatesInLeadingCoverLayout(
6905
- model,
6906
- nodeIndex,
6907
- pageContentWidthPx,
6908
- pageContentHeightPx
6909
- );
6910
- }
6911
- function paragraphActsAsLeadingCoverLayoutPreambleSpacer(model, nodeIndex, paragraph, pageContentWidthPx, pageContentHeightPx) {
6912
- if (!paragraphHasOnlyWhitespaceText(paragraph) || paragraphHasExplicitPageBreak2(paragraph) || paragraphHasActiveNumbering(paragraph)) {
6913
- return false;
6914
- }
6915
- const nextNode = model.nodes[nodeIndex + 1];
6916
- if (!nextNode || nextNode.type !== "paragraph") {
6917
- return false;
6918
- }
6919
- if (!paragraphParticipatesInLeadingCoverLayout(
6920
- model,
6921
- nodeIndex + 1,
6922
- pageContentWidthPx,
6923
- pageContentHeightPx
6924
- )) {
6925
- return false;
6926
- }
6927
- for (let probeIndex = nodeIndex - 1; probeIndex >= 0; probeIndex -= 1) {
6928
- const previousNode = model.nodes[probeIndex];
6929
- if (!previousNode || previousNode.type !== "paragraph") {
6930
- return false;
6931
- }
6932
- if (paragraphHasOnlyWhitespaceText(previousNode)) {
6933
- continue;
6934
- }
6935
- if (paragraphHasAbsoluteFloatingImage(previousNode)) {
6936
- return false;
6937
- }
6938
- return !paragraphIsLikelyFullPageCoverArtAnchor(
6939
- previousNode,
6940
- pageContentWidthPx,
6941
- pageContentHeightPx
6942
- );
6943
- }
6944
- return true;
6945
- }
6946
- function paragraphStartsNewPageAfterLeadingCoverLayout(model, nodeIndex, paragraph, pageContentWidthPx, pageContentHeightPx) {
6947
- if (nodeIndex <= 0 || !paragraphStartsNormalFlowContent(paragraph) || paragraphHasExplicitPageBreak2(paragraph) || paragraphHasPageBreakBefore2(paragraph)) {
6948
- return false;
6949
- }
6950
- const previousNode = model.nodes[nodeIndex - 1];
6951
- if (!previousNode || previousNode.type !== "paragraph") {
6952
- return false;
6953
- }
6954
- return paragraphActsAsLeadingCoverLayoutSpacer(
6955
- model,
6956
- nodeIndex - 1,
6957
- previousNode,
6958
- pageContentWidthPx,
6959
- pageContentHeightPx
6960
- );
6961
- }
6962
7271
  function paragraphLooksLikeCheckboxChoiceRow(paragraph) {
6963
7272
  if (paragraph.children.some((child) => child.type === "image")) {
6964
7273
  return false;
@@ -7476,7 +7785,8 @@ function buildParagraphPretextLayoutSource(paragraph, options) {
7476
7785
  let combinedText = "";
7477
7786
  const paragraphBaseFontPx = paragraphBaseFontSizePx(paragraph);
7478
7787
  const tabStopPositionsPx = options?.expandTabsForLayout ? resolveParagraphTabStopsPx(paragraph) : [];
7479
- const fallbackTabWidthPx = paragraphLooksLikeCheckboxChoiceRow(paragraph) ? checkboxChoiceRowTabWidthPx(paragraph) : DEFAULT_TAB_STOP_PX;
7788
+ const usesCheckboxRowTabFallback = paragraphLooksLikeCheckboxChoiceRow(paragraph);
7789
+ const fallbackTabWidthPx = usesCheckboxRowTabFallback ? checkboxChoiceRowTabWidthPx(paragraph) : DEFAULT_TAB_STOP_PX;
7480
7790
  let approximateLineWidthPx = 0;
7481
7791
  let lastTextStyle = firstRunStyle(paragraph);
7482
7792
  for (let childIndex = 0; childIndex < paragraph.children.length; childIndex += 1) {
@@ -7542,7 +7852,8 @@ function buildParagraphPretextLayoutSource(paragraph, options) {
7542
7852
  const tabWidthPx = resolveTabSpacerWidthPx(
7543
7853
  tabStopPositionsPx,
7544
7854
  approximateLineWidthPx,
7545
- fallbackTabWidthPx
7855
+ fallbackTabWidthPx,
7856
+ usesCheckboxRowTabFallback
7546
7857
  );
7547
7858
  const spacerText = buildParagraphPretextTabSpacerText(
7548
7859
  tabWidthPx,
@@ -8291,10 +8602,14 @@ function updateEstimatedLineWidthPxForText(currentLineWidthPx, text, style) {
8291
8602
  const trailingSegment = segments[segments.length - 1] ?? "";
8292
8603
  return estimateTextAdvanceWidthPx(trailingSegment, style);
8293
8604
  }
8294
- function resolveTabSpacerWidthPx(tabStopPositionsPx, currentLineWidthPx, fallbackWidthPx) {
8605
+ function resolveTabSpacerWidthPx(tabStopPositionsPx, currentLineWidthPx, fallbackWidthPx, fixedFallback = false) {
8295
8606
  const safeFallback = Math.max(12, Math.round(fallbackWidthPx));
8296
8607
  if (tabStopPositionsPx.length === 0) {
8297
- return safeFallback;
8608
+ if (fixedFallback) {
8609
+ return safeFallback;
8610
+ }
8611
+ const nextStop2 = (Math.floor((currentLineWidthPx + 0.5) / safeFallback) + 1) * safeFallback;
8612
+ return Math.max(2, Math.round(nextStop2 - currentLineWidthPx));
8298
8613
  }
8299
8614
  const nextStop = tabStopPositionsPx.find(
8300
8615
  (stop) => stop > currentLineWidthPx + 0.5
@@ -9047,7 +9362,23 @@ function singleLineAutoScaleForFontFamily(fontFamily) {
9047
9362
  }
9048
9363
  return WORD_SINGLE_LINE_AUTO_SCALE;
9049
9364
  }
9365
+ function emptyParagraphLineScaleForFontFamily(fontFamily) {
9366
+ const normalized = normalizeFontFamilyToken(fontFamily) ?? fontFamily?.toLowerCase();
9367
+ if (!normalized) {
9368
+ return WORD_EMPTY_PARAGRAPH_LINE_SCALE;
9369
+ }
9370
+ if (normalized === "times roman" || normalized === "times new roman" || normalized === "cambria" || normalized === "garamond" || normalized === "georgia" || normalized === "book antiqua" || normalized === "palatino linotype") {
9371
+ return WORD_EMPTY_PARAGRAPH_LINE_SCALE_SERIF;
9372
+ }
9373
+ if (normalized === "arial") {
9374
+ return WORD_EMPTY_PARAGRAPH_LINE_SCALE_SANS;
9375
+ }
9376
+ return WORD_EMPTY_PARAGRAPH_LINE_SCALE;
9377
+ }
9050
9378
  function resolveParagraphSingleLineAutoScale(paragraph, fontFamily) {
9379
+ if (paragraphHasOnlyWhitespaceText(paragraph)) {
9380
+ return emptyParagraphLineScaleForFontFamily(fontFamily);
9381
+ }
9051
9382
  const baseScale = singleLineAutoScaleForFontFamily(fontFamily);
9052
9383
  return paragraphHasCheckboxFormField(paragraph) ? Math.max(1.08, baseScale) : baseScale;
9053
9384
  }
@@ -9762,36 +10093,6 @@ function resolveMaxPretextLineRangeEndIndexThatFits(layout, startLineIndex, maxE
9762
10093
  }
9763
10094
  return bestEnd;
9764
10095
  }
9765
- function estimateAbsoluteFloatingImageFootprintPx(paragraph, image) {
9766
- if (!shouldRenderAbsoluteFloatingImage(image)) {
9767
- return 0;
9768
- }
9769
- const floating = image.floating;
9770
- if (!floating) {
9771
- return 0;
9772
- }
9773
- const wrapType = (floating.wrapType ?? "none").trim().toLowerCase();
9774
- if (wrapType !== "none") {
9775
- return 0;
9776
- }
9777
- const behavesAsTextBearingFloatingTextBox = image.syntheticTextBox && !syntheticTextBoxContainsPictureLayer(image) && Boolean(floatingTextBoxVisibleTextFromImage(image));
9778
- if (behavesAsTextBearingFloatingTextBox) {
9779
- if (paragraphHasVisibleText2(paragraph) || paragraphHasFormField2(paragraph) || paragraphContainsSectionBreakProperties(paragraph)) {
9780
- return 0;
9781
- }
9782
- const imageHeightPx = Number.isFinite(image.heightPx) && image.heightPx > 0 ? Math.round(image.heightPx) : Number.isFinite(image.widthPx) && image.widthPx > 0 ? Math.round(image.widthPx) : MIN_PARAGRAPH_LINE_HEIGHT_PX;
9783
- const distTPx = Math.max(0, Math.round(floating.distTPx ?? 0));
9784
- const distBPx = Math.max(0, Math.round(floating.distBPx ?? 0));
9785
- return Math.max(
9786
- MIN_PARAGRAPH_LINE_HEIGHT_PX,
9787
- imageHeightPx + distTPx + distBPx
9788
- );
9789
- }
9790
- if (floating.behindDocument) {
9791
- return 0;
9792
- }
9793
- return 0;
9794
- }
9795
10096
  function resolveAutoLineSpacingMultiple(lineTwips, fallbackMultiple) {
9796
10097
  if (!Number.isFinite(lineTwips)) {
9797
10098
  return Math.max(MIN_AUTO_LINE_MULTIPLE, fallbackMultiple);
@@ -9889,8 +10190,15 @@ function estimateParagraphLineHeightPx(paragraph, docGridLinePitchPx, disableDoc
9889
10190
  docGridMinimumLineHeightPx ?? 0
9890
10191
  );
9891
10192
  }
9892
- const multiple = calibrateAutoLineSpacingMultiple(
9893
- resolveAutoLineSpacingMultiple(lineTwips, defaultLineMultiple),
10193
+ const resolvedAutoMultiple = resolveAutoLineSpacingMultiple(
10194
+ lineTwips,
10195
+ defaultLineMultiple
10196
+ );
10197
+ const multiple = paragraphHasOnlyWhitespaceText(paragraph) ? Math.max(
10198
+ MIN_AUTO_LINE_MULTIPLE,
10199
+ Number((resolvedAutoMultiple * singleLineScale).toFixed(3))
10200
+ ) : calibrateAutoLineSpacingMultiple(
10201
+ resolvedAutoMultiple,
9894
10202
  baseFontFamily,
9895
10203
  singleLineScale
9896
10204
  );
@@ -9967,19 +10275,6 @@ function estimateParagraphHeightPx(paragraph, availableWidthPx, numberingDefinit
9967
10275
  estimateWrappedFloatingImageFootprintPx(paragraph, child)
9968
10276
  );
9969
10277
  }, 0);
9970
- const absoluteFloatingImageHeightPx = paragraph.children.reduce(
9971
- (largest, child) => {
9972
- if (child.type !== "image") {
9973
- return largest;
9974
- }
9975
- return Math.max(
9976
- largest,
9977
- estimateAbsoluteFloatingImageFootprintPx(paragraph, child)
9978
- );
9979
- },
9980
- 0
9981
- );
9982
- const effectiveAbsoluteFloatingImageHeightPx = collapsibleAbsoluteFloatingAnchorOnlyParagraph || decorativeBehindTextAnchorOnlyParagraph ? 0 : absoluteFloatingImageHeightPx;
9983
10278
  const emptyParagraphHeightPx = decorativeBehindTextAnchorOnlyParagraph ? 0 : paragraphIsEffectivelyEmpty(paragraph) ? lineHeightPx + EMPTY_PARAGRAPH_EXTRA_HEIGHT_PX : 0;
9984
10279
  const topBorderInsetPx = paragraphBorderInsetPx(
9985
10280
  paragraph.style?.borders?.top
@@ -9999,7 +10294,6 @@ function estimateParagraphHeightPx(paragraph, availableWidthPx, numberingDefinit
9999
10294
  textFlowHeightPx,
10000
10295
  inlineImageHeightPx,
10001
10296
  wrappedFloatingImageHeightPx,
10002
- effectiveAbsoluteFloatingImageHeightPx,
10003
10297
  emptyParagraphHeightPx
10004
10298
  );
10005
10299
  if (excludeWrappedFloatingImageFootprint && paragraph.children.some((c) => c.type === "image")) {
@@ -11260,6 +11554,7 @@ function buildDocumentPageNodeSegments(model, pageContentHeightPx, pageContentWi
11260
11554
  let previousParagraphAfterPx = 0;
11261
11555
  let currentMetricsIndex = 0;
11262
11556
  let currentSectionPageFlowOriginPx = 0;
11557
+ let committedKeepNextChainEndNodeIndex = -1;
11263
11558
  let currentPageContentHeightPx = resolveMetricsPageContentHeightPx(
11264
11559
  0,
11265
11560
  metricsBySection[0]
@@ -11318,31 +11613,10 @@ function buildDocumentPageNodeSegments(model, pageContentHeightPx, pageContentWi
11318
11613
  previousParagraphAfterPx = 0;
11319
11614
  continue;
11320
11615
  }
11321
- if (node.type === "paragraph" && paragraphActsAsLeadingCoverLayoutPreambleSpacer(
11322
- model,
11323
- nodeIndex,
11324
- node,
11325
- nodeMetrics.pageContentWidthPx,
11326
- nodeMetrics.pageContentHeightPx
11327
- )) {
11328
- previousParagraphAfterPx = 0;
11329
- continue;
11330
- }
11331
11616
  if (node.type === "paragraph" && paragraphActsAsTrailingRenderedPageBreakSpacer(model, nodeIndex, node)) {
11332
11617
  previousParagraphAfterPx = 0;
11333
11618
  continue;
11334
11619
  }
11335
- if (node.type === "paragraph" && paragraphActsAsLeadingCoverLayoutOverlay(
11336
- model,
11337
- nodeIndex,
11338
- node,
11339
- nodeMetrics.pageContentWidthPx,
11340
- nodeMetrics.pageContentHeightPx
11341
- )) {
11342
- currentPageSegments.push({ nodeIndex });
11343
- previousParagraphAfterPx = 0;
11344
- continue;
11345
- }
11346
11620
  if (node.type === "paragraph" && paragraphActsAsDecorativeBehindTextBackgroundOverlay(node)) {
11347
11621
  currentPageSegments.push({ nodeIndex });
11348
11622
  previousParagraphAfterPx = 0;
@@ -11352,22 +11626,6 @@ function buildDocumentPageNodeSegments(model, pageContentHeightPx, pageContentWi
11352
11626
  continue;
11353
11627
  }
11354
11628
  if (node.type === "paragraph") {
11355
- if (paragraphStartsNewPageAfterLeadingCoverLayout(
11356
- model,
11357
- nodeIndex,
11358
- node,
11359
- nodeMetrics.pageContentWidthPx,
11360
- nodeMetrics.pageContentHeightPx
11361
- ) && currentPageSegments.length > 0) {
11362
- startNextPage();
11363
- pageConsumedHeightPx = 0;
11364
- previousParagraphAfterPx = 0;
11365
- currentSectionPageFlowOriginPx = 0;
11366
- currentPageContentHeightPx = resolveMetricsPageContentHeightPx(
11367
- currentPageIndex,
11368
- nodeMetrics
11369
- );
11370
- }
11371
11629
  if (paragraphHasPageBreakBefore2(node) && currentPageSegments.length > 0) {
11372
11630
  startNextPage();
11373
11631
  pageConsumedHeightPx = 0;
@@ -11451,18 +11709,12 @@ function buildDocumentPageNodeSegments(model, pageContentHeightPx, pageContentWi
11451
11709
  1,
11452
11710
  estimatedOrMeasuredHeightPx - directBeforeSpacingPx - directAfterSpacingPx + beforeSpacingPx + afterSpacingPx
11453
11711
  );
11454
- const coverFootprintPx = paragraphLikelyFullPageCoverFootprintPx(
11455
- model,
11456
- nodeIndex,
11457
- node,
11458
- nodeMetrics.pageContentWidthPx,
11459
- nodeMetrics.pageContentHeightPx
11460
- );
11461
- const paragraphTooTallForSinglePage = Math.max(rawNodeHeightPx, coverFootprintPx) > nodeMetrics.pageContentHeightPx + PAGE_OVERFLOW_TOLERANCE_PX;
11712
+ const paragraphTooTallForSinglePage = rawNodeHeightPx > nodeMetrics.pageContentHeightPx + PAGE_OVERFLOW_TOLERANCE_PX;
11462
11713
  const keepLinesOverflowSplit = node.style?.keepLines === true && paragraphTooTallForSinglePage;
11463
11714
  const keepNextOverflowSplit = node.style?.keepNext === true && paragraphTooTallForSinglePage;
11464
11715
  const forceOverflowSplit = keepLinesOverflowSplit || keepNextOverflowSplit;
11465
- if (forceOverflowSplit && pageConsumedHeightPx > 0 && currentPageSegments.length > 0) {
11716
+ const nodeIsWithinCommittedKeepNextChain = nodeIndex <= committedKeepNextChainEndNodeIndex;
11717
+ if (forceOverflowSplit && !nodeIsWithinCommittedKeepNextChain && pageConsumedHeightPx > 0 && currentPageSegments.length > 0) {
11466
11718
  startNextPage();
11467
11719
  pageConsumedHeightPx = 0;
11468
11720
  previousParagraphAfterPx = 0;
@@ -11475,7 +11727,7 @@ function buildDocumentPageNodeSegments(model, pageContentHeightPx, pageContentWi
11475
11727
  const collapsedMarginPx = pageConsumedHeightPx > 0 ? Math.min(previousParagraphAfterPx, beforeSpacingPx) : 0;
11476
11728
  const collapsedNodeHeightPx = Math.max(
11477
11729
  1,
11478
- Math.max(rawNodeHeightPx, coverFootprintPx) - collapsedMarginPx
11730
+ rawNodeHeightPx - collapsedMarginPx
11479
11731
  );
11480
11732
  const paragraphSupportsPretextSegmentRendering = Boolean(
11481
11733
  paragraphPretextSourceForSegmentRendering
@@ -11501,7 +11753,7 @@ function buildDocumentPageNodeSegments(model, pageContentHeightPx, pageContentWi
11501
11753
  }
11502
11754
  const collapsedNodeHeightPxAdjusted = Math.max(
11503
11755
  1,
11504
- Math.max(rawNodeHeightPx, coverFootprintPx) - collapsedMarginPx
11756
+ rawNodeHeightPx - collapsedMarginPx
11505
11757
  );
11506
11758
  const paragraphPretextLineCount = paragraphContainsExplicitLineBreakText(node) || paragraphContainsTabCharacter(node) ? resolveParagraphPretextLayoutForSegmentRendering()?.lineCount : void 0;
11507
11759
  const supportsImageParagraphLineSplit = paragraphHasImage2(node) && paragraphSupportsPretextSegmentRendering;
@@ -11725,7 +11977,8 @@ function buildDocumentPageNodeSegments(model, pageContentHeightPx, pageContentWi
11725
11977
  continue;
11726
11978
  }
11727
11979
  let requiredHeightPx = collapsedNodeHeightPxAdjusted;
11728
- if (node.style?.keepNext === true && paragraphHasVisibleText2(node)) {
11980
+ let keepNextChainEndNodeIndex = -1;
11981
+ if (node.style?.keepNext === true && !nodeIsWithinCommittedKeepNextChain && paragraphHasVisibleText2(node)) {
11729
11982
  let chainCursor = nodeIndex;
11730
11983
  let chainPreviousParagraphAfterPx = afterSpacingPx;
11731
11984
  while (chainCursor < model.nodes.length - 1) {
@@ -11785,6 +12038,7 @@ function buildDocumentPageNodeSegments(model, pageContentHeightPx, pageContentWi
11785
12038
  );
11786
12039
  chainPreviousParagraphAfterPx = nextAfterSpacingPx;
11787
12040
  }
12041
+ keepNextChainEndNodeIndex = chainCursor;
11788
12042
  }
11789
12043
  const remainingHeightPx = currentPageContentHeightPx - pageConsumedHeightPx;
11790
12044
  const canKeepTrailingSectionTailOnCurrentPage = shouldKeepTrailingSectionTailOnCurrentPage(
@@ -11826,11 +12080,11 @@ function buildDocumentPageNodeSegments(model, pageContentHeightPx, pageContentWi
11826
12080
  nodeMetrics
11827
12081
  );
11828
12082
  }
12083
+ if (pageConsumedHeightPx === 0 && keepNextChainEndNodeIndex > nodeIndex) {
12084
+ committedKeepNextChainEndNodeIndex = keepNextChainEndNodeIndex;
12085
+ }
11829
12086
  currentPageSegments.push({ nodeIndex });
11830
- const effectiveNodeHeightPx = Math.max(
11831
- pageConsumedHeightPx > 0 ? collapsedNodeHeightPx : rawNodeHeightPx,
11832
- coverFootprintPx
11833
- );
12087
+ const effectiveNodeHeightPx = pageConsumedHeightPx > 0 ? collapsedNodeHeightPx : rawNodeHeightPx;
11834
12088
  pageConsumedHeightPx += effectiveNodeHeightPx;
11835
12089
  previousParagraphAfterPx = afterSpacingPx;
11836
12090
  continue;
@@ -12637,11 +12891,15 @@ function paragraphLineHeight(paragraph, docGridLinePitchPx, disableDocGridSnap =
12637
12891
  disableDocGridSnap
12638
12892
  )}px`;
12639
12893
  }
12640
- const lineMultiple = calibrateAutoLineSpacingMultiple(
12641
- resolveAutoLineSpacingMultiple(
12642
- lineTwips,
12643
- DEFAULT_PARAGRAPH_LINE_MULTIPLE
12644
- ),
12894
+ const resolvedAutoMultiple = resolveAutoLineSpacingMultiple(
12895
+ lineTwips,
12896
+ DEFAULT_PARAGRAPH_LINE_MULTIPLE
12897
+ );
12898
+ const lineMultiple = paragraphHasOnlyWhitespaceText(paragraph) ? Math.max(
12899
+ MIN_AUTO_LINE_MULTIPLE,
12900
+ Number((resolvedAutoMultiple * singleLineScale).toFixed(3))
12901
+ ) : calibrateAutoLineSpacingMultiple(
12902
+ resolvedAutoMultiple,
12645
12903
  baseFontFamily,
12646
12904
  singleLineScale
12647
12905
  );
@@ -12985,8 +13243,7 @@ function paragraphBlockStyle(paragraph, numberingDefinitions, headingStyles, doc
12985
13243
  const suppressIndentForFloatingAnchorOnlyParagraph = paragraphIsFloatingImageAnchorOnly(paragraph);
12986
13244
  const suppressStackingContextForBehindTextAnchorOnlyParagraph = paragraphIsBehindTextAbsoluteFloatingImageAnchorOnly(paragraph);
12987
13245
  const suppressFlowFootprintForBehindTextAnchorOnlyParagraph = paragraphActsAsDecorativeBehindTextBackgroundOverlay(paragraph);
12988
- const textBearingAbsoluteFloatingTextBoxFootprintPx = paragraphTextBearingAbsoluteFloatingTextBoxFootprintPx(paragraph);
12989
- const reservedMinHeightPx = suppressFlowFootprintForBehindTextAnchorOnlyParagraph ? void 0 : textBearingAbsoluteFloatingTextBoxFootprintPx > 0 ? textBearingAbsoluteFloatingTextBoxFootprintPx : paragraphIsEffectivelyEmpty(paragraph) ? estimateParagraphLineHeightPx(
13246
+ const reservedMinHeightPx = suppressFlowFootprintForBehindTextAnchorOnlyParagraph ? void 0 : paragraphIsEffectivelyEmpty(paragraph) ? estimateParagraphLineHeightPx(
12990
13247
  paragraph,
12991
13248
  docGridLinePitchPx,
12992
13249
  disableDocGridSnap
@@ -14744,7 +15001,8 @@ function renderParagraphRuns(paragraph, keyPrefix, documentTheme = "light", numb
14744
15001
  const resolveNextTabWidthPx = () => resolveTabSpacerWidthPx(
14745
15002
  tabStopPositionsPx,
14746
15003
  approximateLineWidthPx,
14747
- fallbackTabWidthPx
15004
+ fallbackTabWidthPx,
15005
+ checkboxChoiceRow
14748
15006
  );
14749
15007
  const appendPlainTextWithSoftBreakControl = (target, keySeed, text, style, measureStyle) => {
14750
15008
  const shouldControlSoftBreakStretch = paragraph.style?.align === "justify" && text.includes("\n");
@@ -22822,12 +23080,32 @@ function ensureDocxViewerPageSurfaceRegistry(editor) {
22822
23080
  if (!registry) {
22823
23081
  registry = {
22824
23082
  pageElements: /* @__PURE__ */ new Map(),
23083
+ pageContentKeys: /* @__PURE__ */ new Map(),
22825
23084
  listeners: /* @__PURE__ */ new Set()
22826
23085
  };
22827
23086
  docxViewerPageSurfaceRegistryByEditor.set(owner, registry);
22828
23087
  }
22829
23088
  return registry;
22830
23089
  }
23090
+ function syncDocxViewerPageSurfaceContentKeys(editor, contentKeysByPage) {
23091
+ const registry = ensureDocxViewerPageSurfaceRegistry(editor);
23092
+ let changed = false;
23093
+ contentKeysByPage.forEach((contentKey, pageIndex) => {
23094
+ if (registry.pageContentKeys.get(pageIndex) !== contentKey) {
23095
+ registry.pageContentKeys.set(pageIndex, contentKey);
23096
+ changed = true;
23097
+ }
23098
+ });
23099
+ registry.pageContentKeys.forEach((_, pageIndex) => {
23100
+ if (pageIndex >= contentKeysByPage.length) {
23101
+ registry.pageContentKeys.delete(pageIndex);
23102
+ changed = true;
23103
+ }
23104
+ });
23105
+ if (changed) {
23106
+ notifyDocxViewerPageSurfaceSubscribers(registry);
23107
+ }
23108
+ }
22831
23109
  function subscribeDocxViewerPageSurfaces(editor, listener) {
22832
23110
  const registry = ensureDocxViewerPageSurfaceRegistry(editor);
22833
23111
  registry.listeners.add(listener);
@@ -22891,62 +23169,8 @@ function resolveDocxViewerPageSurfaceSize(element, fallbackWidthPx, fallbackHeig
22891
23169
  heightPx: Math.max(1, Math.round(fallbackHeightPx))
22892
23170
  };
22893
23171
  }
22894
- async function rasterizeDocxViewerPageSurfaceToCanvas(params) {
22895
- if (typeof window === "undefined" || typeof XMLSerializer === "undefined") {
22896
- throw new Error("DOCX thumbnails require a browser environment.");
22897
- }
22898
- const {
22899
- pageElement,
22900
- sourceWidthPx,
22901
- sourceHeightPx,
22902
- canvas,
22903
- widthPx,
22904
- heightPx,
22905
- pixelWidthPx,
22906
- pixelHeightPx
22907
- } = params;
22908
- const safeSourceWidthPx = Math.max(1, Math.round(sourceWidthPx));
22909
- const safeSourceHeightPx = Math.max(1, Math.round(sourceHeightPx));
22910
- const scaleX = widthPx / safeSourceWidthPx;
22911
- const scaleY = heightPx / safeSourceHeightPx;
22912
- const serializedPage = new XMLSerializer().serializeToString(
22913
- pageElement.cloneNode(true)
22914
- );
22915
- const svgMarkup = `
22916
- <svg xmlns="http://www.w3.org/2000/svg" width="${widthPx}" height="${heightPx}" viewBox="0 0 ${widthPx} ${heightPx}">
22917
- <foreignObject x="0" y="0" width="100%" height="100%">
22918
- <div xmlns="http://www.w3.org/1999/xhtml" style="width:${widthPx}px;height:${heightPx}px;overflow:hidden;">
22919
- <div style="width:${safeSourceWidthPx}px;height:${safeSourceHeightPx}px;transform-origin:top left;transform:scale(${scaleX}, ${scaleY});">
22920
- ${serializedPage}
22921
- </div>
22922
- </div>
22923
- </foreignObject>
22924
- </svg>
22925
- `;
22926
- const svgDataUrl = svgDataUri(svgMarkup);
22927
- const image = await new Promise((resolve, reject) => {
22928
- const nextImage = new Image();
22929
- nextImage.decoding = "async";
22930
- nextImage.onload = () => resolve(nextImage);
22931
- nextImage.onerror = () => {
22932
- reject(new Error("Failed to rasterize DOCX page thumbnail."));
22933
- };
22934
- nextImage.src = svgDataUrl;
22935
- });
22936
- canvas.width = Math.max(1, Math.round(pixelWidthPx));
22937
- canvas.height = Math.max(1, Math.round(pixelHeightPx));
22938
- canvas.style.width = `${Math.max(1, Math.round(widthPx))}px`;
22939
- canvas.style.height = `${Math.max(1, Math.round(heightPx))}px`;
22940
- const context = canvas.getContext("2d");
22941
- if (!context) {
22942
- throw new Error("2D canvas context is unavailable for DOCX thumbnails.");
22943
- }
22944
- context.setTransform(1, 0, 0, 1, 0, 0);
22945
- context.clearRect(0, 0, canvas.width, canvas.height);
22946
- context.imageSmoothingEnabled = true;
22947
- context.imageSmoothingQuality = "high";
22948
- context.drawImage(image, 0, 0, canvas.width, canvas.height);
22949
- }
23172
+ var DOCX_THUMBNAIL_SURFACE_CACHE_MAX_ENTRIES = 32;
23173
+ var DOCX_THUMBNAIL_MIN_RASTER_INTERVAL_MS = 200;
22950
23174
  function resolveDocxPageThumbnailResolution(options) {
22951
23175
  const safeSourceWidthPx = Math.max(1, Math.round(options.sourceWidthPx));
22952
23176
  const safeSourceHeightPx = Math.max(1, Math.round(options.sourceHeightPx));
@@ -23020,8 +23244,51 @@ function useDocxPageThumbnails(editor, options = {}) {
23020
23244
  },
23021
23245
  []
23022
23246
  );
23247
+ const thumbnailSurfaceCacheRef = React.useRef(void 0);
23248
+ const thumbnailRasterQueueRef = React.useRef(void 0);
23249
+ const lastPaintedThumbnailKeyByCanvasRef = React.useRef(
23250
+ /* @__PURE__ */ new WeakMap()
23251
+ );
23252
+ const ensureThumbnailSurfaceCache = React.useCallback(() => {
23253
+ if (!thumbnailSurfaceCacheRef.current) {
23254
+ thumbnailSurfaceCacheRef.current = new DocxThumbnailSurfaceCache(
23255
+ DOCX_THUMBNAIL_SURFACE_CACHE_MAX_ENTRIES
23256
+ );
23257
+ }
23258
+ return thumbnailSurfaceCacheRef.current;
23259
+ }, []);
23260
+ const ensureThumbnailRasterQueue = React.useCallback(() => {
23261
+ if (!thumbnailRasterQueueRef.current) {
23262
+ thumbnailRasterQueueRef.current = new SerialIdleTaskQueue({
23263
+ minTaskIntervalMs: DOCX_THUMBNAIL_MIN_RASTER_INTERVAL_MS
23264
+ });
23265
+ }
23266
+ return thumbnailRasterQueueRef.current;
23267
+ }, []);
23268
+ React.useEffect(() => {
23269
+ thumbnailSurfaceCacheRef.current?.clear();
23270
+ thumbnailRasterQueueRef.current?.clear();
23271
+ lastPaintedThumbnailKeyByCanvasRef.current = /* @__PURE__ */ new WeakMap();
23272
+ }, [editor.documentLoadNonce, pageSurfaceRegistryOwner]);
23273
+ const thumbnailResolutionOptionsKey = React.useMemo(() => {
23274
+ const bounds = options.resolution;
23275
+ const boundsKey = typeof bounds === "number" ? `n${bounds}` : bounds ? `b${bounds.maxWidth ?? ""}x${bounds.maxHeight ?? ""}` : "";
23276
+ return `${boundsKey}|${options.maxWidthPx ?? ""}|${options.maxHeightPx ?? ""}|${options.pixelRatio ?? ""}`;
23277
+ }, [
23278
+ options.maxHeightPx,
23279
+ options.maxWidthPx,
23280
+ options.pixelRatio,
23281
+ options.resolution
23282
+ ]);
23283
+ const thumbnailSkipKeyForPage = React.useCallback(
23284
+ (pageIndex) => {
23285
+ const contentKey = pageSurfaceRegistry.pageContentKeys.get(pageIndex);
23286
+ return contentKey === void 0 ? void 0 : `${contentKey}|${editor.documentTheme}|${thumbnailResolutionOptionsKey}`;
23287
+ },
23288
+ [editor.documentTheme, pageSurfaceRegistry, thumbnailResolutionOptionsKey]
23289
+ );
23023
23290
  const renderPageThumbnailToCanvas = React.useCallback(
23024
- async (pageIndex, canvas) => {
23291
+ async (pageIndex, canvas, renderOptions) => {
23025
23292
  if (options.disabled) {
23026
23293
  return;
23027
23294
  }
@@ -23029,63 +23296,101 @@ function useDocxPageThumbnails(editor, options = {}) {
23029
23296
  if (!targetCanvas) {
23030
23297
  return;
23031
23298
  }
23032
- const pageElement = mountedPageElements.get(pageIndex);
23299
+ const pageElement = pageSurfaceRegistry.pageElements.get(pageIndex);
23033
23300
  if (!pageElement || !pageElement.isConnected) {
23034
23301
  updatePageThumbnailState(pageIndex, "unavailable");
23035
23302
  return;
23036
23303
  }
23037
- const sourceSize = resolveDocxViewerPageSurfaceSize(
23038
- pageElement,
23039
- fallbackLayout.pageWidthPx,
23040
- fallbackLayout.pageHeightPx
23041
- );
23042
- const resolution = resolveDocxPageThumbnailResolution({
23043
- sourceWidthPx: sourceSize.widthPx,
23044
- sourceHeightPx: sourceSize.heightPx,
23045
- resolution: options.resolution,
23046
- maxWidthPx: options.maxWidthPx,
23047
- maxHeightPx: options.maxHeightPx,
23048
- pixelRatio: options.pixelRatio
23049
- });
23304
+ const force = renderOptions?.force === true;
23305
+ const lastPaintedKey = lastPaintedThumbnailKeyByCanvasRef.current.get(targetCanvas);
23306
+ if (!force && lastPaintedKey !== void 0 && lastPaintedKey === thumbnailSkipKeyForPage(pageIndex)) {
23307
+ updatePageThumbnailState(pageIndex, "ready");
23308
+ return;
23309
+ }
23050
23310
  updatePageThumbnailState(pageIndex, "rendering");
23051
- try {
23052
- await rasterizeDocxViewerPageSurfaceToCanvas({
23053
- pageElement,
23311
+ await ensureThumbnailRasterQueue().enqueue(targetCanvas, async () => {
23312
+ const livePageElement = pageSurfaceRegistry.pageElements.get(pageIndex);
23313
+ if (!livePageElement || !livePageElement.isConnected) {
23314
+ updatePageThumbnailState(pageIndex, "unavailable");
23315
+ return;
23316
+ }
23317
+ const runSkipKey = thumbnailSkipKeyForPage(pageIndex);
23318
+ const sourceSize = resolveDocxViewerPageSurfaceSize(
23319
+ livePageElement,
23320
+ fallbackLayout.pageWidthPx,
23321
+ fallbackLayout.pageHeightPx
23322
+ );
23323
+ const resolution = resolveDocxPageThumbnailResolution({
23054
23324
  sourceWidthPx: sourceSize.widthPx,
23055
23325
  sourceHeightPx: sourceSize.heightPx,
23056
- canvas: targetCanvas,
23057
- widthPx: resolution.widthPx,
23058
- heightPx: resolution.heightPx,
23059
- pixelWidthPx: resolution.pixelWidthPx,
23060
- pixelHeightPx: resolution.pixelHeightPx
23326
+ resolution: options.resolution,
23327
+ maxWidthPx: options.maxWidthPx,
23328
+ maxHeightPx: options.maxHeightPx,
23329
+ pixelRatio: options.pixelRatio
23061
23330
  });
23062
- updatePageThumbnailState(pageIndex, "ready");
23063
- } catch (error) {
23064
- updatePageThumbnailState(
23065
- pageIndex,
23066
- "error",
23067
- error instanceof Error ? error : new Error("Failed to render DOCX page thumbnail.")
23068
- );
23069
- }
23331
+ const surfaceKey = runSkipKey === void 0 ? void 0 : `${runSkipKey}|${sourceSize.widthPx}x${sourceSize.heightPx}|${resolution.pixelWidthPx}x${resolution.pixelHeightPx}`;
23332
+ const surfaceCache = ensureThumbnailSurfaceCache();
23333
+ try {
23334
+ let surface = !force && surfaceKey !== void 0 ? surfaceCache.get(surfaceKey) : void 0;
23335
+ if (!surface) {
23336
+ surface = await rasterizeDocxThumbnailSurface({
23337
+ pageElement: livePageElement,
23338
+ sourceWidthPx: sourceSize.widthPx,
23339
+ sourceHeightPx: sourceSize.heightPx,
23340
+ widthPx: resolution.widthPx,
23341
+ heightPx: resolution.heightPx,
23342
+ pixelWidthPx: resolution.pixelWidthPx,
23343
+ pixelHeightPx: resolution.pixelHeightPx
23344
+ });
23345
+ if (surfaceKey !== void 0) {
23346
+ surfaceCache.set(surfaceKey, surface);
23347
+ }
23348
+ }
23349
+ blitDocxThumbnailSurface(surface, targetCanvas, resolution);
23350
+ if (runSkipKey !== void 0) {
23351
+ lastPaintedThumbnailKeyByCanvasRef.current.set(
23352
+ targetCanvas,
23353
+ runSkipKey
23354
+ );
23355
+ }
23356
+ updatePageThumbnailState(pageIndex, "ready");
23357
+ } catch (error) {
23358
+ updatePageThumbnailState(
23359
+ pageIndex,
23360
+ "error",
23361
+ error instanceof Error ? error : new Error("Failed to render DOCX page thumbnail.")
23362
+ );
23363
+ }
23364
+ });
23070
23365
  },
23071
23366
  [
23367
+ ensureThumbnailRasterQueue,
23368
+ ensureThumbnailSurfaceCache,
23072
23369
  fallbackLayout.pageHeightPx,
23073
23370
  fallbackLayout.pageWidthPx,
23074
- mountedPageElements,
23075
23371
  options.disabled,
23076
23372
  options.resolution,
23077
23373
  options.maxHeightPx,
23078
23374
  options.maxWidthPx,
23079
23375
  options.pixelRatio,
23376
+ pageSurfaceRegistry,
23377
+ thumbnailSkipKeyForPage,
23080
23378
  updatePageThumbnailState
23081
23379
  ]
23082
23380
  );
23083
- const rerenderAttachedThumbnails = React.useCallback(async () => {
23084
- const tasks = [...attachedCanvasByPageRef.current.keys()].map(
23085
- (pageIndex) => renderPageThumbnailToCanvas(pageIndex)
23086
- );
23087
- await Promise.all(tasks);
23088
- }, [renderPageThumbnailToCanvas]);
23381
+ const requestAttachedThumbnailRenders = React.useCallback(
23382
+ async (renderOptions) => {
23383
+ const tasks = [...attachedCanvasByPageRef.current.keys()].map(
23384
+ (pageIndex) => renderPageThumbnailToCanvas(pageIndex, void 0, renderOptions)
23385
+ );
23386
+ await Promise.all(tasks);
23387
+ },
23388
+ [renderPageThumbnailToCanvas]
23389
+ );
23390
+ const rerenderAttachedThumbnails = React.useCallback(
23391
+ async () => requestAttachedThumbnailRenders({ force: true }),
23392
+ [requestAttachedThumbnailRenders]
23393
+ );
23089
23394
  const renderPageThumbnailToCanvasRef = React.useRef(
23090
23395
  renderPageThumbnailToCanvas
23091
23396
  );
@@ -23097,7 +23402,7 @@ function useDocxPageThumbnails(editor, options = {}) {
23097
23402
  renderToCanvasCallbacksRef.current.clear();
23098
23403
  }, [pageSurfaceRegistryOwner]);
23099
23404
  React.useEffect(() => {
23100
- void rerenderAttachedThumbnails();
23405
+ void requestAttachedThumbnailRenders();
23101
23406
  }, [
23102
23407
  editor.documentLoadNonce,
23103
23408
  editor.documentTheme,
@@ -23108,7 +23413,7 @@ function useDocxPageThumbnails(editor, options = {}) {
23108
23413
  options.maxHeightPx,
23109
23414
  options.maxWidthPx,
23110
23415
  options.pixelRatio,
23111
- rerenderAttachedThumbnails
23416
+ requestAttachedThumbnailRenders
23112
23417
  ]);
23113
23418
  const thumbnails = React.useMemo(() => {
23114
23419
  const totalPages = Math.max(1, editor.totalPages);
@@ -25498,6 +25803,42 @@ function DocxEditorViewer({
25498
25803
  pageNodeSegmentsByPage,
25499
25804
  primarySectionPropertiesXml
25500
25805
  ]);
25806
+ const pageThumbnailContentKeysByPage = React.useMemo(() => {
25807
+ const metadataSignature = docModelThumbnailMetadataSignature(
25808
+ editor.model.metadata
25809
+ );
25810
+ return pageNodeSegmentsByPage.map((pageSegments, pageIndex) => {
25811
+ const sectionInfo = pageSectionInfoByIndex[pageIndex];
25812
+ let nodeSignatures = "";
25813
+ for (const segment of pageSegments) {
25814
+ nodeSignatures += docNodeContentSignature(
25815
+ editor.model.nodes[segment.nodeIndex]
25816
+ );
25817
+ nodeSignatures += ",";
25818
+ }
25819
+ return [
25820
+ editor.documentLoadNonce,
25821
+ trackedChangesEnabled ? "tc1" : "tc0",
25822
+ metadataSignature,
25823
+ sectionInfo ? `${sectionInfo.sectionIndex}.${sectionInfo.pageNumber}` : "s?",
25824
+ pageNodeSegmentIdentityKeysByPage[pageIndex] ?? "",
25825
+ nodeSignatures
25826
+ ].join("|");
25827
+ });
25828
+ }, [
25829
+ editor.documentLoadNonce,
25830
+ editor.model,
25831
+ pageNodeSegmentIdentityKeysByPage,
25832
+ pageNodeSegmentsByPage,
25833
+ pageSectionInfoByIndex,
25834
+ trackedChangesEnabled
25835
+ ]);
25836
+ React.useEffect(() => {
25837
+ syncDocxViewerPageSurfaceContentKeys(
25838
+ editor,
25839
+ pageThumbnailContentKeysByPage
25840
+ );
25841
+ }, [pageSurfaceRegistryOwner, pageThumbnailContentKeysByPage]);
25501
25842
  const resolveStyleRefFieldValueForPage = React.useMemo(() => {
25502
25843
  const valueCache = /* @__PURE__ */ new Map();
25503
25844
  const nodes = editor.model.nodes;
@@ -33214,6 +33555,7 @@ function DocxEditorViewer({
33214
33555
  "span",
33215
33556
  {
33216
33557
  contentEditable: false,
33558
+ ...{ [DOCX_THUMBNAIL_EXCLUDE_ATTRIBUTE]: "true" },
33217
33559
  style: {
33218
33560
  position: "absolute",
33219
33561
  left: rect.left,
@@ -33236,6 +33578,7 @@ function DocxEditorViewer({
33236
33578
  "span",
33237
33579
  {
33238
33580
  contentEditable: false,
33581
+ ...{ [DOCX_THUMBNAIL_EXCLUDE_ATTRIBUTE]: "true" },
33239
33582
  style: {
33240
33583
  position: "absolute",
33241
33584
  left: caretRect.left,
@@ -36439,13 +36782,6 @@ function DocxEditorViewer({
36439
36782
  options?.pageFlowForeignExclusions ?? []
36440
36783
  )
36441
36784
  );
36442
- const leadingCoverLayoutSpacer = paragraphActsAsLeadingCoverLayoutSpacer(
36443
- editor.model,
36444
- nodeIndex,
36445
- node,
36446
- paragraphContentWidthPx,
36447
- resolvedPageLayout.pageHeightPx - resolvedPageLayout.marginsPx.top - resolvedPageLayout.marginsPx.bottom
36448
- );
36449
36785
  const beforeSpacingPx = effectiveParagraphBeforeSpacingPx(
36450
36786
  editor.model,
36451
36787
  nodeIndex,
@@ -36504,9 +36840,6 @@ function DocxEditorViewer({
36504
36840
  lineHeight: 0,
36505
36841
  overflow: "visible"
36506
36842
  } : requiresPageAbsoluteContext ? { position: "static" } : requiresLocalAbsoluteContext ? { position: "relative" } : hasDualWrappedFloatingImage ? { position: "relative" } : void 0,
36507
- ...leadingCoverLayoutSpacer ? {
36508
- minHeight: `${estimateParagraphLineHeightPx(node, nodeDocGridLinePitchPx) + EMPTY_PARAGRAPH_EXTRA_HEIGHT_PX + LEADING_COVER_SPACER_EXTRA_HEIGHT_PX}px`
36509
- } : void 0,
36510
36843
  // Carry the paragraph's run font on the editable host so text typed into
36511
36844
  // it (which is a bare, not-yet-committed text node, not a styled run
36512
36845
  // span) renders in the same font the committed run will use — i.e. the
@@ -37541,6 +37874,22 @@ ${currentText.slice(end)}`;
37541
37874
  const rowSpanValue = cell.style?.rowSpan && cell.style.rowSpan > 1 ? cell.style.rowSpan : 1;
37542
37875
  const colSpan = colSpanValue > 1 ? colSpanValue : void 0;
37543
37876
  const rowSpan = rowSpanValue > 1 ? rowSpanValue : void 0;
37877
+ const exactRowSpanRows = node.rows.slice(
37878
+ rowIndex,
37879
+ rowIndex + rowSpanValue
37880
+ );
37881
+ const exactRowSpanClipHeightPx = exactRowSpanRows.length > 0 && exactRowSpanRows.every((spannedRow, rowOffset) => {
37882
+ const spannedHeightPx = rowHeightsPx[rowIndex + rowOffset];
37883
+ return spannedRow.style?.heightRule === "exact" && Number.isFinite(spannedHeightPx) && spannedHeightPx > 0;
37884
+ }) ? exactRowSpanRows.reduce(
37885
+ (sum, _spannedRow, rowOffset) => sum + Math.max(
37886
+ MIN_PARAGRAPH_LINE_HEIGHT_PX,
37887
+ Math.round(
37888
+ rowHeightsPx[rowIndex + rowOffset]
37889
+ )
37890
+ ),
37891
+ 0
37892
+ ) : void 0;
37544
37893
  const startColumnIndex = columnCursor;
37545
37894
  const boundaryColumnIndex = startColumnIndex + colSpanValue - 1;
37546
37895
  columnCursor += colSpanValue;
@@ -38659,12 +39008,11 @@ ${currentText.slice(end)}`;
38659
39008
  style: {
38660
39009
  display: "grid",
38661
39010
  gap: 0,
38662
- // Word clips content of hRule="exact" rows
38663
- // at the declared height; without this the
38664
- // row balloons to fit content (height on a
38665
- // <tr>/<td> is only ever a minimum).
38666
- ...row.style?.heightRule === "exact" && !isSlicedRow && resolvedRowHeightStyle?.height ? {
38667
- maxHeight: resolvedRowHeightStyle.height,
39011
+ // Without the clip the row balloons to fit
39012
+ // content (height on a <tr>/<td> is only
39013
+ // ever a minimum).
39014
+ ...exactRowSpanClipHeightPx !== void 0 && !isSlicedRow ? {
39015
+ maxHeight: `${exactRowSpanClipHeightPx}px`,
38668
39016
  overflow: "hidden"
38669
39017
  } : void 0
38670
39018
  },
@@ -40451,11 +40799,27 @@ ${currentText.slice(end)}`;
40451
40799
  currentGroup.segments.push(segment);
40452
40800
  return;
40453
40801
  }
40802
+ if (currentGroup && parseSectionStartType(
40803
+ documentSections[currentSectionIndex]?.sectionPropertiesXml
40804
+ ) === "nextcolumn") {
40805
+ const previousColumns = sectionColumnsBySectionIndex[currentGroup.sectionIndex];
40806
+ const nextColumns = sectionColumnsBySectionIndex[currentSectionIndex];
40807
+ const sameGeometry = previousColumns && nextColumns && previousColumns.count === nextColumns.count && Math.abs(
40808
+ previousColumns.gapPx - nextColumns.gapPx
40809
+ ) <= 2 && JSON.stringify(previousColumns.widthsPx ?? null) === JSON.stringify(nextColumns.widthsPx ?? null);
40810
+ if (sameGeometry) {
40811
+ currentGroup.segments.push(segment);
40812
+ return;
40813
+ }
40814
+ }
40454
40815
  sectionGroups.push({
40455
40816
  sectionIndex: currentSectionIndex,
40456
40817
  segments: [segment]
40457
40818
  });
40458
40819
  });
40820
+ if (typeof window !== "undefined" && window.__docxDebugGroups) {
40821
+ console.log("[groups]", pageIndex, JSON.stringify(sectionGroups.map((g) => ({ s: g.sectionIndex, n: g.segments.map((x) => x.nodeIndex) }))));
40822
+ }
40459
40823
  return sectionGroups.map((group, groupIndex) => {
40460
40824
  const sectionColumns = sectionColumnsBySectionIndex[group.sectionIndex];
40461
40825
  const isLastGroupOnPage = groupIndex === sectionGroups.length - 1;
@@ -40624,7 +40988,7 @@ ${currentText.slice(end)}`;
40624
40988
  (editor.model.metadata.sections ?? []).filter(
40625
40989
  (candidate) => parseSectionStartType(
40626
40990
  candidate.sectionPropertiesXml
40627
- ) === "nextColumn"
40991
+ ) === "nextcolumn"
40628
40992
  ).map(
40629
40993
  (candidate) => Math.max(
40630
40994
  0,
@@ -42542,6 +42906,7 @@ function collectDocxEstimatedOverflowBreakStartNodeIndexes(model, hardBreakStart
42542
42906
  let pageConsumedHeightPx = 0;
42543
42907
  let previousParagraphAfterPx = 0;
42544
42908
  let currentMetricsIndex = 0;
42909
+ let committedKeepNextChainEndNodeIndex = -1;
42545
42910
  const suppressSpacingBeforeAfterPageBreak = options?.suppressSpacingBeforeAfterPageBreak ?? false;
42546
42911
  let currentPageContentHeightPx = metricsBySection[0]?.pageContentHeightPx ?? fallbackMetrics.pageContentHeightPx;
42547
42912
  for (let nodeIndex = 0; nodeIndex < model.nodes.length; nodeIndex += 1) {
@@ -42579,7 +42944,8 @@ function collectDocxEstimatedOverflowBreakStartNodeIndexes(model, hardBreakStart
42579
42944
  const collapsedMarginPx = node.type === "paragraph" && pageConsumedHeightPx > 0 ? Math.min(previousParagraphAfterPx, nodeBeforeSpacingPx) : 0;
42580
42945
  const collapsedNodeHeightPx = Math.max(1, rawNodeHeightPx - collapsedMarginPx);
42581
42946
  let requiredHeightPx = collapsedNodeHeightPx;
42582
- if (node.type === "paragraph" && node.style?.keepNext === true && callbacks.paragraphHasVisibleText(node)) {
42947
+ let keepNextChainEndNodeIndex = -1;
42948
+ if (node.type === "paragraph" && node.style?.keepNext === true && nodeIndex > committedKeepNextChainEndNodeIndex && callbacks.paragraphHasVisibleText(node)) {
42583
42949
  let chainCursor = nodeIndex;
42584
42950
  let chainPreviousParagraphAfterPx = paragraphAfterSpacingPx2(node);
42585
42951
  while (chainCursor < model.nodes.length - 1) {
@@ -42637,6 +43003,7 @@ function collectDocxEstimatedOverflowBreakStartNodeIndexes(model, hardBreakStart
42637
43003
  );
42638
43004
  chainPreviousParagraphAfterPx = paragraphAfterSpacingPx2(nextChainNode);
42639
43005
  }
43006
+ keepNextChainEndNodeIndex = chainCursor;
42640
43007
  }
42641
43008
  const remainingHeightPx = currentPageContentHeightPx - pageConsumedHeightPx;
42642
43009
  if (pageConsumedHeightPx > 0 && requiredHeightPx > remainingHeightPx + pageOverflowTolerancePx) {
@@ -42645,6 +43012,9 @@ function collectDocxEstimatedOverflowBreakStartNodeIndexes(model, hardBreakStart
42645
43012
  previousParagraphAfterPx = 0;
42646
43013
  currentPageContentHeightPx = nodeMetrics.pageContentHeightPx;
42647
43014
  }
43015
+ if (pageConsumedHeightPx === 0 && keepNextChainEndNodeIndex > nodeIndex) {
43016
+ committedKeepNextChainEndNodeIndex = keepNextChainEndNodeIndex;
43017
+ }
42648
43018
  const effectiveNodeHeightPx = pageConsumedHeightPx > 0 ? collapsedNodeHeightPx : rawNodeHeightPx;
42649
43019
  pageConsumedHeightPx += effectiveNodeHeightPx;
42650
43020
  previousParagraphAfterPx = node.type === "paragraph" ? paragraphAfterSpacingPx2(node) : 0;
@@ -42696,6 +43066,7 @@ function buildDocumentPageNodeSegments2(model, pageContentHeightPx, pageContentW
42696
43066
  let pageConsumedHeightPx = 0;
42697
43067
  let previousParagraphAfterPx = 0;
42698
43068
  let currentMetricsIndex = 0;
43069
+ let committedKeepNextChainEndNodeIndex = -1;
42699
43070
  let currentPageContentHeightPx = resolvePageContentHeightPx(
42700
43071
  0,
42701
43072
  metricsBySection[0]?.pageContentHeightPx ?? fallbackMetrics.pageContentHeightPx
@@ -42759,7 +43130,8 @@ function buildDocumentPageNodeSegments2(model, pageContentHeightPx, pageContentW
42759
43130
  const keepLinesOverflowSplit = node.style?.keepLines === true && paragraphTooTallForSinglePage;
42760
43131
  const keepNextOverflowSplit = node.style?.keepNext === true && paragraphTooTallForSinglePage;
42761
43132
  const forceOverflowSplit = keepLinesOverflowSplit || keepNextOverflowSplit;
42762
- if (forceOverflowSplit && pageConsumedHeightPx > 0 && currentPageSegments.length > 0) {
43133
+ const nodeIsWithinCommittedKeepNextChain = nodeIndex <= committedKeepNextChainEndNodeIndex;
43134
+ if (forceOverflowSplit && !nodeIsWithinCommittedKeepNextChain && pageConsumedHeightPx > 0 && currentPageSegments.length > 0) {
42763
43135
  startNextPage();
42764
43136
  pageConsumedHeightPx = 0;
42765
43137
  previousParagraphAfterPx = 0;
@@ -42909,7 +43281,8 @@ function buildDocumentPageNodeSegments2(model, pageContentHeightPx, pageContentW
42909
43281
  continue;
42910
43282
  }
42911
43283
  let requiredHeightPx = collapsedNodeHeightPx;
42912
- if (node.style?.keepNext === true && callbacks.paragraphHasVisibleText(node)) {
43284
+ let keepNextChainEndNodeIndex = -1;
43285
+ if (node.style?.keepNext === true && !nodeIsWithinCommittedKeepNextChain && callbacks.paragraphHasVisibleText(node)) {
42913
43286
  let chainCursor = nodeIndex;
42914
43287
  let chainPreviousParagraphAfterPx = afterSpacingPx;
42915
43288
  while (chainCursor < model.nodes.length - 1) {
@@ -42967,6 +43340,7 @@ function buildDocumentPageNodeSegments2(model, pageContentHeightPx, pageContentW
42967
43340
  );
42968
43341
  chainPreviousParagraphAfterPx = paragraphAfterSpacingPx2(nextChainNode);
42969
43342
  }
43343
+ keepNextChainEndNodeIndex = chainCursor;
42970
43344
  }
42971
43345
  const remainingHeightPx = currentPageContentHeightPx - pageConsumedHeightPx;
42972
43346
  if (pageConsumedHeightPx > 0 && requiredHeightPx > remainingHeightPx + pageOverflowTolerancePx) {
@@ -42978,6 +43352,9 @@ function buildDocumentPageNodeSegments2(model, pageContentHeightPx, pageContentW
42978
43352
  nodeMetrics.pageContentHeightPx
42979
43353
  );
42980
43354
  }
43355
+ if (pageConsumedHeightPx === 0 && keepNextChainEndNodeIndex > nodeIndex) {
43356
+ committedKeepNextChainEndNodeIndex = keepNextChainEndNodeIndex;
43357
+ }
42981
43358
  currentPageSegments.push({ nodeIndex });
42982
43359
  const effectiveNodeHeightPx = pageConsumedHeightPx > 0 ? collapsedNodeHeightPx : rawNodeHeightPx;
42983
43360
  pageConsumedHeightPx += effectiveNodeHeightPx;