docxodus 12.6.4 → 13.0.0

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.
Files changed (92) hide show
  1. package/README.md +8 -5
  2. package/dist/canonical.d.ts.map +1 -1
  3. package/dist/canonical.js +7 -3
  4. package/dist/canonical.js.map +1 -1
  5. package/dist/conversion-options.d.ts +16 -0
  6. package/dist/conversion-options.d.ts.map +1 -0
  7. package/dist/conversion-options.js +31 -0
  8. package/dist/conversion-options.js.map +1 -0
  9. package/dist/core.d.ts +17 -9
  10. package/dist/core.d.ts.map +1 -1
  11. package/dist/core.js +23 -24
  12. package/dist/core.js.map +1 -1
  13. package/dist/docxodus.worker.js +2 -1073
  14. package/dist/docxodus.worker.js.map +7 -1
  15. package/dist/editor.bundle.js +35 -14296
  16. package/dist/editor.bundle.js.map +7 -0
  17. package/dist/editor.d.ts +33 -2
  18. package/dist/editor.d.ts.map +1 -1
  19. package/dist/editor.js +6739 -4773
  20. package/dist/editor.js.map +7 -1
  21. package/dist/embed.bundle.js +159 -18703
  22. package/dist/embed.bundle.js.map +7 -0
  23. package/dist/embed.d.ts +1 -1
  24. package/dist/embed.iife.js +37 -18595
  25. package/dist/embed.iife.js.map +7 -0
  26. package/dist/embed.js +1 -1
  27. package/dist/export-assets.json +27 -27
  28. package/dist/export-browser.bundle.js +1388 -477
  29. package/dist/export-browser.d.ts +20 -0
  30. package/dist/export-browser.d.ts.map +1 -1
  31. package/dist/export-browser.js +132 -37
  32. package/dist/export-browser.js.map +1 -1
  33. package/dist/font-runtime.d.ts.map +1 -1
  34. package/dist/font-runtime.js +54 -0
  35. package/dist/font-runtime.js.map +1 -1
  36. package/dist/line-metrics.d.ts +60 -0
  37. package/dist/line-metrics.d.ts.map +1 -0
  38. package/dist/line-metrics.js +343 -0
  39. package/dist/line-metrics.js.map +1 -0
  40. package/dist/pagination.bundle.js +3 -3214
  41. package/dist/pagination.bundle.js.map +7 -0
  42. package/dist/pagination.d.ts +86 -9
  43. package/dist/pagination.d.ts.map +1 -1
  44. package/dist/pagination.js +358 -55
  45. package/dist/pagination.js.map +1 -1
  46. package/dist/react.d.ts.map +1 -1
  47. package/dist/react.js +63 -12
  48. package/dist/react.js.map +1 -1
  49. package/dist/render-report-v2.schema.json +29 -0
  50. package/dist/session.bundle.js +2 -2013
  51. package/dist/session.bundle.js.map +7 -0
  52. package/dist/session.d.ts +14 -5
  53. package/dist/session.d.ts.map +1 -1
  54. package/dist/session.js +62 -29
  55. package/dist/session.js.map +1 -1
  56. package/dist/types.d.ts +137 -21
  57. package/dist/types.d.ts.map +1 -1
  58. package/dist/types.js +14 -0
  59. package/dist/types.js.map +1 -1
  60. package/dist/wasm/_framework/Docxodus.wasm +0 -0
  61. package/dist/wasm/_framework/Docxodus.wasm.br +0 -0
  62. package/dist/wasm/_framework/DocxodusWasm.wasm +0 -0
  63. package/dist/wasm/_framework/DocxodusWasm.wasm.br +0 -0
  64. package/dist/wasm/_framework/System.Collections.wasm +0 -0
  65. package/dist/wasm/_framework/System.Collections.wasm.br +0 -0
  66. package/dist/wasm/_framework/System.IO.Compression.wasm +0 -0
  67. package/dist/wasm/_framework/System.IO.Compression.wasm.br +0 -0
  68. package/dist/wasm/_framework/System.Linq.wasm +0 -0
  69. package/dist/wasm/_framework/System.Linq.wasm.br +0 -0
  70. package/dist/wasm/_framework/System.Memory.wasm +0 -0
  71. package/dist/wasm/_framework/System.Memory.wasm.br +0 -0
  72. package/dist/wasm/_framework/System.Private.CoreLib.wasm +0 -0
  73. package/dist/wasm/_framework/System.Private.CoreLib.wasm.br +0 -0
  74. package/dist/wasm/_framework/System.Private.Xml.Linq.wasm +0 -0
  75. package/dist/wasm/_framework/System.Private.Xml.Linq.wasm.br +0 -0
  76. package/dist/wasm/_framework/System.Text.RegularExpressions.wasm +0 -0
  77. package/dist/wasm/_framework/System.Text.RegularExpressions.wasm.br +0 -0
  78. package/dist/wasm/_framework/dotnet.boot.js +11 -11
  79. package/dist/wasm/_framework/dotnet.boot.js.br +0 -0
  80. package/dist/wasm/_framework/dotnet.native.wasm +0 -0
  81. package/dist/wasm/_framework/dotnet.native.wasm.br +0 -0
  82. package/dist/word-text.d.ts +14 -0
  83. package/dist/word-text.d.ts.map +1 -0
  84. package/dist/word-text.js +377 -0
  85. package/dist/word-text.js.map +1 -0
  86. package/dist/worker-proxy.bundle.js +2 -536
  87. package/dist/worker-proxy.bundle.js.map +7 -0
  88. package/dist/worker-proxy.d.ts +14 -1
  89. package/dist/worker-proxy.d.ts.map +1 -1
  90. package/dist/worker-proxy.js +357 -339
  91. package/dist/worker-proxy.js.map +1 -1
  92. package/package.json +13 -12
@@ -91,10 +91,11 @@ function canonicalValue(value) {
91
91
  if (prototype !== Object.prototype && prototype !== null) {
92
92
  throw new TypeError("Canonical JSON supports only plain objects");
93
93
  }
94
- const result = {};
95
- for (const key of Object.keys(value).sort()) {
94
+ const record = value;
95
+ const result = /* @__PURE__ */ Object.create(null);
96
+ for (const key of Object.keys(record).sort()) {
96
97
  assertWellFormedUnicode(key);
97
- const member = value[key];
98
+ const member = record[key];
98
99
  if (member !== void 0) result[key] = canonicalValue(member);
99
100
  }
100
101
  return result;
@@ -105,6 +106,548 @@ function canonicalJson(value) {
105
106
  return JSON.stringify(canonicalValue(value));
106
107
  }
107
108
 
109
+ // src/line-metrics.ts
110
+ var PROBE_FONT_SIZE_PX = 1e3;
111
+ var LATIN_PROBE = "Hg";
112
+ var MAX_PROBE_CHARACTERS = 256;
113
+ function probeText(element) {
114
+ const characters = /* @__PURE__ */ new Set();
115
+ for (const node of Array.from(element.childNodes)) {
116
+ if (node.nodeType !== 3) continue;
117
+ for (const character of node.textContent ?? "") {
118
+ if (/\s/.test(character)) continue;
119
+ characters.add(character);
120
+ if (characters.size >= MAX_PROBE_CHARACTERS) break;
121
+ }
122
+ }
123
+ const beyondLatin = Array.from(characters).filter((character) => character.codePointAt(0) > 591);
124
+ return beyondLatin.length === 0 ? LATIN_PROBE : LATIN_PROBE + beyondLatin.sort().join("");
125
+ }
126
+ function naturalLineHeightRatios(document2) {
127
+ const ratios = /* @__PURE__ */ new Map();
128
+ return (style, text) => {
129
+ const key = `${style.fontStyle}|${style.fontWeight}|${style.fontStretch}|${style.fontFamily}|${text}`;
130
+ let ratio = ratios.get(key);
131
+ if (ratio === void 0) {
132
+ const probe = document2.createElement("span");
133
+ probe.setAttribute("aria-hidden", "true");
134
+ probe.style.cssText = "position:absolute;left:-100000px;top:0;visibility:hidden;display:inline-block;white-space:nowrap;line-height:normal;margin:0;padding:0;border:0;";
135
+ probe.style.fontFamily = style.fontFamily;
136
+ probe.style.fontStyle = style.fontStyle;
137
+ probe.style.fontWeight = style.fontWeight;
138
+ probe.style.fontStretch = style.fontStretch;
139
+ probe.style.fontSize = `${PROBE_FONT_SIZE_PX}px`;
140
+ probe.textContent = text;
141
+ (document2.body ?? document2.documentElement).appendChild(probe);
142
+ ratio = probe.getBoundingClientRect().height / PROBE_FONT_SIZE_PX;
143
+ probe.remove();
144
+ ratios.set(key, ratio);
145
+ }
146
+ return ratio;
147
+ };
148
+ }
149
+ function applyUnroundedNormalLineHeights(root) {
150
+ const document2 = root.ownerDocument;
151
+ const view = document2.defaultView;
152
+ if (!view) return 0;
153
+ const ratioFor = naturalLineHeightRatios(document2);
154
+ const targets = [];
155
+ for (const element of [root, ...Array.from(root.querySelectorAll("*"))]) {
156
+ if (!(element instanceof view.HTMLElement)) continue;
157
+ const style = view.getComputedStyle(element);
158
+ if (style.lineHeight !== "normal") continue;
159
+ const fontSize = Number.parseFloat(style.fontSize);
160
+ const ratio = ratioFor(style, probeText(element));
161
+ if (!(fontSize > 0) || !(ratio > 0)) continue;
162
+ targets.push({ element, lineHeight: ratio * fontSize });
163
+ }
164
+ for (const { element, lineHeight } of targets) {
165
+ element.style.setProperty("line-height", `${lineHeight.toFixed(4)}px`);
166
+ }
167
+ return targets.length;
168
+ }
169
+ var PARAGRAPH_SELECTOR = "p, h1, h2, h3, h4, h5, h6";
170
+ function baselineOffsets(document2) {
171
+ const offsets = /* @__PURE__ */ new Map();
172
+ const host = document2.body ?? document2.documentElement;
173
+ return (paragraph, child) => {
174
+ const key = [
175
+ paragraph.fontStyle,
176
+ paragraph.fontWeight,
177
+ paragraph.fontStretch,
178
+ paragraph.fontFamily,
179
+ paragraph.fontSize,
180
+ paragraph.lineHeight,
181
+ child.fontStyle,
182
+ child.fontWeight,
183
+ child.fontStretch,
184
+ child.fontFamily,
185
+ child.fontSize,
186
+ child.lineHeight,
187
+ child.verticalAlign,
188
+ child.top
189
+ ].join("|");
190
+ let offset = offsets.get(key);
191
+ if (offset === void 0) {
192
+ const probe = document2.createElement("div");
193
+ probe.setAttribute("aria-hidden", "true");
194
+ probe.style.cssText = "position:absolute;left:-100000px;top:0;visibility:hidden;width:max-content;margin:0;padding:0;border:0;white-space:nowrap;";
195
+ for (const property of ["font-family", "font-style", "font-weight", "font-stretch", "font-size", "line-height"])
196
+ probe.style.setProperty(property, paragraph.getPropertyValue(property));
197
+ const span = document2.createElement("span");
198
+ for (const property of [
199
+ "font-family",
200
+ "font-style",
201
+ "font-weight",
202
+ "font-stretch",
203
+ "font-size",
204
+ "line-height",
205
+ "vertical-align",
206
+ "position",
207
+ "top"
208
+ ])
209
+ span.style.setProperty(property, child.getPropertyValue(property));
210
+ const marker = document2.createElement("span");
211
+ marker.style.cssText = "display:inline-block;width:0;height:0;vertical-align:baseline;margin:0;padding:0;border:0;";
212
+ span.append(marker, LATIN_PROBE);
213
+ probe.append(span);
214
+ host.appendChild(probe);
215
+ offset = marker.getBoundingClientRect().bottom - probe.getBoundingClientRect().top;
216
+ probe.remove();
217
+ offsets.set(key, offset);
218
+ }
219
+ return offset;
220
+ };
221
+ }
222
+ function alignExactLineBaselines(root) {
223
+ const view = root.ownerDocument.defaultView;
224
+ if (!view) return;
225
+ const offsetFor = baselineOffsets(root.ownerDocument);
226
+ for (const run of Array.from(root.querySelectorAll("[data-docx-exact-run]"))) {
227
+ const paragraph = run.closest(PARAGRAPH_SELECTOR);
228
+ if (!paragraph || !movable(run, view)) continue;
229
+ const style = view.getComputedStyle(paragraph);
230
+ if (!style.getPropertyValue("--docx-exact-line-height")) continue;
231
+ const height = Number.parseFloat(style.lineHeight);
232
+ const runStyle = view.getComputedStyle(run);
233
+ if (!(height > 0) || runStyle.display !== "inline" || runStyle.verticalAlign !== "top") continue;
234
+ const top = Number.parseFloat(runStyle.top) || 0;
235
+ const offset = offsetFor(style, runStyle) - top;
236
+ const shift = height * 0.8 - offset;
237
+ if (!Number.isFinite(shift)) continue;
238
+ run.style.position = "relative";
239
+ run.style.top = `${(top + shift).toFixed(4)}px`;
240
+ }
241
+ }
242
+ var EXACT_INK_ATTRIBUTE = "data-docx-exact-ink";
243
+ function preserveExactLineInk(content, topLimit, bottomLimit, paper) {
244
+ const view = content.ownerDocument.defaultView;
245
+ if (!view) return;
246
+ const band = content.getBoundingClientRect();
247
+ const scale = band.width / content.offsetWidth || 1;
248
+ let above = 0;
249
+ let below = 0;
250
+ for (const paragraph of Array.from(content.querySelectorAll(PARAGRAPH_SELECTOR))) {
251
+ if (!view.getComputedStyle(paragraph).getPropertyValue("--docx-exact-line-height")) continue;
252
+ const box = paragraph.getBoundingClientRect();
253
+ if (box.top < band.top - 0.5 || box.bottom > band.bottom + 0.5) continue;
254
+ if (!paragraph.querySelector("[data-docx-exact-run]") || paragraph.querySelector("img, svg, canvas, video, object, iframe"))
255
+ continue;
256
+ if (Array.from(paragraph.querySelectorAll("*")).some((element) => {
257
+ const style = view.getComputedStyle(element);
258
+ return style.display !== "inline" || !["static", "relative"].includes(style.position);
259
+ })) continue;
260
+ const range = content.ownerDocument.createRange();
261
+ range.selectNodeContents(paragraph);
262
+ const rects = Array.from(range.getClientRects()).filter((rect) => rect.width > 0 && rect.height > 0);
263
+ if (rects.length === 0) continue;
264
+ const top = Math.min(...rects.map((rect) => rect.top));
265
+ const bottom = Math.max(...rects.map((rect) => rect.bottom));
266
+ if (top < topLimit || bottom > bottomLimit) continue;
267
+ const extra = Math.max(0, band.top - top, bottom - band.bottom);
268
+ if (extra === 0) continue;
269
+ paragraph.setAttribute(EXACT_INK_ATTRIBUTE, "true");
270
+ above = Math.max(above, band.top - top);
271
+ below = Math.max(below, bottom - band.bottom);
272
+ }
273
+ if (above > 0 || below > 0) {
274
+ const up = Math.ceil(above / scale * 64) / 64;
275
+ const down = Math.ceil(below / scale * 64) / 64;
276
+ content.style.overflowY = "visible";
277
+ content.style.clipPath = `inset(${-up}px ${(band.right - paper.right) / scale}px ${-down}px ${(paper.left - band.left) / scale}px)`;
278
+ }
279
+ }
280
+ function insetClipBounds(value, box, scale) {
281
+ const match = /^inset\(((?:-?\d+(?:\.\d+)?px\s*){1,4})\)$/.exec(value);
282
+ if (!match) return null;
283
+ const parts = match[1].trim().split(/\s+/).map(Number.parseFloat);
284
+ const [top, right = top, bottom = top, left = right] = parts;
285
+ return {
286
+ top: box.top + top * scale,
287
+ right: box.right - right * scale,
288
+ bottom: box.bottom - bottom * scale,
289
+ left: box.left + left * scale
290
+ };
291
+ }
292
+ function isPreservedExactLineInk(element, content) {
293
+ const paragraph = element.closest(`[${EXACT_INK_ATTRIBUTE}]`);
294
+ const view = content.ownerDocument.defaultView;
295
+ if (!paragraph || !view || element === paragraph || !content.contains(paragraph)) return false;
296
+ if (view.getComputedStyle(element).display !== "inline") return false;
297
+ const band = content.getBoundingClientRect();
298
+ const box = paragraph.getBoundingClientRect();
299
+ const clip = insetClipBounds(view.getComputedStyle(content).clipPath, band, band.width / content.offsetWidth || 1);
300
+ const ink = element.getBoundingClientRect();
301
+ return clip !== null && box.top >= band.top - 0.5 && box.bottom <= band.bottom + 0.5 && ink.top >= clip.top - 0.02 && ink.bottom <= clip.bottom + 0.02;
302
+ }
303
+ function alignBaselinesToWord(root) {
304
+ const document2 = root.ownerDocument;
305
+ const view = document2.defaultView;
306
+ if (!view) return 0;
307
+ const descents = /* @__PURE__ */ new Map();
308
+ const ratioFor = naturalLineHeightRatios(document2);
309
+ const chromiumOffset = baselineOffsets(document2);
310
+ const descentRatio = (style) => {
311
+ const font = `${style.fontStyle} ${style.fontWeight} ${PROBE_FONT_SIZE_PX}px ${style.fontFamily}`;
312
+ let ratio = descents.get(font);
313
+ if (ratio === void 0) {
314
+ const context = document2.createElement("canvas").getContext("2d");
315
+ ratio = Number.NaN;
316
+ if (context) {
317
+ context.font = font;
318
+ if (context.font.includes(`${PROBE_FONT_SIZE_PX}px`))
319
+ ratio = context.measureText(LATIN_PROBE).fontBoundingBoxDescent / PROBE_FONT_SIZE_PX;
320
+ }
321
+ descents.set(font, ratio);
322
+ }
323
+ return ratio;
324
+ };
325
+ const moves = [];
326
+ for (const paragraph of Array.from(root.querySelectorAll(PARAGRAPH_SELECTOR))) {
327
+ if (!(paragraph instanceof view.HTMLElement)) continue;
328
+ const style = view.getComputedStyle(paragraph);
329
+ const candidates = Array.from(paragraph.children).filter((child) => child instanceof view.HTMLElement && (child.textContent ?? "").trim() !== "" && movable(child, view) && view.getComputedStyle(child).display === "inline" && view.getComputedStyle(child).verticalAlign === "baseline");
330
+ const first = candidates.find((child) => view.getComputedStyle(child).fontFamily === style.fontFamily) ?? candidates[0];
331
+ if (!first) continue;
332
+ const lineHeight = Number.parseFloat(style.lineHeight);
333
+ const fontSize = Number.parseFloat(style.fontSize);
334
+ if (!(lineHeight > 0) || !(fontSize > 0) || Math.abs(lineHeight - ratioFor(style, LATIN_PROBE) * fontSize) > 0.01) continue;
335
+ const descent = descentRatio(style);
336
+ if (!(descent > 0)) continue;
337
+ const childStyle = view.getComputedStyle(first);
338
+ const shift = lineHeight - descent * fontSize - chromiumOffset(style, childStyle);
339
+ if (!Number.isFinite(shift) || Math.abs(shift) < 1 / 64 || Math.abs(shift) >= lineHeight / 2) continue;
340
+ for (const child of Array.from(paragraph.children))
341
+ if (child instanceof view.HTMLElement && movable(child, view)) {
342
+ const top = Number.parseFloat(view.getComputedStyle(child).top);
343
+ moves.push({ element: child, top: (Number.isFinite(top) ? top : 0) + shift });
344
+ }
345
+ }
346
+ for (const { element, top } of moves) {
347
+ element.style.setProperty("position", "relative");
348
+ element.style.setProperty("top", `${top.toFixed(4)}px`);
349
+ }
350
+ return moves.length;
351
+ }
352
+ function movable(element, view) {
353
+ const position = view.getComputedStyle(element).position;
354
+ if (position !== "static" && position !== "relative") return false;
355
+ if (!["inline", "inline-block"].includes(view.getComputedStyle(element).display)) return false;
356
+ if (element.querySelector("img, svg")) return false;
357
+ for (const descendant of Array.from(element.querySelectorAll("*")))
358
+ if (["absolute", "fixed"].includes(view.getComputedStyle(descendant).position)) return false;
359
+ return true;
360
+ }
361
+
362
+ // src/word-text.ts
363
+ var PREPARED = "data-docx-word-text-prepared";
364
+ var SVG_NS = "http://www.w3.org/2000/svg";
365
+ var PARAGRAPHS = "p, h1, h2, h3, h4, h5, h6";
366
+ function plainDocument(root) {
367
+ if (root.querySelector("table, svg, math, canvas, video, audio, iframe, object, a, ol, ul, aside, template, header, footer, [data-hf-type], section.footnotes, section.endnotes"))
368
+ return false;
369
+ if (Array.from(root.querySelectorAll("img")).some((img) => !img.closest(PARAGRAPHS))) return false;
370
+ return Array.from(root.querySelectorAll(PARAGRAPHS)).every((p) => {
371
+ const run = plainRun(p);
372
+ return run !== null && Number(root.ownerDocument.defaultView?.getComputedStyle(p).getPropertyValue("--docx-auto-line-spacing")) > 0;
373
+ });
374
+ }
375
+ function pictureSibling(element, view) {
376
+ if (!(element instanceof view.HTMLElement) || element.localName !== "span" || element.children.length !== 1)
377
+ return false;
378
+ const wrapper = element.firstElementChild;
379
+ if (wrapper.localName !== "span" || wrapper.children.length !== 1 || wrapper.firstElementChild?.localName !== "img")
380
+ return false;
381
+ if (wrapper.dataset.docxEdgePicture !== "true") return false;
382
+ const style = view.getComputedStyle(wrapper);
383
+ const outer = view.getComputedStyle(element);
384
+ const image = view.getComputedStyle(wrapper.firstElementChild);
385
+ return ["left", "right"].includes(style.cssFloat) && style.position === "static" && style.transform === "none" && outer.display === "inline" && ["static", "relative"].includes(outer.position) && outer.transform === "none" && image.transform === "none" && image.position === "static";
386
+ }
387
+ function plainRun(paragraph) {
388
+ const view = paragraph.ownerDocument.defaultView;
389
+ if (!view) return null;
390
+ const children = Array.from(paragraph.children);
391
+ const content = children.filter((child) => child.textContent !== "");
392
+ if (content.length !== 1) return null;
393
+ const run = content[0];
394
+ if (children.length !== 1) {
395
+ const runStyle = view.getComputedStyle(run);
396
+ for (const empty of children.filter((child) => child !== run)) {
397
+ if (pictureSibling(empty, view)) {
398
+ const pictureStyle = view.getComputedStyle(empty);
399
+ if (["font-size", "font-style", "font-weight", "vertical-align"].some((property) => pictureStyle.getPropertyValue(property) !== runStyle.getPropertyValue(property))) return null;
400
+ continue;
401
+ }
402
+ if (paragraph.dataset.columnBreakBefore !== "true") return null;
403
+ if (!(empty instanceof view.HTMLElement) || empty.localName !== "span" || empty.childNodes.length || Array.from(empty.attributes).some((a) => !["class", "style", "lang", "dir"].includes(a.name))) return null;
404
+ const emptyStyle = view.getComputedStyle(empty);
405
+ if ([
406
+ "font-family",
407
+ "font-size",
408
+ "font-style",
409
+ "font-weight",
410
+ "font-stretch",
411
+ "line-height",
412
+ "display",
413
+ "position",
414
+ "top",
415
+ "vertical-align"
416
+ ].some((property) => emptyStyle.getPropertyValue(property) !== runStyle.getPropertyValue(property))) return null;
417
+ }
418
+ }
419
+ if (!(run instanceof view.HTMLElement) || run.localName !== "span" || run.children.length || run.childNodes.length !== 1 || run.firstChild?.nodeType !== 3) return null;
420
+ const text = run.textContent ?? "";
421
+ if (!text.trim() || /^\s/.test(text) || !/^[\u0020-\u024f]*$/u.test(text)) return null;
422
+ if (Array.from(run.attributes).some((a) => !["class", "style", "lang", "dir"].includes(a.name))) return null;
423
+ const style = view.getComputedStyle(run);
424
+ const parent = view.getComputedStyle(paragraph);
425
+ if (["Top", "Right", "Bottom", "Left"].some((side) => parent.getPropertyValue(`padding-${side.toLowerCase()}`) !== "0px" || parent.getPropertyValue(`border-${side.toLowerCase()}-width`) !== "0px")) return null;
426
+ if (parent.direction !== "ltr" || parent.writingMode !== "horizontal-tb" || style.display !== "inline" || !["static", "relative"].includes(style.position) || style.textTransform !== "none" || style.fontVariantCaps !== "normal" || style.textDecorationLine !== "none" || !["normal", "0px"].includes(style.letterSpacing) || !["normal", "100%"].includes(style.fontStretch) || style.backgroundColor !== "rgba(0, 0, 0, 0)" || style.textShadow !== "none" || style.transform !== "none" || style.opacity !== "1" || style.verticalAlign !== "baseline") return null;
427
+ return run;
428
+ }
429
+ function contourRows(image, paragraph, run, lineCount, checkpoint) {
430
+ const view = paragraph.ownerDocument.defaultView;
431
+ const wrapper = image.parentElement;
432
+ if (wrapper.dataset.docxWrapText !== "bothSides" || image.closest(".page-content") !== paragraph.closest(".page-content")) return null;
433
+ const wrapStyle = view.getComputedStyle(wrapper);
434
+ if (wrapStyle.marginTop !== "0px" || wrapStyle.marginBottom !== "0px") return null;
435
+ const polygon = /^polygon\((.*)\) border-box$/.exec(wrapStyle.shapeOutside);
436
+ if (!polygon) return null;
437
+ const points = polygon[1].split(",").map((point) => {
438
+ const match = /^\s*([\d.+-]+)%\s+([\d.+-]+)%\s*$/.exec(point);
439
+ return match ? { x: Number(match[1]) / 100, y: Number(match[2]) / 100 } : null;
440
+ });
441
+ if (points.length < 3 || points.some((p) => !p || !Number.isFinite(p.x + p.y))) return null;
442
+ const vertices = points;
443
+ let orientation = 0;
444
+ for (let i = 0; i < vertices.length; i++) {
445
+ const a = vertices[i], b = vertices[(i + 1) % vertices.length], c = vertices[(i + 2) % vertices.length];
446
+ const cross = (b.x - a.x) * (c.y - b.y) - (b.y - a.y) * (c.x - b.x);
447
+ if (Math.abs(cross) < 1e-8) continue;
448
+ if (orientation && Math.sign(cross) !== orientation) return null;
449
+ orientation = Math.sign(cross);
450
+ }
451
+ if (!orientation) return null;
452
+ const left = Number.parseFloat(wrapStyle.getPropertyValue("--docx-wrap-distance-left")) * 4 / 3;
453
+ const right = Number.parseFloat(wrapStyle.getPropertyValue("--docx-wrap-distance-right")) * 4 / 3;
454
+ if (!Number.isFinite(left + right) || left < 0 || right < 0) return null;
455
+ const box = paragraph.getBoundingClientRect();
456
+ const picture = image.getBoundingClientRect();
457
+ if (box.top >= picture.bottom || box.bottom <= picture.top) return null;
458
+ const style = view.getComputedStyle(run);
459
+ if (!["start", "left"].includes(view.getComputedStyle(paragraph).textAlign)) return null;
460
+ const pitch = Number.parseFloat(style.lineHeight);
461
+ const nativeHeight = Number.parseFloat(style.getPropertyValue("--docx-word-native-height"));
462
+ if (!(nativeHeight > 0) || !(pitch > 0) || !(picture.width > 0) || !(picture.height > 0)) return null;
463
+ const context = paragraph.ownerDocument.createElement("canvas").getContext("2d");
464
+ if (!context) return null;
465
+ const measureScale = Number.parseFloat(style.fontSize) / 1e4;
466
+ context.font = `${style.fontStyle} ${style.fontWeight} 10000px ${style.fontFamily}`;
467
+ context.fontKerning = style.fontKerning;
468
+ if (!context.font.includes("10000px")) return null;
469
+ const words = Array.from(run.textContent.matchAll(/\S+\s*/gu), (match, index) => {
470
+ if ((index & 63) === 0) checkpoint?.();
471
+ return {
472
+ text: match[0],
473
+ width: context.measureText(match[0].trimEnd()).width * measureScale,
474
+ advance: context.measureText(match[0]).width * measureScale
475
+ };
476
+ });
477
+ const rows = /* @__PURE__ */ new Map();
478
+ let next = 0;
479
+ for (let line = 0; line < lineCount && next < words.length; line++) {
480
+ checkpoint?.();
481
+ const top = (box.top + line * pitch - picture.top) / picture.height;
482
+ const bottom = top + nativeHeight / picture.height;
483
+ const crossings = [];
484
+ for (let i = 0; i < vertices.length; i++) {
485
+ const a = vertices[i], b = vertices[(i + 1) % vertices.length];
486
+ if (a.y >= top && a.y <= bottom) crossings.push(a.x);
487
+ for (const y of [top, bottom])
488
+ if (a.y !== b.y && y >= Math.min(a.y, b.y) && y <= Math.max(a.y, b.y))
489
+ crossings.push(a.x + (b.x - a.x) * (y - a.y) / (b.y - a.y));
490
+ }
491
+ const slots = crossings.length ? [
492
+ [box.left, Math.min(box.right, picture.left + Math.min(...crossings) * picture.width - left)],
493
+ [Math.max(box.left, picture.left + Math.max(...crossings) * picture.width + right), box.right]
494
+ ] : [[box.left, box.right]];
495
+ const row = [];
496
+ for (const [start, end] of slots) {
497
+ let x = start;
498
+ while (next < words.length && x + words[next].width <= end + 1 / 64) {
499
+ const word = words[next++];
500
+ row.push({ text: word.text, x: x - box.left });
501
+ x += word.advance;
502
+ }
503
+ }
504
+ if (!row.length) return null;
505
+ rows.set(line, row);
506
+ }
507
+ return next === words.length && rows.size === lineCount ? rows : null;
508
+ }
509
+ function prepareWordText(root, checkpoint) {
510
+ if (!plainDocument(root)) return 0;
511
+ const view = root.ownerDocument.defaultView;
512
+ if (!view) return 0;
513
+ let prepared = 0;
514
+ for (const paragraph of Array.from(root.querySelectorAll(PARAGRAPHS))) {
515
+ checkpoint?.();
516
+ const run = plainRun(paragraph);
517
+ if (!run) continue;
518
+ const style = view.getComputedStyle(paragraph);
519
+ const multiple = Number(style.getPropertyValue("--docx-auto-line-spacing"));
520
+ const runStyle = view.getComputedStyle(run);
521
+ const kerning = runStyle.getPropertyValue("--docx-font-kerning").trim() || "none";
522
+ const height = Number.parseFloat(runStyle.lineHeight);
523
+ if (!(multiple > 0) || !(height > 0) || !["none", "normal"].includes(kerning)) continue;
524
+ const nativeTwips = Math.round(height / multiple * 15);
525
+ const twips = Math.floor(nativeTwips * multiple);
526
+ if (!(twips > 0)) continue;
527
+ run.style.fontKerning = kerning;
528
+ run.style.lineHeight = `${twips / 15}px`;
529
+ run.style.setProperty("--docx-word-native-height", `${nativeTwips / 15}px`);
530
+ for (const picture of Array.from(paragraph.children).filter((child) => pictureSibling(child, view))) {
531
+ picture.style.lineHeight = run.style.lineHeight;
532
+ picture.style.top = "0";
533
+ }
534
+ for (const empty of Array.from(paragraph.children).filter((child) => child !== run && !child.children.length)) empty.remove();
535
+ paragraph.style.lineHeight = run.style.lineHeight;
536
+ paragraph.setAttribute(PREPARED, "true");
537
+ prepared++;
538
+ }
539
+ return prepared;
540
+ }
541
+ function paintWordText(root, checkpoint) {
542
+ const document2 = root.ownerDocument;
543
+ const view = document2.defaultView;
544
+ if (!view) return 0;
545
+ const paragraphs = Array.from(root.querySelectorAll(`[${PREPARED}]`));
546
+ const pictures = Array.from(root.querySelectorAll("img"));
547
+ const hasPictures = pictures.length > 0;
548
+ const columnOf = (paragraph) => {
549
+ for (let parent = paragraph.parentElement; parent && parent !== root; parent = parent.parentElement)
550
+ if (Number(view.getComputedStyle(parent).columnCount) > 1) return parent;
551
+ return null;
552
+ };
553
+ const fragmentedColumns = /* @__PURE__ */ new Set();
554
+ for (const paragraph of paragraphs) {
555
+ checkpoint?.();
556
+ const column = columnOf(paragraph);
557
+ if (column && paragraph.getClientRects().length !== 1) fragmentedColumns.add(column);
558
+ }
559
+ const metrics = /* @__PURE__ */ new Map();
560
+ function fontMetrics(style) {
561
+ const font = `${style.fontStyle} ${style.fontWeight} 10000px ${style.fontFamily}`;
562
+ const cached = metrics.get(font);
563
+ if (cached) return cached;
564
+ const probe = document2.createElement("span");
565
+ probe.setAttribute("aria-hidden", "true");
566
+ probe.style.cssText = `position:absolute;left:-100000px;top:0;visibility:hidden;display:inline-block;white-space:nowrap;margin:0;padding:0;border:0;font:${font};line-height:normal`;
567
+ probe.textContent = "Hg";
568
+ document2.body.append(probe);
569
+ const height = probe.getBoundingClientRect().height / 1e4;
570
+ probe.remove();
571
+ const context = document2.createElement("canvas").getContext("2d");
572
+ if (!context) return null;
573
+ context.font = font;
574
+ if (!context.font.includes("10000px")) return null;
575
+ const descent = context.measureText("Hg").fontBoundingBoxDescent / 1e4;
576
+ if (!(height > 0) || !(descent > 0)) return null;
577
+ const result = { height, descent };
578
+ metrics.set(font, result);
579
+ return result;
580
+ }
581
+ const pending = [];
582
+ for (const paragraph of paragraphs) {
583
+ checkpoint?.();
584
+ const column = columnOf(paragraph);
585
+ if (column && fragmentedColumns.has(column)) {
586
+ paragraph.removeAttribute(PREPARED);
587
+ continue;
588
+ }
589
+ const run = plainRun(paragraph);
590
+ if (!run) continue;
591
+ const style = view.getComputedStyle(run);
592
+ const metric = fontMetrics(style);
593
+ const fontSize = Number.parseFloat(style.fontSize);
594
+ const pitch = Number.parseFloat(style.lineHeight);
595
+ const box = paragraph.getBoundingClientRect();
596
+ if (!metric || !(fontSize > 0) || !(pitch > 0) || !(box.width > 0) || !(box.height > 0)) continue;
597
+ const columnBand = column || hasPictures ? paragraph.closest(".page-content") : null;
598
+ const band = columnBand?.getBoundingClientRect() ?? box;
599
+ const text = run.textContent;
600
+ let rows = /* @__PURE__ */ new Map();
601
+ let wordCount = 0;
602
+ for (const match of text.matchAll(/\S+\s*/gu)) {
603
+ if ((wordCount++ & 63) === 0) checkpoint?.();
604
+ const range = document2.createRange();
605
+ range.setStart(run.firstChild, match.index);
606
+ range.setEnd(run.firstChild, match.index + match[0].trimEnd().length);
607
+ const rect = range.getBoundingClientRect();
608
+ const row = Math.round(rect.top * 4);
609
+ const words = rows.get(row) ?? [];
610
+ words.push({ text: match[0], x: rect.left - box.left });
611
+ rows.set(row, words);
612
+ }
613
+ if (!rows.size) continue;
614
+ if (pictures.length === 1) rows = contourRows(pictures[0], paragraph, run, rows.size, checkpoint) ?? rows;
615
+ const svg = document2.createElementNS(SVG_NS, "svg");
616
+ svg.setAttribute("width", String(band.width));
617
+ svg.setAttribute("height", String(band.height));
618
+ svg.setAttribute("viewBox", `0 0 ${band.width * 4} ${band.height * 4}`);
619
+ svg.setAttribute("role", "group");
620
+ svg.style.cssText = columnBand ? "position:absolute;left:0;top:0;display:block;overflow:visible" : "display:block;overflow:visible";
621
+ const baseline = (metric.height - metric.descent) * fontSize;
622
+ Array.from(rows.values()).forEach((words, index) => {
623
+ const line = document2.createElementNS(SVG_NS, "text");
624
+ line.setAttribute("y", String((box.top - band.top + baseline + index * pitch) * 4));
625
+ line.setAttribute("xml:space", "preserve");
626
+ for (const property of ["font-family", "font-weight", "font-style", "font-stretch", "font-kerning"])
627
+ line.style.setProperty(property, style.getPropertyValue(property));
628
+ line.style.fontSize = `${fontSize * 4}px`;
629
+ line.style.fill = style.color;
630
+ for (const word of words) {
631
+ const span = document2.createElementNS(SVG_NS, "tspan");
632
+ span.setAttribute("x", String((box.left - band.left + word.x) * 4));
633
+ span.textContent = word.text;
634
+ line.append(span);
635
+ }
636
+ svg.append(line);
637
+ });
638
+ pending.push({ paragraph, run, svg, height: box.height });
639
+ }
640
+ for (const { paragraph, run, svg, height } of pending) {
641
+ checkpoint?.();
642
+ if (Array.from(paragraph.children).some((child) => pictureSibling(child, view))) run.replaceWith(svg);
643
+ else paragraph.replaceChildren(svg);
644
+ paragraph.style.setProperty("--docx-word-text-height", `${height}px`);
645
+ paragraph.style.height = "var(--docx-word-text-height)";
646
+ paragraph.removeAttribute(PREPARED);
647
+ }
648
+ return pending.length;
649
+ }
650
+
108
651
  // src/page-number-format.ts
109
652
  var ROMAN_ONES = ["", "i", "ii", "iii", "iv", "v", "vi", "vii", "viii", "ix"];
110
653
  var ROMAN_TENS = ["", "x", "xx", "xxx", "xl", "l", "lx", "lxx", "lxxx", "xc"];
@@ -235,6 +778,128 @@ function samePageBox(a, b) {
235
778
  var MAX_FOOTNOTE_AREA_RATIO = 0.6;
236
779
  var FOOTNOTE_MEASUREMENT_GUARD_PT = 2;
237
780
  var FOOTNOTE_SOURCE_POSITION_ATTR = "data-pagination-footnote-source-position";
781
+ function collapsedBlockMarginsPx(view, element) {
782
+ return {
783
+ topPx: collapseMarginChain(view, element, "top"),
784
+ bottomPx: collapseMarginChain(view, element, "bottom")
785
+ };
786
+ }
787
+ function collapseMarginChain(view, element, edge) {
788
+ const margins = [];
789
+ let current = element;
790
+ while (current) {
791
+ const style = view.getComputedStyle(current);
792
+ margins.push(parseFloat(edge === "top" ? style.marginTop : style.marginBottom) || 0);
793
+ current = collapsesThroughTo(view, current, style, edge);
794
+ }
795
+ const positive = Math.max(0, ...margins);
796
+ const negative = Math.min(0, ...margins);
797
+ return positive + negative;
798
+ }
799
+ function collapsesThroughTo(view, parent, style, edge) {
800
+ const ordinaryBlock = (style.display === "block" || style.display === "list-item") && style.overflowX === "visible" && style.overflowY === "visible" && style.float === "none" && style.position !== "absolute" && style.position !== "fixed";
801
+ if (!ordinaryBlock) return null;
802
+ if (style.columnCount !== "auto" || style.columnWidth !== "auto") return null;
803
+ const border = parseFloat(edge === "top" ? style.borderTopWidth : style.borderBottomWidth) || 0;
804
+ const padding = parseFloat(edge === "top" ? style.paddingTop : style.paddingBottom) || 0;
805
+ if (border !== 0 || padding !== 0) return null;
806
+ if (edge === "bottom" && parent.style.height !== "") return null;
807
+ const nodes = Array.from(parent.childNodes);
808
+ if (edge === "bottom") nodes.reverse();
809
+ for (const node of nodes) {
810
+ if (node.nodeType === Node.TEXT_NODE) {
811
+ if ((node.textContent ?? "").trim() !== "") return null;
812
+ continue;
813
+ }
814
+ if (node.nodeType !== Node.ELEMENT_NODE) continue;
815
+ const child = node;
816
+ const childStyle = view.getComputedStyle(child);
817
+ if (childStyle.display === "none") continue;
818
+ if (childStyle.position === "absolute" || childStyle.position === "fixed" || childStyle.float !== "none") continue;
819
+ const blockLevel = childStyle.display === "block" || childStyle.display === "list-item" || childStyle.display === "table" || childStyle.display === "flow-root" || childStyle.display === "flex" || childStyle.display === "grid";
820
+ return blockLevel ? child : null;
821
+ }
822
+ return null;
823
+ }
824
+ var DRAWING_HOST_ATTRIBUTE = "data-docx-anchor-host";
825
+ var HOSTS_PROMOTED_DRAWING_ATTRIBUTE = "data-docx-hosts-promoted-drawing";
826
+ function rendersOnlyItsLine(element) {
827
+ return (element.textContent ?? "").replace(/[\s\u00a0]/g, "") === "" && element.querySelector("img, svg, canvas, [data-docx-drawing-anchor]") === null;
828
+ }
829
+ function createRenderedSourceMeasurer(clipInset, drawingHostAttribute, hostsPromotedDrawingAttribute) {
830
+ function visibleWithinPage(view, element, page, pageRect, rect) {
831
+ let left = Math.max(rect.left, pageRect.left);
832
+ let top = Math.max(rect.top, pageRect.top);
833
+ let right = Math.min(rect.right, pageRect.right);
834
+ let bottom = Math.min(rect.bottom, pageRect.bottom);
835
+ const clips = (value) => value === "hidden" || value === "clip" || value === "scroll" || value === "auto";
836
+ for (let ancestor = element.parentElement; ancestor && ancestor !== page; ancestor = ancestor.parentElement) {
837
+ const style = view.getComputedStyle(ancestor);
838
+ const clipsX = clips(style.overflowX);
839
+ const clipsY = clips(style.overflowY);
840
+ if (!clipsX && !clipsY && !style.clipPath.startsWith("inset(")) continue;
841
+ const ancestorRect = ancestor.getBoundingClientRect();
842
+ const scale = ancestorRect.width / ancestor.offsetWidth || 1;
843
+ const inset = clipInset(style.clipPath, ancestorRect, scale);
844
+ if (inset) {
845
+ left = Math.max(left, inset.left);
846
+ top = Math.max(top, inset.top);
847
+ right = Math.min(right, inset.right);
848
+ bottom = Math.min(bottom, inset.bottom);
849
+ }
850
+ if (clipsX) {
851
+ left = Math.max(left, ancestorRect.left);
852
+ right = Math.min(right, ancestorRect.right);
853
+ }
854
+ if (clipsY) {
855
+ top = Math.max(top, ancestorRect.top);
856
+ bottom = Math.min(bottom, ancestorRect.bottom);
857
+ }
858
+ }
859
+ return { left, top, right, bottom };
860
+ }
861
+ return function measureRenderedSource2(view, element, page, pageRect = page.getBoundingClientRect()) {
862
+ const own = element.getBoundingClientRect();
863
+ const ownResult = () => ({ rect: own, visible: visibleWithinPage(view, element, page, pageRect, own) });
864
+ const ownMeasurable = own.width > 0 && own.height > 0;
865
+ const hostsPromotedDrawing = element.hasAttribute(hostsPromotedDrawingAttribute);
866
+ if (ownMeasurable && !hostsPromotedDrawing) return ownResult();
867
+ const hostAnchorId = element.dataset.sourceAnchorId;
868
+ const promoted = hostAnchorId ? Array.from(page.querySelectorAll(`[${drawingHostAttribute}]`)).filter((drawing) => drawing.getAttribute(drawingHostAttribute) === hostAnchorId) : [];
869
+ if (ownMeasurable && promoted.length === 0) return ownResult();
870
+ const rendered = [
871
+ ...ownMeasurable ? [element] : Array.from(element.querySelectorAll("*")),
872
+ ...promoted.flatMap((drawing) => [drawing, ...Array.from(drawing.querySelectorAll("*"))])
873
+ ];
874
+ let left = Infinity;
875
+ let top = Infinity;
876
+ let right = -Infinity;
877
+ let bottom = -Infinity;
878
+ for (const child of rendered) {
879
+ const style = view.getComputedStyle(child);
880
+ if (style.display === "none" || style.visibility === "hidden") continue;
881
+ const childRect = child.getBoundingClientRect();
882
+ if (childRect.width <= 0 || childRect.height <= 0) continue;
883
+ const visible = visibleWithinPage(view, child, page, pageRect, childRect);
884
+ if (visible.right <= visible.left || visible.bottom <= visible.top) continue;
885
+ left = Math.min(left, visible.left);
886
+ top = Math.min(top, visible.top);
887
+ right = Math.max(right, visible.right);
888
+ bottom = Math.max(bottom, visible.bottom);
889
+ }
890
+ if (!(left < right && top < bottom)) return ownResult();
891
+ return { rect: new DOMRect(left, top, right - left, bottom - top), visible: { left, top, right, bottom } };
892
+ };
893
+ }
894
+ var measureRenderedSource = createRenderedSourceMeasurer(
895
+ insetClipBounds,
896
+ DRAWING_HOST_ATTRIBUTE,
897
+ HOSTS_PROMOTED_DRAWING_ATTRIBUTE
898
+ );
899
+ function renderedSourceMeasurerExpression() {
900
+ return `(${createRenderedSourceMeasurer.toString()})(${insetClipBounds.toString()},
901
+ ${JSON.stringify(DRAWING_HOST_ATTRIBUTE)}, ${JSON.stringify(HOSTS_PROMOTED_DRAWING_ATTRIBUTE)})`;
902
+ }
238
903
  var PaginationEngine = class {
239
904
  /**
240
905
  * Creates a new pagination engine.
@@ -278,6 +943,7 @@ var PaginationEngine = class {
278
943
  this.pageGap = options.pageGap ?? 20;
279
944
  this.fragmentParagraphs = options.fragmentParagraphs ?? false;
280
945
  this.deferFragmentIdentities = options.deferFragmentIdentities ?? false;
946
+ this.bodyBudgetReductionsPt = options.bodyBudgetReductionsPt ?? {};
281
947
  this.cancellationCheckpoint = options.checkCancellation;
282
948
  this.pageCountCheckpoint = options.checkPageCount;
283
949
  this.layoutToken = options.layoutToken;
@@ -426,7 +1092,8 @@ var PaginationEngine = class {
426
1092
  sectionDims,
427
1093
  sectionIndex,
428
1094
  columnCount,
429
- Number.isFinite(gap) ? gap : 36
1095
+ Number.isFinite(gap) ? gap : 36,
1096
+ section.dataset.sectionType === "continuous" && section === sectionsToProcess[sectionsToProcess.length - 1]
430
1097
  ));
431
1098
  } else {
432
1099
  blocks.push(...this.measureBlocks(section, sectionDims, sectionIndex));
@@ -533,12 +1200,11 @@ var PaginationEngine = class {
533
1200
  if (!anchorId || !element.dataset.pageFragmentId || !Number.isInteger(parseInt(element.dataset.fragmentIndex || "", 10))) {
534
1201
  throw new Error(`page ${page.pageNumber} contains an unqualified source anchor`);
535
1202
  }
536
- const rect = element.getBoundingClientRect();
537
1203
  const style = this.view.getComputedStyle(element);
538
1204
  const deliberatelyHidden = style.display === "none" || style.visibility === "hidden";
539
1205
  if (deliberatelyHidden) continue;
1206
+ const { rect, visible: visibleRect } = measureRenderedSource(this.view, element, page.element, pageRect);
540
1207
  requiredAnchorIds.add(anchorId);
541
- const visibleRect = this.intersectWithClippingAncestors(element, page.element, pageRect, rect);
542
1208
  const left = visibleRect.left;
543
1209
  const top = visibleRect.top;
544
1210
  const right = visibleRect.right;
@@ -599,8 +1265,7 @@ var PaginationEngine = class {
599
1265
  if (!anchorId) continue;
600
1266
  const style = this.view.getComputedStyle(element);
601
1267
  if (style.display === "none" || style.visibility === "hidden") continue;
602
- const rect = element.getBoundingClientRect();
603
- const visible = this.intersectWithClippingAncestors(element, page.element, pageRect, rect);
1268
+ const { rect, visible } = measureRenderedSource(this.view, element, page.element, pageRect);
604
1269
  if (rect.width <= 0 || rect.height <= 0 || visible.right <= visible.left || visible.bottom <= visible.top) continue;
605
1270
  const fragmentIndex = emittedFragmentCounts.get(anchorId) ?? 0;
606
1271
  emittedFragmentCounts.set(anchorId, fragmentIndex + 1);
@@ -610,34 +1275,6 @@ var PaginationEngine = class {
610
1275
  }
611
1276
  }
612
1277
  }
613
- /**
614
- * Intersect an element with every ancestor that establishes an overflow clip before the page
615
- * root. getBoundingClientRect() reports layout outside those clips, which is not rendered and
616
- * therefore must not satisfy PageMap completeness or inflate portable geometry.
617
- */
618
- intersectWithClippingAncestors(element, page, pageRect, rect) {
619
- let left = Math.max(rect.left, pageRect.left);
620
- let top = Math.max(rect.top, pageRect.top);
621
- let right = Math.min(rect.right, pageRect.right);
622
- let bottom = Math.min(rect.bottom, pageRect.bottom);
623
- const clips = (value) => value === "hidden" || value === "clip" || value === "scroll" || value === "auto";
624
- for (let ancestor = element.parentElement; ancestor && ancestor !== page; ancestor = ancestor.parentElement) {
625
- const style = this.view.getComputedStyle(ancestor);
626
- const clipsX = clips(style.overflowX);
627
- const clipsY = clips(style.overflowY);
628
- if (!clipsX && !clipsY) continue;
629
- const ancestorRect = ancestor.getBoundingClientRect();
630
- if (clipsX) {
631
- left = Math.max(left, ancestorRect.left);
632
- right = Math.min(right, ancestorRect.right);
633
- }
634
- if (clipsY) {
635
- top = Math.max(top, ancestorRect.top);
636
- bottom = Math.min(bottom, ancestorRect.bottom);
637
- }
638
- }
639
- return { left, top, right, bottom };
640
- }
641
1278
  storyForCanonicalAnchor(anchorId) {
642
1279
  const first = anchorId.indexOf(":");
643
1280
  const second = first < 0 ? -1 : anchorId.indexOf(":", first + 1);
@@ -829,9 +1466,7 @@ var PaginationEngine = class {
829
1466
  }
830
1467
  }
831
1468
  const rect = child.getBoundingClientRect();
832
- const style = this.view.getComputedStyle(child);
833
- const marginTopPx = parseFloat(style.marginTop) || 0;
834
- const marginBottomPx = parseFloat(style.marginBottom) || 0;
1469
+ const { topPx: marginTopPx, bottomPx: marginBottomPx } = collapsedBlockMarginsPx(this.view, child);
835
1470
  const heightPt = pxToPt(rect.height);
836
1471
  const marginTopPt = pxToPt(marginTopPx);
837
1472
  const marginBottomPt = pxToPt(marginBottomPx);
@@ -874,7 +1509,24 @@ var PaginationEngine = class {
874
1509
  for (const rule of sectionChildren.filter((child) => child.tagName === "HR")) {
875
1510
  this.checkpoint();
876
1511
  const clonedRule = rule.cloneNode(true);
877
- if (blocks.length === 0) clonedRule.style.marginTop = sectionStyle.marginTop;
1512
+ const ruleStyle = this.view.getComputedStyle(rule);
1513
+ for (const property of [
1514
+ "height",
1515
+ "width",
1516
+ "max-width",
1517
+ "border",
1518
+ "background-color",
1519
+ "color",
1520
+ "margin-top",
1521
+ "margin-right",
1522
+ "margin-bottom",
1523
+ "margin-left"
1524
+ ]) {
1525
+ clonedRule.style.setProperty(property, ruleStyle.getPropertyValue(property));
1526
+ }
1527
+ if (blocks.length === 0) {
1528
+ clonedRule.style.marginTop = `calc(${sectionStyle.marginTop} + ${ruleStyle.marginTop})`;
1529
+ }
878
1530
  blocks.push(this.measureElement(clonedRule, dims, sectionIndex));
879
1531
  }
880
1532
  const listStyle = this.view.getComputedStyle(list);
@@ -908,12 +1560,14 @@ var PaginationEngine = class {
908
1560
  if (clone.id && clone.id !== itemId) return null;
909
1561
  clone.id = itemId;
910
1562
  }
911
- const value = parseInt(item.getAttribute("value") || String(itemIndex + 1), 10);
912
- const marker = this.formatOrderedListMarker(
913
- Number.isFinite(value) ? value : itemIndex + 1,
914
- listStyle.listStyleType
915
- );
916
- clone.insertBefore(this.document.createTextNode(`${marker}. `), clone.firstChild);
1563
+ if (!clone.querySelector("[data-endnote-number]")) {
1564
+ const value = parseInt(item.getAttribute("value") || String(itemIndex + 1), 10);
1565
+ const marker = this.formatOrderedListMarker(
1566
+ Number.isFinite(value) ? value : itemIndex + 1,
1567
+ listStyle.listStyleType
1568
+ );
1569
+ clone.insertBefore(this.document.createTextNode(`${marker}. `), clone.firstChild);
1570
+ }
917
1571
  }
918
1572
  blocks.push(this.measureElement(clone, dims, sectionIndex));
919
1573
  }
@@ -943,15 +1597,16 @@ var PaginationEngine = class {
943
1597
  /**
944
1598
  * Flows a multi-column (`w:cols`) section's children into CSS-multicol container
945
1599
  * blocks. Word lays such a section out as N columns inside the same body extent;
946
- * a balanced `column-count` container reproduces that geometry, and the paginator
1600
+ * a `column-count` container reproduces that geometry, and the paginator
947
1601
  * then places each container as one ordinary measured block. A container grows
948
- * greedily until its balanced height would exceed the smallest page body available
949
- * to the section, so a long columned section still splits across pages at block
1602
+ * greedily until it would exceed the smallest page body available to the section.
1603
+ * Final continuous sections fill that height sequentially; other sections retain
1604
+ * balancing. A long columned section still splits across pages at block
950
1605
  * boundaries. Each child lands in exactly one container, so anchors never
951
1606
  * duplicate. An explicit page break child passes through as its own block, which
952
1607
  * ends the current container and lets the normal flow logic turn the page.
953
1608
  */
954
- buildColumnBlocks(section, dims, sectionIndex, columnCount, columnGapPt) {
1609
+ buildColumnBlocks(section, dims, sectionIndex, columnCount, columnGapPt, sequential = false) {
955
1610
  const children = Array.from(section.children);
956
1611
  const blocks = [];
957
1612
  const maxFragmentHeight = this.smallestEffectiveContentHeight(dims, sectionIndex);
@@ -960,9 +1615,15 @@ var PaginationEngine = class {
960
1615
  const container = this.document.createElement("div");
961
1616
  container.style.columnCount = String(columnCount);
962
1617
  container.style.columnGap = `${columnGapPt}pt`;
1618
+ if (sequential) {
1619
+ container.dataset.paginationSequentialColumns = "true";
1620
+ container.style.columnFill = "auto";
1621
+ container.style.maxHeight = `${maxFragmentHeight}pt`;
1622
+ }
963
1623
  for (const child of slice) {
964
1624
  container.appendChild(child.cloneNode(true));
965
1625
  }
1626
+ if (sequential) this.liftColumnSpaceBefore(container, dims);
966
1627
  return container;
967
1628
  };
968
1629
  let start = 0;
@@ -975,12 +1636,17 @@ var PaginationEngine = class {
975
1636
  }
976
1637
  let end = start + 1;
977
1638
  let container = makeContainer(children.slice(start, end));
1639
+ if (sequential && this.columnContainerOverflows(container, dims)) {
1640
+ delete container.dataset.paginationSequentialColumns;
1641
+ container.style.columnFill = "balance";
1642
+ container.style.maxHeight = "";
1643
+ }
978
1644
  let measured = this.measureElement(container, dims, sectionIndex);
979
1645
  while (end < children.length && !isBreak(children[end])) {
980
1646
  this.checkpoint();
981
1647
  const candidate = makeContainer(children.slice(start, end + 1));
982
1648
  const candidateMeasured = this.measureElement(candidate, dims, sectionIndex);
983
- if (candidateMeasured.heightPt > maxFragmentHeight) break;
1649
+ if (candidateMeasured.heightPt > maxFragmentHeight || sequential && this.columnContainerOverflows(candidate, dims)) break;
984
1650
  container = candidate;
985
1651
  measured = candidateMeasured;
986
1652
  end++;
@@ -1001,6 +1667,76 @@ var PaginationEngine = class {
1001
1667
  }
1002
1668
  return blocks;
1003
1669
  }
1670
+ /** Keep Word's paragraph space-before in the ordinary inter-block spacing budget. */
1671
+ liftColumnSpaceBefore(container, dims) {
1672
+ container.style.marginTop = "";
1673
+ const first = container.firstElementChild;
1674
+ if (!first || !this.isWordParagraphElement(first)) return;
1675
+ const host = this.document.createElement("div");
1676
+ host.style.width = `${dims.contentWidth}pt`;
1677
+ host.appendChild(container);
1678
+ this.stagingElement.appendChild(host);
1679
+ try {
1680
+ const spaceBefore = first.dataset.paginationColumnSpaceBefore ?? this.view.getComputedStyle(first).marginTop;
1681
+ first.dataset.paginationColumnSpaceBefore = spaceBefore;
1682
+ first.style.marginTop = "0";
1683
+ container.style.marginTop = spaceBefore;
1684
+ } finally {
1685
+ container.remove();
1686
+ host.remove();
1687
+ }
1688
+ }
1689
+ /** A height-constrained multicol box overflows horizontally when it needs another column. */
1690
+ columnContainerOverflows(element, dims) {
1691
+ const host = this.document.createElement("div");
1692
+ host.style.width = `${dims.contentWidth}pt`;
1693
+ const clone = element.cloneNode(true);
1694
+ host.appendChild(clone);
1695
+ this.stagingElement.appendChild(host);
1696
+ try {
1697
+ if (clone.scrollWidth > clone.clientWidth + 1) return true;
1698
+ return Array.from(clone.querySelectorAll('[data-keep-lines="true"]')).some((paragraph) => paragraph.getClientRects().length > 1);
1699
+ } finally {
1700
+ host.remove();
1701
+ }
1702
+ }
1703
+ /**
1704
+ * Reflow a final continuous column section against the actual remaining body band.
1705
+ * A preceding title, running story or note reservation can make it shorter than the
1706
+ * fresh-page band used while collecting source blocks. Split at source-child boundaries
1707
+ * when the narrower band would create an extra horizontal column; the tail is reconsidered
1708
+ * normally on the following page. No source child is duplicated or discarded.
1709
+ */
1710
+ reflowSequentialColumns(block, dims, availableHeightPt) {
1711
+ if (block.element.dataset.paginationSequentialColumns !== "true" || availableHeightPt <= 0) return null;
1712
+ const maximumHeight = this.smallestEffectiveContentHeight(dims, block.sectionIndex);
1713
+ const height = Math.min(availableHeightPt, maximumHeight);
1714
+ if (Math.abs(parseFloat(block.element.style.maxHeight) - height) < 0.01) return null;
1715
+ const children = Array.from(block.element.children);
1716
+ const make = (slice, limit) => {
1717
+ const container = block.element.cloneNode(false);
1718
+ container.style.maxHeight = `${limit}pt`;
1719
+ for (const child of slice) container.appendChild(child.cloneNode(true));
1720
+ this.liftColumnSpaceBefore(container, dims);
1721
+ return container;
1722
+ };
1723
+ const whole = make(children, height);
1724
+ if (!this.columnContainerOverflows(whole, dims)) {
1725
+ return [this.measureElement(whole, dims, block.sectionIndex)];
1726
+ }
1727
+ let end = 0;
1728
+ for (let count = 1; count < children.length; count++) {
1729
+ this.checkpoint();
1730
+ const candidate = make(children.slice(0, count), height);
1731
+ if (this.columnContainerOverflows(candidate, dims)) break;
1732
+ if (children[count - 1].dataset.keepWithNext !== "true") end = count;
1733
+ }
1734
+ if (end === 0) return null;
1735
+ return [
1736
+ this.measureElement(make(children.slice(0, end), height), dims, block.sectionIndex),
1737
+ this.measureElement(make(children.slice(end), maximumHeight), dims, block.sectionIndex)
1738
+ ];
1739
+ }
1004
1740
  /**
1005
1741
  * Measures one element in the same hidden staging context used for the source blocks.
1006
1742
  * This is intentionally DOM-based: table row heights cannot be inferred from individual
@@ -1016,13 +1752,13 @@ var PaginationEngine = class {
1016
1752
  measurementHost.appendChild(measuredElement);
1017
1753
  this.stagingElement.appendChild(measurementHost);
1018
1754
  const rect = measuredElement.getBoundingClientRect();
1019
- const style = this.view.getComputedStyle(measuredElement);
1755
+ const margins = collapsedBlockMarginsPx(this.view, measuredElement);
1020
1756
  const measured = {
1021
1757
  element,
1022
1758
  sectionIndex,
1023
1759
  heightPt: pxToPt(rect.height),
1024
- marginTopPt: pxToPt(parseFloat(style.marginTop) || 0),
1025
- marginBottomPt: pxToPt(parseFloat(style.marginBottom) || 0),
1760
+ marginTopPt: pxToPt(margins.topPx),
1761
+ marginBottomPt: pxToPt(margins.bottomPx),
1026
1762
  keepWithNext: element.dataset.keepWithNext === "true",
1027
1763
  keepLines: element.dataset.keepLines === "true",
1028
1764
  pageBreakBefore: element.dataset.pageBreakBefore === "true",
@@ -2250,6 +2986,10 @@ var PaginationEngine = class {
2250
2986
  * Deterministic: it depends only on the section's page setup and the registry's pre-measured
2251
2987
  * story heights, never on the page's content, which is what keeps it lazy-loading compatible.
2252
2988
  */
2989
+ /** The body height a physical page may fill: its body band less any caller-requested reduction. */
2990
+ bodyBudgetPt(bodyHeightPt, physicalPageNumber) {
2991
+ return Math.max(0, bodyHeightPt - (this.bodyBudgetReductionsPt[physicalPageNumber] ?? 0));
2992
+ }
2253
2993
  getPageBands(dims, sectionIndex, pageInSection, displayedPageNumber) {
2254
2994
  const sectionHf = this.hfRegistry.get(sectionIndex);
2255
2995
  return resolvePageBands(
@@ -2334,12 +3074,12 @@ var PaginationEngine = class {
2334
3074
  const currentPhysicalNumber = ownerNumbering.start !== void 0 ? ownerNumbering.start + pageInSection(pageSectionIndex) - 1 : precedingDisplayedPageNumber + 1;
2335
3075
  return currentPhysicalNumber + physicalPage - pageNumber;
2336
3076
  };
2337
- let { bodyHeight: effectiveContentHeight } = this.getPageBands(
3077
+ let effectiveContentHeight = this.bodyBudgetPt(this.getPageBands(
2338
3078
  dimensionsFor(),
2339
3079
  pageSectionIndex,
2340
3080
  pageInSection(),
2341
3081
  displayedPageNumber()
2342
- );
3082
+ ).bodyHeight, pageNumber);
2343
3083
  let remainingHeight = effectiveContentHeight;
2344
3084
  let prevMarginBottomPt = 0;
2345
3085
  let currentFootnoteIds = [];
@@ -2359,7 +3099,7 @@ var PaginationEngine = class {
2359
3099
  pageInSection(owner),
2360
3100
  displayedPageNumber(owner)
2361
3101
  );
2362
- effectiveContentHeight = bands.bodyHeight;
3102
+ effectiveContentHeight = this.bodyBudgetPt(bands.bodyHeight, pageNumber);
2363
3103
  remainingHeight = effectiveContentHeight;
2364
3104
  };
2365
3105
  const admitCurrentPage = () => {
@@ -2494,7 +3234,7 @@ var PaginationEngine = class {
2494
3234
  pageInSection(),
2495
3235
  displayedPageNumber()
2496
3236
  );
2497
- effectiveContentHeight = newBands.bodyHeight;
3237
+ effectiveContentHeight = this.bodyBudgetPt(newBands.bodyHeight, pageNumber);
2498
3238
  remainingHeight = effectiveContentHeight;
2499
3239
  prevMarginBottomPt = 0;
2500
3240
  currentFootnoteIds = [];
@@ -2657,6 +3397,16 @@ var PaginationEngine = class {
2657
3397
  isFirstOnPage,
2658
3398
  pageInSection(block.sectionIndex)
2659
3399
  );
3400
+ const columnFragments = this.reflowSequentialColumns(
3401
+ block,
3402
+ blockDimensions,
3403
+ remainingHeight - currentFootnoteHeight - additionalFootnoteHeight - effectiveMarginTop
3404
+ );
3405
+ if (columnFragments) {
3406
+ blocks.splice(i, 1, ...columnFragments);
3407
+ i--;
3408
+ continue;
3409
+ }
2660
3410
  const blockSpace = effectiveMarginTop + block.heightPt + additionalFootnoteHeight;
2661
3411
  const effectiveRemainingHeight = remainingHeight - currentFootnoteHeight;
2662
3412
  const bodyContentUsed = effectiveContentHeight - remainingHeight;
@@ -2891,7 +3641,7 @@ var PaginationEngine = class {
2891
3641
  context
2892
3642
  );
2893
3643
  const widthPercent = this.anchorNumber(anchor, "width-percent");
2894
- if (widthPercent !== void 0) {
3644
+ if (widthPercent !== void 0 && widthPercent > 0) {
2895
3645
  anchor.style.width = `${widthReference.size * widthPercent / 100}pt`;
2896
3646
  }
2897
3647
  const heightReference = this.verticalAnchorReference(
@@ -2901,7 +3651,7 @@ var PaginationEngine = class {
2901
3651
  context
2902
3652
  );
2903
3653
  const heightPercent = this.anchorNumber(anchor, "height-percent");
2904
- if (heightPercent !== void 0 && anchor.dataset.docxAnchorAutofit !== "true") {
3654
+ if (heightPercent !== void 0 && heightPercent > 0 && anchor.dataset.docxAnchorAutofit !== "true") {
2905
3655
  anchor.style.height = `${heightReference.size * heightPercent / 100}pt`;
2906
3656
  }
2907
3657
  const sizedRect = anchor.getBoundingClientRect();
@@ -2933,7 +3683,12 @@ var PaginationEngine = class {
2933
3683
  this.anchorNumber(anchor, "v-offset"),
2934
3684
  pageNumber
2935
3685
  );
3686
+ const hostElement = anchor.parentElement?.closest("[data-source-anchor-id]");
3687
+ const hostAnchorId = hostElement?.dataset.sourceAnchorId;
3688
+ if (hostAnchorId) anchor.setAttribute(DRAWING_HOST_ATTRIBUTE, hostAnchorId);
2936
3689
  pageBox.appendChild(anchor);
3690
+ if (hostElement && rendersOnlyItsLine(hostElement))
3691
+ hostElement.setAttribute(HOSTS_PROMOTED_DRAWING_ATTRIBUTE, "true");
2937
3692
  anchor.style.position = "absolute";
2938
3693
  anchor.style.left = `${left}pt`;
2939
3694
  anchor.style.top = `${top}pt`;
@@ -3171,6 +3926,20 @@ var PaginationEngine = class {
3171
3926
  contentArea.style.height = `${Math.max(0, contentAreaHeight - notesHeightPt)}pt`;
3172
3927
  }
3173
3928
  }
3929
+ alignExactLineBaselines(contentArea);
3930
+ const paper = pageBox.getBoundingClientRect();
3931
+ const header = pageBox.querySelector(`.${this.cssPrefix}header`);
3932
+ const footer = pageBox.querySelector(`.${this.cssPrefix}footer`);
3933
+ preserveExactLineInk(
3934
+ contentArea,
3935
+ Math.max(paper.top, header?.getBoundingClientRect().bottom ?? paper.top),
3936
+ Math.min(
3937
+ paper.bottom,
3938
+ footer?.getBoundingClientRect().top ?? paper.bottom,
3939
+ notesEl?.getBoundingClientRect().top ?? paper.bottom
3940
+ ),
3941
+ paper
3942
+ );
3174
3943
  return {
3175
3944
  pageNumber,
3176
3945
  sectionIndex,
@@ -3222,6 +3991,22 @@ var WorkerOperationError = class extends Error {
3222
3991
  function workerError(message, code) {
3223
3992
  return new WorkerOperationError(message || "Unknown error", code);
3224
3993
  }
3994
+ function requireField(response, field) {
3995
+ const value = response[field];
3996
+ if (value === void 0 || value === null) {
3997
+ throw workerError(
3998
+ `The worker's "${response.type ?? "unknown"}" response reported success without its ${String(field)}`,
3999
+ "missing_result"
4000
+ );
4001
+ }
4002
+ return value;
4003
+ }
4004
+ function checkTimeout(timeoutMs, name) {
4005
+ if (timeoutMs !== void 0 && !(Number.isFinite(timeoutMs) && timeoutMs > 0)) {
4006
+ throw new RangeError(`${name} must be a positive, finite number of milliseconds; got ${timeoutMs}`);
4007
+ }
4008
+ return timeoutMs;
4009
+ }
3225
4010
  function workerErrorCode(error) {
3226
4011
  return error instanceof WorkerOperationError ? error.code : void 0;
3227
4012
  }
@@ -3250,6 +4035,7 @@ async function createWorkerDocxodus(options) {
3250
4035
  if (options?.signal?.aborted) {
3251
4036
  throw new Error("Worker creation aborted");
3252
4037
  }
4038
+ const requestTimeoutMs = checkTimeout(options?.requestTimeoutMs, "requestTimeoutMs");
3253
4039
  const wasmBasePath = options?.wasmBasePath ?? deriveWasmBasePath();
3254
4040
  const workerScriptPath = new URL("./docxodus.worker.js", import.meta.url).href;
3255
4041
  const worker = new Worker(workerScriptPath, { type: "module" });
@@ -3292,13 +4078,54 @@ async function createWorkerDocxodus(options) {
3292
4078
  worker.onerror = (error) => {
3293
4079
  stopWorker(`Worker error: ${error.message}`);
3294
4080
  };
3295
- function sendRequest(request, transfer) {
4081
+ worker.onmessageerror = () => {
4082
+ for (const pending of [...pendingRequests.values()]) {
4083
+ pending.reject(workerError(
4084
+ "A worker response could not be deserialized, so every request in flight was rejected",
4085
+ "message_error"
4086
+ ));
4087
+ }
4088
+ };
4089
+ function sendRequest(request, transfer, requestOptions) {
3296
4090
  return new Promise((resolve, reject) => {
3297
4091
  if (!isWorkerActive) {
3298
4092
  reject(new Error("Worker has been terminated"));
3299
4093
  return;
3300
4094
  }
3301
- pendingRequests.set(request.id, { resolve, reject });
4095
+ const signal = requestOptions?.signal;
4096
+ if (signal?.aborted) {
4097
+ reject(workerError(`The worker request "${request.type}" was aborted before it was sent`, "aborted"));
4098
+ return;
4099
+ }
4100
+ const timeoutMs = requestOptions?.timeoutMs ?? requestTimeoutMs;
4101
+ let timer;
4102
+ let onAbort;
4103
+ const finish = () => {
4104
+ pendingRequests.delete(request.id);
4105
+ if (timer !== void 0) clearTimeout(timer);
4106
+ if (onAbort) signal?.removeEventListener("abort", onAbort);
4107
+ };
4108
+ pendingRequests.set(request.id, {
4109
+ resolve: (value) => {
4110
+ finish();
4111
+ resolve(value);
4112
+ },
4113
+ reject: (error) => {
4114
+ finish();
4115
+ reject(error);
4116
+ }
4117
+ });
4118
+ const fail2 = (error) => pendingRequests.get(request.id)?.reject(error);
4119
+ if (timeoutMs !== void 0) {
4120
+ timer = setTimeout(() => fail2(workerError(
4121
+ `The worker request "${request.type}" timed out after ${timeoutMs} ms`,
4122
+ "timeout"
4123
+ )), timeoutMs);
4124
+ }
4125
+ if (signal) {
4126
+ onAbort = () => fail2(workerError(`The worker request "${request.type}" was aborted`, "aborted"));
4127
+ signal.addEventListener("abort", onAbort, { once: true });
4128
+ }
3302
4129
  try {
3303
4130
  if (transfer && transfer.length > 0) {
3304
4131
  worker.postMessage(request, transfer);
@@ -3306,8 +4133,7 @@ async function createWorkerDocxodus(options) {
3306
4133
  worker.postMessage(request);
3307
4134
  }
3308
4135
  } catch (error) {
3309
- pendingRequests.delete(request.id);
3310
- reject(error instanceof Error ? error : new Error(String(error)));
4136
+ fail2(error instanceof Error ? error : new Error(String(error)));
3311
4137
  }
3312
4138
  });
3313
4139
  }
@@ -3321,391 +4147,349 @@ async function createWorkerDocxodus(options) {
3321
4147
  stopWorker("Worker initialization failed");
3322
4148
  throw error;
3323
4149
  }
3324
- return {
3325
- async generatePackageManifest(document2, limits) {
3326
- const bytes = await toBytes(document2);
3327
- const response = await sendRequest(
3328
- {
3329
- id: generateId(),
3330
- type: "generatePackageManifest",
3331
- documentBytes: bytes,
3332
- limits,
3333
- representation: "object"
3334
- },
3335
- [bytes.buffer]
3336
- );
3337
- return response.manifest;
3338
- },
3339
- async verifyDeliverable(document2, baseline, request) {
3340
- const bytes = await toBytes(document2);
3341
- const baselineBytes = baseline === void 0 ? void 0 : await toBytes(baseline);
3342
- const transfer = [bytes.buffer];
3343
- if (baselineBytes !== void 0) transfer.push(baselineBytes.buffer);
3344
- const response = await sendRequest(
3345
- {
3346
- id: generateId(),
3347
- type: "verifyDeliverable",
3348
- documentBytes: bytes,
3349
- baselineBytes,
3350
- requestJson: request === void 0 ? void 0 : serializeVerificationRequest(request)
3351
- },
3352
- transfer
3353
- );
3354
- if (!response.success || !response.verification) {
3355
- throw new Error(response.error ?? "verifyDeliverable failed");
3356
- }
3357
- return response.verification;
3358
- },
3359
- async proveRedlineReversibility(baseline, intendedFinal, redline) {
3360
- const baselineBytes = await toBytes(baseline);
3361
- const intendedFinalBytes = await toBytes(intendedFinal);
3362
- const redlineBytes = await toBytes(redline);
3363
- const response = await sendRequest(
3364
- {
3365
- id: generateId(),
3366
- type: "proveRedlineReversibility",
3367
- baselineBytes,
3368
- intendedFinalBytes,
3369
- redlineBytes
3370
- },
3371
- [baselineBytes.buffer, intendedFinalBytes.buffer, redlineBytes.buffer]
3372
- );
3373
- if (!response.success || !response.proof) {
3374
- throw new Error(response.error ?? "proveRedlineReversibility failed");
3375
- }
3376
- return response.proof;
3377
- },
3378
- async getSemanticChanges(left, right, settings) {
3379
- const leftBytes = await toBytes(left);
3380
- const rightBytes = await toBytes(right);
3381
- const response = await sendRequest(
3382
- {
3383
- id: generateId(),
3384
- type: "getSemanticChanges",
3385
- leftBytes,
3386
- rightBytes,
3387
- settings
3388
- },
3389
- [leftBytes.buffer, rightBytes.buffer]
3390
- );
3391
- return response.semanticChanges;
3392
- },
3393
- async createExternalAnnotationSet(document2, documentId) {
3394
- const bytes = await toBytes(document2);
3395
- const response = await sendRequest(
3396
- { id: generateId(), type: "createExternalAnnotationSet", documentBytes: bytes, documentId },
3397
- [bytes.buffer]
3398
- );
3399
- if (!response.success || !response.annotationSet) {
3400
- throw new Error(response.error ?? "createExternalAnnotationSet failed");
3401
- }
3402
- return response.annotationSet;
3403
- },
3404
- async validateExternalAnnotations(document2, annotationSet) {
3405
- const bytes = await toBytes(document2);
3406
- const response = await sendRequest(
3407
- { id: generateId(), type: "validateExternalAnnotations", documentBytes: bytes, annotationSet },
3408
- [bytes.buffer]
3409
- );
3410
- if (!response.success || !response.validation) {
3411
- throw new Error(response.error ?? "validateExternalAnnotations failed");
3412
- }
3413
- return response.validation;
3414
- },
3415
- async projectAnnotationsOntoHtml(html, annotationSet, projectionOptions) {
3416
- const response = await sendRequest({
3417
- id: generateId(),
3418
- type: "projectAnnotationsOntoHtml",
3419
- html,
3420
- annotationSet,
3421
- projectionOptions
3422
- });
3423
- if (!response.success || response.html === void 0) {
3424
- throw new Error(response.error ?? "projectAnnotationsOntoHtml failed");
3425
- }
3426
- return response.html;
3427
- },
3428
- async convertDocxToHtmlWithExternalAnnotations(document2, annotationSet, conversionOptions2, projectionOptions) {
3429
- const bytes = await toBytes(document2);
3430
- const response = await sendRequest(
3431
- {
4150
+ const createApi = (requestOptions) => {
4151
+ const send = (request, transfer) => sendRequest(request, transfer, requestOptions);
4152
+ return {
4153
+ async generatePackageManifest(document2, limits) {
4154
+ const bytes = await toBytes(document2);
4155
+ const response = await send(
4156
+ {
4157
+ id: generateId(),
4158
+ type: "generatePackageManifest",
4159
+ documentBytes: bytes,
4160
+ limits,
4161
+ representation: "object"
4162
+ },
4163
+ [bytes.buffer]
4164
+ );
4165
+ return requireField(response, "manifest");
4166
+ },
4167
+ async verifyDeliverable(document2, baseline, request) {
4168
+ const bytes = await toBytes(document2);
4169
+ const baselineBytes = baseline === void 0 ? void 0 : await toBytes(baseline);
4170
+ const transfer = [bytes.buffer];
4171
+ if (baselineBytes !== void 0) transfer.push(baselineBytes.buffer);
4172
+ const response = await send(
4173
+ {
4174
+ id: generateId(),
4175
+ type: "verifyDeliverable",
4176
+ documentBytes: bytes,
4177
+ baselineBytes,
4178
+ requestJson: request === void 0 ? void 0 : serializeVerificationRequest(request)
4179
+ },
4180
+ transfer
4181
+ );
4182
+ return requireField(response, "verification");
4183
+ },
4184
+ async proveRedlineReversibility(baseline, intendedFinal, redline) {
4185
+ const baselineBytes = await toBytes(baseline);
4186
+ const intendedFinalBytes = await toBytes(intendedFinal);
4187
+ const redlineBytes = await toBytes(redline);
4188
+ const response = await send(
4189
+ {
4190
+ id: generateId(),
4191
+ type: "proveRedlineReversibility",
4192
+ baselineBytes,
4193
+ intendedFinalBytes,
4194
+ redlineBytes
4195
+ },
4196
+ [baselineBytes.buffer, intendedFinalBytes.buffer, redlineBytes.buffer]
4197
+ );
4198
+ return requireField(response, "proof");
4199
+ },
4200
+ async getSemanticChanges(left, right, settings) {
4201
+ const leftBytes = await toBytes(left);
4202
+ const rightBytes = await toBytes(right);
4203
+ const response = await send(
4204
+ {
4205
+ id: generateId(),
4206
+ type: "getSemanticChanges",
4207
+ leftBytes,
4208
+ rightBytes,
4209
+ settings
4210
+ },
4211
+ [leftBytes.buffer, rightBytes.buffer]
4212
+ );
4213
+ return requireField(response, "semanticChanges");
4214
+ },
4215
+ async createExternalAnnotationSet(document2, documentId) {
4216
+ const bytes = await toBytes(document2);
4217
+ const response = await send(
4218
+ { id: generateId(), type: "createExternalAnnotationSet", documentBytes: bytes, documentId },
4219
+ [bytes.buffer]
4220
+ );
4221
+ return requireField(response, "annotationSet");
4222
+ },
4223
+ async validateExternalAnnotations(document2, annotationSet) {
4224
+ const bytes = await toBytes(document2);
4225
+ const response = await send(
4226
+ { id: generateId(), type: "validateExternalAnnotations", documentBytes: bytes, annotationSet },
4227
+ [bytes.buffer]
4228
+ );
4229
+ return requireField(response, "validation");
4230
+ },
4231
+ async projectAnnotationsOntoHtml(html, annotationSet, projectionOptions) {
4232
+ const response = await send({
3432
4233
  id: generateId(),
3433
- type: "convertDocxToHtmlWithExternalAnnotations",
3434
- documentBytes: bytes,
4234
+ type: "projectAnnotationsOntoHtml",
4235
+ html,
3435
4236
  annotationSet,
3436
- conversionOptions: conversionOptions2,
3437
4237
  projectionOptions
3438
- },
3439
- [bytes.buffer]
3440
- );
3441
- if (!response.success || response.html === void 0) {
3442
- throw new Error(response.error ?? "convertDocxToHtmlWithExternalAnnotations failed");
3443
- }
3444
- return response.html;
3445
- },
3446
- async exportToOpenContract(document2) {
3447
- const bytes = await toBytes(document2);
3448
- const response = await sendRequest(
3449
- { id: generateId(), type: "exportToOpenContract", documentBytes: bytes },
3450
- [bytes.buffer]
3451
- );
3452
- if (!response.success || !response.export) {
3453
- throw new Error(response.error ?? "exportToOpenContract failed");
3454
- }
3455
- return response.export;
3456
- },
3457
- async generatePackageManifestJson(document2, limits) {
3458
- const bytes = await toBytes(document2);
3459
- const response = await sendRequest(
3460
- {
3461
- id: generateId(),
3462
- type: "generatePackageManifest",
3463
- documentBytes: bytes,
3464
- limits,
3465
- representation: "json"
3466
- },
3467
- [bytes.buffer]
3468
- );
3469
- if (response.manifestJson === void 0) {
3470
- throw new Error("Package manifest worker response omitted canonical JSON");
3471
- }
3472
- return response.manifestJson;
3473
- },
3474
- async projectReviewProfile(document2, profile, maximumOutputBytes) {
3475
- const bytes = await toBytes(document2);
3476
- const response = await sendRequest(
3477
- {
3478
- id: generateId(),
3479
- type: "projectReviewProfile",
3480
- documentBytes: bytes,
3481
- profile,
3482
- maximumOutputBytes
3483
- },
3484
- [bytes.buffer]
3485
- );
3486
- if (!response.documentBytes || response.documentBytes.byteLength === 0) {
3487
- throw new Error(`Failed to derive the ${profile} review profile`);
3488
- }
3489
- return response.documentBytes;
3490
- },
3491
- async convertDocxToHtml(document2, options2, maximumOutputBytes) {
3492
- const bytes = await toBytes(document2);
3493
- const response = await sendRequest(
3494
- {
3495
- id: generateId(),
3496
- type: "convertDocxToHtml",
3497
- documentBytes: bytes,
3498
- options: options2,
3499
- maximumOutputBytes
3500
- },
3501
- [bytes.buffer]
3502
- );
3503
- return response.html;
3504
- },
3505
- async compareDocuments(original, modified, options2) {
3506
- const originalBytes = await toBytes(original);
3507
- const modifiedBytes = await toBytes(modified);
3508
- const response = await sendRequest(
3509
- {
3510
- id: generateId(),
3511
- type: "compareDocuments",
3512
- originalBytes,
3513
- modifiedBytes,
3514
- options: options2
3515
- },
3516
- [originalBytes.buffer, modifiedBytes.buffer]
3517
- );
3518
- return response.documentBytes;
3519
- },
3520
- async compareDocumentsToHtml(original, modified, options2) {
3521
- const originalBytes = await toBytes(original);
3522
- const modifiedBytes = await toBytes(modified);
3523
- const response = await sendRequest(
3524
- {
3525
- id: generateId(),
3526
- type: "compareDocumentsToHtml",
3527
- originalBytes,
3528
- modifiedBytes,
3529
- options: options2
3530
- },
3531
- [originalBytes.buffer, modifiedBytes.buffer]
3532
- );
3533
- return response.html;
3534
- },
3535
- async getRevisions(document2) {
3536
- const bytes = await toBytes(document2);
3537
- const response = await sendRequest(
3538
- {
3539
- id: generateId(),
3540
- type: "getRevisions",
3541
- documentBytes: bytes
3542
- },
3543
- [bytes.buffer]
3544
- );
3545
- return response.revisions;
3546
- },
3547
- async getComments(document2) {
3548
- const bytes = await toBytes(document2);
3549
- const response = await sendRequest(
3550
- { id: generateId(), type: "getComments", documentBytes: bytes },
3551
- [bytes.buffer]
3552
- );
3553
- return response.comments;
3554
- },
3555
- async getDocumentMetadata(document2) {
3556
- const bytes = await toBytes(document2);
3557
- const response = await sendRequest(
3558
- {
3559
- id: generateId(),
3560
- type: "getDocumentMetadata",
3561
- documentBytes: bytes
3562
- },
3563
- [bytes.buffer]
3564
- );
3565
- return response.metadata;
3566
- },
3567
- async getVersion() {
3568
- const response = await sendRequest({
3569
- id: generateId(),
3570
- type: "getVersion"
3571
- });
3572
- return response.version;
3573
- },
3574
- prepare() {
3575
- if (preparePromise) {
3576
- return preparePromise;
3577
- }
3578
- preparePromise = sendRequest({
3579
- id: generateId(),
3580
- type: "prepare"
3581
- }).then(() => void 0);
3582
- preparePromise.catch(() => {
3583
- preparePromise = null;
3584
- });
3585
- return preparePromise;
3586
- },
3587
- async openDocxSession(document2, settings) {
3588
- const bytes = await toBytes(document2);
3589
- const settingsJson = settings ? JSON.stringify(settings) : "";
3590
- const openResponse = await sendRequest(
3591
- {
3592
- id: generateId(),
3593
- type: "sessionOpen",
3594
- documentBytes: bytes,
3595
- settingsJson
3596
- },
3597
- [bytes.buffer]
3598
- );
3599
- if (!openResponse.success || openResponse.handle === void 0) {
3600
- throw new Error(
3601
- `Failed to open worker DocxSession: ${openResponse.error ?? "unknown error"}`
3602
- );
3603
- }
3604
- const handle = openResponse.handle;
3605
- return {
3606
- async getPackageManifest() {
3607
- const res = await sendRequest({
4238
+ });
4239
+ return requireField(response, "html");
4240
+ },
4241
+ async convertDocxToHtmlWithExternalAnnotations(document2, annotationSet, conversionOptions2, projectionOptions) {
4242
+ const bytes = await toBytes(document2);
4243
+ const response = await send(
4244
+ {
3608
4245
  id: generateId(),
3609
- type: "sessionGetPackageManifest",
3610
- handle
3611
- });
3612
- if (!res.success || !res.manifest) {
3613
- throw new Error(res.error ?? "sessionGetPackageManifest failed");
3614
- }
3615
- return res.manifest;
3616
- },
3617
- async getSemanticChanges() {
3618
- const res = await sendRequest({
4246
+ type: "convertDocxToHtmlWithExternalAnnotations",
4247
+ documentBytes: bytes,
4248
+ annotationSet,
4249
+ conversionOptions: conversionOptions2,
4250
+ projectionOptions
4251
+ },
4252
+ [bytes.buffer]
4253
+ );
4254
+ return requireField(response, "html");
4255
+ },
4256
+ async exportToOpenContract(document2) {
4257
+ const bytes = await toBytes(document2);
4258
+ const response = await send(
4259
+ { id: generateId(), type: "exportToOpenContract", documentBytes: bytes },
4260
+ [bytes.buffer]
4261
+ );
4262
+ return requireField(response, "export");
4263
+ },
4264
+ async generatePackageManifestJson(document2, limits) {
4265
+ const bytes = await toBytes(document2);
4266
+ const response = await send(
4267
+ {
3619
4268
  id: generateId(),
3620
- type: "sessionGetSemanticChanges",
3621
- handle
3622
- });
3623
- if (!res.success || !res.semanticChanges) {
3624
- throw new Error(res.error ?? "sessionGetSemanticChanges failed");
3625
- }
3626
- return res.semanticChanges;
3627
- },
3628
- async verifyDeliverable(request) {
3629
- const res = await sendRequest({
4269
+ type: "generatePackageManifest",
4270
+ documentBytes: bytes,
4271
+ limits,
4272
+ representation: "json"
4273
+ },
4274
+ [bytes.buffer]
4275
+ );
4276
+ return requireField(response, "manifestJson");
4277
+ },
4278
+ async projectReviewProfile(document2, profile, maximumOutputBytes) {
4279
+ const bytes = await toBytes(document2);
4280
+ const response = await send(
4281
+ {
3630
4282
  id: generateId(),
3631
- type: "sessionVerifyDeliverable",
3632
- handle,
3633
- requestJson: request === void 0 ? void 0 : serializeVerificationRequest(request)
3634
- });
3635
- if (!res.success || !res.verification) {
3636
- throw new Error(res.error ?? "sessionVerifyDeliverable failed");
3637
- }
3638
- return res.verification;
3639
- },
3640
- async addAnnotation(anchorId, span, annotation) {
3641
- const res = await sendRequest({
4283
+ type: "projectReviewProfile",
4284
+ documentBytes: bytes,
4285
+ profile,
4286
+ maximumOutputBytes
4287
+ },
4288
+ [bytes.buffer]
4289
+ );
4290
+ if (!response.documentBytes || response.documentBytes.byteLength === 0) {
4291
+ throw new Error(`Failed to derive the ${profile} review profile`);
4292
+ }
4293
+ return response.documentBytes;
4294
+ },
4295
+ async convertDocxToHtml(document2, options2, maximumOutputBytes) {
4296
+ const bytes = await toBytes(document2);
4297
+ const response = await send(
4298
+ {
3642
4299
  id: generateId(),
3643
- type: "sessionAddAnnotation",
3644
- handle,
3645
- anchorId,
3646
- spanJson: span ? JSON.stringify(span) : "",
3647
- annotationJson: JSON.stringify(annotation)
3648
- });
3649
- if (!res.success) {
3650
- throw new Error(res.error ?? "sessionAddAnnotation failed");
3651
- }
3652
- return res.result;
3653
- },
3654
- async removeAnnotation(annotationId) {
3655
- const res = await sendRequest({
4300
+ type: "convertDocxToHtml",
4301
+ documentBytes: bytes,
4302
+ options: options2,
4303
+ maximumOutputBytes
4304
+ },
4305
+ [bytes.buffer]
4306
+ );
4307
+ return requireField(response, "html");
4308
+ },
4309
+ async compareDocuments(original, modified, options2) {
4310
+ const originalBytes = await toBytes(original);
4311
+ const modifiedBytes = await toBytes(modified);
4312
+ const response = await send(
4313
+ {
3656
4314
  id: generateId(),
3657
- type: "sessionRemoveAnnotation",
3658
- handle,
3659
- annotationId
3660
- });
3661
- if (!res.success) {
3662
- throw new Error(res.error ?? "sessionRemoveAnnotation failed");
3663
- }
3664
- return res.result;
3665
- },
3666
- async updateAnnotation(annotationId, update) {
3667
- const res = await sendRequest({
4315
+ type: "compareDocuments",
4316
+ originalBytes,
4317
+ modifiedBytes,
4318
+ options: options2
4319
+ },
4320
+ [originalBytes.buffer, modifiedBytes.buffer]
4321
+ );
4322
+ return requireField(response, "documentBytes");
4323
+ },
4324
+ async compareDocumentsToHtml(original, modified, options2) {
4325
+ const originalBytes = await toBytes(original);
4326
+ const modifiedBytes = await toBytes(modified);
4327
+ const response = await send(
4328
+ {
3668
4329
  id: generateId(),
3669
- type: "sessionUpdateAnnotation",
3670
- handle,
3671
- annotationId,
3672
- updateJson: JSON.stringify(update)
3673
- });
3674
- if (!res.success) {
3675
- throw new Error(res.error ?? "sessionUpdateAnnotation failed");
3676
- }
3677
- return res.result;
3678
- },
3679
- async moveAnnotation(annotationId, newAnchorId, newSpan) {
3680
- const res = await sendRequest({
4330
+ type: "compareDocumentsToHtml",
4331
+ originalBytes,
4332
+ modifiedBytes,
4333
+ options: options2
4334
+ },
4335
+ [originalBytes.buffer, modifiedBytes.buffer]
4336
+ );
4337
+ return requireField(response, "html");
4338
+ },
4339
+ async getRevisions(document2) {
4340
+ const bytes = await toBytes(document2);
4341
+ const response = await send(
4342
+ {
3681
4343
  id: generateId(),
3682
- type: "sessionMoveAnnotation",
3683
- handle,
3684
- annotationId,
3685
- newAnchorId,
3686
- newSpanJson: newSpan ? JSON.stringify(newSpan) : ""
3687
- });
3688
- if (!res.success) {
3689
- throw new Error(res.error ?? "sessionMoveAnnotation failed");
3690
- }
3691
- return res.result;
3692
- },
3693
- async close() {
3694
- await sendRequest({
4344
+ type: "getRevisions",
4345
+ documentBytes: bytes
4346
+ },
4347
+ [bytes.buffer]
4348
+ );
4349
+ return requireField(response, "revisions");
4350
+ },
4351
+ async getComments(document2) {
4352
+ const bytes = await toBytes(document2);
4353
+ const response = await send(
4354
+ { id: generateId(), type: "getComments", documentBytes: bytes },
4355
+ [bytes.buffer]
4356
+ );
4357
+ return requireField(response, "comments");
4358
+ },
4359
+ async getDocumentMetadata(document2) {
4360
+ const bytes = await toBytes(document2);
4361
+ const response = await send(
4362
+ {
3695
4363
  id: generateId(),
3696
- type: "sessionClose",
3697
- handle
3698
- });
4364
+ type: "getDocumentMetadata",
4365
+ documentBytes: bytes
4366
+ },
4367
+ [bytes.buffer]
4368
+ );
4369
+ return requireField(response, "metadata");
4370
+ },
4371
+ async getVersion() {
4372
+ const response = await send({
4373
+ id: generateId(),
4374
+ type: "getVersion"
4375
+ });
4376
+ return requireField(response, "version");
4377
+ },
4378
+ prepare() {
4379
+ if (preparePromise) {
4380
+ return preparePromise;
3699
4381
  }
3700
- };
3701
- },
3702
- terminate() {
3703
- stopWorker("Worker terminated");
3704
- },
3705
- isActive() {
3706
- return isWorkerActive;
3707
- }
4382
+ preparePromise = send({
4383
+ id: generateId(),
4384
+ type: "prepare"
4385
+ }).then(() => void 0);
4386
+ preparePromise.catch(() => {
4387
+ preparePromise = null;
4388
+ });
4389
+ return preparePromise;
4390
+ },
4391
+ async openDocxSession(document2, settings) {
4392
+ const bytes = await toBytes(document2);
4393
+ const settingsJson = settings ? JSON.stringify(settings) : "";
4394
+ const openResponse = await send(
4395
+ {
4396
+ id: generateId(),
4397
+ type: "sessionOpen",
4398
+ documentBytes: bytes,
4399
+ settingsJson
4400
+ },
4401
+ [bytes.buffer]
4402
+ );
4403
+ const handle = requireField(openResponse, "handle");
4404
+ return {
4405
+ async getPackageManifest() {
4406
+ const res = await send({
4407
+ id: generateId(),
4408
+ type: "sessionGetPackageManifest",
4409
+ handle
4410
+ });
4411
+ return requireField(res, "manifest");
4412
+ },
4413
+ async getSemanticChanges() {
4414
+ const res = await send({
4415
+ id: generateId(),
4416
+ type: "sessionGetSemanticChanges",
4417
+ handle
4418
+ });
4419
+ return requireField(res, "semanticChanges");
4420
+ },
4421
+ async verifyDeliverable(request) {
4422
+ const res = await send({
4423
+ id: generateId(),
4424
+ type: "sessionVerifyDeliverable",
4425
+ handle,
4426
+ requestJson: request === void 0 ? void 0 : serializeVerificationRequest(request)
4427
+ });
4428
+ return requireField(res, "verification");
4429
+ },
4430
+ async addAnnotation(anchorId, span, annotation) {
4431
+ const res = await send({
4432
+ id: generateId(),
4433
+ type: "sessionAddAnnotation",
4434
+ handle,
4435
+ anchorId,
4436
+ spanJson: span ? JSON.stringify(span) : "",
4437
+ annotationJson: JSON.stringify(annotation)
4438
+ });
4439
+ return requireField(res, "result");
4440
+ },
4441
+ async removeAnnotation(annotationId) {
4442
+ const res = await send({
4443
+ id: generateId(),
4444
+ type: "sessionRemoveAnnotation",
4445
+ handle,
4446
+ annotationId
4447
+ });
4448
+ return requireField(res, "result");
4449
+ },
4450
+ async updateAnnotation(annotationId, update) {
4451
+ const res = await send({
4452
+ id: generateId(),
4453
+ type: "sessionUpdateAnnotation",
4454
+ handle,
4455
+ annotationId,
4456
+ updateJson: JSON.stringify(update)
4457
+ });
4458
+ return requireField(res, "result");
4459
+ },
4460
+ async moveAnnotation(annotationId, newAnchorId, newSpan) {
4461
+ const res = await send({
4462
+ id: generateId(),
4463
+ type: "sessionMoveAnnotation",
4464
+ handle,
4465
+ annotationId,
4466
+ newAnchorId,
4467
+ newSpanJson: newSpan ? JSON.stringify(newSpan) : ""
4468
+ });
4469
+ return requireField(res, "result");
4470
+ },
4471
+ async close() {
4472
+ await sendRequest(
4473
+ { id: generateId(), type: "sessionClose", handle },
4474
+ void 0,
4475
+ { timeoutMs: requestOptions?.timeoutMs }
4476
+ );
4477
+ }
4478
+ };
4479
+ },
4480
+ terminate() {
4481
+ stopWorker("Worker terminated");
4482
+ },
4483
+ withRequestOptions(more) {
4484
+ checkTimeout(more.timeoutMs, "timeoutMs");
4485
+ return createApi({ ...requestOptions, ...more });
4486
+ },
4487
+ isActive() {
4488
+ return isWorkerActive;
4489
+ }
4490
+ };
3708
4491
  };
4492
+ return createApi();
3709
4493
  }
3710
4494
 
3711
4495
  // src/font-contract.ts
@@ -4048,6 +4832,45 @@ function collectFontInventory(document2, limits) {
4048
4832
  }
4049
4833
  uses.push({ element, requestKey: key, originalStyle: element.getAttribute("style") });
4050
4834
  }
4835
+ const requestsByStack = /* @__PURE__ */ new Map();
4836
+ for (const [key, request] of requests) {
4837
+ const stackKey = JSON.stringify([request.familyStack, request.familyKinds]);
4838
+ const list = requestsByStack.get(stackKey) ?? [];
4839
+ list.push({ key, request });
4840
+ requestsByStack.set(stackKey, list);
4841
+ }
4842
+ const closestRequestKey = (descriptor) => {
4843
+ const candidates = requestsByStack.get(JSON.stringify([descriptor.familyStack, descriptor.familyKinds]));
4844
+ if (!candidates) return void 0;
4845
+ let best;
4846
+ for (const { key, request } of candidates) {
4847
+ const distance = (request.style === descriptor.style ? 0 : 1e4) + Math.abs(request.weight - descriptor.weight) + Math.abs(request.stretch - descriptor.stretch);
4848
+ if (!best || distance < best.distance || distance === best.distance && compareText(key, best.key) < 0) {
4849
+ best = { key, distance };
4850
+ }
4851
+ }
4852
+ return best?.key;
4853
+ };
4854
+ const used = new Set(uses.map((use) => use.element));
4855
+ for (const element of Array.from(document2.body.querySelectorAll("*"))) {
4856
+ if (!(element instanceof view.HTMLElement) || used.has(element)) continue;
4857
+ if (/^(?:script|style|template|noscript)$/i.test(element.localName)) continue;
4858
+ if (!participatesInRendering(element, view)) continue;
4859
+ const computed = view.getComputedStyle(element);
4860
+ const parsedFamilies = parseCssFontFamilyTokens(computed.fontFamily);
4861
+ if (parsedFamilies.length === 0) continue;
4862
+ const descriptor = {
4863
+ familyStack: parsedFamilies.map(({ name }) => name),
4864
+ familyKinds: parsedFamilies.map(({ kind }) => kind),
4865
+ style: faceStyle(computed.fontStyle),
4866
+ weight: faceWeight(computed.fontWeight),
4867
+ stretch: faceStretch(computed.fontStretch)
4868
+ };
4869
+ const exactKey = requestKey(descriptor);
4870
+ const key = requests.has(exactKey) ? exactKey : closestRequestKey(descriptor);
4871
+ if (key === void 0) continue;
4872
+ uses.push({ element, requestKey: key, originalStyle: element.getAttribute("style") });
4873
+ }
4051
4874
  const ordered = Array.from(requests, ([key, request]) => ({ key, request })).sort((left, right) => compareText(left.key, right.key));
4052
4875
  const idByKey = /* @__PURE__ */ new Map();
4053
4876
  const result = Object.freeze(ordered.map(({ key, request }, index) => {
@@ -4908,6 +5731,7 @@ var REPORT_SCHEMA = "https://docxodus.dev/schemas/render/render-report/v2";
4908
5731
  var TEXT_ENCODER2 = new TextEncoder();
4909
5732
  var ALLOWED_REVIEW_PROFILES = /* @__PURE__ */ new Set(["final", "original", "markup"]);
4910
5733
  var ALLOWED_COMMENT_PROFILES = /* @__PURE__ */ new Set(["hidden", "inline", "endnotes", "margin"]);
5734
+ var ALLOWED_MARKUP_PRESENTATIONS = /* @__PURE__ */ new Set(["docxodus", "word"]);
4911
5735
  var ALLOWED_UNSUPPORTED_POLICIES = /* @__PURE__ */ new Set(["warn", "strict"]);
4912
5736
  var PACKAGE_LIMIT_FINDINGS = /* @__PURE__ */ new Set([
4913
5737
  "entry_count_limit_exceeded",
@@ -4983,6 +5807,23 @@ function normalizeOptions(options) {
4983
5807
  "Use unchanged source bytes for markup, or choose final/original."
4984
5808
  );
4985
5809
  }
5810
+ const markupPresentation = options.markupPresentation ?? "docxodus";
5811
+ if (!ALLOWED_MARKUP_PRESENTATIONS.has(markupPresentation)) {
5812
+ fail(
5813
+ "invalid_argument",
5814
+ "input_validation",
5815
+ "markupPresentation is invalid.",
5816
+ "Use docxodus or word."
5817
+ );
5818
+ }
5819
+ if (markupPresentation !== "docxodus" && options.reviewProfile !== "markup") {
5820
+ fail(
5821
+ "invalid_argument",
5822
+ "input_validation",
5823
+ "markupPresentation applies only to the markup profile.",
5824
+ "Omit markupPresentation, or use reviewProfile markup."
5825
+ );
5826
+ }
4986
5827
  const unsupportedContent = options.unsupportedContent ?? "warn";
4987
5828
  if (!ALLOWED_UNSUPPORTED_POLICIES.has(unsupportedContent)) {
4988
5829
  fail(
@@ -5121,6 +5962,7 @@ function normalizeOptions(options) {
5121
5962
  expectedSourceDigest: options.expectedSourceDigest,
5122
5963
  reviewProfile: options.reviewProfile,
5123
5964
  reviewProfileAlreadyApplied,
5965
+ markupPresentation,
5124
5966
  commentProfile: options.commentProfile,
5125
5967
  title,
5126
5968
  unsupportedContent,
@@ -5169,6 +6011,22 @@ async function ownedBytes(document2, maximum, signal) {
5169
6011
  function monotonicNow() {
5170
6012
  return globalThis.performance?.now() ?? Date.now();
5171
6013
  }
6014
+ function checkpointWordText(state) {
6015
+ if (state.signal?.aborted)
6016
+ fail(
6017
+ "operation_cancelled",
6018
+ state.phase,
6019
+ "Export was cancelled during text layout.",
6020
+ "Retry with a non-aborted signal."
6021
+ );
6022
+ if (monotonicNow() >= state.deadline)
6023
+ fail(
6024
+ "readiness_timeout",
6025
+ state.phase,
6026
+ "Export timed out during text layout.",
6027
+ "Increase timeoutMs or reduce document complexity."
6028
+ );
6029
+ }
5172
6030
  async function runPhase(state, phase, pending, operation) {
5173
6031
  state.phase = phase;
5174
6032
  const started = monotonicNow();
@@ -5382,6 +6240,26 @@ async function boundedResponseBytes(response, maximum, label) {
5382
6240
  }
5383
6241
  return bytes;
5384
6242
  }
6243
+ var hostVerifiedAssetManifestSha256;
6244
+ function attestHostVerifiedRuntimeAssets(manifestSha256) {
6245
+ if (typeof manifestSha256 !== "string" || !/^[0-9a-f]{64}$/.test(manifestSha256)) {
6246
+ fail(
6247
+ "unsupported_runtime",
6248
+ "wasm_initialization",
6249
+ "The host runtime asset attestation is not a SHA-256 digest.",
6250
+ "Use the bootstrap module @docxodus/export generates."
6251
+ );
6252
+ }
6253
+ if (hostVerifiedAssetManifestSha256 !== void 0 && hostVerifiedAssetManifestSha256 !== manifestSha256) {
6254
+ fail(
6255
+ "unsupported_runtime",
6256
+ "wasm_initialization",
6257
+ "The host runtime asset attestation may be set only once.",
6258
+ "Load one host bootstrap module per page."
6259
+ );
6260
+ }
6261
+ hostVerifiedAssetManifestSha256 = manifestSha256;
6262
+ }
5385
6263
  async function loadRuntimeAssetIdentity(wasmBasePath, signal) {
5386
6264
  const manifestUrl = new URL("./export-assets.json", import.meta.url);
5387
6265
  const response = await globalThis.fetch(manifestUrl, {
@@ -5539,7 +6417,8 @@ async function loadRuntimeAssetIdentity(wasmBasePath, signal) {
5539
6417
  "Deploy the bundle and asset graph from the same Docxodus build."
5540
6418
  );
5541
6419
  }
5542
- const verifiedRuntimeAssets = assets.filter((entry) => entry.path === "./docxodus.worker.js" || entry.path.startsWith("./wasm/_framework/"));
6420
+ const hostVerified = hostVerifiedAssetManifestSha256 !== void 0 && await sha2562(graphBytes) === hostVerifiedAssetManifestSha256;
6421
+ const verifiedRuntimeAssets = hostVerified ? [] : assets.filter((entry) => entry.path === "./docxodus.worker.js" || entry.path.startsWith("./wasm/_framework/"));
5543
6422
  const resolvedWasmBasePath = new URL(
5544
6423
  wasmBasePath.endsWith("/") ? wasmBasePath : `${wasmBasePath}/`,
5545
6424
  import.meta.url
@@ -6181,6 +7060,7 @@ function conversionOptions(options) {
6181
7060
  renderFootnotesAndEndnotes: true,
6182
7061
  renderHeadersAndFooters: true,
6183
7062
  renderTrackedChanges: options.reviewProfile === "markup",
7063
+ revisionPresentation: options.markupPresentation === "word" ? 1 /* Word */ : 0 /* Docxodus */,
6184
7064
  showDeletedContent: true,
6185
7065
  renderMoveOperations: true,
6186
7066
  renderUnsupportedContentPlaceholders: true,
@@ -7070,7 +7950,7 @@ function finalizePageTree(document2, pages, state, options) {
7070
7950
  style.textContent = standaloneStyle(pages);
7071
7951
  document2.head.appendChild(style);
7072
7952
  }
7073
- function assertNoClippedContent(document2) {
7953
+ function findClippedContent(document2) {
7074
7954
  for (const footnotes of Array.from(document2.querySelectorAll(".page-footnotes"))) {
7075
7955
  if (footnotes.scrollHeight <= footnotes.clientHeight + 1) continue;
7076
7956
  const boundary = footnotes.getBoundingClientRect().bottom;
@@ -7084,24 +7964,33 @@ function assertNoClippedContent(document2) {
7084
7964
  );
7085
7965
  }
7086
7966
  }
7967
+ const clipped = [];
7087
7968
  for (const content of Array.from(document2.querySelectorAll(".page-content"))) {
7088
7969
  if (content.scrollHeight > content.clientHeight + 1) {
7089
7970
  const pageNumber = content.closest(".page-box")?.dataset.pageNumber ?? "unknown";
7090
7971
  const boundary = content.getBoundingClientRect().bottom;
7091
- const overflow = Array.from(content.querySelectorAll("*")).map((element) => ({
7972
+ const overflowing = Array.from(content.querySelectorAll("*")).map((element) => ({
7092
7973
  element,
7093
7974
  bottom: element.getBoundingClientRect().bottom
7094
- })).filter(({ bottom }) => bottom > boundary + 1).sort((left, right) => right.bottom - left.bottom).slice(0, 3).map(({ element, bottom }) => `${element.localName}${element.className ? `.${String(element.className).trim().replace(/\s+/g, ".")}` : ""} (+${(bottom - boundary).toFixed(1)}px)`);
7095
- if (overflow.length === 0) continue;
7096
- fail(
7097
- "pagination_failure",
7098
- "running_story_placement",
7099
- `Page ${pageNumber} body content is clipped (${content.scrollHeight}px scroll height in a ${content.clientHeight}px band; ${overflow.join(", ")}).`,
7100
- "Split the oversized block or reduce its dimensions before export."
7101
- );
7975
+ })).filter(({ element, bottom }) => bottom > boundary + 1 && !isPreservedExactLineInk(element, content)).sort((left, right) => right.bottom - left.bottom);
7976
+ if (overflowing.length === 0) continue;
7977
+ const overflow = overflowing.slice(0, 3).map(({ element, bottom }) => `${element.localName}${element.className ? `.${String(element.className).trim().replace(/\s+/g, ".")}` : ""} (+${(bottom - boundary).toFixed(1)}px)`);
7978
+ clipped.push({
7979
+ pageNumber: Number.parseInt(pageNumber, 10),
7980
+ overflowPx: overflowing[0].bottom - boundary,
7981
+ message: `Page ${pageNumber} body content is clipped (${content.scrollHeight}px scroll height in a ${content.clientHeight}px band; ${overflow.join(", ")}).`
7982
+ });
7102
7983
  }
7103
7984
  }
7985
+ return clipped;
7104
7986
  }
7987
+ var MAX_BODY_REFLOW_ROUNDS = 3;
7988
+ var BodyReflowRequest = class extends Error {
7989
+ constructor() {
7990
+ super("body reflow requested");
7991
+ this.name = "BodyReflowRequest";
7992
+ }
7993
+ };
7105
7994
  function treeSignature(document2, pages) {
7106
7995
  const geometry = pages.map((page) => {
7107
7996
  const rect = page.getBoundingClientRect();
@@ -7241,11 +8130,16 @@ function automaticResourceCount(document2) {
7241
8130
  }
7242
8131
  return { count, bytes };
7243
8132
  }
8133
+ function markupPresentationField(options) {
8134
+ return options.markupPresentation === "word" ? { markupPresentation: "word" } : {};
8135
+ }
7244
8136
  async function layoutDigestForOptions(options) {
7245
8137
  const layoutContract = {
7246
8138
  title: options.title,
7247
8139
  reviewProfile: options.reviewProfile,
7248
8140
  reviewProfileAlreadyApplied: options.reviewProfileAlreadyApplied,
8141
+ // Absent for the default, so default layouts keep the digest they had before the option.
8142
+ ...markupPresentationField(options),
7249
8143
  commentProfile: options.commentProfile,
7250
8144
  pagination: {
7251
8145
  mode: "paginated",
@@ -7349,28 +8243,6 @@ function storyForAnchor(anchorId) {
7349
8243
  if (scope === "cmt") return "comment";
7350
8244
  return "body";
7351
8245
  }
7352
- function visibleRectWithinPage(document2, element, page) {
7353
- const pageRect = page.getBoundingClientRect();
7354
- const rect = element.getBoundingClientRect();
7355
- let left = Math.max(rect.left, pageRect.left);
7356
- let top = Math.max(rect.top, pageRect.top);
7357
- let right = Math.min(rect.right, pageRect.right);
7358
- let bottom = Math.min(rect.bottom, pageRect.bottom);
7359
- const clips = (value) => value === "hidden" || value === "clip" || value === "scroll" || value === "auto";
7360
- for (let ancestor = element.parentElement; ancestor && ancestor !== page; ancestor = ancestor.parentElement) {
7361
- const style = document2.defaultView.getComputedStyle(ancestor);
7362
- const ancestorRect = ancestor.getBoundingClientRect();
7363
- if (clips(style.overflowX)) {
7364
- left = Math.max(left, ancestorRect.left);
7365
- right = Math.min(right, ancestorRect.right);
7366
- }
7367
- if (clips(style.overflowY)) {
7368
- top = Math.max(top, ancestorRect.top);
7369
- bottom = Math.min(bottom, ancestorRect.bottom);
7370
- }
7371
- }
7372
- return { left, top, right, bottom };
7373
- }
7374
8246
  function assertStandaloneResourceAudit(document2) {
7375
8247
  if (document2.querySelector(
7376
8248
  "script, iframe, object, embed, video, audio, source, track, link, base, meta[http-equiv='refresh' i]"
@@ -7475,8 +8347,7 @@ async function verifyOfflineReopen(hostDocument, html, expectedPageMap, state) {
7475
8347
  )) {
7476
8348
  const style = reopened.defaultView.getComputedStyle(element);
7477
8349
  if (style.display === "none" || style.visibility === "hidden") continue;
7478
- const rect = element.getBoundingClientRect();
7479
- const visible = visibleRectWithinPage(reopened, element, page);
8350
+ const { rect, visible } = measureRenderedSource(reopened.defaultView, element, page, pageRect);
7480
8351
  if (rect.width <= 0 || rect.height <= 0 || visible.right <= visible.left || visible.bottom <= visible.top) continue;
7481
8352
  const anchorId = element.dataset.sourceAnchorId;
7482
8353
  actualFragments.push({
@@ -7529,6 +8400,7 @@ function reportBase(manifest, sourceBytes, derivedManifest, derivedBytes, option
7529
8400
  options: {
7530
8401
  reviewProfile: options.reviewProfile,
7531
8402
  reviewProfileAlreadyApplied: options.reviewProfileAlreadyApplied,
8403
+ ...markupPresentationField(options),
7532
8404
  commentProfile: options.commentProfile,
7533
8405
  title: options.title,
7534
8406
  outputs: ["html"],
@@ -7779,6 +8651,8 @@ async function convertDocxToPaginatedHtml(document2, requestedOptions) {
7779
8651
  });
7780
8652
  preflightConvertedHtml(convertedHtml, options);
7781
8653
  const attemptCheckpoint = checkpointAttemptState(state);
8654
+ const bodyBudgetReductionsPt = {};
8655
+ let bodyReflowRounds = 0;
7782
8656
  let finalized;
7783
8657
  let firstAttemptSignature;
7784
8658
  let firstAttemptFontIdentity;
@@ -7849,6 +8723,8 @@ async function convertDocxToPaginatedHtml(document2, requestedOptions) {
7849
8723
  "Use the paginated converter contract and report the malformed conversion output."
7850
8724
  );
7851
8725
  }
8726
+ applyUnroundedNormalLineHeights(renderDocument2.body);
8727
+ prepareWordText(renderDocument2.body, () => checkpointWordText(state));
7852
8728
  const engine2 = new PaginationEngine(staging, container, {
7853
8729
  scale: 1,
7854
8730
  cssPrefix: "page-",
@@ -7859,6 +8735,7 @@ async function convertDocxToPaginatedHtml(document2, requestedOptions) {
7859
8735
  // restamps through normalizePageMapFragmentIdentities(); stamping inside paginate()
7860
8736
  // would be a second full forced-layout pass whose result is immediately overwritten.
7861
8737
  deferFragmentIdentities: true,
8738
+ bodyBudgetReductionsPt,
7862
8739
  checkCancellation: () => {
7863
8740
  if (state.signal?.aborted) {
7864
8741
  fail(
@@ -7899,11 +8776,32 @@ async function convertDocxToPaginatedHtml(document2, requestedOptions) {
7899
8776
  "Verify that the DOCX has a renderable main document body."
7900
8777
  );
7901
8778
  }
7902
- await runPhase(state, "running_story_placement", ["headers, footers, and notes"], () => {
7903
- finalizePageTree(renderDocument2, pages2, state, options);
7904
- engine2.normalizePageMapFragmentIdentities();
7905
- assertNoClippedContent(renderDocument2);
7906
- });
8779
+ const clippedPages = await runPhase(
8780
+ state,
8781
+ "running_story_placement",
8782
+ ["headers, footers, and notes"],
8783
+ () => {
8784
+ finalizePageTree(renderDocument2, pages2, state, options);
8785
+ engine2.normalizePageMapFragmentIdentities();
8786
+ const clipped = findClippedContent(renderDocument2);
8787
+ if (clipped.length > 0 && bodyReflowRounds >= MAX_BODY_REFLOW_ROUNDS) {
8788
+ fail(
8789
+ "pagination_failure",
8790
+ "running_story_placement",
8791
+ clipped[0].message,
8792
+ "Split the oversized block or reduce its dimensions before export."
8793
+ );
8794
+ }
8795
+ return clipped;
8796
+ }
8797
+ );
8798
+ if (clippedPages.length > 0) {
8799
+ bodyReflowRounds++;
8800
+ for (const page of clippedPages) {
8801
+ bodyBudgetReductionsPt[page.pageNumber] = (bodyBudgetReductionsPt[page.pageNumber] ?? 0) + (page.overflowPx + 1) * 72 / 96;
8802
+ }
8803
+ throw new BodyReflowRequest();
8804
+ }
7907
8805
  countDomNodes(renderDocument2, options.limits.domNodes, "running_story_placement");
7908
8806
  const automaticResources = automaticResourceCount(renderDocument2);
7909
8807
  enforceLimit(
@@ -7936,6 +8834,12 @@ async function convertDocxToPaginatedHtml(document2, requestedOptions) {
7936
8834
  } catch (error) {
7937
8835
  frame?.remove();
7938
8836
  frame = void 0;
8837
+ if (error instanceof BodyReflowRequest) {
8838
+ restoreAttemptState(state, attemptCheckpoint);
8839
+ firstAttemptSignature = void 0;
8840
+ attempt = 0;
8841
+ continue;
8842
+ }
7939
8843
  if (!(error instanceof PageTreeInstabilityError) || attempt === 2) throw error;
7940
8844
  restoreAttemptState(state, attemptCheckpoint);
7941
8845
  addWarning(state, {
@@ -7957,6 +8861,11 @@ async function convertDocxToPaginatedHtml(document2, requestedOptions) {
7957
8861
  }
7958
8862
  frame = finalized.frame;
7959
8863
  const { document: renderDocument, engine, pages } = finalized;
8864
+ alignBaselinesToWord(renderDocument.body);
8865
+ await runPhase(state, "output_verification", ["Word text baselines"], () => paintWordText(
8866
+ renderDocument.getElementById("pagination-container"),
8867
+ () => checkpointWordText(state)
8868
+ ));
7960
8869
  const renderer = await runPhase(state, "output_verification", ["renderer identity"], () => rendererIdentity(
7961
8870
  renderDocument,
7962
8871
  layoutDigest,
@@ -8112,11 +9021,13 @@ export {
8112
9021
  FONT_SUBSTITUTION_CONTRACT_VERSION,
8113
9022
  HARD_EXPORT_RESOURCE_LIMITS,
8114
9023
  HARD_EXPORT_TIMEOUT_MS,
9024
+ attestHostVerifiedRuntimeAssets,
8115
9025
  canonicalJson,
8116
9026
  convertDocxToPaginatedHtml,
8117
9027
  fontFamilyKey,
8118
9028
  inventoryDocumentFontRequests,
8119
9029
  normalizeFontFamilyName,
8120
9030
  parseCssFontFamily,
8121
- reconstructDocxodusExportError
9031
+ reconstructDocxodusExportError,
9032
+ renderedSourceMeasurerExpression
8122
9033
  };