docxodus 9.9.0 → 11.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.
- package/README.md +182 -4
- package/dist/canonical.d.ts +7 -0
- package/dist/canonical.d.ts.map +1 -0
- package/dist/canonical.js +56 -0
- package/dist/canonical.js.map +1 -0
- package/dist/docxodus.worker.d.ts +2 -1
- package/dist/docxodus.worker.d.ts.map +1 -1
- package/dist/docxodus.worker.js +250 -41
- package/dist/docxodus.worker.js.map +1 -1
- package/dist/editor.bundle.js +1980 -423
- package/dist/editor.d.ts +22 -2
- package/dist/editor.d.ts.map +1 -1
- package/dist/editor.js +94 -9
- package/dist/editor.js.map +1 -1
- package/dist/embed.bundle.js +2968 -597
- package/dist/embed.d.ts +1 -1
- package/dist/embed.iife.js +2969 -598
- package/dist/embed.js +1 -1
- package/dist/export-assets.json +277 -0
- package/dist/export-browser.bundle.js +7989 -0
- package/dist/export-browser.d.ts +266 -0
- package/dist/export-browser.d.ts.map +1 -0
- package/dist/export-browser.js +2915 -0
- package/dist/export-browser.js.map +1 -0
- package/dist/export-resource-limits-v1.json +57 -0
- package/dist/font-contract.d.ts +150 -0
- package/dist/font-contract.d.ts.map +1 -0
- package/dist/font-contract.js +59 -0
- package/dist/font-contract.js.map +1 -0
- package/dist/font-runtime.d.ts +33 -0
- package/dist/font-runtime.d.ts.map +1 -0
- package/dist/font-runtime.js +1100 -0
- package/dist/font-runtime.js.map +1 -0
- package/dist/index.d.ts +83 -65
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +194 -149
- package/dist/index.js.map +1 -1
- package/dist/page-geometry.d.ts +8 -1
- package/dist/page-geometry.d.ts.map +1 -1
- package/dist/page-geometry.js +37 -11
- package/dist/page-geometry.js.map +1 -1
- package/dist/pagination.bundle.js +1827 -410
- package/dist/pagination.d.ts +249 -15
- package/dist/pagination.d.ts.map +1 -1
- package/dist/pagination.js +1942 -453
- package/dist/pagination.js.map +1 -1
- package/dist/react.d.ts +13 -6
- package/dist/react.d.ts.map +1 -1
- package/dist/react.js +38 -5
- package/dist/react.js.map +1 -1
- package/dist/render-report-v2.schema.json +1581 -0
- package/dist/ribbon-chrome.d.ts +2 -2
- package/dist/ribbon-chrome.d.ts.map +1 -1
- package/dist/ribbon-chrome.js +72 -4
- package/dist/ribbon-chrome.js.map +1 -1
- package/dist/ribbon.js +119 -0
- package/dist/ribbon.js.map +1 -1
- package/dist/session.bundle.js +747 -29
- package/dist/session.d.ts +159 -19
- package/dist/session.d.ts.map +1 -1
- package/dist/session.js +593 -25
- package/dist/session.js.map +1 -1
- package/dist/types.d.ts +1287 -238
- package/dist/types.d.ts.map +1 -1
- package/dist/types.js +9 -75
- package/dist/types.js.map +1 -1
- package/dist/wasm/_framework/DocumentFormat.OpenXml.Framework.wasm +0 -0
- package/dist/wasm/_framework/DocumentFormat.OpenXml.Framework.wasm.br +0 -0
- package/dist/wasm/_framework/DocumentFormat.OpenXml.wasm +0 -0
- package/dist/wasm/_framework/DocumentFormat.OpenXml.wasm.br +0 -0
- package/dist/wasm/_framework/Docxodus.wasm +0 -0
- package/dist/wasm/_framework/Docxodus.wasm.br +0 -0
- package/dist/wasm/_framework/DocxodusWasm.wasm +0 -0
- package/dist/wasm/_framework/DocxodusWasm.wasm.br +0 -0
- package/dist/wasm/_framework/System.Collections.Concurrent.wasm +0 -0
- package/dist/wasm/_framework/System.Collections.Concurrent.wasm.br +0 -0
- package/dist/wasm/_framework/System.Collections.Immutable.wasm +0 -0
- package/dist/wasm/_framework/System.Collections.Immutable.wasm.br +0 -0
- package/dist/wasm/_framework/System.Collections.wasm +0 -0
- package/dist/wasm/_framework/System.Collections.wasm.br +0 -0
- package/dist/wasm/_framework/System.ComponentModel.Primitives.wasm +0 -0
- package/dist/wasm/_framework/System.ComponentModel.Primitives.wasm.br +0 -0
- package/dist/wasm/_framework/System.IO.Compression.wasm +0 -0
- package/dist/wasm/_framework/System.IO.Compression.wasm.br +0 -0
- package/dist/wasm/_framework/System.IO.Packaging.wasm +0 -0
- package/dist/wasm/_framework/System.IO.Packaging.wasm.br +0 -0
- package/dist/wasm/_framework/System.IO.Pipelines.wasm +0 -0
- package/dist/wasm/_framework/System.IO.Pipelines.wasm.br +0 -0
- package/dist/wasm/_framework/System.Linq.wasm +0 -0
- package/dist/wasm/_framework/System.Linq.wasm.br +0 -0
- package/dist/wasm/_framework/System.Private.CoreLib.wasm +0 -0
- package/dist/wasm/_framework/System.Private.CoreLib.wasm.br +0 -0
- package/dist/wasm/_framework/System.Private.Uri.wasm +0 -0
- package/dist/wasm/_framework/System.Private.Uri.wasm.br +0 -0
- package/dist/wasm/_framework/System.Private.Xml.Linq.wasm +0 -0
- package/dist/wasm/_framework/System.Private.Xml.Linq.wasm.br +0 -0
- package/dist/wasm/_framework/System.Private.Xml.wasm +0 -0
- package/dist/wasm/_framework/System.Private.Xml.wasm.br +0 -0
- package/dist/wasm/_framework/System.Runtime.InteropServices.JavaScript.wasm +0 -0
- package/dist/wasm/_framework/System.Runtime.InteropServices.JavaScript.wasm.br +0 -0
- package/dist/wasm/_framework/System.Runtime.wasm +0 -0
- package/dist/wasm/_framework/System.Runtime.wasm.br +0 -0
- package/dist/wasm/_framework/System.Security.Cryptography.wasm +0 -0
- package/dist/wasm/_framework/System.Security.Cryptography.wasm.br +0 -0
- package/dist/wasm/_framework/System.Text.Encodings.Web.wasm +0 -0
- package/dist/wasm/_framework/System.Text.Encodings.Web.wasm.br +0 -0
- package/dist/wasm/_framework/System.Text.Json.wasm +0 -0
- package/dist/wasm/_framework/System.Text.Json.wasm.br +0 -0
- package/dist/wasm/_framework/System.Text.RegularExpressions.wasm +0 -0
- package/dist/wasm/_framework/System.Text.RegularExpressions.wasm.br +0 -0
- package/dist/wasm/_framework/dotnet.boot.js +34 -40
- package/dist/wasm/_framework/dotnet.boot.js.br +0 -0
- package/dist/wasm/_framework/dotnet.native.js +39 -3
- package/dist/wasm/_framework/dotnet.native.js.br +0 -0
- package/dist/wasm/_framework/dotnet.native.wasm +0 -0
- package/dist/wasm/_framework/dotnet.native.wasm.br +0 -0
- package/dist/worker-proxy.bundle.js +198 -32
- package/dist/worker-proxy.d.ts +37 -4
- package/dist/worker-proxy.d.ts.map +1 -1
- package/dist/worker-proxy.js +187 -35
- package/dist/worker-proxy.js.map +1 -1
- package/package.json +22 -6
- package/dist/wasm/_framework/System.Diagnostics.Process.wasm +0 -0
- package/dist/wasm/_framework/System.Diagnostics.Process.wasm.br +0 -0
package/dist/embed.iife.js
CHANGED
|
@@ -127,7 +127,6 @@ var Docxodus = (() => {
|
|
|
127
127
|
AnchorRenderMode: () => AnchorRenderMode,
|
|
128
128
|
AnnotationLabelMode: () => AnnotationLabelMode,
|
|
129
129
|
CommentRenderMode: () => CommentRenderMode,
|
|
130
|
-
ComparisonEngine: () => ComparisonEngine,
|
|
131
130
|
ComparisonLogCodes: () => ComparisonLogCodes,
|
|
132
131
|
ComparisonLogLevel: () => ComparisonLogLevel,
|
|
133
132
|
ConflictResolution: () => ConflictResolution,
|
|
@@ -154,10 +153,9 @@ var Docxodus = (() => {
|
|
|
154
153
|
addAnnotation: () => addAnnotation,
|
|
155
154
|
addAnnotationToHtml: () => addAnnotationToHtml,
|
|
156
155
|
addAnnotationWithTarget: () => addAnnotationWithTarget,
|
|
156
|
+
clearPageCitationHighlight: () => clearPageCitationHighlight,
|
|
157
157
|
compareDocuments: () => compareDocuments,
|
|
158
158
|
compareDocumentsToHtml: () => compareDocumentsToHtml,
|
|
159
|
-
compareDocumentsToHtmlWithLog: () => compareDocumentsToHtmlWithLog,
|
|
160
|
-
compareDocumentsWithLog: () => compareDocumentsWithLog,
|
|
161
159
|
computeDocumentHash: () => computeDocumentHash,
|
|
162
160
|
convertDocxToHtml: () => convertDocxToHtml,
|
|
163
161
|
convertDocxToHtmlWithExternalAnnotations: () => convertDocxToHtmlWithExternalAnnotations,
|
|
@@ -168,24 +166,28 @@ var Docxodus = (() => {
|
|
|
168
166
|
createEditor: () => createEditor,
|
|
169
167
|
createExternalAnnotationSet: () => createExternalAnnotationSet,
|
|
170
168
|
createRibbonEditor: () => createRibbonEditor,
|
|
169
|
+
createUnavailablePageMap: () => createUnavailablePageMap,
|
|
171
170
|
createViewer: () => createViewer,
|
|
172
171
|
docxDiffAcceptRevisions: () => docxDiffAcceptRevisions,
|
|
173
172
|
docxDiffCompare: () => docxDiffCompare,
|
|
173
|
+
docxDiffCompareBatch: () => docxDiffCompareBatch,
|
|
174
|
+
docxDiffCompareProducts: () => docxDiffCompareProducts,
|
|
174
175
|
docxDiffConsolidate: () => docxDiffConsolidate,
|
|
175
176
|
docxDiffGetConflicts: () => docxDiffGetConflicts,
|
|
176
177
|
docxDiffGetConsolidatedEditScript: () => docxDiffGetConsolidatedEditScript,
|
|
177
178
|
docxDiffGetConsolidatedRevisions: () => docxDiffGetConsolidatedRevisions,
|
|
178
179
|
docxDiffGetEditScript: () => docxDiffGetEditScript,
|
|
179
180
|
docxDiffGetRevisions: () => docxDiffGetRevisions,
|
|
181
|
+
docxDiffGetSemanticChanges: () => docxDiffGetSemanticChanges,
|
|
180
182
|
docxDiffRejectRevisions: () => docxDiffRejectRevisions,
|
|
181
183
|
exportToOpenContract: () => exportToOpenContract,
|
|
182
184
|
findElementById: () => findElementById,
|
|
183
185
|
findElementsByType: () => findElementsByType,
|
|
184
|
-
findMovePair: () => findMovePair,
|
|
185
186
|
findTextOccurrences: () => findTextOccurrences,
|
|
186
187
|
fitScale: () => fitScale,
|
|
187
188
|
generateAnnotationCss: () => generateAnnotationCss,
|
|
188
189
|
generateAnnotationVisibilityCss: () => generateAnnotationVisibilityCss,
|
|
190
|
+
generatePackageManifest: () => generatePackageManifest,
|
|
189
191
|
getAnnotations: () => getAnnotations,
|
|
190
192
|
getDocumentMetadata: () => getDocumentMetadata,
|
|
191
193
|
getDocumentStructure: () => getDocumentStructure,
|
|
@@ -202,13 +204,13 @@ var Docxodus = (() => {
|
|
|
202
204
|
isInitialized: () => isInitialized,
|
|
203
205
|
isInsertion: () => isInsertion,
|
|
204
206
|
isMove: () => isMove,
|
|
205
|
-
isMoveDestination: () => isMoveDestination,
|
|
206
|
-
isMoveSource: () => isMoveSource,
|
|
207
207
|
mountRibbon: () => mountRibbon,
|
|
208
|
+
navigateToPageCitation: () => navigateToPageCitation,
|
|
208
209
|
openDocxSession: () => openDocxSession2,
|
|
209
210
|
paginateHtml: () => paginateHtml,
|
|
210
211
|
parseSectionDimensions: () => parseSectionDimensions,
|
|
211
212
|
projectAnnotationsOntoHtml: () => projectAnnotationsOntoHtml,
|
|
213
|
+
proveRedlineReversibility: () => proveRedlineReversibility,
|
|
212
214
|
ptToPx: () => ptToPx,
|
|
213
215
|
pxToPt: () => pxToPt,
|
|
214
216
|
removeAnnotation: () => removeAnnotation,
|
|
@@ -228,6 +230,8 @@ var Docxodus = (() => {
|
|
|
228
230
|
targetTableRow: () => targetTableRow,
|
|
229
231
|
toDocumentBytes: () => toDocumentBytes,
|
|
230
232
|
validateExternalAnnotations: () => validateExternalAnnotations,
|
|
233
|
+
verifyDeliverable: () => verifyDeliverable,
|
|
234
|
+
verifyDeliveryReceipt: () => verifyDeliveryReceipt,
|
|
231
235
|
wasmBasePath: () => wasmBasePath
|
|
232
236
|
});
|
|
233
237
|
|
|
@@ -285,11 +289,6 @@ var Docxodus = (() => {
|
|
|
285
289
|
RevisionType2["FormatChanged"] = "FormatChanged";
|
|
286
290
|
return RevisionType2;
|
|
287
291
|
})(RevisionType || {});
|
|
288
|
-
var ComparisonEngine = /* @__PURE__ */ ((ComparisonEngine2) => {
|
|
289
|
-
ComparisonEngine2[ComparisonEngine2["WmlComparer"] = 0] = "WmlComparer";
|
|
290
|
-
ComparisonEngine2[ComparisonEngine2["DocxDiff"] = 1] = "DocxDiff";
|
|
291
|
-
return ComparisonEngine2;
|
|
292
|
-
})(ComparisonEngine || {});
|
|
293
292
|
var CommentRenderMode = /* @__PURE__ */ ((CommentRenderMode2) => {
|
|
294
293
|
CommentRenderMode2[CommentRenderMode2["Disabled"] = -1] = "Disabled";
|
|
295
294
|
CommentRenderMode2[CommentRenderMode2["EndnoteStyle"] = 0] = "EndnoteStyle";
|
|
@@ -326,30 +325,16 @@ var Docxodus = (() => {
|
|
|
326
325
|
return ConflictResolution2;
|
|
327
326
|
})(ConflictResolution || {});
|
|
328
327
|
function isInsertion(revision) {
|
|
329
|
-
return revision.
|
|
328
|
+
return revision.family === "content_insert";
|
|
330
329
|
}
|
|
331
330
|
function isDeletion(revision) {
|
|
332
|
-
return revision.
|
|
331
|
+
return revision.family === "content_delete";
|
|
333
332
|
}
|
|
334
333
|
function isMove(revision) {
|
|
335
|
-
return revision.
|
|
334
|
+
return revision.family === "move";
|
|
336
335
|
}
|
|
337
336
|
function isFormatChange(revision) {
|
|
338
|
-
return revision.
|
|
339
|
-
}
|
|
340
|
-
function isMoveSource(revision) {
|
|
341
|
-
return isMove(revision) && revision.isMoveSource === true;
|
|
342
|
-
}
|
|
343
|
-
function isMoveDestination(revision) {
|
|
344
|
-
return isMove(revision) && revision.isMoveSource === false;
|
|
345
|
-
}
|
|
346
|
-
function findMovePair(revision, allRevisions) {
|
|
347
|
-
if (!isMove(revision) || revision.moveGroupId === void 0) {
|
|
348
|
-
return void 0;
|
|
349
|
-
}
|
|
350
|
-
return allRevisions.find(
|
|
351
|
-
(r) => r.moveGroupId === revision.moveGroupId && r.isMoveSource !== revision.isMoveSource
|
|
352
|
-
);
|
|
337
|
+
return revision.family === "properties_change";
|
|
353
338
|
}
|
|
354
339
|
var PlaceholderKinds = {
|
|
355
340
|
BlankFill: 1,
|
|
@@ -480,7 +465,46 @@ var Docxodus = (() => {
|
|
|
480
465
|
}
|
|
481
466
|
|
|
482
467
|
// src/session.ts
|
|
483
|
-
|
|
468
|
+
function mutationBatchChangeSet(before, after, key) {
|
|
469
|
+
const beforeGroups = /* @__PURE__ */ new Map();
|
|
470
|
+
const afterGroups = /* @__PURE__ */ new Map();
|
|
471
|
+
const group = (items, target) => {
|
|
472
|
+
items.forEach((item, index) => {
|
|
473
|
+
const identity = key(item);
|
|
474
|
+
const indices = target.get(identity) ?? [];
|
|
475
|
+
indices.push(index);
|
|
476
|
+
target.set(identity, indices);
|
|
477
|
+
});
|
|
478
|
+
};
|
|
479
|
+
group(before, beforeGroups);
|
|
480
|
+
group(after, afterGroups);
|
|
481
|
+
const beforeMatched = before.map(() => false);
|
|
482
|
+
const afterMatched = after.map(() => false);
|
|
483
|
+
const modified = after.map(() => false);
|
|
484
|
+
for (const [identity, afterIndices] of afterGroups) {
|
|
485
|
+
const beforeIndices = beforeGroups.get(identity) ?? [];
|
|
486
|
+
for (const afterIndex of afterIndices) {
|
|
487
|
+
const beforeIndex = beforeIndices.find((index) => !beforeMatched[index] && JSON.stringify(before[index]) === JSON.stringify(after[afterIndex]));
|
|
488
|
+
if (beforeIndex === void 0) continue;
|
|
489
|
+
beforeMatched[beforeIndex] = true;
|
|
490
|
+
afterMatched[afterIndex] = true;
|
|
491
|
+
}
|
|
492
|
+
const remainingBefore = beforeIndices.filter((index) => !beforeMatched[index]);
|
|
493
|
+
const remainingAfter = afterIndices.filter((index) => !afterMatched[index]);
|
|
494
|
+
const modifiedCount = Math.min(remainingBefore.length, remainingAfter.length);
|
|
495
|
+
for (let index = 0; index < modifiedCount; index++) {
|
|
496
|
+
beforeMatched[remainingBefore[index]] = true;
|
|
497
|
+
afterMatched[remainingAfter[index]] = true;
|
|
498
|
+
modified[remainingAfter[index]] = true;
|
|
499
|
+
}
|
|
500
|
+
}
|
|
501
|
+
return {
|
|
502
|
+
added: after.filter((_, index) => !afterMatched[index]),
|
|
503
|
+
removed: before.filter((_, index) => !beforeMatched[index]),
|
|
504
|
+
modified: after.filter((_, index) => modified[index])
|
|
505
|
+
};
|
|
506
|
+
}
|
|
507
|
+
var DocxSession = class _DocxSession {
|
|
484
508
|
/** @internal */
|
|
485
509
|
constructor(handle, wasm) {
|
|
486
510
|
// ─── Raw escape hatch ────────────────────────────────────────────────
|
|
@@ -496,6 +520,336 @@ var Docxodus = (() => {
|
|
|
496
520
|
project() {
|
|
497
521
|
return JSON.parse(this.wasm.Project(this.handle));
|
|
498
522
|
}
|
|
523
|
+
/** Monotonic document version (0 at open; +1 per committed mutation/undo/redo). */
|
|
524
|
+
getVersion() {
|
|
525
|
+
return JSON.parse(this.wasm.GetVersion(this.handle)).version;
|
|
526
|
+
}
|
|
527
|
+
/**
|
|
528
|
+
* Verification manifest for the current logical checkpoint. Unsaved edits are included and
|
|
529
|
+
* the read does not mutate package bytes, caches, history, or the document version.
|
|
530
|
+
*/
|
|
531
|
+
getPackageManifest() {
|
|
532
|
+
return JSON.parse(this.wasm.GetPackageManifest(this.handle));
|
|
533
|
+
}
|
|
534
|
+
/** Register a browser-materialized PageMap without changing the document version. */
|
|
535
|
+
registerPageMap(pageMap, expectedRendererFingerprint) {
|
|
536
|
+
return JSON.parse(this.wasm.RegisterPageMap(
|
|
537
|
+
this.handle,
|
|
538
|
+
JSON.stringify(pageMap),
|
|
539
|
+
expectedRendererFingerprint ?? ""
|
|
540
|
+
));
|
|
541
|
+
}
|
|
542
|
+
getPageMapStatus(request) {
|
|
543
|
+
return JSON.parse(this.wasm.GetPageMapStatus(
|
|
544
|
+
this.handle,
|
|
545
|
+
request ? JSON.stringify(request) : ""
|
|
546
|
+
));
|
|
547
|
+
}
|
|
548
|
+
getPageCitation(anchorId, request) {
|
|
549
|
+
return JSON.parse(this.wasm.GetPageCitation(
|
|
550
|
+
this.handle,
|
|
551
|
+
anchorId,
|
|
552
|
+
JSON.stringify(request)
|
|
553
|
+
));
|
|
554
|
+
}
|
|
555
|
+
/** Evaluate optimistic guards without mutating or advancing the version. */
|
|
556
|
+
checkPreconditions(preconditions) {
|
|
557
|
+
return JSON.parse(
|
|
558
|
+
this.wasm.CheckPreconditions(this.handle, JSON.stringify(preconditions))
|
|
559
|
+
);
|
|
560
|
+
}
|
|
561
|
+
/**
|
|
562
|
+
* Guard any synchronous mutation. WASM calls are synchronous and single-threaded, so the
|
|
563
|
+
* check and callback form one uninterrupted client-side operation. Prefer a method's native
|
|
564
|
+
* `preconditions` option where it has one (notably replaceTextRange's match-count guard).
|
|
565
|
+
*/
|
|
566
|
+
runWithPreconditions(preconditions, mutation) {
|
|
567
|
+
const checked = this.checkPreconditions(preconditions);
|
|
568
|
+
return checked.success ? mutation() : checked;
|
|
569
|
+
}
|
|
570
|
+
/**
|
|
571
|
+
* Execute synchronous mutations atomically by default. Atomic success is one undo/version
|
|
572
|
+
* unit; any failed or thrown step restores the exact package and history checkpoint.
|
|
573
|
+
*/
|
|
574
|
+
executeBatch(steps, mode = "atomic") {
|
|
575
|
+
if (mode !== "atomic" && mode !== "best_effort") {
|
|
576
|
+
throw new RangeError(`unknown mutation batch mode: ${String(mode)}`);
|
|
577
|
+
}
|
|
578
|
+
const baseVersion = this.getVersion();
|
|
579
|
+
const observationWarnings = [];
|
|
580
|
+
const inspect = (label, read, fallback) => {
|
|
581
|
+
try {
|
|
582
|
+
return read();
|
|
583
|
+
} catch (error) {
|
|
584
|
+
observationWarnings.push(`${label} unavailable: ${error instanceof Error ? error.message : String(error)}`);
|
|
585
|
+
return fallback;
|
|
586
|
+
}
|
|
587
|
+
};
|
|
588
|
+
const beforeRevisions = inspect("Revision delta inspection", () => this.listRevisions(), []);
|
|
589
|
+
const beforeComments = inspect("Comment delta inspection", () => this.listComments(), []);
|
|
590
|
+
const beforeAnnotations = inspect("Annotation delta inspection", () => this.listAnnotations(), []);
|
|
591
|
+
const complete = (result) => {
|
|
592
|
+
try {
|
|
593
|
+
const revisionChanges = mutationBatchChangeSet(
|
|
594
|
+
beforeRevisions,
|
|
595
|
+
inspect("Revision delta inspection", () => this.listRevisions(), beforeRevisions),
|
|
596
|
+
(revision) => revision.id
|
|
597
|
+
);
|
|
598
|
+
const commentChanges = mutationBatchChangeSet(
|
|
599
|
+
beforeComments,
|
|
600
|
+
inspect("Comment delta inspection", () => this.listComments(), beforeComments),
|
|
601
|
+
(comment) => comment.anchorId
|
|
602
|
+
);
|
|
603
|
+
const annotationChanges = mutationBatchChangeSet(
|
|
604
|
+
beforeAnnotations,
|
|
605
|
+
inspect("Annotation delta inspection", () => this.listAnnotations(), beforeAnnotations),
|
|
606
|
+
(annotation) => annotation.id ?? ""
|
|
607
|
+
);
|
|
608
|
+
const resultVersion = inspect(
|
|
609
|
+
"Result version inspection",
|
|
610
|
+
() => this.getVersion(),
|
|
611
|
+
baseVersion
|
|
612
|
+
);
|
|
613
|
+
const warnings = [...observationWarnings];
|
|
614
|
+
if ([...revisionChanges.added, ...revisionChanges.modified].some((revision) => revision.date !== void 0 && revision.date !== null)) {
|
|
615
|
+
warnings.push("Tracked-revision date attributes may use the execution clock; compare revision ids, authors, types, text, and anchors across separate executions.");
|
|
616
|
+
}
|
|
617
|
+
if ([...commentChanges.added, ...commentChanges.modified].some((comment) => comment.date !== void 0 && comment.date !== null)) {
|
|
618
|
+
warnings.push("Comment date attributes may be generated from the execution clock; supply dates explicitly when byte-identical replay is required.");
|
|
619
|
+
}
|
|
620
|
+
if (annotationChanges.added.some((annotation) => annotation.created !== void 0 && annotation.created !== null)) {
|
|
621
|
+
warnings.push("Auto-generated annotation ids or creation timestamps are execution metadata; supply id and created explicitly when byte-identical replay is required.");
|
|
622
|
+
}
|
|
623
|
+
if (result.steps.some((step) => step.results.some((edit) => edit.created.length > 0))) {
|
|
624
|
+
warnings.push("Created anchors and related OOXML ids may be generated independently on replay; preview/apply equivalence is semantic and packageHash or anchor ids may differ.");
|
|
625
|
+
}
|
|
626
|
+
if (mode === "best_effort" && !result.success) {
|
|
627
|
+
warnings.push("Best-effort execution retains every successful step despite later failures.");
|
|
628
|
+
}
|
|
629
|
+
let packageHash = null;
|
|
630
|
+
if (!this.wasm.GetPackageContentHash) {
|
|
631
|
+
warnings.push("This WASM bundle predates package equivalence hashes; packageHash is unavailable.");
|
|
632
|
+
} else {
|
|
633
|
+
try {
|
|
634
|
+
packageHash = this.wasm.GetPackageContentHash(this.handle);
|
|
635
|
+
} catch (error) {
|
|
636
|
+
warnings.push(`Package equivalence hash unavailable: ${error instanceof Error ? error.message : String(error)}`);
|
|
637
|
+
}
|
|
638
|
+
}
|
|
639
|
+
return {
|
|
640
|
+
...result,
|
|
641
|
+
preview: false,
|
|
642
|
+
baseVersion,
|
|
643
|
+
resultVersion,
|
|
644
|
+
packageHash,
|
|
645
|
+
revisionChanges,
|
|
646
|
+
commentChanges,
|
|
647
|
+
annotationChanges,
|
|
648
|
+
warnings,
|
|
649
|
+
html: null
|
|
650
|
+
};
|
|
651
|
+
} catch (error) {
|
|
652
|
+
return {
|
|
653
|
+
...result,
|
|
654
|
+
preview: false,
|
|
655
|
+
baseVersion,
|
|
656
|
+
resultVersion: inspect("Result version inspection", () => this.getVersion(), baseVersion),
|
|
657
|
+
packageHash: null,
|
|
658
|
+
revisionChanges: { added: [], removed: [], modified: [] },
|
|
659
|
+
commentChanges: { added: [], removed: [], modified: [] },
|
|
660
|
+
annotationChanges: { added: [], removed: [], modified: [] },
|
|
661
|
+
warnings: [
|
|
662
|
+
...observationWarnings,
|
|
663
|
+
`Batch receipt enrichment unavailable: ${error instanceof Error ? error.message : String(error)}`
|
|
664
|
+
],
|
|
665
|
+
html: null
|
|
666
|
+
};
|
|
667
|
+
}
|
|
668
|
+
};
|
|
669
|
+
const internalFailure = (value) => ({
|
|
670
|
+
success: false,
|
|
671
|
+
error: { code: "internal_error", message: value instanceof Error ? value.message : String(value) },
|
|
672
|
+
created: [],
|
|
673
|
+
removed: [],
|
|
674
|
+
modified: []
|
|
675
|
+
});
|
|
676
|
+
const run = (step) => {
|
|
677
|
+
try {
|
|
678
|
+
const value = step.mutation();
|
|
679
|
+
const results = Array.isArray(value) ? value : [value];
|
|
680
|
+
if (results.some((result) => result === null || typeof result !== "object" || typeof result.success !== "boolean")) {
|
|
681
|
+
return [internalFailure("batch mutation returned invalid edit results")];
|
|
682
|
+
}
|
|
683
|
+
return results;
|
|
684
|
+
} catch (error) {
|
|
685
|
+
return [internalFailure(error)];
|
|
686
|
+
}
|
|
687
|
+
};
|
|
688
|
+
const failureOf = (step, rolledBack) => ({
|
|
689
|
+
index: step.index,
|
|
690
|
+
tool: step.tool,
|
|
691
|
+
action: step.action,
|
|
692
|
+
error: step.results.find((result) => !result.success)?.error ?? { code: "internal_error", message: "batch step failed without an error" },
|
|
693
|
+
rolledBack
|
|
694
|
+
});
|
|
695
|
+
const preflightOne = (step) => {
|
|
696
|
+
try {
|
|
697
|
+
return step.preflight?.();
|
|
698
|
+
} catch (error) {
|
|
699
|
+
return internalFailure(error).error;
|
|
700
|
+
}
|
|
701
|
+
};
|
|
702
|
+
if (mode === "atomic") {
|
|
703
|
+
const preflight = steps.map(preflightOne);
|
|
704
|
+
const failedPreflight = preflight.findIndex((error) => error !== void 0);
|
|
705
|
+
if (failedPreflight >= 0) {
|
|
706
|
+
const source = steps[failedPreflight];
|
|
707
|
+
const failed2 = {
|
|
708
|
+
index: failedPreflight,
|
|
709
|
+
tool: source.tool,
|
|
710
|
+
action: source.action,
|
|
711
|
+
success: false,
|
|
712
|
+
rolledBack: true,
|
|
713
|
+
results: [{ success: false, error: preflight[failedPreflight], created: [], removed: [], modified: [] }]
|
|
714
|
+
};
|
|
715
|
+
return complete({
|
|
716
|
+
mode,
|
|
717
|
+
status: "failed",
|
|
718
|
+
success: false,
|
|
719
|
+
rolledBack: true,
|
|
720
|
+
steps: [failed2],
|
|
721
|
+
failure: failureOf(failed2, true)
|
|
722
|
+
});
|
|
723
|
+
}
|
|
724
|
+
const transaction = this.wasm.BeginTransaction(this.handle);
|
|
725
|
+
const completed2 = [];
|
|
726
|
+
try {
|
|
727
|
+
for (let index = 0; index < steps.length; index++) {
|
|
728
|
+
const source = steps[index];
|
|
729
|
+
const results = run(source);
|
|
730
|
+
const step = {
|
|
731
|
+
index,
|
|
732
|
+
tool: source.tool,
|
|
733
|
+
action: source.action,
|
|
734
|
+
success: results.every((result) => result.success),
|
|
735
|
+
rolledBack: false,
|
|
736
|
+
results
|
|
737
|
+
};
|
|
738
|
+
completed2.push(step);
|
|
739
|
+
if (!step.success) {
|
|
740
|
+
this.wasm.RollbackTransaction(transaction);
|
|
741
|
+
const rolledBack = completed2.map((value) => ({ ...value, rolledBack: true }));
|
|
742
|
+
const failed2 = rolledBack[rolledBack.length - 1];
|
|
743
|
+
return complete({
|
|
744
|
+
mode,
|
|
745
|
+
status: "failed",
|
|
746
|
+
success: false,
|
|
747
|
+
rolledBack: true,
|
|
748
|
+
steps: rolledBack,
|
|
749
|
+
failure: failureOf(failed2, true)
|
|
750
|
+
});
|
|
751
|
+
}
|
|
752
|
+
}
|
|
753
|
+
this.wasm.CommitTransaction(transaction);
|
|
754
|
+
return complete({ mode, status: "ok", success: true, rolledBack: false, steps: completed2 });
|
|
755
|
+
} catch (error) {
|
|
756
|
+
try {
|
|
757
|
+
this.wasm.RollbackTransaction(transaction);
|
|
758
|
+
} catch {
|
|
759
|
+
}
|
|
760
|
+
throw error;
|
|
761
|
+
}
|
|
762
|
+
}
|
|
763
|
+
const completed = steps.map((source, index) => {
|
|
764
|
+
const preflight = preflightOne(source);
|
|
765
|
+
const results = preflight ? [{ success: false, error: preflight, created: [], removed: [], modified: [] }] : run(source);
|
|
766
|
+
return {
|
|
767
|
+
index,
|
|
768
|
+
tool: source.tool,
|
|
769
|
+
action: source.action,
|
|
770
|
+
success: results.every((result) => result.success),
|
|
771
|
+
rolledBack: false,
|
|
772
|
+
results
|
|
773
|
+
};
|
|
774
|
+
});
|
|
775
|
+
const failed = completed.find((step) => !step.success);
|
|
776
|
+
return complete({
|
|
777
|
+
mode,
|
|
778
|
+
status: failed ? completed.some((step) => step.success) ? "partial" : "failed" : "ok",
|
|
779
|
+
success: failed === void 0,
|
|
780
|
+
rolledBack: false,
|
|
781
|
+
steps: completed,
|
|
782
|
+
failure: failed ? failureOf(failed, false) : void 0
|
|
783
|
+
});
|
|
784
|
+
}
|
|
785
|
+
/**
|
|
786
|
+
* Execute the same callback batch algorithm against a complete isolated package clone.
|
|
787
|
+
* Callbacks receive the shadow session explicitly; mutate that argument. The live session's
|
|
788
|
+
* package, caches, version, configuration, and undo/redo history are never execution targets.
|
|
789
|
+
*/
|
|
790
|
+
previewBatch(steps, mode = "atomic", options) {
|
|
791
|
+
if (mode !== "atomic" && mode !== "best_effort") {
|
|
792
|
+
throw new RangeError(`unknown mutation batch mode: ${String(mode)}`);
|
|
793
|
+
}
|
|
794
|
+
const htmlMode = options?.html ?? "none";
|
|
795
|
+
if (htmlMode !== "none" && htmlMode !== "scoped" && htmlMode !== "full") {
|
|
796
|
+
throw new RangeError(`unknown preview HTML mode: ${String(htmlMode)}`);
|
|
797
|
+
}
|
|
798
|
+
if (!this.wasm.OpenPreviewSession) {
|
|
799
|
+
throw new Error("This WASM bundle does not support isolated mutation previews.");
|
|
800
|
+
}
|
|
801
|
+
const shadow = new _DocxSession(this.wasm.OpenPreviewSession(this.handle), this.wasm);
|
|
802
|
+
try {
|
|
803
|
+
const result = shadow.executeBatch(
|
|
804
|
+
steps.map((step) => ({
|
|
805
|
+
tool: step.tool,
|
|
806
|
+
action: step.action,
|
|
807
|
+
mutation: () => step.mutation(shadow),
|
|
808
|
+
preflight: step.preflight ? () => step.preflight(shadow) : void 0
|
|
809
|
+
})),
|
|
810
|
+
mode
|
|
811
|
+
);
|
|
812
|
+
const warnings = [...result.warnings];
|
|
813
|
+
let html = null;
|
|
814
|
+
const unwrapRendered = (rendered) => {
|
|
815
|
+
if (rendered.trimStart().startsWith("{")) {
|
|
816
|
+
const envelope = JSON.parse(rendered);
|
|
817
|
+
if (envelope.error) {
|
|
818
|
+
warnings.push(`Preview HTML could not be generated: ${envelope.error}`);
|
|
819
|
+
return null;
|
|
820
|
+
}
|
|
821
|
+
}
|
|
822
|
+
return rendered;
|
|
823
|
+
};
|
|
824
|
+
const legacyProfileWarning = "This WASM bundle predates the shared preview HTML profile; preview HTML omits comments, annotations and headers/footers and may differ from other surfaces.";
|
|
825
|
+
try {
|
|
826
|
+
if (htmlMode === "scoped") {
|
|
827
|
+
if (!options?.htmlAnchorId) {
|
|
828
|
+
warnings.push("Scoped HTML was requested without htmlAnchorId; no HTML was generated.");
|
|
829
|
+
} else if (this.wasm.RenderPreviewBlockHtml) {
|
|
830
|
+
html = unwrapRendered(
|
|
831
|
+
this.wasm.RenderPreviewBlockHtml(shadow.handle, options.htmlAnchorId)
|
|
832
|
+
);
|
|
833
|
+
} else {
|
|
834
|
+
warnings.push(legacyProfileWarning);
|
|
835
|
+
html = shadow.renderBlock(options.htmlAnchorId);
|
|
836
|
+
}
|
|
837
|
+
} else if (htmlMode === "full") {
|
|
838
|
+
if (this.wasm.RenderPreviewHtml) {
|
|
839
|
+
html = unwrapRendered(this.wasm.RenderPreviewHtml(shadow.handle));
|
|
840
|
+
} else {
|
|
841
|
+
warnings.push(legacyProfileWarning);
|
|
842
|
+
html = unwrapRendered(this.wasm.RenderHtmlForReview ? this.wasm.RenderHtmlForReview(shadow.handle, "docx-", false, false, 1, true) : this.wasm.RenderHtml(shadow.handle, "docx-", false, false, 1));
|
|
843
|
+
}
|
|
844
|
+
}
|
|
845
|
+
} catch (error) {
|
|
846
|
+
warnings.push(`Preview HTML could not be generated: ${error instanceof Error ? error.message : String(error)}`);
|
|
847
|
+
}
|
|
848
|
+
return { ...result, preview: true, warnings, html };
|
|
849
|
+
} finally {
|
|
850
|
+
shadow.close();
|
|
851
|
+
}
|
|
852
|
+
}
|
|
499
853
|
/**
|
|
500
854
|
* Project a slice of the document keyed off an anchor — useful for showing
|
|
501
855
|
* one section to an LLM at a time without paying the cost of projecting the
|
|
@@ -511,9 +865,9 @@ var Docxodus = (() => {
|
|
|
511
865
|
*
|
|
512
866
|
* @see docs/architecture/docx_mutation_api.md
|
|
513
867
|
*/
|
|
514
|
-
projectAnchor(anchorId, depth = 2 /* SubtreeAndFollowingSiblings
|
|
868
|
+
projectAnchor(anchorId, depth = 2 /* SubtreeAndFollowingSiblings */, citation) {
|
|
515
869
|
return JSON.parse(
|
|
516
|
-
this.wasm.ProjectAnchor(this.handle, anchorId, depth)
|
|
870
|
+
citation ? this.wasm.ProjectAnchorWithCitations(this.handle, anchorId, depth, JSON.stringify(citation)) : this.wasm.ProjectAnchor(this.handle, anchorId, depth)
|
|
517
871
|
);
|
|
518
872
|
}
|
|
519
873
|
/**
|
|
@@ -537,11 +891,21 @@ var Docxodus = (() => {
|
|
|
537
891
|
return html;
|
|
538
892
|
}
|
|
539
893
|
// ─── Tier A: text CRUD ───────────────────────────────────────────────
|
|
540
|
-
replaceText(anchorId, markdown) {
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
return
|
|
894
|
+
replaceText(anchorId, markdown, preconditions) {
|
|
895
|
+
const apply = () => JSON.parse(
|
|
896
|
+
this.wasm.ReplaceText(this.handle, anchorId, markdown)
|
|
897
|
+
);
|
|
898
|
+
return preconditions ? this.runWithPreconditions(
|
|
899
|
+
{ ...preconditions, anchorId: preconditions.anchorId ?? anchorId },
|
|
900
|
+
apply
|
|
901
|
+
) : apply();
|
|
902
|
+
}
|
|
903
|
+
deleteBlock(anchorId, preconditions) {
|
|
904
|
+
const apply = () => JSON.parse(this.wasm.DeleteBlock(this.handle, anchorId));
|
|
905
|
+
return preconditions ? this.runWithPreconditions(
|
|
906
|
+
{ ...preconditions, anchorId: preconditions.anchorId ?? anchorId },
|
|
907
|
+
apply
|
|
908
|
+
) : apply();
|
|
545
909
|
}
|
|
546
910
|
/** Reorder one top-level paragraph/heading/list/table block relative to another. */
|
|
547
911
|
moveBlock(sourceAnchorId, targetAnchorId, position) {
|
|
@@ -596,7 +960,7 @@ var Docxodus = (() => {
|
|
|
596
960
|
}
|
|
597
961
|
/**
|
|
598
962
|
* Insert a `rows`×`cols` table before/after the block. `options` controls borders, row-major
|
|
599
|
-
* cell markdown, and cell alignment. The returned `EditResult.created` lists
|
|
963
|
+
* cell markdown, and cell alignment. The returned `EditResult.created` lists canonical `tc`
|
|
600
964
|
* anchors (row-major), so each cell can then be addressed to fill/format.
|
|
601
965
|
*/
|
|
602
966
|
insertTable(anchorId, position, rows, cols, options) {
|
|
@@ -605,10 +969,26 @@ var Docxodus = (() => {
|
|
|
605
969
|
this.wasm.InsertTable(this.handle, anchorId, position, rows, cols, optionsJson)
|
|
606
970
|
);
|
|
607
971
|
}
|
|
972
|
+
/** Resolve a canonical `tbl` anchor to explicit table/row/column/cell identities. */
|
|
973
|
+
getTableMetadata(tableAnchorId) {
|
|
974
|
+
return JSON.parse(this.wasm.GetTableMetadata(this.handle, tableAnchorId));
|
|
975
|
+
}
|
|
976
|
+
/** Resolve a canonical `tc` anchor to its zero-based table-grid coordinate and spans. */
|
|
977
|
+
resolveTableCellAnchor(cellAnchorId) {
|
|
978
|
+
return JSON.parse(
|
|
979
|
+
this.wasm.ResolveTableCellAnchor(this.handle, cellAnchorId)
|
|
980
|
+
);
|
|
981
|
+
}
|
|
982
|
+
/** Resolve a zero-based table-grid coordinate to the physical `tc` covering it. */
|
|
983
|
+
resolveTableCellCoordinate(tableAnchorId, rowIndex, columnIndex) {
|
|
984
|
+
return JSON.parse(
|
|
985
|
+
this.wasm.ResolveTableCellCoordinate(this.handle, tableAnchorId, rowIndex, columnIndex)
|
|
986
|
+
);
|
|
987
|
+
}
|
|
608
988
|
/**
|
|
609
|
-
* Table row/column editing, addressed by
|
|
610
|
-
* {@link insertTable}'s `created`
|
|
611
|
-
* empty (`created` lists
|
|
989
|
+
* Table row/column editing, addressed by the canonical `tc` anchor returned from
|
|
990
|
+
* {@link insertTable}'s `created` or table metadata. Insert clones the reference row/column's
|
|
991
|
+
* widths and starts empty (`created` lists new `tc` anchors); delete of the last row/column removes
|
|
612
992
|
* the whole table. All four are grid-aware: inserting across a merge extends it, deleting
|
|
613
993
|
* through one narrows it, and deleting a vertical merge's lead row promotes the next row to
|
|
614
994
|
* carry it — the grid is never left ragged.
|
|
@@ -648,7 +1028,7 @@ var Docxodus = (() => {
|
|
|
648
1028
|
return JSON.parse(this.wasm.UnmergeCells(this.handle, cellAnchorId));
|
|
649
1029
|
}
|
|
650
1030
|
/**
|
|
651
|
-
* Table styling, addressed by a
|
|
1031
|
+
* Table styling, addressed by a canonical `tc` anchor — the post-insert counterpart of
|
|
652
1032
|
* {@link insertTable}'s options (issue #315 Stage A). `setColumnWidths` retunes `w:tblGrid` +
|
|
653
1033
|
* every row's cell width (one positive twip value per column) and pins the table to fixed
|
|
654
1034
|
* layout, exactly as inserting with explicit `columnWidths` would.
|
|
@@ -688,6 +1068,19 @@ var Docxodus = (() => {
|
|
|
688
1068
|
this.wasm.SetRepeatHeaderRow(this.handle, cellAnchorId, repeat)
|
|
689
1069
|
);
|
|
690
1070
|
}
|
|
1071
|
+
/** Apply row layout options to the row containing the canonical cell anchor. */
|
|
1072
|
+
setTableRowOptions(cellAnchorId, options) {
|
|
1073
|
+
return JSON.parse(
|
|
1074
|
+
this.wasm.SetTableRowOptions(
|
|
1075
|
+
this.handle,
|
|
1076
|
+
cellAnchorId,
|
|
1077
|
+
options.repeatHeader ?? null,
|
|
1078
|
+
options.allowBreakAcrossPages ?? null,
|
|
1079
|
+
options.heightTwips ?? null,
|
|
1080
|
+
options.heightRule ?? "atLeast"
|
|
1081
|
+
)
|
|
1082
|
+
);
|
|
1083
|
+
}
|
|
691
1084
|
// ─── Headers / footers / page numbers ────────────────────────────────
|
|
692
1085
|
/**
|
|
693
1086
|
* Set the running header story for the section that owns `anchorId` (any body block in that
|
|
@@ -738,6 +1131,58 @@ var Docxodus = (() => {
|
|
|
738
1131
|
this.wasm.SetPageNumbering(this.handle, anchorId, JSON.stringify(op))
|
|
739
1132
|
);
|
|
740
1133
|
}
|
|
1134
|
+
/**
|
|
1135
|
+
* Insert a **table of contents** before or after `anchorId` (issue #607). The field is written
|
|
1136
|
+
* dirty and the document asks for a field update on open, so Word paginates and fills the table
|
|
1137
|
+
* itself — nothing here ships a cached result that is stale the moment anything above it moves.
|
|
1138
|
+
*
|
|
1139
|
+
* The table is wrapped in the `w:sdt` content control Word puts around one, which is what gives
|
|
1140
|
+
* it the *Update Table* control in Word's UI. Word's `TOCHeading` and `TOC1` styles are
|
|
1141
|
+
* find-or-created; a document that already defines them keeps its own.
|
|
1142
|
+
*
|
|
1143
|
+
* Body anchors only, and refused under tracked-change recording: a generated table is regenerated
|
|
1144
|
+
* wholesale on every field update, so there is no reversible way to redline it.
|
|
1145
|
+
*/
|
|
1146
|
+
insertTableOfContents(anchorId, pos = "before", options) {
|
|
1147
|
+
return JSON.parse(
|
|
1148
|
+
this.wasm.InsertTableOfContents(
|
|
1149
|
+
this.handle,
|
|
1150
|
+
anchorId,
|
|
1151
|
+
pos,
|
|
1152
|
+
options ? JSON.stringify(options) : ""
|
|
1153
|
+
)
|
|
1154
|
+
);
|
|
1155
|
+
}
|
|
1156
|
+
/**
|
|
1157
|
+
* Insert a **table of figures** — the captions carrying `options.captionLabel` and their page
|
|
1158
|
+
* numbers. Same field mechanics as {@link insertTableOfContents}; Word writes this one as a bare
|
|
1159
|
+
* paragraph rather than inside a content control, so this does too.
|
|
1160
|
+
*/
|
|
1161
|
+
insertTableOfFigures(anchorId, pos = "before", options) {
|
|
1162
|
+
return JSON.parse(
|
|
1163
|
+
this.wasm.InsertTableOfFigures(
|
|
1164
|
+
this.handle,
|
|
1165
|
+
anchorId,
|
|
1166
|
+
pos,
|
|
1167
|
+
options ? JSON.stringify(options) : ""
|
|
1168
|
+
)
|
|
1169
|
+
);
|
|
1170
|
+
}
|
|
1171
|
+
/**
|
|
1172
|
+
* Insert a **table of authorities** — the cases, statutes or other authorities marked in the
|
|
1173
|
+
* document, grouped by `options.category`. The table lists entries the document has MARKED with
|
|
1174
|
+
* `TA` fields; a document with no marked citations produces a table that is correct and empty.
|
|
1175
|
+
*/
|
|
1176
|
+
insertTableOfAuthorities(anchorId, pos = "before", options) {
|
|
1177
|
+
return JSON.parse(
|
|
1178
|
+
this.wasm.InsertTableOfAuthorities(
|
|
1179
|
+
this.handle,
|
|
1180
|
+
anchorId,
|
|
1181
|
+
pos,
|
|
1182
|
+
options ? JSON.stringify(options) : ""
|
|
1183
|
+
)
|
|
1184
|
+
);
|
|
1185
|
+
}
|
|
741
1186
|
/**
|
|
742
1187
|
* Remove the section's page-numbering start/format: it reverts to continuing the previous
|
|
743
1188
|
* section's numbering in Word's default `1, 2, 3`. Chapter-numbering attributes
|
|
@@ -782,6 +1227,25 @@ var Docxodus = (() => {
|
|
|
782
1227
|
this.wasm.InsertFootnote(this.handle, anchorId, characterOffset, markdown)
|
|
783
1228
|
);
|
|
784
1229
|
}
|
|
1230
|
+
/**
|
|
1231
|
+
* Insert a Word-faithful internal cross-reference — a `REF` field targeting an existing
|
|
1232
|
+
* bookmark — at a character offset (issue #545). The field carries a cached result run
|
|
1233
|
+
* (the bookmarked text, or the target's auto-number under `referenceNumber`), so
|
|
1234
|
+
* renderers that do not recompute fields show a faithful snapshot and Word updates it
|
|
1235
|
+
* like a hand-authored cross-reference. A missing or incoherent bookmark fails with
|
|
1236
|
+
* `missing_bookmark_target`.
|
|
1237
|
+
*/
|
|
1238
|
+
insertCrossReference(anchorId, characterOffset, bookmarkName, options) {
|
|
1239
|
+
return JSON.parse(
|
|
1240
|
+
this.wasm.InsertCrossReference(
|
|
1241
|
+
this.handle,
|
|
1242
|
+
anchorId,
|
|
1243
|
+
characterOffset,
|
|
1244
|
+
bookmarkName,
|
|
1245
|
+
options ? JSON.stringify(options) : ""
|
|
1246
|
+
)
|
|
1247
|
+
);
|
|
1248
|
+
}
|
|
785
1249
|
/** Create an endnote — see {@link insertFootnote}; writes the endnotes part and a
|
|
786
1250
|
* `w:endnoteReference`, and the created definition anchor has kind `en`. */
|
|
787
1251
|
insertEndnote(anchorId, characterOffset, markdown) {
|
|
@@ -873,6 +1337,170 @@ var Docxodus = (() => {
|
|
|
873
1337
|
listComments() {
|
|
874
1338
|
return JSON.parse(this.wasm.ListComments(this.handle));
|
|
875
1339
|
}
|
|
1340
|
+
listHyperlinks(scopes = 63 /* All */) {
|
|
1341
|
+
return JSON.parse(this.wasm.ListHyperlinks(this.handle, scopes));
|
|
1342
|
+
}
|
|
1343
|
+
addHyperlink(anchorId, span, kind, target) {
|
|
1344
|
+
return JSON.parse(this.wasm.AddHyperlink(
|
|
1345
|
+
this.handle,
|
|
1346
|
+
anchorId,
|
|
1347
|
+
span.start,
|
|
1348
|
+
span.length,
|
|
1349
|
+
kind,
|
|
1350
|
+
target
|
|
1351
|
+
));
|
|
1352
|
+
}
|
|
1353
|
+
updateHyperlink(hyperlinkId, kind, target) {
|
|
1354
|
+
return JSON.parse(this.wasm.UpdateHyperlink(this.handle, hyperlinkId, kind, target));
|
|
1355
|
+
}
|
|
1356
|
+
removeHyperlink(hyperlinkId) {
|
|
1357
|
+
return JSON.parse(this.wasm.RemoveHyperlink(this.handle, hyperlinkId));
|
|
1358
|
+
}
|
|
1359
|
+
/** Versioned operational facts for native image inspection/mutation in this runtime. */
|
|
1360
|
+
getImageCapabilities() {
|
|
1361
|
+
return JSON.parse(this.wasm.GetImageCapabilities());
|
|
1362
|
+
}
|
|
1363
|
+
listImages(scopes = 63 /* All */) {
|
|
1364
|
+
return JSON.parse(this.wasm.ListImages(this.handle, scopes));
|
|
1365
|
+
}
|
|
1366
|
+
insertImage(anchorId, characterOffset, bytes, options = {}) {
|
|
1367
|
+
return JSON.parse(this.wasm.InsertImage(
|
|
1368
|
+
this.handle,
|
|
1369
|
+
anchorId,
|
|
1370
|
+
characterOffset,
|
|
1371
|
+
imageBytesToBase64(bytes),
|
|
1372
|
+
JSON.stringify(options)
|
|
1373
|
+
));
|
|
1374
|
+
}
|
|
1375
|
+
replaceImage(imageId, bytes) {
|
|
1376
|
+
return JSON.parse(this.wasm.ReplaceImage(
|
|
1377
|
+
this.handle,
|
|
1378
|
+
imageId,
|
|
1379
|
+
imageBytesToBase64(bytes)
|
|
1380
|
+
));
|
|
1381
|
+
}
|
|
1382
|
+
setImageDimensions(imageId, dimensions) {
|
|
1383
|
+
return JSON.parse(this.wasm.SetImageDimensions(
|
|
1384
|
+
this.handle,
|
|
1385
|
+
imageId,
|
|
1386
|
+
JSON.stringify(dimensions)
|
|
1387
|
+
));
|
|
1388
|
+
}
|
|
1389
|
+
setImageMetadata(imageId, altText, title) {
|
|
1390
|
+
return JSON.parse(this.wasm.SetImageMetadata(
|
|
1391
|
+
this.handle,
|
|
1392
|
+
imageId,
|
|
1393
|
+
altText,
|
|
1394
|
+
title
|
|
1395
|
+
));
|
|
1396
|
+
}
|
|
1397
|
+
setImageFloatingLayout(imageId, layout) {
|
|
1398
|
+
return JSON.parse(this.wasm.SetImageFloatingLayout(
|
|
1399
|
+
this.handle,
|
|
1400
|
+
imageId,
|
|
1401
|
+
JSON.stringify(layout)
|
|
1402
|
+
));
|
|
1403
|
+
}
|
|
1404
|
+
removeImage(imageId) {
|
|
1405
|
+
return JSON.parse(this.wasm.RemoveImage(this.handle, imageId));
|
|
1406
|
+
}
|
|
1407
|
+
/** Native Word structured-document tags, in outer-before-inner story order. */
|
|
1408
|
+
listContentControls(scopes = 63 /* All */) {
|
|
1409
|
+
return JSON.parse(this.wasm.ListContentControls(this.handle, scopes));
|
|
1410
|
+
}
|
|
1411
|
+
fillContentControlText(anchorId, text, options = {}) {
|
|
1412
|
+
return JSON.parse(this.wasm.FillContentControlText(
|
|
1413
|
+
this.handle,
|
|
1414
|
+
anchorId,
|
|
1415
|
+
text,
|
|
1416
|
+
JSON.stringify(options)
|
|
1417
|
+
));
|
|
1418
|
+
}
|
|
1419
|
+
fillContentControlRichText(anchorId, markdown, options = {}) {
|
|
1420
|
+
return JSON.parse(this.wasm.FillContentControlRichText(
|
|
1421
|
+
this.handle,
|
|
1422
|
+
anchorId,
|
|
1423
|
+
markdown,
|
|
1424
|
+
JSON.stringify(options)
|
|
1425
|
+
));
|
|
1426
|
+
}
|
|
1427
|
+
setContentControlChecked(anchorId, isChecked, options = {}) {
|
|
1428
|
+
return JSON.parse(this.wasm.SetContentControlChecked(
|
|
1429
|
+
this.handle,
|
|
1430
|
+
anchorId,
|
|
1431
|
+
isChecked,
|
|
1432
|
+
JSON.stringify(options)
|
|
1433
|
+
));
|
|
1434
|
+
}
|
|
1435
|
+
setContentControlDate(anchorId, value, displayText, options = {}) {
|
|
1436
|
+
const timestamp = value instanceof Date ? value.toISOString() : value;
|
|
1437
|
+
return JSON.parse(this.wasm.SetContentControlDate(
|
|
1438
|
+
this.handle,
|
|
1439
|
+
anchorId,
|
|
1440
|
+
timestamp,
|
|
1441
|
+
displayText ?? null,
|
|
1442
|
+
JSON.stringify(options)
|
|
1443
|
+
));
|
|
1444
|
+
}
|
|
1445
|
+
selectContentControlItem(anchorId, value, options = {}) {
|
|
1446
|
+
return JSON.parse(this.wasm.SelectContentControlItem(
|
|
1447
|
+
this.handle,
|
|
1448
|
+
anchorId,
|
|
1449
|
+
value,
|
|
1450
|
+
JSON.stringify(options)
|
|
1451
|
+
));
|
|
1452
|
+
}
|
|
1453
|
+
fillContentControlPicture(anchorId, bytes, options = {}) {
|
|
1454
|
+
return JSON.parse(this.wasm.FillContentControlPicture(
|
|
1455
|
+
this.handle,
|
|
1456
|
+
anchorId,
|
|
1457
|
+
imageBytesToBase64(bytes),
|
|
1458
|
+
JSON.stringify(options)
|
|
1459
|
+
));
|
|
1460
|
+
}
|
|
1461
|
+
addRepeatingSectionItem(sectionAnchorId, afterItemAnchorId, options = {}) {
|
|
1462
|
+
return JSON.parse(this.wasm.AddRepeatingSectionItem(
|
|
1463
|
+
this.handle,
|
|
1464
|
+
sectionAnchorId,
|
|
1465
|
+
afterItemAnchorId ?? "",
|
|
1466
|
+
JSON.stringify(options)
|
|
1467
|
+
));
|
|
1468
|
+
}
|
|
1469
|
+
removeRepeatingSectionItem(itemAnchorId) {
|
|
1470
|
+
return JSON.parse(this.wasm.RemoveRepeatingSectionItem(
|
|
1471
|
+
this.handle,
|
|
1472
|
+
itemAnchorId
|
|
1473
|
+
));
|
|
1474
|
+
}
|
|
1475
|
+
listBookmarks(scopes = 63 /* All */) {
|
|
1476
|
+
return JSON.parse(this.wasm.ListBookmarks(this.handle, scopes));
|
|
1477
|
+
}
|
|
1478
|
+
addBookmark(name, range) {
|
|
1479
|
+
return JSON.parse(this.wasm.AddBookmark(
|
|
1480
|
+
this.handle,
|
|
1481
|
+
name,
|
|
1482
|
+
range.startAnchorId,
|
|
1483
|
+
range.startOffset,
|
|
1484
|
+
range.endAnchorId,
|
|
1485
|
+
range.endOffset
|
|
1486
|
+
));
|
|
1487
|
+
}
|
|
1488
|
+
renameBookmark(name, newName) {
|
|
1489
|
+
return JSON.parse(this.wasm.RenameBookmark(this.handle, name, newName));
|
|
1490
|
+
}
|
|
1491
|
+
moveBookmark(name, range) {
|
|
1492
|
+
return JSON.parse(this.wasm.MoveBookmark(
|
|
1493
|
+
this.handle,
|
|
1494
|
+
name,
|
|
1495
|
+
range.startAnchorId,
|
|
1496
|
+
range.startOffset,
|
|
1497
|
+
range.endAnchorId,
|
|
1498
|
+
range.endOffset
|
|
1499
|
+
));
|
|
1500
|
+
}
|
|
1501
|
+
removeBookmark(name) {
|
|
1502
|
+
return JSON.parse(this.wasm.RemoveBookmark(this.handle, name));
|
|
1503
|
+
}
|
|
876
1504
|
// ─── Tracked revisions (issue #318) ──────────────────────────────────
|
|
877
1505
|
/** Markup-native tracked-revision listing, in document order across body, headers,
|
|
878
1506
|
* footers, footnotes, and endnotes. Ids are stable while the underlying markup
|
|
@@ -893,6 +1521,22 @@ var Docxodus = (() => {
|
|
|
893
1521
|
rejectRevision(revisionId) {
|
|
894
1522
|
return JSON.parse(this.wasm.RejectRevision(this.handle, revisionId));
|
|
895
1523
|
}
|
|
1524
|
+
/**
|
|
1525
|
+
* Accept every live revision as one undoable session mutation.
|
|
1526
|
+
*
|
|
1527
|
+
* Fails closed: an unsupported, malformed, or ambiguous registry entry aborts the whole
|
|
1528
|
+
* operation (`revisionUnsupported`/`revisionMalformed`/`revisionAmbiguous`) and nothing is
|
|
1529
|
+
* mutated. There is no force mode — call {@link listRevisions} and read each entry's
|
|
1530
|
+
* `diagnostic` to see what blocks it.
|
|
1531
|
+
*/
|
|
1532
|
+
acceptAllRevisions() {
|
|
1533
|
+
return JSON.parse(this.wasm.AcceptAllRevisions(this.handle));
|
|
1534
|
+
}
|
|
1535
|
+
/** Reject every live revision as one undoable session mutation. Fails closed exactly like
|
|
1536
|
+
* {@link acceptAllRevisions}. */
|
|
1537
|
+
rejectAllRevisions() {
|
|
1538
|
+
return JSON.parse(this.wasm.RejectAllRevisions(this.handle));
|
|
1539
|
+
}
|
|
896
1540
|
// ─── Tier C: formatting ──────────────────────────────────────────────
|
|
897
1541
|
applyFormat(anchorId, span, op) {
|
|
898
1542
|
const spanJson = span ? JSON.stringify(span) : "";
|
|
@@ -998,6 +1642,10 @@ var Docxodus = (() => {
|
|
|
998
1642
|
* replaces it with `replace`, preserving the surrounding run formatting that
|
|
999
1643
|
* the match didn't touch. Returns one `EditResult` per attempted match.
|
|
1000
1644
|
*
|
|
1645
|
+
* A `find` that matches nothing fails with a single `text_not_found` result
|
|
1646
|
+
* naming the anchor and the needle — never an empty array. Pass
|
|
1647
|
+
* `expectedMatchCount: 0` to assert absence as a successful no-op instead.
|
|
1648
|
+
*
|
|
1001
1649
|
* Run-formatting contract: the replacement text inherits the formatting of
|
|
1002
1650
|
* the FIRST run the match spanned. Middle/trailing runs keep their `w:rPr`
|
|
1003
1651
|
* but lose the slice of text the match consumed.
|
|
@@ -1174,10 +1822,16 @@ var Docxodus = (() => {
|
|
|
1174
1822
|
*
|
|
1175
1823
|
* @see docs/architecture/docx_mutation_api.md#findplaceholders
|
|
1176
1824
|
*/
|
|
1177
|
-
findPlaceholders(kinds = PlaceholderKinds.All, scope = 1, contextChars = 80, boundary = ContextBoundary.Char) {
|
|
1178
|
-
|
|
1179
|
-
this.
|
|
1180
|
-
|
|
1825
|
+
findPlaceholders(kinds = PlaceholderKinds.All, scope = 1, contextChars = 80, boundary = ContextBoundary.Char, citation) {
|
|
1826
|
+
const json = citation ? this.wasm.FindPlaceholdersWithCitations(
|
|
1827
|
+
this.handle,
|
|
1828
|
+
kinds,
|
|
1829
|
+
scope,
|
|
1830
|
+
contextChars,
|
|
1831
|
+
boundary,
|
|
1832
|
+
JSON.stringify(citation)
|
|
1833
|
+
) : this.wasm.FindPlaceholders(this.handle, kinds, scope, contextChars, boundary);
|
|
1834
|
+
return JSON.parse(json);
|
|
1181
1835
|
}
|
|
1182
1836
|
/**
|
|
1183
1837
|
* Returns a snapshot of edit-state introspection signals — placeholder counts,
|
|
@@ -1200,6 +1854,23 @@ var Docxodus = (() => {
|
|
|
1200
1854
|
}
|
|
1201
1855
|
return raw;
|
|
1202
1856
|
}
|
|
1857
|
+
/**
|
|
1858
|
+
* Return stable semantic changes from the package opened for this session to
|
|
1859
|
+
* its current state. Requires `captureInitialProjection` (enabled by default).
|
|
1860
|
+
*/
|
|
1861
|
+
getSemanticChanges() {
|
|
1862
|
+
return JSON.parse(this.wasm.GetSemanticChanges(this.handle));
|
|
1863
|
+
}
|
|
1864
|
+
/**
|
|
1865
|
+
* Run the default deliverable gate on this session's normal clean-save checkpoint.
|
|
1866
|
+
* With initial projection capture enabled (the default), exact opening bytes are the
|
|
1867
|
+
* baseline used for dispositions and semantic/package deltas.
|
|
1868
|
+
*/
|
|
1869
|
+
verifyDeliverable() {
|
|
1870
|
+
return JSON.parse(
|
|
1871
|
+
this.wasm.VerifyDeliverable(this.handle)
|
|
1872
|
+
);
|
|
1873
|
+
}
|
|
1203
1874
|
// ─── Annotation-based anchor discovery (#132) ────────────────────────
|
|
1204
1875
|
/**
|
|
1205
1876
|
* Resolves an annotation's range to the block-level markdown anchors covering
|
|
@@ -1215,8 +1886,9 @@ var Docxodus = (() => {
|
|
|
1215
1886
|
*
|
|
1216
1887
|
* @see docs/architecture/docx_mutation_api.md#findbyannotation
|
|
1217
1888
|
*/
|
|
1218
|
-
findByAnnotation(annotationId) {
|
|
1219
|
-
|
|
1889
|
+
findByAnnotation(annotationId, citation) {
|
|
1890
|
+
const json = citation ? this.wasm.FindByAnnotationWithCitations(this.handle, annotationId, JSON.stringify(citation)) : this.wasm.FindByAnnotation(this.handle, annotationId);
|
|
1891
|
+
return JSON.parse(json);
|
|
1220
1892
|
}
|
|
1221
1893
|
/**
|
|
1222
1894
|
* Finds every annotation whose `labelId` matches and resolves each of their
|
|
@@ -1225,8 +1897,9 @@ var Docxodus = (() => {
|
|
|
1225
1897
|
* annotations on different paragraphs become three entries). Annotations
|
|
1226
1898
|
* whose bookmark resolves to no anchors are omitted from the result.
|
|
1227
1899
|
*/
|
|
1228
|
-
findByLabel(labelId) {
|
|
1229
|
-
|
|
1900
|
+
findByLabel(labelId, citation) {
|
|
1901
|
+
const json = citation ? this.wasm.FindByLabelWithCitations(this.handle, labelId, JSON.stringify(citation)) : this.wasm.FindByLabel(this.handle, labelId);
|
|
1902
|
+
return JSON.parse(json);
|
|
1230
1903
|
}
|
|
1231
1904
|
/**
|
|
1232
1905
|
* Resolves any bookmark in the main document part (Docxodus-managed or
|
|
@@ -1234,8 +1907,9 @@ var Docxodus = (() => {
|
|
|
1234
1907
|
* order. Empty when the bookmark name is unknown. Use this for raw bookmark
|
|
1235
1908
|
* names that didn't come from the annotation system.
|
|
1236
1909
|
*/
|
|
1237
|
-
findByBookmark(bookmarkName) {
|
|
1238
|
-
|
|
1910
|
+
findByBookmark(bookmarkName, citation) {
|
|
1911
|
+
const json = citation ? this.wasm.FindByBookmarkWithCitations(this.handle, bookmarkName, JSON.stringify(citation)) : this.wasm.FindByBookmark(this.handle, bookmarkName);
|
|
1912
|
+
return JSON.parse(json);
|
|
1239
1913
|
}
|
|
1240
1914
|
// ─── Text/kind-based anchor discovery (#171) ─────────────────────────
|
|
1241
1915
|
/**
|
|
@@ -1280,15 +1954,25 @@ var Docxodus = (() => {
|
|
|
1280
1954
|
);
|
|
1281
1955
|
}
|
|
1282
1956
|
/**
|
|
1283
|
-
* Return every anchor of the given `kind`
|
|
1284
|
-
* `"
|
|
1957
|
+
* Return every anchor of the given `kind` — one of `"p"`, `"h"`, `"li"`,
|
|
1958
|
+
* `"tbl"`, `"tr"`, `"tc"`, `"col"`, `"sdt"`, `"sec"`, `"fn"`, `"en"`,
|
|
1959
|
+
* `"cmt"`, `"unk"` — in document order. Matching is an exact string comparison;
|
|
1960
|
+
* in particular the row and cell tokens are `"tr"` and `"tc"`, never
|
|
1961
|
+
* `"row"`/`"cell"`. Unknown strings are not parsed or rejected: they simply
|
|
1962
|
+
* return an empty array. `"img"` and `"drw"` likewise return empty because the
|
|
1963
|
+
* projection never assigns those reserved kinds; address images through the
|
|
1964
|
+
* image surface instead. Reads the projection's anchor
|
|
1285
1965
|
* index directly — no text scan. Pass `scope` (e.g. `"body"`) to restrict to
|
|
1286
1966
|
* a single part; omit it to span all scopes.
|
|
1287
1967
|
*/
|
|
1288
|
-
findByKind(kind, scope) {
|
|
1289
|
-
|
|
1290
|
-
this.
|
|
1291
|
-
|
|
1968
|
+
findByKind(kind, scope, citation) {
|
|
1969
|
+
const json = citation ? this.wasm.FindByKindWithCitations(
|
|
1970
|
+
this.handle,
|
|
1971
|
+
kind,
|
|
1972
|
+
scope ?? "",
|
|
1973
|
+
JSON.stringify(citation)
|
|
1974
|
+
) : this.wasm.FindByKind(this.handle, kind, scope ?? "");
|
|
1975
|
+
return JSON.parse(json);
|
|
1292
1976
|
}
|
|
1293
1977
|
/**
|
|
1294
1978
|
* Look up a single anchor's preview info — `{ id, kind, scope, textPreview }`.
|
|
@@ -1342,6 +2026,18 @@ var Docxodus = (() => {
|
|
|
1342
2026
|
const raw = this.wasm.GetSectionInfo(this.handle, anchorId);
|
|
1343
2027
|
return JSON.parse(raw);
|
|
1344
2028
|
}
|
|
2029
|
+
/** Enumerate the document's explicit style catalog with resolved high-signal properties. */
|
|
2030
|
+
listStyles() {
|
|
2031
|
+
return JSON.parse(this.wasm.ListStyles(this.handle));
|
|
2032
|
+
}
|
|
2033
|
+
/** Inspect direct and effective paragraph/run formatting for one paragraph anchor. */
|
|
2034
|
+
getFormatting(anchorId) {
|
|
2035
|
+
return JSON.parse(this.wasm.GetFormatting(this.handle, anchorId));
|
|
2036
|
+
}
|
|
2037
|
+
/** Enumerate text-bearing runs as mutation-compatible anchor/span pairs. */
|
|
2038
|
+
listInlineSpans(anchorId) {
|
|
2039
|
+
return JSON.parse(this.wasm.ListInlineSpans(this.handle, anchorId));
|
|
2040
|
+
}
|
|
1345
2041
|
/**
|
|
1346
2042
|
* Enumerates every annotation persisted in the document. Lets an agent prime
|
|
1347
2043
|
* itself with "here are the labeled regions you can target" before committing
|
|
@@ -1406,6 +2102,13 @@ var Docxodus = (() => {
|
|
|
1406
2102
|
this.close();
|
|
1407
2103
|
}
|
|
1408
2104
|
};
|
|
2105
|
+
function imageBytesToBase64(bytes) {
|
|
2106
|
+
let binary = "";
|
|
2107
|
+
for (let offset = 0; offset < bytes.length; offset += 32768) {
|
|
2108
|
+
binary += String.fromCharCode(...bytes.subarray(offset, offset + 32768));
|
|
2109
|
+
}
|
|
2110
|
+
return globalThis.btoa(binary);
|
|
2111
|
+
}
|
|
1409
2112
|
function openDocxSession(bytes, wasmExports2, settings) {
|
|
1410
2113
|
const bridge = wasmExports2.DocxSessionBridge;
|
|
1411
2114
|
const handle = bridge.OpenSession(bytes, settings ? JSON.stringify(settings) : "");
|
|
@@ -1457,19 +2160,43 @@ var Docxodus = (() => {
|
|
|
1457
2160
|
function ptToPx(pt) {
|
|
1458
2161
|
return pt / 0.75;
|
|
1459
2162
|
}
|
|
1460
|
-
function
|
|
1461
|
-
|
|
1462
|
-
const
|
|
1463
|
-
|
|
1464
|
-
|
|
1465
|
-
|
|
1466
|
-
const
|
|
1467
|
-
|
|
1468
|
-
|
|
1469
|
-
|
|
1470
|
-
const
|
|
1471
|
-
return
|
|
1472
|
-
|
|
2163
|
+
function finiteDataNumber(value, fallback) {
|
|
2164
|
+
if (value === void 0 || value.trim() === "") return fallback;
|
|
2165
|
+
const parsed = Number(value);
|
|
2166
|
+
return Number.isFinite(parsed) ? parsed : fallback;
|
|
2167
|
+
}
|
|
2168
|
+
function positiveDataNumber(value, fallback) {
|
|
2169
|
+
const parsed = finiteDataNumber(value, fallback);
|
|
2170
|
+
return parsed > 0 ? parsed : fallback;
|
|
2171
|
+
}
|
|
2172
|
+
function nonNegativeDataNumber(value, fallback) {
|
|
2173
|
+
const parsed = finiteDataNumber(value, fallback);
|
|
2174
|
+
return parsed >= 0 ? parsed : fallback;
|
|
2175
|
+
}
|
|
2176
|
+
function positiveFinite(value, fallback) {
|
|
2177
|
+
return Number.isFinite(value) && value > 0 ? value : fallback;
|
|
2178
|
+
}
|
|
2179
|
+
function parseSectionDimensions(section) {
|
|
2180
|
+
const pageWidth = positiveDataNumber(section.dataset.pageWidth, DEFAULT_PAGE_WIDTH);
|
|
2181
|
+
const pageHeight = positiveDataNumber(section.dataset.pageHeight, DEFAULT_PAGE_HEIGHT);
|
|
2182
|
+
const marginTop = finiteDataNumber(section.dataset.marginTop, DEFAULT_MARGIN);
|
|
2183
|
+
const marginRight = finiteDataNumber(section.dataset.marginRight, DEFAULT_MARGIN);
|
|
2184
|
+
const marginBottom = finiteDataNumber(section.dataset.marginBottom, DEFAULT_MARGIN);
|
|
2185
|
+
const marginLeft = finiteDataNumber(section.dataset.marginLeft, DEFAULT_MARGIN);
|
|
2186
|
+
const derivedContentWidth = positiveFinite(pageWidth - marginLeft - marginRight, pageWidth);
|
|
2187
|
+
const derivedContentHeight = positiveFinite(pageHeight - marginTop - marginBottom, pageHeight);
|
|
2188
|
+
const contentWidth = positiveDataNumber(section.dataset.contentWidth, derivedContentWidth);
|
|
2189
|
+
const contentHeight = positiveDataNumber(section.dataset.contentHeight, derivedContentHeight);
|
|
2190
|
+
const headerDistance = nonNegativeDataNumber(
|
|
2191
|
+
section.dataset.headerHeight,
|
|
2192
|
+
DEFAULT_HEADER_FOOTER_DISTANCE
|
|
2193
|
+
);
|
|
2194
|
+
const footerDistance = nonNegativeDataNumber(
|
|
2195
|
+
section.dataset.footerHeight,
|
|
2196
|
+
DEFAULT_HEADER_FOOTER_DISTANCE
|
|
2197
|
+
);
|
|
2198
|
+
return {
|
|
2199
|
+
pageWidth,
|
|
1473
2200
|
pageHeight,
|
|
1474
2201
|
contentWidth,
|
|
1475
2202
|
contentHeight,
|
|
@@ -1547,8 +2274,93 @@ var Docxodus = (() => {
|
|
|
1547
2274
|
function samePageBox(a, b) {
|
|
1548
2275
|
return a.pageWidth === b.pageWidth && a.pageHeight === b.pageHeight && a.marginTop === b.marginTop && a.marginRight === b.marginRight && a.marginBottom === b.marginBottom && a.marginLeft === b.marginLeft;
|
|
1549
2276
|
}
|
|
2277
|
+
function createUnavailablePageMap(documentVersion, rendererFingerprint, mode = "continuous") {
|
|
2278
|
+
if (!rendererFingerprint) throw new Error("rendererFingerprint must be non-empty");
|
|
2279
|
+
return {
|
|
2280
|
+
schemaVersion: 1,
|
|
2281
|
+
mode,
|
|
2282
|
+
availability: "unavailable",
|
|
2283
|
+
documentVersion,
|
|
2284
|
+
rendererFingerprint,
|
|
2285
|
+
pages: [],
|
|
2286
|
+
fragments: []
|
|
2287
|
+
};
|
|
2288
|
+
}
|
|
2289
|
+
var activePageCitationHighlights = /* @__PURE__ */ new WeakMap();
|
|
2290
|
+
function clearPageCitationHighlight(root) {
|
|
2291
|
+
const active = activePageCitationHighlights.get(root);
|
|
2292
|
+
if (!active) return;
|
|
2293
|
+
if (active.highlightClass && active.addedClass) {
|
|
2294
|
+
active.target.classList.remove(active.highlightClass);
|
|
2295
|
+
}
|
|
2296
|
+
for (const style of active.inlineStyles ?? []) {
|
|
2297
|
+
if (style.value) {
|
|
2298
|
+
active.target.style.setProperty(style.name, style.value, style.priority);
|
|
2299
|
+
} else {
|
|
2300
|
+
active.target.style.removeProperty(style.name);
|
|
2301
|
+
}
|
|
2302
|
+
}
|
|
2303
|
+
activePageCitationHighlights.delete(root);
|
|
2304
|
+
}
|
|
2305
|
+
function navigateToPageCitation(root, citation, options = {}) {
|
|
2306
|
+
clearPageCitationHighlight(root);
|
|
2307
|
+
if (citation.availability !== "available" || citation.fragments.length === 0) {
|
|
2308
|
+
return { navigated: false, unavailableReason: "citation_unavailable" };
|
|
2309
|
+
}
|
|
2310
|
+
const byAttribute = (name, value, within = root) => {
|
|
2311
|
+
for (const node of Array.from(within.querySelectorAll(`[${name}]`))) {
|
|
2312
|
+
if (node.getAttribute(name) === value) return node;
|
|
2313
|
+
}
|
|
2314
|
+
return null;
|
|
2315
|
+
};
|
|
2316
|
+
const fragment = citation.fragments[0];
|
|
2317
|
+
let target = byAttribute("data-page-fragment-id", fragment.fragmentId);
|
|
2318
|
+
if (!target) {
|
|
2319
|
+
const page = byAttribute("data-page-number", String(fragment.pageNumber));
|
|
2320
|
+
if (page) target = byAttribute("data-source-anchor-id", citation.anchorId, page) ?? page;
|
|
2321
|
+
}
|
|
2322
|
+
if (!target) {
|
|
2323
|
+
return {
|
|
2324
|
+
navigated: false,
|
|
2325
|
+
pageNumber: fragment.pageNumber,
|
|
2326
|
+
fragmentId: fragment.fragmentId,
|
|
2327
|
+
unavailableReason: "fragment_not_found"
|
|
2328
|
+
};
|
|
2329
|
+
}
|
|
2330
|
+
if (options.highlightClass) {
|
|
2331
|
+
const addedClass = !target.classList.contains(options.highlightClass);
|
|
2332
|
+
target.classList.add(options.highlightClass);
|
|
2333
|
+
activePageCitationHighlights.set(root, {
|
|
2334
|
+
target,
|
|
2335
|
+
highlightClass: options.highlightClass,
|
|
2336
|
+
addedClass
|
|
2337
|
+
});
|
|
2338
|
+
} else if (options.highlight !== false) {
|
|
2339
|
+
const names = ["outline", "outline-offset", "background-color"];
|
|
2340
|
+
const inlineStyles = names.map((name) => ({
|
|
2341
|
+
name,
|
|
2342
|
+
value: target.style.getPropertyValue(name),
|
|
2343
|
+
priority: target.style.getPropertyPriority(name)
|
|
2344
|
+
}));
|
|
2345
|
+
target.style.setProperty("outline", "3px solid #f4b400", "important");
|
|
2346
|
+
target.style.setProperty("outline-offset", "2px", "important");
|
|
2347
|
+
target.style.setProperty("background-color", "rgba(255, 235, 59, .18)", "important");
|
|
2348
|
+
activePageCitationHighlights.set(root, { target, inlineStyles });
|
|
2349
|
+
}
|
|
2350
|
+
target.scrollIntoView({
|
|
2351
|
+
behavior: options.behavior ?? "smooth",
|
|
2352
|
+
block: options.block ?? "center"
|
|
2353
|
+
});
|
|
2354
|
+
return {
|
|
2355
|
+
navigated: true,
|
|
2356
|
+
target,
|
|
2357
|
+
pageNumber: fragment.pageNumber,
|
|
2358
|
+
fragmentId: fragment.fragmentId
|
|
2359
|
+
};
|
|
2360
|
+
}
|
|
1550
2361
|
var MAX_FOOTNOTE_AREA_RATIO = 0.6;
|
|
1551
|
-
var
|
|
2362
|
+
var FOOTNOTE_MEASUREMENT_GUARD_PT = 2;
|
|
2363
|
+
var FOOTNOTE_SOURCE_POSITION_ATTR = "data-pagination-footnote-source-position";
|
|
1552
2364
|
var PaginationEngine = class {
|
|
1553
2365
|
/**
|
|
1554
2366
|
* Creates a new pagination engine.
|
|
@@ -1558,24 +2370,46 @@ var Docxodus = (() => {
|
|
|
1558
2370
|
* @param options - Pagination options
|
|
1559
2371
|
*/
|
|
1560
2372
|
constructor(staging, container, options = {}) {
|
|
2373
|
+
this.createdPageCount = 0;
|
|
2374
|
+
this.footnoteSeparator = null;
|
|
2375
|
+
this.footnoteContinuationSeparator = null;
|
|
2376
|
+
this.footnoteLayoutLikeWord8 = false;
|
|
1561
2377
|
this.pendingFootnoteContinuation = null;
|
|
1562
2378
|
/** Per-section `w:pgNumType` (start / format), read off the section wrappers. */
|
|
1563
2379
|
this.pageNumbering = /* @__PURE__ */ new Map();
|
|
1564
|
-
this.
|
|
1565
|
-
this.
|
|
2380
|
+
this.lastPages = [];
|
|
2381
|
+
this.expectedPageMapAnchorIds = /* @__PURE__ */ new Set();
|
|
2382
|
+
this.state = "ready";
|
|
2383
|
+
const ownerDocument = typeof staging !== "string" ? staging.ownerDocument : typeof container !== "string" ? container.ownerDocument : globalThis.document;
|
|
2384
|
+
this.stagingElement = typeof staging === "string" ? ownerDocument.getElementById(staging) : staging;
|
|
2385
|
+
this.containerElement = typeof container === "string" ? ownerDocument.getElementById(container) : container;
|
|
1566
2386
|
if (!this.stagingElement) {
|
|
1567
2387
|
throw new Error("Staging element not found");
|
|
1568
2388
|
}
|
|
1569
2389
|
if (!this.containerElement) {
|
|
1570
2390
|
throw new Error("Container element not found");
|
|
1571
2391
|
}
|
|
2392
|
+
if (this.stagingElement.ownerDocument !== this.containerElement.ownerDocument) {
|
|
2393
|
+
throw new Error("Staging and container elements must belong to the same document");
|
|
2394
|
+
}
|
|
2395
|
+
const view = ownerDocument.defaultView;
|
|
2396
|
+
if (!view) {
|
|
2397
|
+
throw new Error("Pagination requires an attached document with a defaultView");
|
|
2398
|
+
}
|
|
2399
|
+
this.document = ownerDocument;
|
|
2400
|
+
this.view = view;
|
|
1572
2401
|
this.scale = options.scale ?? 1;
|
|
1573
2402
|
this.cssPrefix = options.cssPrefix ?? "page-";
|
|
1574
2403
|
this.showPageNumbers = options.showPageNumbers ?? true;
|
|
1575
2404
|
this.pageGap = options.pageGap ?? 20;
|
|
1576
2405
|
this.fragmentParagraphs = options.fragmentParagraphs ?? false;
|
|
2406
|
+
this.deferFragmentIdentities = options.deferFragmentIdentities ?? false;
|
|
2407
|
+
this.cancellationCheckpoint = options.checkCancellation;
|
|
2408
|
+
this.pageCountCheckpoint = options.checkPageCount;
|
|
2409
|
+
this.layoutToken = options.layoutToken;
|
|
1577
2410
|
this.hfRegistry = /* @__PURE__ */ new Map();
|
|
1578
2411
|
this.footnoteRegistry = /* @__PURE__ */ new Map();
|
|
2412
|
+
this.commentMarginRegistry = /* @__PURE__ */ new Map();
|
|
1579
2413
|
}
|
|
1580
2414
|
/**
|
|
1581
2415
|
* Runs the pagination process.
|
|
@@ -1583,58 +2417,469 @@ var Docxodus = (() => {
|
|
|
1583
2417
|
* @returns PaginationResult with page information
|
|
1584
2418
|
*/
|
|
1585
2419
|
paginate() {
|
|
1586
|
-
|
|
1587
|
-
|
|
1588
|
-
|
|
1589
|
-
|
|
1590
|
-
|
|
1591
|
-
"
|
|
1592
|
-
|
|
1593
|
-
|
|
1594
|
-
|
|
1595
|
-
|
|
1596
|
-
|
|
1597
|
-
|
|
1598
|
-
const
|
|
1599
|
-
|
|
1600
|
-
|
|
1601
|
-
|
|
1602
|
-
|
|
1603
|
-
|
|
1604
|
-
|
|
1605
|
-
|
|
2420
|
+
this.checkpoint();
|
|
2421
|
+
if (this.state !== "ready") {
|
|
2422
|
+
throw new Error(`PaginationEngine is one-shot and is already ${this.state}`);
|
|
2423
|
+
}
|
|
2424
|
+
if (!this.stagingElement.isConnected || !this.containerElement.isConnected) {
|
|
2425
|
+
throw new Error("Pagination requires staging and container elements attached to their document");
|
|
2426
|
+
}
|
|
2427
|
+
if (this.document.documentElement.getBoundingClientRect().width <= 0) {
|
|
2428
|
+
throw new Error("Pagination requires a browsing context with non-zero layout");
|
|
2429
|
+
}
|
|
2430
|
+
this.state = "running";
|
|
2431
|
+
try {
|
|
2432
|
+
const pages = [];
|
|
2433
|
+
let pageNumber = 1;
|
|
2434
|
+
this.hfRegistry = this.parseHeaderFooterRegistry();
|
|
2435
|
+
this.footnoteRegistry = this.parseFootnoteRegistry();
|
|
2436
|
+
({
|
|
2437
|
+
normal: this.footnoteSeparator,
|
|
2438
|
+
continuation: this.footnoteContinuationSeparator
|
|
2439
|
+
} = this.parseFootnoteSeparators());
|
|
2440
|
+
this.commentMarginRegistry = this.parseCommentMarginRegistry();
|
|
2441
|
+
this.footnoteLayoutLikeWord8 = this.stagingElement.dataset.footnoteLayoutLikeWord8 === "true";
|
|
2442
|
+
const sections = this.stagingElement.querySelectorAll(
|
|
2443
|
+
"[data-section-index]"
|
|
2444
|
+
);
|
|
2445
|
+
this.pageNumbering = this.parsePageNumbering(sections);
|
|
2446
|
+
const sectionsToProcess = sections.length > 0 ? Array.from(sections) : [this.stagingElement];
|
|
2447
|
+
this.expectedPageMapAnchorIds = /* @__PURE__ */ new Set();
|
|
2448
|
+
const referencedFootnoteIds = /* @__PURE__ */ new Set();
|
|
2449
|
+
const referencedCommentIds = /* @__PURE__ */ new Set();
|
|
2450
|
+
for (const section of sectionsToProcess) {
|
|
2451
|
+
this.checkpoint();
|
|
2452
|
+
this.collectExpectedSourceAnchors(section, this.expectedPageMapAnchorIds, true);
|
|
2453
|
+
for (const reference of Array.from(section.querySelectorAll("[data-footnote-id]"))) {
|
|
2454
|
+
this.checkpoint();
|
|
2455
|
+
if (reference.closest("#pagination-footnote-registry, #pagination-hf-registry")) continue;
|
|
2456
|
+
const id = reference.dataset.footnoteId;
|
|
2457
|
+
if (id) referencedFootnoteIds.add(id);
|
|
2458
|
+
}
|
|
2459
|
+
for (const reference of Array.from(section.querySelectorAll("[data-comment-id]"))) {
|
|
2460
|
+
this.checkpoint();
|
|
2461
|
+
if (reference.closest(
|
|
2462
|
+
"#pagination-comment-margin-registry, #pagination-footnote-registry, #pagination-hf-registry"
|
|
2463
|
+
)) continue;
|
|
2464
|
+
const id = reference.dataset.commentId;
|
|
2465
|
+
if (id) referencedCommentIds.add(id);
|
|
2466
|
+
}
|
|
2467
|
+
}
|
|
2468
|
+
for (const id of referencedFootnoteIds) {
|
|
2469
|
+
this.checkpoint();
|
|
2470
|
+
const source = this.footnoteRegistry.get(id);
|
|
2471
|
+
if (!source) continue;
|
|
2472
|
+
this.collectExpectedSourceAnchors(source, this.expectedPageMapAnchorIds);
|
|
2473
|
+
for (const reference of Array.from(
|
|
2474
|
+
source.querySelectorAll("[data-comment-id]")
|
|
2475
|
+
)) {
|
|
2476
|
+
this.checkpoint();
|
|
2477
|
+
const commentId = reference.dataset.commentId;
|
|
2478
|
+
if (commentId) referencedCommentIds.add(commentId);
|
|
2479
|
+
}
|
|
2480
|
+
}
|
|
2481
|
+
for (const id of referencedCommentIds) {
|
|
2482
|
+
this.checkpoint();
|
|
2483
|
+
const source = this.commentMarginRegistry.get(id);
|
|
2484
|
+
if (source) this.collectExpectedSourceAnchors(source, this.expectedPageMapAnchorIds);
|
|
2485
|
+
}
|
|
2486
|
+
const runs = [];
|
|
2487
|
+
for (const section of sectionsToProcess) {
|
|
2488
|
+
this.checkpoint();
|
|
2489
|
+
const dims = parseSectionDimensions(section);
|
|
2490
|
+
const previous = runs[runs.length - 1];
|
|
2491
|
+
if (previous && section.dataset.sectionType === "continuous" && samePageBox(previous.dims, dims)) {
|
|
2492
|
+
previous.sections.push(section);
|
|
2493
|
+
previous.sectionDimensions.set(
|
|
2494
|
+
parseInt(section.dataset.sectionIndex || "0", 10),
|
|
2495
|
+
dims
|
|
2496
|
+
);
|
|
2497
|
+
} else {
|
|
2498
|
+
const sectionIndex = parseInt(section.dataset.sectionIndex || "0", 10);
|
|
2499
|
+
runs.push({
|
|
2500
|
+
sections: [section],
|
|
2501
|
+
sectionIndex,
|
|
2502
|
+
sectionType: section.dataset.sectionType ?? "nextPage",
|
|
2503
|
+
dims,
|
|
2504
|
+
sectionDimensions: /* @__PURE__ */ new Map([[sectionIndex, dims]])
|
|
2505
|
+
});
|
|
2506
|
+
}
|
|
2507
|
+
}
|
|
2508
|
+
for (const run of runs) {
|
|
2509
|
+
this.checkpoint();
|
|
2510
|
+
const needsParityFiller = pages.length > 0 && (run.sectionType === "oddPage" && pageNumber % 2 === 0 || run.sectionType === "evenPage" && pageNumber % 2 === 1);
|
|
2511
|
+
if (needsParityFiller) {
|
|
2512
|
+
const precedingPage = pages[pages.length - 1];
|
|
2513
|
+
const precedingPageInSection = parseInt(
|
|
2514
|
+
precedingPage.element.dataset.pageInSection ?? "1",
|
|
2515
|
+
10
|
|
2516
|
+
);
|
|
2517
|
+
const precedingDisplayedPageNumber = parseInt(
|
|
2518
|
+
precedingPage.element.dataset.displayedPageNumber ?? String(precedingPage.pageNumber),
|
|
2519
|
+
10
|
|
2520
|
+
);
|
|
2521
|
+
const filler = this.createPage(
|
|
2522
|
+
precedingPage.dimensions,
|
|
2523
|
+
pageNumber,
|
|
2524
|
+
precedingPage.sectionIndex,
|
|
2525
|
+
precedingDisplayedPageNumber + 1,
|
|
2526
|
+
[],
|
|
2527
|
+
precedingPageInSection + 1,
|
|
2528
|
+
[],
|
|
2529
|
+
0,
|
|
2530
|
+
null,
|
|
2531
|
+
void 0,
|
|
2532
|
+
true
|
|
2533
|
+
);
|
|
2534
|
+
pages.push(filler);
|
|
2535
|
+
pageNumber++;
|
|
2536
|
+
}
|
|
2537
|
+
this.stagingElement.style.visibility = "hidden";
|
|
2538
|
+
this.stagingElement.style.position = "absolute";
|
|
2539
|
+
this.stagingElement.style.left = "-9999px";
|
|
2540
|
+
this.stagingElement.style.display = "block";
|
|
2541
|
+
const blocks = [];
|
|
2542
|
+
for (const section of run.sections) {
|
|
2543
|
+
this.checkpoint();
|
|
2544
|
+
const sectionIndex = parseInt(section.dataset.sectionIndex || "0", 10);
|
|
2545
|
+
const sectionDims = run.sectionDimensions.get(sectionIndex) ?? run.dims;
|
|
2546
|
+
section.style.width = `${sectionDims.contentWidth}pt`;
|
|
2547
|
+
const columnCount = parseInt(section.dataset.cols || "1", 10);
|
|
2548
|
+
if (columnCount > 1) {
|
|
2549
|
+
const gap = parseFloat(section.dataset.colGap || "");
|
|
2550
|
+
blocks.push(...this.buildColumnBlocks(
|
|
2551
|
+
section,
|
|
2552
|
+
sectionDims,
|
|
2553
|
+
sectionIndex,
|
|
2554
|
+
columnCount,
|
|
2555
|
+
Number.isFinite(gap) ? gap : 36
|
|
2556
|
+
));
|
|
2557
|
+
} else {
|
|
2558
|
+
blocks.push(...this.measureBlocks(section, sectionDims, sectionIndex));
|
|
2559
|
+
}
|
|
2560
|
+
}
|
|
2561
|
+
const sectionPages = this.flowToPages(
|
|
2562
|
+
blocks,
|
|
2563
|
+
run.dims,
|
|
2564
|
+
pageNumber,
|
|
2565
|
+
run.sectionIndex,
|
|
2566
|
+
run.sectionDimensions
|
|
2567
|
+
);
|
|
2568
|
+
this.checkpoint();
|
|
2569
|
+
pages.push(...sectionPages);
|
|
2570
|
+
pageNumber += sectionPages.length;
|
|
2571
|
+
}
|
|
2572
|
+
this.stagingElement.style.display = "none";
|
|
2573
|
+
this.substitutePageNumberFields(pages.length);
|
|
2574
|
+
for (const page of pages) {
|
|
2575
|
+
this.checkpoint();
|
|
2576
|
+
if (page.element.dataset.sectionFiller === "true") continue;
|
|
2577
|
+
const pageInSection = parseInt(page.element.dataset.pageInSection || "1", 10);
|
|
2578
|
+
const displayedPageNumber = parseInt(
|
|
2579
|
+
page.element.dataset.displayedPageNumber || String(page.pageNumber),
|
|
2580
|
+
10
|
|
2581
|
+
);
|
|
2582
|
+
const header = this.selectHeader(page.sectionIndex, pageInSection, displayedPageNumber);
|
|
2583
|
+
const footer = this.selectFooter(page.sectionIndex, pageInSection, displayedPageNumber);
|
|
2584
|
+
if (header) this.collectExpectedSourceAnchors(header, this.expectedPageMapAnchorIds);
|
|
2585
|
+
if (footer) this.collectExpectedSourceAnchors(footer, this.expectedPageMapAnchorIds);
|
|
2586
|
+
}
|
|
2587
|
+
this.qualifyPageFragments(pages);
|
|
2588
|
+
this.transferVisibleFragmentTargets();
|
|
2589
|
+
if (!this.deferFragmentIdentities) this.normalizeVisiblePageFragments(pages);
|
|
2590
|
+
this.lastPages = pages;
|
|
2591
|
+
const result = {
|
|
2592
|
+
totalPages: pages.length,
|
|
2593
|
+
pages,
|
|
2594
|
+
pageMap: this.layoutToken ? this.materializePageMap(
|
|
2595
|
+
this.layoutToken.documentVersion,
|
|
2596
|
+
this.layoutToken.rendererFingerprint
|
|
2597
|
+
) : void 0
|
|
2598
|
+
};
|
|
2599
|
+
this.state = "complete";
|
|
2600
|
+
return result;
|
|
2601
|
+
} catch (error) {
|
|
2602
|
+
this.state = "failed";
|
|
2603
|
+
throw error;
|
|
2604
|
+
}
|
|
2605
|
+
}
|
|
2606
|
+
checkpoint() {
|
|
2607
|
+
this.cancellationCheckpoint?.();
|
|
2608
|
+
}
|
|
2609
|
+
/**
|
|
2610
|
+
* Normalize visible fragment identities after a caller applies final standalone styles. This
|
|
2611
|
+
* deliberately runs before the stability barrier; materializePageMap is read-only so PageMap
|
|
2612
|
+
* measurement cannot mutate a tree after it was declared stable.
|
|
2613
|
+
*/
|
|
2614
|
+
normalizePageMapFragmentIdentities() {
|
|
2615
|
+
if (this.lastPages.length === 0) {
|
|
2616
|
+
throw new Error("paginate() must complete before fragment identities can be normalized");
|
|
2617
|
+
}
|
|
2618
|
+
this.normalizeVisiblePageFragments(this.lastPages);
|
|
2619
|
+
}
|
|
2620
|
+
/**
|
|
2621
|
+
* Materialize the last completed browser layout as portable page-relative point geometry.
|
|
2622
|
+
* The caller supplies both invalidation tokens; this engine never guesses a document version
|
|
2623
|
+
* or renderer fingerprint.
|
|
2624
|
+
*/
|
|
2625
|
+
materializePageMap(documentVersion, rendererFingerprint) {
|
|
2626
|
+
if (!Number.isSafeInteger(documentVersion) || documentVersion < 0) {
|
|
2627
|
+
throw new Error("documentVersion must be a non-negative safe integer");
|
|
2628
|
+
}
|
|
2629
|
+
if (!rendererFingerprint) throw new Error("rendererFingerprint must be non-empty");
|
|
2630
|
+
if (this.lastPages.length === 0) throw new Error("paginate() must complete before materializePageMap()");
|
|
2631
|
+
const pages = this.lastPages.map((page) => ({
|
|
2632
|
+
pageNumber: page.pageNumber,
|
|
2633
|
+
pageInSection: parseInt(page.element.dataset.pageInSection || "1", 10),
|
|
2634
|
+
width: page.dimensions.pageWidth,
|
|
2635
|
+
height: page.dimensions.pageHeight,
|
|
2636
|
+
sectionIndex: page.sectionIndex,
|
|
2637
|
+
pageName: `docxodus-section-${page.sectionIndex}`
|
|
2638
|
+
}));
|
|
2639
|
+
const fragments = [];
|
|
2640
|
+
const requiredAnchorIds = new Set(this.expectedPageMapAnchorIds);
|
|
2641
|
+
if (requiredAnchorIds.size === 0) {
|
|
2642
|
+
throw new Error("cannot publish an available PageMap without canonical source inventory");
|
|
2643
|
+
}
|
|
2644
|
+
const measuredAnchorIds = /* @__PURE__ */ new Set();
|
|
2645
|
+
const emittedFragmentCounts = /* @__PURE__ */ new Map();
|
|
2646
|
+
for (const page of this.lastPages) {
|
|
2647
|
+
this.checkpoint();
|
|
2648
|
+
const pageRect = page.element.getBoundingClientRect();
|
|
2649
|
+
if (pageRect.width <= 0 || pageRect.height <= 0) {
|
|
2650
|
+
throw new Error(`page ${page.pageNumber} has no measurable geometry`);
|
|
2651
|
+
}
|
|
2652
|
+
const pointPerRenderedX = page.dimensions.pageWidth / pageRect.width;
|
|
2653
|
+
const pointPerRenderedY = page.dimensions.pageHeight / pageRect.height;
|
|
2654
|
+
const nodes = page.element.querySelectorAll("[data-source-anchor-id]");
|
|
2655
|
+
for (const element of Array.from(nodes)) {
|
|
2656
|
+
this.checkpoint();
|
|
2657
|
+
if (this.isDeliberatelyUnrenderedSource(element, page.element)) continue;
|
|
2658
|
+
const anchorId = element.dataset.sourceAnchorId;
|
|
2659
|
+
if (!anchorId || !element.dataset.pageFragmentId || !Number.isInteger(parseInt(element.dataset.fragmentIndex || "", 10))) {
|
|
2660
|
+
throw new Error(`page ${page.pageNumber} contains an unqualified source anchor`);
|
|
2661
|
+
}
|
|
2662
|
+
const rect = element.getBoundingClientRect();
|
|
2663
|
+
const style = this.view.getComputedStyle(element);
|
|
2664
|
+
const deliberatelyHidden = style.display === "none" || style.visibility === "hidden";
|
|
2665
|
+
if (deliberatelyHidden) continue;
|
|
2666
|
+
requiredAnchorIds.add(anchorId);
|
|
2667
|
+
const visibleRect = this.intersectWithClippingAncestors(element, page.element, pageRect, rect);
|
|
2668
|
+
const left2 = visibleRect.left;
|
|
2669
|
+
const top2 = visibleRect.top;
|
|
2670
|
+
const right2 = visibleRect.right;
|
|
2671
|
+
const bottom2 = visibleRect.bottom;
|
|
2672
|
+
if (rect.width <= 0 || rect.height <= 0 || right2 <= left2 || bottom2 <= top2) {
|
|
2673
|
+
continue;
|
|
2674
|
+
}
|
|
2675
|
+
measuredAnchorIds.add(anchorId);
|
|
2676
|
+
const fragmentIndex = emittedFragmentCounts.get(anchorId) ?? 0;
|
|
2677
|
+
emittedFragmentCounts.set(anchorId, fragmentIndex + 1);
|
|
2678
|
+
const fragmentId = `p${page.pageNumber}-f${fragmentIndex}-${anchorId}`;
|
|
2679
|
+
if (element.dataset.fragmentIndex !== String(fragmentIndex) || element.dataset.pageFragmentId !== fragmentId || element.dataset.pageNumber !== String(page.pageNumber)) {
|
|
2680
|
+
throw new Error(
|
|
2681
|
+
`page ${page.pageNumber} fragment identity changed after final-tree normalization`
|
|
2682
|
+
);
|
|
2683
|
+
}
|
|
2684
|
+
fragments.push({
|
|
2685
|
+
fragmentId,
|
|
2686
|
+
anchorId,
|
|
2687
|
+
fragmentIndex,
|
|
2688
|
+
pageNumber: page.pageNumber,
|
|
2689
|
+
geometry: {
|
|
2690
|
+
x: (left2 - pageRect.left) * pointPerRenderedX,
|
|
2691
|
+
y: (top2 - pageRect.top) * pointPerRenderedY,
|
|
2692
|
+
width: (right2 - left2) * pointPerRenderedX,
|
|
2693
|
+
height: (bottom2 - top2) * pointPerRenderedY
|
|
2694
|
+
},
|
|
2695
|
+
story: this.storyForCanonicalAnchor(anchorId),
|
|
2696
|
+
inTableCell: element.matches("td,th") || element.closest("td,th") !== null
|
|
1606
2697
|
});
|
|
1607
2698
|
}
|
|
1608
2699
|
}
|
|
1609
|
-
|
|
1610
|
-
|
|
1611
|
-
|
|
1612
|
-
|
|
1613
|
-
|
|
1614
|
-
|
|
1615
|
-
|
|
1616
|
-
|
|
1617
|
-
|
|
1618
|
-
|
|
1619
|
-
|
|
1620
|
-
|
|
1621
|
-
|
|
1622
|
-
|
|
1623
|
-
|
|
1624
|
-
|
|
1625
|
-
|
|
1626
|
-
|
|
1627
|
-
|
|
1628
|
-
|
|
2700
|
+
const missingAnchor = Array.from(requiredAnchorIds).find((id) => !measuredAnchorIds.has(id));
|
|
2701
|
+
if (missingAnchor) {
|
|
2702
|
+
throw new Error(`source anchor ${missingAnchor} has no measurable fragment in the paginated layout`);
|
|
2703
|
+
}
|
|
2704
|
+
return {
|
|
2705
|
+
schemaVersion: 1,
|
|
2706
|
+
mode: "paginated",
|
|
2707
|
+
availability: "available",
|
|
2708
|
+
documentVersion,
|
|
2709
|
+
rendererFingerprint,
|
|
2710
|
+
pages,
|
|
2711
|
+
fragments
|
|
2712
|
+
};
|
|
2713
|
+
}
|
|
2714
|
+
normalizeVisiblePageFragments(pages) {
|
|
2715
|
+
const emittedFragmentCounts = /* @__PURE__ */ new Map();
|
|
2716
|
+
for (const page of pages) {
|
|
2717
|
+
this.checkpoint();
|
|
2718
|
+
const pageRect = page.element.getBoundingClientRect();
|
|
2719
|
+
for (const element of Array.from(
|
|
2720
|
+
page.element.querySelectorAll("[data-source-anchor-id]")
|
|
2721
|
+
)) {
|
|
2722
|
+
this.checkpoint();
|
|
2723
|
+
if (this.isDeliberatelyUnrenderedSource(element, page.element)) continue;
|
|
2724
|
+
const anchorId = element.dataset.sourceAnchorId;
|
|
2725
|
+
if (!anchorId) continue;
|
|
2726
|
+
const style = this.view.getComputedStyle(element);
|
|
2727
|
+
if (style.display === "none" || style.visibility === "hidden") continue;
|
|
2728
|
+
const rect = element.getBoundingClientRect();
|
|
2729
|
+
const visible = this.intersectWithClippingAncestors(element, page.element, pageRect, rect);
|
|
2730
|
+
if (rect.width <= 0 || rect.height <= 0 || visible.right <= visible.left || visible.bottom <= visible.top) continue;
|
|
2731
|
+
const fragmentIndex = emittedFragmentCounts.get(anchorId) ?? 0;
|
|
2732
|
+
emittedFragmentCounts.set(anchorId, fragmentIndex + 1);
|
|
2733
|
+
element.dataset.pageNumber = String(page.pageNumber);
|
|
2734
|
+
element.dataset.fragmentIndex = String(fragmentIndex);
|
|
2735
|
+
element.dataset.pageFragmentId = `p${page.pageNumber}-f${fragmentIndex}-${anchorId}`;
|
|
2736
|
+
}
|
|
2737
|
+
}
|
|
2738
|
+
}
|
|
2739
|
+
/**
|
|
2740
|
+
* Intersect an element with every ancestor that establishes an overflow clip before the page
|
|
2741
|
+
* root. getBoundingClientRect() reports layout outside those clips, which is not rendered and
|
|
2742
|
+
* therefore must not satisfy PageMap completeness or inflate portable geometry.
|
|
2743
|
+
*/
|
|
2744
|
+
intersectWithClippingAncestors(element, page, pageRect, rect) {
|
|
2745
|
+
let left2 = Math.max(rect.left, pageRect.left);
|
|
2746
|
+
let top2 = Math.max(rect.top, pageRect.top);
|
|
2747
|
+
let right2 = Math.min(rect.right, pageRect.right);
|
|
2748
|
+
let bottom2 = Math.min(rect.bottom, pageRect.bottom);
|
|
2749
|
+
const clips = (value) => value === "hidden" || value === "clip" || value === "scroll" || value === "auto";
|
|
2750
|
+
for (let ancestor = element.parentElement; ancestor && ancestor !== page; ancestor = ancestor.parentElement) {
|
|
2751
|
+
const style = this.view.getComputedStyle(ancestor);
|
|
2752
|
+
const clipsX = clips(style.overflowX);
|
|
2753
|
+
const clipsY = clips(style.overflowY);
|
|
2754
|
+
if (!clipsX && !clipsY) continue;
|
|
2755
|
+
const ancestorRect = ancestor.getBoundingClientRect();
|
|
2756
|
+
if (clipsX) {
|
|
2757
|
+
left2 = Math.max(left2, ancestorRect.left);
|
|
2758
|
+
right2 = Math.min(right2, ancestorRect.right);
|
|
2759
|
+
}
|
|
2760
|
+
if (clipsY) {
|
|
2761
|
+
top2 = Math.max(top2, ancestorRect.top);
|
|
2762
|
+
bottom2 = Math.min(bottom2, ancestorRect.bottom);
|
|
2763
|
+
}
|
|
2764
|
+
}
|
|
2765
|
+
return { left: left2, top: top2, right: right2, bottom: bottom2 };
|
|
2766
|
+
}
|
|
2767
|
+
storyForCanonicalAnchor(anchorId) {
|
|
2768
|
+
const first = anchorId.indexOf(":");
|
|
2769
|
+
const second = first < 0 ? -1 : anchorId.indexOf(":", first + 1);
|
|
2770
|
+
const scope = first >= 0 && second > first ? anchorId.slice(first + 1, second) : "body";
|
|
2771
|
+
if (scope.startsWith("hdr")) return "header";
|
|
2772
|
+
if (scope.startsWith("ftr")) return "footer";
|
|
2773
|
+
if (scope === "fn") return "footnote";
|
|
2774
|
+
if (scope === "en") return "endnote";
|
|
2775
|
+
if (scope === "cmt") return "comment";
|
|
2776
|
+
return "body";
|
|
2777
|
+
}
|
|
2778
|
+
/**
|
|
2779
|
+
* Add canonical IDs from an addressable source subtree to the pre-pagination inventory.
|
|
2780
|
+
* Registry wrappers are excluded when scanning staging because selectable registry contents are
|
|
2781
|
+
* inventoried separately. Producers may explicitly mark content that has no visual substrate
|
|
2782
|
+
* with `data-page-map-exclude="true"`; native hidden semantics carry the same signal.
|
|
2783
|
+
*/
|
|
2784
|
+
collectExpectedSourceAnchors(source, destination, excludeRegistries = false) {
|
|
2785
|
+
const candidates = source.matches("[data-source-anchor-id]") ? [source] : [];
|
|
2786
|
+
candidates.push(...Array.from(source.querySelectorAll("[data-source-anchor-id]")));
|
|
2787
|
+
for (const element of candidates) {
|
|
2788
|
+
if (excludeRegistries && element.closest(
|
|
2789
|
+
"#pagination-hf-registry, #pagination-footnote-registry, #pagination-comment-margin-registry"
|
|
2790
|
+
)) {
|
|
2791
|
+
continue;
|
|
2792
|
+
}
|
|
2793
|
+
if (this.isZeroHeightExplicitBreakCarrier(element)) {
|
|
2794
|
+
element.dataset.pageMapExclude = "true";
|
|
2795
|
+
}
|
|
2796
|
+
if (this.isDeliberatelyUnrenderedSource(element, source)) continue;
|
|
2797
|
+
const anchorId = element.dataset.sourceAnchorId;
|
|
2798
|
+
if (anchorId) destination.add(anchorId);
|
|
2799
|
+
}
|
|
2800
|
+
}
|
|
2801
|
+
isZeroHeightExplicitBreakCarrier(element) {
|
|
2802
|
+
const next = element.nextElementSibling;
|
|
2803
|
+
const bounds = element.getBoundingClientRect();
|
|
2804
|
+
return element.hasAttribute("data-source-anchor-id") && (element.textContent ?? "").replace(/\u00a0/g, "").trim() === "" && bounds.height <= 0.01 && !element.querySelector("[data-source-anchor-id]") && !element.querySelector("img,svg,canvas,table,hr,input,textarea,select") && (next?.dataset.pageBreak === "true" || next?.classList.contains(`${this.cssPrefix}break`) === true);
|
|
2805
|
+
}
|
|
2806
|
+
isDeliberatelyUnrenderedSource(element, sourceRoot) {
|
|
2807
|
+
for (let current = element; current; current = current.parentElement) {
|
|
2808
|
+
const zeroHeightExplicitBreakCarrier = current === element && this.isZeroHeightExplicitBreakCarrier(current);
|
|
2809
|
+
if (current.dataset.pageMapExclude === "true" || current.dataset.pageBreak === "true" || current.classList.contains(`${this.cssPrefix}break`) || zeroHeightExplicitBreakCarrier || current.hidden || current.getAttribute("aria-hidden") === "true" || current.style.display === "none" || current.style.visibility === "hidden") {
|
|
2810
|
+
return true;
|
|
2811
|
+
}
|
|
2812
|
+
if (current === sourceRoot) break;
|
|
2813
|
+
}
|
|
2814
|
+
return false;
|
|
2815
|
+
}
|
|
2816
|
+
/**
|
|
2817
|
+
* Keep exactly one active bare-Unid editor anchor per source block. Presentation clones use
|
|
2818
|
+
* canonical source identity plus page/fragment qualification instead.
|
|
2819
|
+
*/
|
|
2820
|
+
qualifyPageFragments(pages) {
|
|
2821
|
+
const fragmentCounts = /* @__PURE__ */ new Map();
|
|
2822
|
+
const activeCanonicalIds = /* @__PURE__ */ new Set();
|
|
2823
|
+
const activeBareAnchorIds = /* @__PURE__ */ new Set();
|
|
2824
|
+
const makeInactive = (element) => {
|
|
2825
|
+
element.removeAttribute("data-anchor");
|
|
2826
|
+
element.removeAttribute("data-committed-text");
|
|
2827
|
+
if (element.hasAttribute("contenteditable")) element.setAttribute("contenteditable", "false");
|
|
2828
|
+
};
|
|
2829
|
+
for (const page of pages) {
|
|
2830
|
+
const nodes = page.element.querySelectorAll("[data-source-anchor-id]");
|
|
2831
|
+
for (const element of Array.from(nodes)) {
|
|
2832
|
+
const anchorId = element.dataset.sourceAnchorId;
|
|
2833
|
+
if (!anchorId) continue;
|
|
2834
|
+
const fragmentIndex = fragmentCounts.get(anchorId) ?? 0;
|
|
2835
|
+
fragmentCounts.set(anchorId, fragmentIndex + 1);
|
|
2836
|
+
element.dataset.pageNumber = String(page.pageNumber);
|
|
2837
|
+
element.dataset.fragmentIndex = String(fragmentIndex);
|
|
2838
|
+
element.dataset.pageFragmentId = `p${page.pageNumber}-f${fragmentIndex}-${anchorId}`;
|
|
2839
|
+
const story = this.storyForCanonicalAnchor(anchorId);
|
|
2840
|
+
const mayOwnActiveEditorAnchor = story === "body" || story === "comment" || story === "footnote" || story === "endnote";
|
|
2841
|
+
if (element.hasAttribute("data-anchor")) {
|
|
2842
|
+
const bareAnchorId = element.dataset.anchor;
|
|
2843
|
+
if (mayOwnActiveEditorAnchor && !activeCanonicalIds.has(anchorId) && !activeBareAnchorIds.has(bareAnchorId)) {
|
|
2844
|
+
activeCanonicalIds.add(anchorId);
|
|
2845
|
+
activeBareAnchorIds.add(bareAnchorId);
|
|
2846
|
+
} else {
|
|
2847
|
+
makeInactive(element);
|
|
2848
|
+
}
|
|
1629
2849
|
}
|
|
1630
2850
|
}
|
|
1631
|
-
const sectionPages = this.flowToPages(blocks, run.dims, pageNumber, run.sectionIndex);
|
|
1632
|
-
pages.push(...sectionPages);
|
|
1633
|
-
pageNumber += sectionPages.length;
|
|
1634
2851
|
}
|
|
1635
|
-
|
|
1636
|
-
|
|
1637
|
-
|
|
2852
|
+
const activeStagingCanonicalIds = /* @__PURE__ */ new Set();
|
|
2853
|
+
for (const element of Array.from(
|
|
2854
|
+
this.stagingElement.querySelectorAll("[data-source-anchor-id][data-anchor]")
|
|
2855
|
+
)) {
|
|
2856
|
+
const anchorId = element.dataset.sourceAnchorId;
|
|
2857
|
+
if (!anchorId) continue;
|
|
2858
|
+
const bareAnchorId = element.dataset.anchor;
|
|
2859
|
+
if (activeCanonicalIds.has(anchorId) || activeStagingCanonicalIds.has(anchorId) || activeBareAnchorIds.has(bareAnchorId)) {
|
|
2860
|
+
makeInactive(element);
|
|
2861
|
+
} else {
|
|
2862
|
+
activeStagingCanonicalIds.add(anchorId);
|
|
2863
|
+
activeBareAnchorIds.add(bareAnchorId);
|
|
2864
|
+
}
|
|
2865
|
+
}
|
|
2866
|
+
}
|
|
2867
|
+
/**
|
|
2868
|
+
* Page flow clones source blocks while the hidden staging tree stays in the document. Any HTML
|
|
2869
|
+
* fragment target copied into a visible page would therefore resolve to its earlier hidden
|
|
2870
|
+
* source. Transfer target ownership to the page presentation after flow is complete; registry
|
|
2871
|
+
* and wrapper IDs that have no visible counterpart remain available to pagination internals.
|
|
2872
|
+
*/
|
|
2873
|
+
transferVisibleFragmentTargets() {
|
|
2874
|
+
const visibleIds = new Set(Array.from(
|
|
2875
|
+
this.containerElement.querySelectorAll("[id]")
|
|
2876
|
+
).map((element) => element.id).filter(Boolean));
|
|
2877
|
+
if (visibleIds.size === 0) return;
|
|
2878
|
+
for (const source of Array.from(
|
|
2879
|
+
this.stagingElement.querySelectorAll("[id]")
|
|
2880
|
+
)) {
|
|
2881
|
+
if (visibleIds.has(source.id)) source.removeAttribute("id");
|
|
2882
|
+
}
|
|
1638
2883
|
}
|
|
1639
2884
|
/** Read each section's `w:pgNumType` off its wrapper (see {@link SectionPageNumbering}). */
|
|
1640
2885
|
parsePageNumbering(sections) {
|
|
@@ -1678,9 +2923,8 @@ var Docxodus = (() => {
|
|
|
1678
2923
|
if (markers.length === 0) continue;
|
|
1679
2924
|
const sectionIndex = parseInt(box.dataset.sectionIndex || "0", 10);
|
|
1680
2925
|
const pageNumber = parseInt(box.dataset.pageNumber || "1", 10);
|
|
1681
|
-
const pageInSection = parseInt(box.dataset.pageInSection || "1", 10);
|
|
1682
2926
|
const numbering = this.pageNumbering.get(sectionIndex) ?? {};
|
|
1683
|
-
const displayed =
|
|
2927
|
+
const displayed = parseInt(box.dataset.displayedPageNumber || String(pageNumber), 10);
|
|
1684
2928
|
for (const marker of Array.from(markers)) {
|
|
1685
2929
|
const kind = marker.dataset.field;
|
|
1686
2930
|
if (kind !== "PAGE" && kind !== "NUMPAGES") continue;
|
|
@@ -1692,17 +2936,26 @@ var Docxodus = (() => {
|
|
|
1692
2936
|
/**
|
|
1693
2937
|
* Measures all content blocks in a section.
|
|
1694
2938
|
*/
|
|
1695
|
-
measureBlocks(section, dims) {
|
|
2939
|
+
measureBlocks(section, dims, sectionIndex) {
|
|
1696
2940
|
const blocks = [];
|
|
1697
2941
|
const children = Array.from(section.children);
|
|
1698
2942
|
for (const child of children) {
|
|
2943
|
+
this.checkpoint();
|
|
1699
2944
|
if (child.dataset.sectionIndex !== void 0) {
|
|
1700
|
-
const
|
|
2945
|
+
const nestedSectionIndex = parseInt(child.dataset.sectionIndex || String(sectionIndex), 10);
|
|
2946
|
+
const nestedBlocks = this.measureBlocks(child, dims, nestedSectionIndex);
|
|
1701
2947
|
blocks.push(...nestedBlocks);
|
|
1702
2948
|
continue;
|
|
1703
2949
|
}
|
|
2950
|
+
if (child.matches("section.endnotes")) {
|
|
2951
|
+
const endnoteBlocks = this.measureSafeEndnoteBlocks(child, dims, sectionIndex);
|
|
2952
|
+
if (endnoteBlocks) {
|
|
2953
|
+
blocks.push(...endnoteBlocks);
|
|
2954
|
+
continue;
|
|
2955
|
+
}
|
|
2956
|
+
}
|
|
1704
2957
|
const rect = child.getBoundingClientRect();
|
|
1705
|
-
const style =
|
|
2958
|
+
const style = this.view.getComputedStyle(child);
|
|
1706
2959
|
const marginTopPx = parseFloat(style.marginTop) || 0;
|
|
1707
2960
|
const marginBottomPx = parseFloat(style.marginBottom) || 0;
|
|
1708
2961
|
const heightPt = pxToPt(rect.height);
|
|
@@ -1711,6 +2964,7 @@ var Docxodus = (() => {
|
|
|
1711
2964
|
const isPageBreak = child.dataset.pageBreak === "true" || child.classList.contains(`${this.cssPrefix}break`);
|
|
1712
2965
|
blocks.push({
|
|
1713
2966
|
element: child,
|
|
2967
|
+
sectionIndex,
|
|
1714
2968
|
heightPt,
|
|
1715
2969
|
marginTopPt,
|
|
1716
2970
|
marginBottomPt,
|
|
@@ -1723,6 +2977,95 @@ var Docxodus = (() => {
|
|
|
1723
2977
|
}
|
|
1724
2978
|
return blocks;
|
|
1725
2979
|
}
|
|
2980
|
+
/**
|
|
2981
|
+
* Flatten the converter's `section.endnotes > ol > li > p` presentation into
|
|
2982
|
+
* ordinary paragraph blocks. This preserves paragraph formatting and canonical
|
|
2983
|
+
* p:en/en:en identities while allowing the existing paragraph fragmenter to
|
|
2984
|
+
* split a long endnote across page boundaries.
|
|
2985
|
+
*/
|
|
2986
|
+
measureSafeEndnoteBlocks(section, dims, sectionIndex) {
|
|
2987
|
+
const sectionChildren = Array.from(section.children);
|
|
2988
|
+
const list = sectionChildren.find((child) => child.tagName === "OL");
|
|
2989
|
+
if (!list || sectionChildren.some((child) => child.tagName !== "HR" && child !== list) || Array.from(list.children).some((child) => child.tagName !== "LI")) {
|
|
2990
|
+
return null;
|
|
2991
|
+
}
|
|
2992
|
+
const items = Array.from(list.children);
|
|
2993
|
+
if (items.length === 0 || items.some(
|
|
2994
|
+
(item) => item.children.length === 0 || Array.from(item.children).some((child) => child.tagName !== "P")
|
|
2995
|
+
)) {
|
|
2996
|
+
return null;
|
|
2997
|
+
}
|
|
2998
|
+
const blocks = [];
|
|
2999
|
+
const sectionStyle = this.view.getComputedStyle(section);
|
|
3000
|
+
for (const rule of sectionChildren.filter((child) => child.tagName === "HR")) {
|
|
3001
|
+
this.checkpoint();
|
|
3002
|
+
const clonedRule = rule.cloneNode(true);
|
|
3003
|
+
if (blocks.length === 0) clonedRule.style.marginTop = sectionStyle.marginTop;
|
|
3004
|
+
blocks.push(this.measureElement(clonedRule, dims, sectionIndex));
|
|
3005
|
+
}
|
|
3006
|
+
const listStyle = this.view.getComputedStyle(list);
|
|
3007
|
+
for (let itemIndex = 0; itemIndex < items.length; itemIndex++) {
|
|
3008
|
+
this.checkpoint();
|
|
3009
|
+
const item = items[itemIndex];
|
|
3010
|
+
const ownerAnchorId = item.dataset.sourceAnchorId;
|
|
3011
|
+
const paragraphs = Array.from(item.children);
|
|
3012
|
+
for (let paragraphIndex = 0; paragraphIndex < paragraphs.length; paragraphIndex++) {
|
|
3013
|
+
this.checkpoint();
|
|
3014
|
+
const paragraph = paragraphs[paragraphIndex];
|
|
3015
|
+
if (!this.hasRangeFragmentSafeLayout(paragraph)) {
|
|
3016
|
+
return null;
|
|
3017
|
+
}
|
|
3018
|
+
const clone = paragraph.cloneNode(true);
|
|
3019
|
+
clone.dataset.paginationSafeEndnote = "true";
|
|
3020
|
+
clone.style.fontSize ||= sectionStyle.fontSize;
|
|
3021
|
+
clone.style.lineHeight ||= sectionStyle.lineHeight;
|
|
3022
|
+
clone.style.paddingLeft ||= listStyle.paddingLeft;
|
|
3023
|
+
if (ownerAnchorId && !Array.from(
|
|
3024
|
+
clone.querySelectorAll("[data-source-anchor-id]")
|
|
3025
|
+
).some((node) => node.dataset.sourceAnchorId === ownerAnchorId)) {
|
|
3026
|
+
const owner = this.document.createElement("span");
|
|
3027
|
+
owner.dataset.sourceAnchorId = ownerAnchorId;
|
|
3028
|
+
while (clone.firstChild) owner.appendChild(clone.firstChild);
|
|
3029
|
+
clone.appendChild(owner);
|
|
3030
|
+
}
|
|
3031
|
+
if (paragraphIndex === 0) {
|
|
3032
|
+
const itemId = item.id;
|
|
3033
|
+
if (itemId) {
|
|
3034
|
+
if (clone.id && clone.id !== itemId) return null;
|
|
3035
|
+
clone.id = itemId;
|
|
3036
|
+
}
|
|
3037
|
+
const value = parseInt(item.getAttribute("value") || String(itemIndex + 1), 10);
|
|
3038
|
+
const marker = this.formatOrderedListMarker(
|
|
3039
|
+
Number.isFinite(value) ? value : itemIndex + 1,
|
|
3040
|
+
listStyle.listStyleType
|
|
3041
|
+
);
|
|
3042
|
+
clone.insertBefore(this.document.createTextNode(`${marker}. `), clone.firstChild);
|
|
3043
|
+
}
|
|
3044
|
+
blocks.push(this.measureElement(clone, dims, sectionIndex));
|
|
3045
|
+
}
|
|
3046
|
+
}
|
|
3047
|
+
return blocks;
|
|
3048
|
+
}
|
|
3049
|
+
/** Render the CSS ordered-list formats emitted by the converter after an endnote is flattened. */
|
|
3050
|
+
formatOrderedListMarker(value, listStyleType) {
|
|
3051
|
+
const format = (() => {
|
|
3052
|
+
switch (listStyleType) {
|
|
3053
|
+
case "lower-roman":
|
|
3054
|
+
return "lowerRoman";
|
|
3055
|
+
case "upper-roman":
|
|
3056
|
+
return "upperRoman";
|
|
3057
|
+
case "lower-alpha":
|
|
3058
|
+
case "lower-latin":
|
|
3059
|
+
return "lowerLetter";
|
|
3060
|
+
case "upper-alpha":
|
|
3061
|
+
case "upper-latin":
|
|
3062
|
+
return "upperLetter";
|
|
3063
|
+
default:
|
|
3064
|
+
return "decimal";
|
|
3065
|
+
}
|
|
3066
|
+
})();
|
|
3067
|
+
return formatPageNumber(value, format);
|
|
3068
|
+
}
|
|
1726
3069
|
/**
|
|
1727
3070
|
* Flows a multi-column (`w:cols`) section's children into CSS-multicol container
|
|
1728
3071
|
* blocks. Word lays such a section out as N columns inside the same body extent;
|
|
@@ -1740,7 +3083,7 @@ var Docxodus = (() => {
|
|
|
1740
3083
|
const maxFragmentHeight = this.smallestEffectiveContentHeight(dims, sectionIndex);
|
|
1741
3084
|
const isBreak = (child) => child.dataset.pageBreak === "true" || child.classList.contains(`${this.cssPrefix}break`);
|
|
1742
3085
|
const makeContainer = (slice) => {
|
|
1743
|
-
const container = document.createElement("div");
|
|
3086
|
+
const container = this.document.createElement("div");
|
|
1744
3087
|
container.style.columnCount = String(columnCount);
|
|
1745
3088
|
container.style.columnGap = `${columnGapPt}pt`;
|
|
1746
3089
|
for (const child of slice) {
|
|
@@ -1750,17 +3093,19 @@ var Docxodus = (() => {
|
|
|
1750
3093
|
};
|
|
1751
3094
|
let start2 = 0;
|
|
1752
3095
|
while (start2 < children.length) {
|
|
3096
|
+
this.checkpoint();
|
|
1753
3097
|
if (isBreak(children[start2])) {
|
|
1754
|
-
blocks.push(this.measureElement(children[start2], dims));
|
|
3098
|
+
blocks.push(this.measureElement(children[start2], dims, sectionIndex));
|
|
1755
3099
|
start2++;
|
|
1756
3100
|
continue;
|
|
1757
3101
|
}
|
|
1758
3102
|
let end = start2 + 1;
|
|
1759
3103
|
let container = makeContainer(children.slice(start2, end));
|
|
1760
|
-
let measured = this.measureElement(container, dims);
|
|
3104
|
+
let measured = this.measureElement(container, dims, sectionIndex);
|
|
1761
3105
|
while (end < children.length && !isBreak(children[end])) {
|
|
3106
|
+
this.checkpoint();
|
|
1762
3107
|
const candidate = makeContainer(children.slice(start2, end + 1));
|
|
1763
|
-
const candidateMeasured = this.measureElement(candidate, dims);
|
|
3108
|
+
const candidateMeasured = this.measureElement(candidate, dims, sectionIndex);
|
|
1764
3109
|
if (candidateMeasured.heightPt > maxFragmentHeight) break;
|
|
1765
3110
|
container = candidate;
|
|
1766
3111
|
measured = candidateMeasured;
|
|
@@ -1768,6 +3113,7 @@ var Docxodus = (() => {
|
|
|
1768
3113
|
}
|
|
1769
3114
|
blocks.push({
|
|
1770
3115
|
element: container,
|
|
3116
|
+
sectionIndex,
|
|
1771
3117
|
heightPt: measured.heightPt,
|
|
1772
3118
|
marginTopPt: measured.marginTopPt,
|
|
1773
3119
|
marginBottomPt: measured.marginBottomPt,
|
|
@@ -1786,8 +3132,8 @@ var Docxodus = (() => {
|
|
|
1786
3132
|
* This is intentionally DOM-based: table row heights cannot be inferred from individual
|
|
1787
3133
|
* rows because wrapping and collapsed borders change the height of a fragment.
|
|
1788
3134
|
*/
|
|
1789
|
-
measureElement(element, dims) {
|
|
1790
|
-
const measurementHost = document.createElement("div");
|
|
3135
|
+
measureElement(element, dims, sectionIndex) {
|
|
3136
|
+
const measurementHost = this.document.createElement("div");
|
|
1791
3137
|
measurementHost.style.position = "absolute";
|
|
1792
3138
|
measurementHost.style.visibility = "hidden";
|
|
1793
3139
|
measurementHost.style.left = "-9999px";
|
|
@@ -1796,9 +3142,10 @@ var Docxodus = (() => {
|
|
|
1796
3142
|
measurementHost.appendChild(measuredElement);
|
|
1797
3143
|
this.stagingElement.appendChild(measurementHost);
|
|
1798
3144
|
const rect = measuredElement.getBoundingClientRect();
|
|
1799
|
-
const style =
|
|
3145
|
+
const style = this.view.getComputedStyle(measuredElement);
|
|
1800
3146
|
const measured = {
|
|
1801
3147
|
element,
|
|
3148
|
+
sectionIndex,
|
|
1802
3149
|
heightPt: pxToPt(rect.height),
|
|
1803
3150
|
marginTopPt: pxToPt(parseFloat(style.marginTop) || 0),
|
|
1804
3151
|
marginBottomPt: pxToPt(parseFloat(style.marginBottom) || 0),
|
|
@@ -1892,6 +3239,7 @@ var Docxodus = (() => {
|
|
|
1892
3239
|
const knownIds = new Set(existingFootnoteIds);
|
|
1893
3240
|
const newIds = [];
|
|
1894
3241
|
for (const block of blocks) {
|
|
3242
|
+
this.checkpoint();
|
|
1895
3243
|
for (const id of this.extractFootnoteRefs(block.element)) {
|
|
1896
3244
|
if (!knownIds.has(id)) {
|
|
1897
3245
|
knownIds.add(id);
|
|
@@ -1909,8 +3257,8 @@ var Docxodus = (() => {
|
|
|
1909
3257
|
smallestEffectiveContentHeight(dims, sectionIndex) {
|
|
1910
3258
|
return Math.min(
|
|
1911
3259
|
this.getPageBands(dims, sectionIndex, 1, 1).bodyHeight,
|
|
1912
|
-
this.getPageBands(dims, sectionIndex, 2,
|
|
1913
|
-
this.getPageBands(dims, sectionIndex,
|
|
3260
|
+
this.getPageBands(dims, sectionIndex, 2, 2).bodyHeight,
|
|
3261
|
+
this.getPageBands(dims, sectionIndex, 3, 3).bodyHeight
|
|
1914
3262
|
);
|
|
1915
3263
|
}
|
|
1916
3264
|
/**
|
|
@@ -1948,10 +3296,11 @@ var Docxodus = (() => {
|
|
|
1948
3296
|
if (wrapper.tagName !== "DIV" || wrapper.children.length !== 1 || block.keepWithNext || block.keepLines || block.pageBreakBefore || block.isPageBreak) {
|
|
1949
3297
|
return null;
|
|
1950
3298
|
}
|
|
1951
|
-
const
|
|
1952
|
-
if (!
|
|
3299
|
+
const tableElement = wrapper.firstElementChild;
|
|
3300
|
+
if (!tableElement || tableElement.localName !== "table") {
|
|
1953
3301
|
return null;
|
|
1954
3302
|
}
|
|
3303
|
+
const table = tableElement;
|
|
1955
3304
|
const body = table.tBodies.length === 1 ? table.tBodies[0] : null;
|
|
1956
3305
|
if (!body || table.tHead || table.tFoot || body.rows.length < 2 || Array.from(table.children).some((child) => child !== body && child.tagName !== "COLGROUP") || table.querySelector("table, [rowspan], [colspan], [data-footnote-id]") || wrapper.querySelector("[data-footnote-id]")) {
|
|
1957
3306
|
return null;
|
|
@@ -1965,8 +3314,10 @@ var Docxodus = (() => {
|
|
|
1965
3314
|
const groups = [];
|
|
1966
3315
|
let start2 = 0;
|
|
1967
3316
|
while (start2 < rows.length) {
|
|
3317
|
+
this.checkpoint();
|
|
1968
3318
|
let end = start2;
|
|
1969
3319
|
while (end < rows.length) {
|
|
3320
|
+
this.checkpoint();
|
|
1970
3321
|
const candidate = this.createSimpleTableFragment(
|
|
1971
3322
|
wrapper,
|
|
1972
3323
|
table,
|
|
@@ -1974,7 +3325,7 @@ var Docxodus = (() => {
|
|
|
1974
3325
|
rows.slice(start2, end + 1),
|
|
1975
3326
|
start2 === 0
|
|
1976
3327
|
);
|
|
1977
|
-
const measured = this.measureElement(candidate, dims);
|
|
3328
|
+
const measured = this.measureElement(candidate, dims, block.sectionIndex);
|
|
1978
3329
|
if (measured.heightPt > maximumFragmentHeight) {
|
|
1979
3330
|
break;
|
|
1980
3331
|
}
|
|
@@ -2006,7 +3357,7 @@ var Docxodus = (() => {
|
|
|
2006
3357
|
if (!isLast) {
|
|
2007
3358
|
fragment.style.setProperty("margin-bottom", "0", "important");
|
|
2008
3359
|
}
|
|
2009
|
-
const measured = this.measureElement(fragment, dims);
|
|
3360
|
+
const measured = this.measureElement(fragment, dims, block.sectionIndex);
|
|
2010
3361
|
if (measured.heightPt + measured.marginTopPt + measured.marginBottomPt > minimumContentHeight) {
|
|
2011
3362
|
return null;
|
|
2012
3363
|
}
|
|
@@ -2021,35 +3372,198 @@ var Docxodus = (() => {
|
|
|
2021
3372
|
return fragments;
|
|
2022
3373
|
}
|
|
2023
3374
|
/**
|
|
2024
|
-
*
|
|
2025
|
-
*
|
|
2026
|
-
*
|
|
2027
|
-
|
|
2028
|
-
|
|
2029
|
-
|
|
2030
|
-
|
|
3375
|
+
* DOM endpoints that can finish a paragraph fragment. The flattened UTF-16
|
|
3376
|
+
* offsets are checked against the browser's Unicode grapheme segmenter, so a
|
|
3377
|
+
* formatting-run boundary can never bisect a surrogate pair, combining
|
|
3378
|
+
* sequence, or joined emoji. NBSP/word-joiner boundaries remain indivisible.
|
|
3379
|
+
* PAGE/NUMPAGES field results are atomic because splitting their marker would
|
|
3380
|
+
* make later substitution duplicate or replace only half of the field.
|
|
3381
|
+
*/
|
|
3382
|
+
paragraphFragmentEndpoints(paragraph, emergencyGraphemeBreaks = false) {
|
|
3383
|
+
const textNodes = [];
|
|
3384
|
+
const textStarts = /* @__PURE__ */ new Map();
|
|
3385
|
+
const lastTextByField = /* @__PURE__ */ new Map();
|
|
3386
|
+
const walker = this.document.createTreeWalker(paragraph, this.view.NodeFilter.SHOW_TEXT);
|
|
3387
|
+
const flattenedChunks = [];
|
|
3388
|
+
let flattenedLength = 0;
|
|
2031
3389
|
let textNode;
|
|
2032
3390
|
while (textNode = walker.nextNode()) {
|
|
2033
|
-
|
|
2034
|
-
|
|
2035
|
-
|
|
3391
|
+
this.checkpoint();
|
|
3392
|
+
textStarts.set(textNode, flattenedLength);
|
|
3393
|
+
textNodes.push(textNode);
|
|
3394
|
+
flattenedChunks.push(textNode.data);
|
|
3395
|
+
flattenedLength += textNode.data.length;
|
|
3396
|
+
let field = textNode.parentElement?.closest("[data-field]") ?? null;
|
|
3397
|
+
while (field?.parentElement?.closest("[data-field]")) {
|
|
3398
|
+
field = field.parentElement.closest("[data-field]");
|
|
3399
|
+
}
|
|
3400
|
+
if (field && paragraph.contains(field)) lastTextByField.set(field, textNode);
|
|
3401
|
+
}
|
|
3402
|
+
const flattenedText = flattenedChunks.join("");
|
|
3403
|
+
if (!this.hasVisibleText(flattenedText)) return [];
|
|
3404
|
+
const invisible = /[\s\u200B-\u200F\uFEFF]/;
|
|
3405
|
+
let firstVisibleOffset = -1;
|
|
3406
|
+
let lastVisibleOffset = -1;
|
|
3407
|
+
for (let index = 0; index < flattenedText.length; index++) {
|
|
3408
|
+
if (index % 4096 === 0) this.checkpoint();
|
|
3409
|
+
if (invisible.test(flattenedText[index])) continue;
|
|
3410
|
+
if (firstVisibleOffset < 0) firstVisibleOffset = index;
|
|
3411
|
+
lastVisibleOffset = index;
|
|
3412
|
+
}
|
|
3413
|
+
const graphemeBoundaries = this.graphemeBoundaryOffsets(flattenedText);
|
|
3414
|
+
const candidates = [];
|
|
3415
|
+
const addCandidate = (candidate) => {
|
|
3416
|
+
const { textOffset } = candidate;
|
|
3417
|
+
if (textOffset <= 0 || textOffset >= flattenedText.length) return;
|
|
3418
|
+
if (textOffset <= firstVisibleOffset || textOffset > lastVisibleOffset) return;
|
|
3419
|
+
if (this.isNonBreakingTextBoundary(flattenedText, textOffset)) return;
|
|
3420
|
+
if (!this.isLegalParagraphLineBoundary(paragraph, flattenedText, textOffset)) return;
|
|
3421
|
+
if (graphemeBoundaries) {
|
|
3422
|
+
if (!graphemeBoundaries.has(textOffset)) return;
|
|
3423
|
+
} else if (!this.isConservativeFallbackTextBoundary(flattenedText, textOffset)) {
|
|
3424
|
+
return;
|
|
3425
|
+
}
|
|
3426
|
+
candidates.push(candidate);
|
|
3427
|
+
};
|
|
3428
|
+
for (const node of textNodes) {
|
|
3429
|
+
this.checkpoint();
|
|
3430
|
+
const start2 = textStarts.get(node);
|
|
3431
|
+
const atomic = node.parentElement?.closest(
|
|
3432
|
+
"[data-field], [data-list-marker], a[data-comment-id]"
|
|
3433
|
+
);
|
|
3434
|
+
if (atomic && paragraph.contains(atomic)) continue;
|
|
3435
|
+
const whitespace = /[\u0009-\u000D\u0020]+/g;
|
|
2036
3436
|
let match;
|
|
2037
|
-
|
|
2038
|
-
|
|
3437
|
+
let matchesSinceCheckpoint = 0;
|
|
3438
|
+
while ((match = whitespace.exec(node.data)) !== null) {
|
|
3439
|
+
if (++matchesSinceCheckpoint % 256 === 0) this.checkpoint();
|
|
3440
|
+
const offset = match.index + match[0].length;
|
|
3441
|
+
addCandidate({ node, offset, textOffset: start2 + offset, priority: 0 });
|
|
3442
|
+
}
|
|
3443
|
+
addCandidate({
|
|
3444
|
+
node,
|
|
3445
|
+
offset: node.data.length,
|
|
3446
|
+
textOffset: start2 + node.data.length,
|
|
3447
|
+
priority: 0
|
|
3448
|
+
});
|
|
3449
|
+
if (emergencyGraphemeBreaks) {
|
|
3450
|
+
for (let offset = 1; offset < node.data.length; offset++) {
|
|
3451
|
+
if (offset % 256 === 0) this.checkpoint();
|
|
3452
|
+
addCandidate({
|
|
3453
|
+
node,
|
|
3454
|
+
offset,
|
|
3455
|
+
textOffset: start2 + offset,
|
|
3456
|
+
priority: -1
|
|
3457
|
+
});
|
|
3458
|
+
}
|
|
2039
3459
|
}
|
|
2040
|
-
|
|
2041
|
-
|
|
3460
|
+
}
|
|
3461
|
+
for (const field of Array.from(paragraph.querySelectorAll("[data-field]"))) {
|
|
3462
|
+
this.checkpoint();
|
|
3463
|
+
if (field.parentElement?.closest("[data-field]")) continue;
|
|
3464
|
+
const last = lastTextByField.get(field);
|
|
3465
|
+
const parent = field.parentNode;
|
|
3466
|
+
if (!last || !parent) continue;
|
|
3467
|
+
const childIndex = Array.prototype.indexOf.call(parent.childNodes, field);
|
|
3468
|
+
if (childIndex < 0) continue;
|
|
3469
|
+
addCandidate({
|
|
3470
|
+
node: parent,
|
|
3471
|
+
offset: childIndex + 1,
|
|
3472
|
+
textOffset: textStarts.get(last) + last.data.length,
|
|
3473
|
+
priority: 1
|
|
3474
|
+
});
|
|
3475
|
+
}
|
|
3476
|
+
candidates.sort((left2, right2) => left2.textOffset - right2.textOffset || right2.priority - left2.priority);
|
|
3477
|
+
const endpoints = [];
|
|
3478
|
+
for (const candidate of candidates) {
|
|
3479
|
+
if (endpoints[endpoints.length - 1]?.textOffset !== candidate.textOffset) {
|
|
3480
|
+
endpoints.push(candidate);
|
|
2042
3481
|
}
|
|
2043
3482
|
}
|
|
2044
3483
|
return endpoints;
|
|
2045
3484
|
}
|
|
3485
|
+
/** Browser-native UAX #29 boundaries; null keeps older runtimes conservative. */
|
|
3486
|
+
graphemeBoundaryOffsets(text) {
|
|
3487
|
+
const SegmenterConstructor = this.view.Intl.Segmenter;
|
|
3488
|
+
if (!SegmenterConstructor) return null;
|
|
3489
|
+
const boundaries = /* @__PURE__ */ new Set([0, text.length]);
|
|
3490
|
+
let segmentCount = 0;
|
|
3491
|
+
for (const part of new SegmenterConstructor("en", { granularity: "grapheme" }).segment(text)) {
|
|
3492
|
+
if (++segmentCount % 256 === 0) this.checkpoint();
|
|
3493
|
+
boundaries.add(part.index);
|
|
3494
|
+
boundaries.add(part.index + part.segment.length);
|
|
3495
|
+
}
|
|
3496
|
+
return boundaries;
|
|
3497
|
+
}
|
|
3498
|
+
/**
|
|
3499
|
+
* Conservative UAX #14/CSS wrapping opportunities used for synthetic page
|
|
3500
|
+
* boundaries. In particular, a formatting-run boundary is not itself a word
|
|
3501
|
+
* boundary, and Japanese opening/closing punctuation stays with its pair.
|
|
3502
|
+
* Arbitrary grapheme breaks are admitted only when the paragraph's CSS asks
|
|
3503
|
+
* the browser to wrap anywhere.
|
|
3504
|
+
*/
|
|
3505
|
+
isLegalParagraphLineBoundary(paragraph, text, offset) {
|
|
3506
|
+
const lastCodeUnit = text.charCodeAt(offset - 1);
|
|
3507
|
+
const beforeOffset = lastCodeUnit >= 56320 && lastCodeUnit <= 57343 ? Math.max(0, offset - 2) : offset - 1;
|
|
3508
|
+
const beforeCodePoint = text.codePointAt(beforeOffset);
|
|
3509
|
+
const afterCodePoint = text.codePointAt(offset);
|
|
3510
|
+
const before = beforeCodePoint === void 0 ? "" : String.fromCodePoint(beforeCodePoint);
|
|
3511
|
+
const after = afterCodePoint === void 0 ? "" : String.fromCodePoint(afterCodePoint);
|
|
3512
|
+
if (/^[\u0009-\u000D\u0020]$/.test(before) || /^[\u0009-\u000D\u0020]$/.test(after)) return true;
|
|
3513
|
+
if (/[-\u00AD\u2010]$/u.test(before)) return true;
|
|
3514
|
+
const style = this.view.getComputedStyle(paragraph);
|
|
3515
|
+
if (style.wordBreak === "break-all" || style.overflowWrap === "anywhere" || style.overflowWrap === "break-word") return true;
|
|
3516
|
+
const eastAsian = /[\p{Script=Han}\p{Script=Hiragana}\p{Script=Katakana}\p{Script=Hangul}]/u;
|
|
3517
|
+
if (!eastAsian.test(before) && !eastAsian.test(after)) return false;
|
|
3518
|
+
const opening = "([{<\u2018\u201C\u3008\u300A\u300C\u300E\u3010\u3014\u3016\u3018\u301A\uFF08\uFF3B\uFF5B";
|
|
3519
|
+
const closing = ")]}>\u2019\u201D\u3001\u3002\u3009\u300B\u300D\u300F\u3011\u3015\u3017\u3019\u301B\uFF01\uFF05\uFF09\uFF0C\uFF0E\uFF1A\uFF1B\uFF1F\uFF3D\uFF5D";
|
|
3520
|
+
return !opening.includes(before) && !closing.includes(after);
|
|
3521
|
+
}
|
|
3522
|
+
/** Never fragment across characters whose line-break meaning is explicitly non-breaking. */
|
|
3523
|
+
isNonBreakingTextBoundary(text, offset) {
|
|
3524
|
+
const codePointBefore = (index) => {
|
|
3525
|
+
if (index <= 0) return void 0;
|
|
3526
|
+
const last = text.charCodeAt(index - 1);
|
|
3527
|
+
const start2 = last >= 56320 && last <= 57343 ? index - 2 : index - 1;
|
|
3528
|
+
return text.codePointAt(Math.max(0, start2));
|
|
3529
|
+
};
|
|
3530
|
+
const nonBreaking = /* @__PURE__ */ new Set([
|
|
3531
|
+
160,
|
|
3532
|
+
8206,
|
|
3533
|
+
8207,
|
|
3534
|
+
8209,
|
|
3535
|
+
8234,
|
|
3536
|
+
8235,
|
|
3537
|
+
8236,
|
|
3538
|
+
8237,
|
|
3539
|
+
8238,
|
|
3540
|
+
8239,
|
|
3541
|
+
8288,
|
|
3542
|
+
8294,
|
|
3543
|
+
8295,
|
|
3544
|
+
8296,
|
|
3545
|
+
8297,
|
|
3546
|
+
65279
|
|
3547
|
+
]);
|
|
3548
|
+
return nonBreaking.has(codePointBefore(offset) ?? -1) || nonBreaking.has(text.codePointAt(offset) ?? -1);
|
|
3549
|
+
}
|
|
3550
|
+
/**
|
|
3551
|
+
* Without Intl.Segmenter, admit only an all-ASCII boundary. This still
|
|
3552
|
+
* fragments ordinary prose while refusing to guess about Unicode clusters.
|
|
3553
|
+
*/
|
|
3554
|
+
isConservativeFallbackTextBoundary(text, offset) {
|
|
3555
|
+
return text.charCodeAt(offset - 1) <= 127 && text.charCodeAt(offset) <= 127;
|
|
3556
|
+
}
|
|
2046
3557
|
/**
|
|
2047
3558
|
* Whether a range contains visible text after ignoring bidi/zero-width marks.
|
|
2048
3559
|
* Paragraph fragmentation deliberately excludes non-textual descendants, so
|
|
2049
3560
|
* this is enough to reject empty head or tail fragments.
|
|
2050
3561
|
*/
|
|
2051
3562
|
hasVisibleFragmentText(fragment) {
|
|
2052
|
-
return (fragment.textContent || "")
|
|
3563
|
+
return this.hasVisibleText(fragment.textContent || "");
|
|
3564
|
+
}
|
|
3565
|
+
hasVisibleText(text) {
|
|
3566
|
+
return text.replace(/[\u200B-\u200F\uFEFF]/g, "").trim().length > 0;
|
|
2053
3567
|
}
|
|
2054
3568
|
/**
|
|
2055
3569
|
* Phase one only handles text-like paragraph descendants. Objects, explicit
|
|
@@ -2058,13 +3572,25 @@ var Docxodus = (() => {
|
|
|
2058
3572
|
* fallback instead of risking broken content or duplicate anchors.
|
|
2059
3573
|
*/
|
|
2060
3574
|
canFragmentParagraph(block) {
|
|
2061
|
-
if (!this.fragmentParagraphs
|
|
3575
|
+
if (!this.fragmentParagraphs) {
|
|
2062
3576
|
return false;
|
|
2063
3577
|
}
|
|
2064
3578
|
const paragraph = block.element;
|
|
2065
3579
|
if (block.keepWithNext || block.keepLines || block.pageBreakBefore || block.isPageBreak || paragraph.dataset.widowControl === "true" || paragraph.hasAttribute("contenteditable")) {
|
|
2066
3580
|
return false;
|
|
2067
3581
|
}
|
|
3582
|
+
return this.canRangeFragmentParagraph(paragraph);
|
|
3583
|
+
}
|
|
3584
|
+
/**
|
|
3585
|
+
* Shared structural gate for body and note paragraph fragmentation. Footnote
|
|
3586
|
+
* first paragraphs are inline beside their marker, so that one known layout
|
|
3587
|
+
* context may opt into an inline root while retaining every descendant and
|
|
3588
|
+
* break-safety restriction used for body text.
|
|
3589
|
+
*/
|
|
3590
|
+
canRangeFragmentParagraph(paragraph, allowInlineRoot = false) {
|
|
3591
|
+
if (paragraph.tagName !== "P" || paragraph.dataset.keepWithNext === "true" || paragraph.dataset.keepLines === "true" || paragraph.dataset.pageBreakBefore === "true" || paragraph.dataset.widowControl === "true" || paragraph.hasAttribute("contenteditable")) {
|
|
3592
|
+
return false;
|
|
3593
|
+
}
|
|
2068
3594
|
const unsupportedDescendants = [
|
|
2069
3595
|
"br",
|
|
2070
3596
|
"img",
|
|
@@ -2095,21 +3621,27 @@ var Docxodus = (() => {
|
|
|
2095
3621
|
"fieldset",
|
|
2096
3622
|
"details",
|
|
2097
3623
|
"[data-footnote-id]",
|
|
2098
|
-
"[data-list-marker]",
|
|
2099
|
-
"[data-anchor]",
|
|
2100
|
-
"[id]",
|
|
2101
3624
|
"[contenteditable]"
|
|
2102
3625
|
].join(", ");
|
|
2103
3626
|
if (paragraph.querySelector(unsupportedDescendants)) {
|
|
2104
3627
|
return false;
|
|
2105
3628
|
}
|
|
2106
|
-
const
|
|
2107
|
-
|
|
3629
|
+
const isValidatedEndnote = paragraph.dataset.paginationSafeEndnote === "true";
|
|
3630
|
+
return isValidatedEndnote || this.hasRangeFragmentSafeLayout(paragraph, allowInlineRoot);
|
|
3631
|
+
}
|
|
3632
|
+
/**
|
|
3633
|
+
* A range clone preserves nested inline formatting exactly. Anything that establishes its own
|
|
3634
|
+
* box/layout context is deferred until a future fragmenter can model it accurately. Callers
|
|
3635
|
+
* must invoke this while the paragraph is attached to the styled document.
|
|
3636
|
+
*/
|
|
3637
|
+
hasRangeFragmentSafeLayout(paragraph, allowInlineRoot = false) {
|
|
3638
|
+
const paragraphStyle = this.view.getComputedStyle(paragraph);
|
|
3639
|
+
if (paragraphStyle.display !== "block" && !(allowInlineRoot && paragraphStyle.display === "inline") || paragraphStyle.position !== "static" || paragraphStyle.float !== "none" || paragraphStyle.whiteSpace !== "normal" && paragraphStyle.whiteSpace !== "pre-wrap" || paragraphStyle.breakBefore !== "auto" || paragraphStyle.breakAfter !== "auto" || paragraphStyle.breakInside === "avoid" || paragraphStyle.pageBreakBefore !== "auto" || paragraphStyle.pageBreakAfter !== "auto" || paragraphStyle.pageBreakInside === "avoid") {
|
|
2108
3640
|
return false;
|
|
2109
3641
|
}
|
|
2110
3642
|
for (const descendant of Array.from(paragraph.querySelectorAll("*"))) {
|
|
2111
|
-
const style =
|
|
2112
|
-
if (style.display !== "inline" || style.position !== "static" || style.float !== "none" || style.whiteSpace !== "normal") {
|
|
3643
|
+
const style = this.view.getComputedStyle(descendant);
|
|
3644
|
+
if (style.display !== "inline" || style.position !== "static" || style.float !== "none" || style.whiteSpace !== "normal" && style.whiteSpace !== "pre-wrap") {
|
|
2113
3645
|
return false;
|
|
2114
3646
|
}
|
|
2115
3647
|
}
|
|
@@ -2136,58 +3668,122 @@ var Docxodus = (() => {
|
|
|
2136
3668
|
return fragment;
|
|
2137
3669
|
}
|
|
2138
3670
|
/**
|
|
2139
|
-
*
|
|
2140
|
-
*
|
|
2141
|
-
*
|
|
3671
|
+
* Range clones repeat an inline ancestor when the split lands inside it. Keep
|
|
3672
|
+
* semantic wrappers (links, comments, formatting) on both sides, but retain a
|
|
3673
|
+
* duplicated HTML/editor identity only on the leading fragment. Targets that
|
|
3674
|
+
* occur wholly after the split are absent from `head` and remain on `tail`.
|
|
2142
3675
|
*/
|
|
2143
|
-
|
|
2144
|
-
|
|
2145
|
-
|
|
2146
|
-
|
|
2147
|
-
|
|
2148
|
-
const
|
|
2149
|
-
|
|
2150
|
-
|
|
2151
|
-
}
|
|
2152
|
-
|
|
2153
|
-
|
|
2154
|
-
|
|
2155
|
-
|
|
2156
|
-
|
|
2157
|
-
|
|
2158
|
-
|
|
2159
|
-
|
|
2160
|
-
|
|
2161
|
-
|
|
2162
|
-
|
|
2163
|
-
|
|
2164
|
-
|
|
3676
|
+
reconcileParagraphFragmentIdentities(head, tail) {
|
|
3677
|
+
const elements = (root, selector2) => [
|
|
3678
|
+
...root.matches(selector2) ? [root] : [],
|
|
3679
|
+
...Array.from(root.querySelectorAll(selector2))
|
|
3680
|
+
];
|
|
3681
|
+
const headIds = new Set(elements(head, "[id]").map((element) => element.id));
|
|
3682
|
+
for (const element of elements(tail, "[id]")) {
|
|
3683
|
+
if (headIds.has(element.id)) element.removeAttribute("id");
|
|
3684
|
+
}
|
|
3685
|
+
const headAnchors = new Set(elements(head, "[data-anchor]").map((element) => element.dataset.anchor).filter((anchor) => Boolean(anchor)));
|
|
3686
|
+
for (const element of elements(tail, "[data-anchor]")) {
|
|
3687
|
+
if (!element.dataset.anchor || !headAnchors.has(element.dataset.anchor)) continue;
|
|
3688
|
+
element.removeAttribute("data-anchor");
|
|
3689
|
+
element.removeAttribute("data-committed-text");
|
|
3690
|
+
}
|
|
3691
|
+
}
|
|
3692
|
+
/**
|
|
3693
|
+
* Range-clone the largest safe prefix accepted by `fits`. The measurement
|
|
3694
|
+
* policy stays with the caller, allowing body blocks and note bands to share
|
|
3695
|
+
* one DOM fragmenter while measuring in their respective layout contexts.
|
|
3696
|
+
*/
|
|
3697
|
+
splitParagraphAtLargestFit(paragraph, fits, options = {}) {
|
|
3698
|
+
const largestFit = (endpoints2) => {
|
|
3699
|
+
const lastEndpoint = endpoints2[endpoints2.length - 1];
|
|
3700
|
+
if (lastEndpoint) {
|
|
3701
|
+
const lastRange = this.document.createRange();
|
|
3702
|
+
lastRange.setStart(paragraph, 0);
|
|
3703
|
+
lastRange.setEnd(lastEndpoint.node, lastEndpoint.offset);
|
|
3704
|
+
const lastContents = lastRange.cloneContents();
|
|
3705
|
+
if (this.hasVisibleFragmentText(lastContents)) {
|
|
3706
|
+
const lastHead = this.createParagraphFragment(paragraph, lastRange, true, false);
|
|
3707
|
+
if (fits(lastHead)) return { endpoint: lastEndpoint, head: lastHead };
|
|
3708
|
+
}
|
|
2165
3709
|
}
|
|
2166
|
-
|
|
2167
|
-
|
|
2168
|
-
|
|
2169
|
-
|
|
2170
|
-
|
|
2171
|
-
|
|
2172
|
-
|
|
3710
|
+
let low = 0;
|
|
3711
|
+
let high = endpoints2.length - 2;
|
|
3712
|
+
let best2 = null;
|
|
3713
|
+
while (low <= high) {
|
|
3714
|
+
this.checkpoint();
|
|
3715
|
+
const middle = Math.floor((low + high) / 2);
|
|
3716
|
+
const endpoint = endpoints2[middle];
|
|
3717
|
+
const headRange = this.document.createRange();
|
|
3718
|
+
headRange.setStart(paragraph, 0);
|
|
3719
|
+
headRange.setEnd(endpoint.node, endpoint.offset);
|
|
3720
|
+
const headContents = headRange.cloneContents();
|
|
3721
|
+
if (!this.hasVisibleFragmentText(headContents)) {
|
|
3722
|
+
low = middle + 1;
|
|
3723
|
+
continue;
|
|
3724
|
+
}
|
|
3725
|
+
const head = this.createParagraphFragment(paragraph, headRange, true, false);
|
|
3726
|
+
if (fits(head)) {
|
|
3727
|
+
best2 = { endpoint, head };
|
|
3728
|
+
low = middle + 1;
|
|
3729
|
+
} else {
|
|
3730
|
+
high = middle - 1;
|
|
3731
|
+
}
|
|
2173
3732
|
}
|
|
3733
|
+
return best2;
|
|
3734
|
+
};
|
|
3735
|
+
let endpoints = this.paragraphFragmentEndpoints(paragraph);
|
|
3736
|
+
let best = largestFit(endpoints);
|
|
3737
|
+
if (!best && options.emergencyGraphemeBreaks) {
|
|
3738
|
+
endpoints = this.paragraphFragmentEndpoints(paragraph, true);
|
|
3739
|
+
best = largestFit(endpoints);
|
|
3740
|
+
}
|
|
3741
|
+
if (!best && options.forceFirstOnNoFit && endpoints.length > 0) {
|
|
3742
|
+
const endpoint = endpoints[0];
|
|
3743
|
+
const headRange = this.document.createRange();
|
|
3744
|
+
headRange.setStart(paragraph, 0);
|
|
3745
|
+
headRange.setEnd(endpoint.node, endpoint.offset);
|
|
3746
|
+
best = {
|
|
3747
|
+
endpoint,
|
|
3748
|
+
head: this.createParagraphFragment(paragraph, headRange, true, false)
|
|
3749
|
+
};
|
|
2174
3750
|
}
|
|
2175
3751
|
if (!best) {
|
|
2176
|
-
return
|
|
3752
|
+
return endpoints.length > 0 ? { kind: "no-fit" } : { kind: "indivisible" };
|
|
2177
3753
|
}
|
|
2178
|
-
const tailRange = document.createRange();
|
|
3754
|
+
const tailRange = this.document.createRange();
|
|
2179
3755
|
tailRange.setStart(best.endpoint.node, best.endpoint.offset);
|
|
2180
3756
|
tailRange.setEnd(paragraph, paragraph.childNodes.length);
|
|
2181
3757
|
const tailContents = tailRange.cloneContents();
|
|
2182
|
-
if (!this.hasVisibleFragmentText(tailContents)) {
|
|
3758
|
+
if (!this.hasVisibleFragmentText(tailContents)) return { kind: "indivisible" };
|
|
3759
|
+
const tail = this.createParagraphFragment(paragraph, tailRange, false, true);
|
|
3760
|
+
this.reconcileParagraphFragmentIdentities(best.head, tail);
|
|
3761
|
+
return {
|
|
3762
|
+
kind: "split",
|
|
3763
|
+
head: best.head,
|
|
3764
|
+
tail
|
|
3765
|
+
};
|
|
3766
|
+
}
|
|
3767
|
+
/**
|
|
3768
|
+
* Splits a simple paragraph at the largest DOM Range endpoint that fits the
|
|
3769
|
+
* currently available body space. The caller then processes the tail normally,
|
|
3770
|
+
* allowing it to fragment again on later pages when necessary.
|
|
3771
|
+
*/
|
|
3772
|
+
tryFragmentParagraph(block, dims, availableHeightPt, effectiveMarginTopPt) {
|
|
3773
|
+
if (!this.canFragmentParagraph(block) || availableHeightPt <= effectiveMarginTopPt) {
|
|
2183
3774
|
return null;
|
|
2184
3775
|
}
|
|
2185
|
-
const
|
|
2186
|
-
|
|
3776
|
+
const split = this.splitParagraphAtLargestFit(block.element, (head) => {
|
|
3777
|
+
const measured = this.measureElement(head, dims, block.sectionIndex);
|
|
3778
|
+
return effectiveMarginTopPt + measured.heightPt <= availableHeightPt;
|
|
3779
|
+
});
|
|
3780
|
+
if (split.kind !== "split") return null;
|
|
3781
|
+
const headMeasured = this.measureElement(split.head, dims, block.sectionIndex);
|
|
3782
|
+
const tailMeasured = this.measureElement(split.tail, dims, block.sectionIndex);
|
|
2187
3783
|
return [
|
|
2188
3784
|
{
|
|
2189
|
-
...
|
|
2190
|
-
element:
|
|
3785
|
+
...headMeasured,
|
|
3786
|
+
element: split.head,
|
|
2191
3787
|
keepWithNext: false,
|
|
2192
3788
|
keepLines: false,
|
|
2193
3789
|
pageBreakBefore: false,
|
|
@@ -2195,7 +3791,7 @@ var Docxodus = (() => {
|
|
|
2195
3791
|
},
|
|
2196
3792
|
{
|
|
2197
3793
|
...tailMeasured,
|
|
2198
|
-
element: tail,
|
|
3794
|
+
element: split.tail,
|
|
2199
3795
|
keepWithNext: false,
|
|
2200
3796
|
keepLines: false,
|
|
2201
3797
|
pageBreakBefore: false,
|
|
@@ -2266,7 +3862,7 @@ var Docxodus = (() => {
|
|
|
2266
3862
|
const registry = /* @__PURE__ */ new Map();
|
|
2267
3863
|
const registryEl = this.stagingElement.querySelector("#pagination-footnote-registry");
|
|
2268
3864
|
if (!registryEl) return registry;
|
|
2269
|
-
const entries = Array.from(registryEl.
|
|
3865
|
+
const entries = Array.from(registryEl.children).filter((entry) => entry instanceof this.view.HTMLElement && entry.hasAttribute("data-footnote-id"));
|
|
2270
3866
|
for (const entry of entries) {
|
|
2271
3867
|
const footnoteId = entry.dataset.footnoteId;
|
|
2272
3868
|
if (footnoteId) {
|
|
@@ -2275,6 +3871,68 @@ var Docxodus = (() => {
|
|
|
2275
3871
|
}
|
|
2276
3872
|
return registry;
|
|
2277
3873
|
}
|
|
3874
|
+
/** Read Word's optional normal and continuation separator stories. */
|
|
3875
|
+
parseFootnoteSeparators() {
|
|
3876
|
+
const registry = this.stagingElement.querySelector("#pagination-footnote-registry");
|
|
3877
|
+
const clone = (kind) => {
|
|
3878
|
+
const source = Array.from(registry?.children ?? []).find(
|
|
3879
|
+
(entry) => entry instanceof this.view.HTMLElement && entry.getAttribute("data-footnote-separator") === kind
|
|
3880
|
+
);
|
|
3881
|
+
return source?.cloneNode(true);
|
|
3882
|
+
};
|
|
3883
|
+
return {
|
|
3884
|
+
normal: clone("normal") ?? null,
|
|
3885
|
+
continuation: clone("continuation") ?? null
|
|
3886
|
+
};
|
|
3887
|
+
}
|
|
3888
|
+
/** Append the exact separator story selected for this initial/continued note band. */
|
|
3889
|
+
appendFootnoteSeparator(container, continuation) {
|
|
3890
|
+
const kind = continuation ? "continuation" : "normal";
|
|
3891
|
+
const source = continuation ? this.footnoteContinuationSeparator : this.footnoteSeparator;
|
|
3892
|
+
if (!source) {
|
|
3893
|
+
const fallback = this.document.createElement("hr");
|
|
3894
|
+
fallback.dataset.footnoteSeparator = kind;
|
|
3895
|
+
container.appendChild(fallback);
|
|
3896
|
+
return;
|
|
3897
|
+
}
|
|
3898
|
+
const clone = source.cloneNode(true);
|
|
3899
|
+
for (const element of [clone, ...Array.from(clone.querySelectorAll("*"))]) {
|
|
3900
|
+
element.removeAttribute("id");
|
|
3901
|
+
element.removeAttribute("data-anchor");
|
|
3902
|
+
element.removeAttribute("data-committed-text");
|
|
3903
|
+
element.removeAttribute("data-source-anchor-id");
|
|
3904
|
+
element.removeAttribute("data-page-fragment-id");
|
|
3905
|
+
element.removeAttribute("data-fragment-index");
|
|
3906
|
+
element.removeAttribute("data-footnote-id");
|
|
3907
|
+
element.removeAttribute("data-comment-id");
|
|
3908
|
+
element.removeAttribute("name");
|
|
3909
|
+
if (element instanceof this.view.HTMLAnchorElement && element.getAttribute("href")?.startsWith("#")) {
|
|
3910
|
+
element.removeAttribute("href");
|
|
3911
|
+
}
|
|
3912
|
+
element.setAttribute("contenteditable", "false");
|
|
3913
|
+
}
|
|
3914
|
+
container.appendChild(clone);
|
|
3915
|
+
}
|
|
3916
|
+
/** Parses the hidden source notes used to render paginated margin comments. */
|
|
3917
|
+
parseCommentMarginRegistry() {
|
|
3918
|
+
const registry = /* @__PURE__ */ new Map();
|
|
3919
|
+
const registryEl = this.stagingElement.querySelector(
|
|
3920
|
+
"#pagination-comment-margin-registry"
|
|
3921
|
+
);
|
|
3922
|
+
if (!registryEl) return registry;
|
|
3923
|
+
for (const entry of Array.from(
|
|
3924
|
+
registryEl.querySelectorAll("[data-comment-id]")
|
|
3925
|
+
)) {
|
|
3926
|
+
const commentId = entry.dataset.commentId;
|
|
3927
|
+
if (!commentId) continue;
|
|
3928
|
+
let root = entry;
|
|
3929
|
+
for (let parent = root.parentElement?.closest("[data-comment-id]"); parent && registryEl.contains(parent); parent = root.parentElement?.closest("[data-comment-id]")) {
|
|
3930
|
+
root = parent;
|
|
3931
|
+
}
|
|
3932
|
+
registry.set(commentId, root.cloneNode(true));
|
|
3933
|
+
}
|
|
3934
|
+
return registry;
|
|
3935
|
+
}
|
|
2278
3936
|
/**
|
|
2279
3937
|
* Extracts footnote reference IDs from an element.
|
|
2280
3938
|
*/
|
|
@@ -2289,6 +3947,92 @@ var Docxodus = (() => {
|
|
|
2289
3947
|
}
|
|
2290
3948
|
return ids;
|
|
2291
3949
|
}
|
|
3950
|
+
/** Clone a note child for the continuation queue with its original selector position. */
|
|
3951
|
+
cloneFootnoteElementForContinuation(element, sourceIndex) {
|
|
3952
|
+
const clone = element.cloneNode(true);
|
|
3953
|
+
clone.setAttribute(
|
|
3954
|
+
FOOTNOTE_SOURCE_POSITION_ATTR,
|
|
3955
|
+
sourceIndex === 0 ? "first" : "later"
|
|
3956
|
+
);
|
|
3957
|
+
return clone;
|
|
3958
|
+
}
|
|
3959
|
+
/** Remove identities that belong only to the initial registry presentation shell. */
|
|
3960
|
+
makeContinuationShellInert(element) {
|
|
3961
|
+
element.removeAttribute("id");
|
|
3962
|
+
element.removeAttribute("data-anchor");
|
|
3963
|
+
element.removeAttribute("data-committed-text");
|
|
3964
|
+
}
|
|
3965
|
+
/**
|
|
3966
|
+
* Build the exact continuation shape shared by measurement and paint.
|
|
3967
|
+
*
|
|
3968
|
+
* Keep the source `.footnote-item > .footnote-content` ancestry: document
|
|
3969
|
+
* CSS, inherited direction/language, and custom classes frequently target
|
|
3970
|
+
* those shells. Only the number and initial HTML/editor identities are
|
|
3971
|
+
* omitted. A hidden sentinel preserves `p:not(:first-of-type)` for a page
|
|
3972
|
+
* whose first carried element was a later source paragraph.
|
|
3973
|
+
*/
|
|
3974
|
+
createFootnoteContinuationWrapper(continuation, elements = continuation.remainingElements) {
|
|
3975
|
+
const source = this.footnoteRegistry.get(continuation.footnoteId);
|
|
3976
|
+
const wrapper = source ? source.cloneNode(false) : this.document.createElement("div");
|
|
3977
|
+
this.makeContinuationShellInert(wrapper);
|
|
3978
|
+
wrapper.classList.add("footnote-continuation");
|
|
3979
|
+
wrapper.dataset.footnoteId = continuation.footnoteId;
|
|
3980
|
+
if (continuation.sourceAnchorId) {
|
|
3981
|
+
wrapper.dataset.sourceAnchorId = continuation.sourceAnchorId;
|
|
3982
|
+
}
|
|
3983
|
+
const sourceContent = source?.querySelector(".footnote-content");
|
|
3984
|
+
const content = sourceContent ? sourceContent.cloneNode(false) : this.document.createElement("span");
|
|
3985
|
+
this.makeContinuationShellInert(content);
|
|
3986
|
+
if (!sourceContent) content.className = "footnote-content";
|
|
3987
|
+
const first = elements[0];
|
|
3988
|
+
if (first?.tagName === "P" && first.getAttribute(FOOTNOTE_SOURCE_POSITION_ATTR) === "later") {
|
|
3989
|
+
const sentinel = this.document.createElement("p");
|
|
3990
|
+
sentinel.className = "footnote-continuation-position-sentinel";
|
|
3991
|
+
sentinel.setAttribute("aria-hidden", "true");
|
|
3992
|
+
sentinel.style.setProperty("display", "none", "important");
|
|
3993
|
+
content.appendChild(sentinel);
|
|
3994
|
+
}
|
|
3995
|
+
for (let index = 0; index < elements.length; index++) {
|
|
3996
|
+
this.checkpoint();
|
|
3997
|
+
const element = elements[index];
|
|
3998
|
+
const clone = element.cloneNode(true);
|
|
3999
|
+
clone.removeAttribute(FOOTNOTE_SOURCE_POSITION_ATTR);
|
|
4000
|
+
if (index === 0 && clone.tagName === "P") {
|
|
4001
|
+
clone.style.setProperty("display", "block", "important");
|
|
4002
|
+
}
|
|
4003
|
+
content.appendChild(clone);
|
|
4004
|
+
}
|
|
4005
|
+
wrapper.appendChild(content);
|
|
4006
|
+
return wrapper;
|
|
4007
|
+
}
|
|
4008
|
+
/** Build the exact partial-note item shape shared by measurement and paint. */
|
|
4009
|
+
createPartialFootnoteItem(footnote, fittingElements) {
|
|
4010
|
+
const item = footnote.cloneNode(false);
|
|
4011
|
+
const number = footnote.querySelector(".footnote-number");
|
|
4012
|
+
if (number) item.appendChild(number.cloneNode(true));
|
|
4013
|
+
const sourceContent = footnote.querySelector(".footnote-content");
|
|
4014
|
+
const content = sourceContent ? sourceContent.cloneNode(false) : this.document.createElement("span");
|
|
4015
|
+
if (!sourceContent) content.className = "footnote-content";
|
|
4016
|
+
for (const element of fittingElements) {
|
|
4017
|
+
this.checkpoint();
|
|
4018
|
+
content.appendChild(element.cloneNode(true));
|
|
4019
|
+
}
|
|
4020
|
+
item.appendChild(content);
|
|
4021
|
+
return item;
|
|
4022
|
+
}
|
|
4023
|
+
/**
|
|
4024
|
+
* Where a note measurement tree is attached.
|
|
4025
|
+
*
|
|
4026
|
+
* A note band is reserved from a measurement and then painted; if the two
|
|
4027
|
+
* happen under different inherited typography the painted notes overflow the
|
|
4028
|
+
* reserve, which is the invisible clipping this engine exists to avoid. The
|
|
4029
|
+
* registry lives in the staging tree but a band paints inside the output
|
|
4030
|
+
* container, so measure there. The host is never a page box, so a page's
|
|
4031
|
+
* `zoom` cannot scale a reserve that is accounted for in unscaled points.
|
|
4032
|
+
*/
|
|
4033
|
+
noteMeasurementHost() {
|
|
4034
|
+
return this.containerElement.isConnected ? this.containerElement : this.stagingElement;
|
|
4035
|
+
}
|
|
2292
4036
|
/**
|
|
2293
4037
|
* Measures the height of footnotes for given IDs (in points).
|
|
2294
4038
|
* Creates a temporary container to measure the footnotes.
|
|
@@ -2296,140 +4040,246 @@ var Docxodus = (() => {
|
|
|
2296
4040
|
* @param contentWidth - Width for measurement
|
|
2297
4041
|
* @param continuation - Optional continuation content to include first
|
|
2298
4042
|
*/
|
|
2299
|
-
measureFootnotesHeight(footnoteIds, contentWidth, continuation) {
|
|
4043
|
+
measureFootnotesHeight(footnoteIds, contentWidth, continuation, partialFootnotes) {
|
|
2300
4044
|
const hasContinuation = continuation && continuation.remainingElements.length > 0;
|
|
2301
|
-
if (footnoteIds.length === 0 && !hasContinuation
|
|
4045
|
+
if (footnoteIds.length === 0 && !hasContinuation) {
|
|
2302
4046
|
return 0;
|
|
2303
4047
|
}
|
|
2304
|
-
|
|
4048
|
+
if (this.footnoteRegistry.size === 0 && !hasContinuation) {
|
|
4049
|
+
return 0;
|
|
4050
|
+
}
|
|
4051
|
+
const measureContainer = this.document.createElement("div");
|
|
2305
4052
|
measureContainer.style.position = "absolute";
|
|
2306
4053
|
measureContainer.style.visibility = "hidden";
|
|
2307
4054
|
measureContainer.style.width = `${contentWidth}pt`;
|
|
2308
4055
|
measureContainer.style.left = "-9999px";
|
|
2309
4056
|
measureContainer.className = this.cssPrefix + "footnotes";
|
|
2310
|
-
|
|
2311
|
-
measureContainer.appendChild(hr);
|
|
4057
|
+
this.appendFootnoteSeparator(measureContainer, Boolean(hasContinuation));
|
|
2312
4058
|
if (hasContinuation) {
|
|
2313
|
-
|
|
2314
|
-
contWrapper.className = "footnote-continuation";
|
|
2315
|
-
for (const el of continuation.remainingElements) {
|
|
2316
|
-
contWrapper.appendChild(el.cloneNode(true));
|
|
2317
|
-
}
|
|
2318
|
-
measureContainer.appendChild(contWrapper);
|
|
4059
|
+
measureContainer.appendChild(this.createFootnoteContinuationWrapper(continuation));
|
|
2319
4060
|
}
|
|
4061
|
+
const partialById = new Map(
|
|
4062
|
+
partialFootnotes?.map((partial) => [partial.footnoteId, partial]) ?? []
|
|
4063
|
+
);
|
|
2320
4064
|
for (const id of footnoteIds) {
|
|
4065
|
+
this.checkpoint();
|
|
2321
4066
|
const footnote = this.footnoteRegistry.get(id);
|
|
2322
4067
|
if (footnote) {
|
|
2323
|
-
|
|
4068
|
+
const partial = partialById.get(id);
|
|
4069
|
+
measureContainer.appendChild(partial ? this.createPartialFootnoteItem(footnote, partial.fittingElements) : footnote.cloneNode(true));
|
|
2324
4070
|
}
|
|
2325
4071
|
}
|
|
2326
|
-
this.
|
|
2327
|
-
|
|
2328
|
-
|
|
2329
|
-
|
|
2330
|
-
|
|
4072
|
+
this.noteMeasurementHost().appendChild(measureContainer);
|
|
4073
|
+
try {
|
|
4074
|
+
return pxToPt(measureContainer.getBoundingClientRect().height);
|
|
4075
|
+
} finally {
|
|
4076
|
+
measureContainer.remove();
|
|
4077
|
+
}
|
|
2331
4078
|
}
|
|
2332
4079
|
/**
|
|
2333
|
-
*
|
|
4080
|
+
* Partition a continuation for one page's note band, preferring complete children
|
|
4081
|
+
* and range-fragmenting an eligible paragraph when necessary. Always advances by
|
|
4082
|
+
* at least one element so an indivisible oversized paragraph follows the established
|
|
4083
|
+
* clipped fallback without trapping pagination in a loop.
|
|
2334
4084
|
*/
|
|
2335
|
-
|
|
2336
|
-
|
|
2337
|
-
|
|
2338
|
-
|
|
2339
|
-
const measureContainer = document.createElement("div");
|
|
4085
|
+
splitContinuationForPage(continuation, availableHeightPt, contentWidth) {
|
|
4086
|
+
const fitting = [];
|
|
4087
|
+
let remaining = [];
|
|
4088
|
+
const measureContainer = this.document.createElement("div");
|
|
2340
4089
|
measureContainer.style.position = "absolute";
|
|
2341
4090
|
measureContainer.style.visibility = "hidden";
|
|
2342
4091
|
measureContainer.style.width = `${contentWidth}pt`;
|
|
2343
4092
|
measureContainer.style.left = "-9999px";
|
|
2344
4093
|
measureContainer.className = this.cssPrefix + "footnotes";
|
|
2345
|
-
|
|
2346
|
-
|
|
2347
|
-
|
|
2348
|
-
|
|
2349
|
-
|
|
2350
|
-
|
|
2351
|
-
|
|
2352
|
-
|
|
2353
|
-
|
|
2354
|
-
|
|
2355
|
-
|
|
2356
|
-
|
|
2357
|
-
|
|
2358
|
-
|
|
2359
|
-
|
|
2360
|
-
|
|
2361
|
-
|
|
2362
|
-
|
|
2363
|
-
|
|
2364
|
-
|
|
2365
|
-
|
|
2366
|
-
|
|
2367
|
-
|
|
2368
|
-
|
|
2369
|
-
|
|
2370
|
-
|
|
2371
|
-
|
|
2372
|
-
|
|
2373
|
-
|
|
2374
|
-
|
|
2375
|
-
|
|
2376
|
-
|
|
2377
|
-
|
|
2378
|
-
|
|
2379
|
-
|
|
2380
|
-
|
|
2381
|
-
|
|
2382
|
-
|
|
2383
|
-
|
|
2384
|
-
|
|
2385
|
-
|
|
2386
|
-
|
|
2387
|
-
|
|
2388
|
-
|
|
2389
|
-
|
|
2390
|
-
|
|
2391
|
-
|
|
2392
|
-
|
|
2393
|
-
|
|
2394
|
-
|
|
2395
|
-
|
|
2396
|
-
|
|
2397
|
-
|
|
2398
|
-
|
|
2399
|
-
|
|
2400
|
-
|
|
2401
|
-
|
|
2402
|
-
|
|
2403
|
-
|
|
2404
|
-
|
|
2405
|
-
|
|
2406
|
-
|
|
2407
|
-
|
|
2408
|
-
|
|
4094
|
+
this.appendFootnoteSeparator(measureContainer, true);
|
|
4095
|
+
const wrapper = this.createFootnoteContinuationWrapper(continuation, []);
|
|
4096
|
+
const content = wrapper.querySelector(":scope > .footnote-content");
|
|
4097
|
+
if (!content) {
|
|
4098
|
+
throw new Error("Footnote continuation is missing its content shell");
|
|
4099
|
+
}
|
|
4100
|
+
measureContainer.appendChild(wrapper);
|
|
4101
|
+
this.noteMeasurementHost().appendChild(measureContainer);
|
|
4102
|
+
try {
|
|
4103
|
+
for (let index = 0; index < continuation.remainingElements.length; index++) {
|
|
4104
|
+
this.checkpoint();
|
|
4105
|
+
const element = continuation.remainingElements[index];
|
|
4106
|
+
const sourcePosition = element.getAttribute(FOOTNOTE_SOURCE_POSITION_ATTR);
|
|
4107
|
+
if (fitting.length === 0 && element.tagName === "P" && element.getAttribute(FOOTNOTE_SOURCE_POSITION_ATTR) === "later") {
|
|
4108
|
+
const sentinel = this.document.createElement("p");
|
|
4109
|
+
sentinel.className = "footnote-continuation-position-sentinel";
|
|
4110
|
+
sentinel.setAttribute("aria-hidden", "true");
|
|
4111
|
+
sentinel.style.setProperty("display", "none", "important");
|
|
4112
|
+
content.appendChild(sentinel);
|
|
4113
|
+
}
|
|
4114
|
+
const candidate = element.cloneNode(true);
|
|
4115
|
+
candidate.removeAttribute(FOOTNOTE_SOURCE_POSITION_ATTR);
|
|
4116
|
+
if (fitting.length === 0 && candidate.tagName === "P") {
|
|
4117
|
+
candidate.style.setProperty("display", "block", "important");
|
|
4118
|
+
}
|
|
4119
|
+
content.appendChild(candidate);
|
|
4120
|
+
const candidateHeight = pxToPt(measureContainer.getBoundingClientRect().height);
|
|
4121
|
+
if (candidateHeight <= availableHeightPt) {
|
|
4122
|
+
fitting.push(element);
|
|
4123
|
+
continue;
|
|
4124
|
+
}
|
|
4125
|
+
const canSplitCandidate = candidate.tagName === "P" && this.canRangeFragmentParagraph(candidate);
|
|
4126
|
+
candidate.remove();
|
|
4127
|
+
let split = null;
|
|
4128
|
+
if (canSplitCandidate) {
|
|
4129
|
+
split = this.splitParagraphAtLargestFit(candidate, (head) => {
|
|
4130
|
+
content.appendChild(head);
|
|
4131
|
+
try {
|
|
4132
|
+
return pxToPt(measureContainer.getBoundingClientRect().height) <= availableHeightPt;
|
|
4133
|
+
} finally {
|
|
4134
|
+
head.remove();
|
|
4135
|
+
}
|
|
4136
|
+
}, {
|
|
4137
|
+
// Both fallbacks below cut where ordinary line breaking would not,
|
|
4138
|
+
// so they are only ever right for an element that owns the whole
|
|
4139
|
+
// band: if this page already carries earlier content, a paragraph
|
|
4140
|
+
// that will not start here belongs intact in the next note band.
|
|
4141
|
+
emergencyGraphemeBreaks: fitting.length === 0,
|
|
4142
|
+
// A continuation page owns the full note band. If even one legal
|
|
4143
|
+
// grapheme is taller than it, clip only that unit and keep draining.
|
|
4144
|
+
forceFirstOnNoFit: fitting.length === 0
|
|
4145
|
+
});
|
|
4146
|
+
}
|
|
4147
|
+
if (split?.kind === "split") {
|
|
4148
|
+
if (sourcePosition) {
|
|
4149
|
+
split.head.setAttribute(FOOTNOTE_SOURCE_POSITION_ATTR, sourcePosition);
|
|
4150
|
+
split.tail.setAttribute(FOOTNOTE_SOURCE_POSITION_ATTR, sourcePosition);
|
|
4151
|
+
}
|
|
4152
|
+
fitting.push(split.head);
|
|
4153
|
+
remaining = [split.tail, ...continuation.remainingElements.slice(index + 1)];
|
|
4154
|
+
} else if (fitting.length === 0) {
|
|
4155
|
+
fitting.push(element);
|
|
4156
|
+
remaining = continuation.remainingElements.slice(index + 1);
|
|
4157
|
+
} else {
|
|
4158
|
+
remaining = continuation.remainingElements.slice(index);
|
|
2409
4159
|
}
|
|
2410
4160
|
break;
|
|
2411
4161
|
}
|
|
4162
|
+
} finally {
|
|
4163
|
+
measureContainer.remove();
|
|
2412
4164
|
}
|
|
2413
|
-
return {
|
|
4165
|
+
return {
|
|
4166
|
+
current: {
|
|
4167
|
+
footnoteId: continuation.footnoteId,
|
|
4168
|
+
sourceAnchorId: continuation.sourceAnchorId,
|
|
4169
|
+
remainingElements: fitting
|
|
4170
|
+
},
|
|
4171
|
+
overflow: remaining.length > 0 ? {
|
|
4172
|
+
footnoteId: continuation.footnoteId,
|
|
4173
|
+
sourceAnchorId: continuation.sourceAnchorId,
|
|
4174
|
+
remainingElements: remaining
|
|
4175
|
+
} : null
|
|
4176
|
+
};
|
|
2414
4177
|
}
|
|
2415
4178
|
/**
|
|
2416
|
-
*
|
|
4179
|
+
* Splits a footnote element into parts that fit within the available height.
|
|
4180
|
+
* Returns the elements that fit and the elements that need to continue.
|
|
2417
4181
|
*/
|
|
2418
|
-
|
|
2419
|
-
const
|
|
2420
|
-
|
|
2421
|
-
|
|
4182
|
+
splitFootnoteToFit(footnoteElement, availableHeightPt, contentWidth, forceProgress = false, existingPayload) {
|
|
4183
|
+
const layoutContext = this.document.createElement("div");
|
|
4184
|
+
layoutContext.style.position = "absolute";
|
|
4185
|
+
layoutContext.style.visibility = "hidden";
|
|
4186
|
+
layoutContext.style.width = `${contentWidth}pt`;
|
|
4187
|
+
layoutContext.style.left = "-9999px";
|
|
4188
|
+
layoutContext.className = this.cssPrefix + "footnotes";
|
|
4189
|
+
this.appendFootnoteSeparator(layoutContext, false);
|
|
4190
|
+
const attachedFootnote = footnoteElement.cloneNode(true);
|
|
4191
|
+
layoutContext.appendChild(attachedFootnote);
|
|
4192
|
+
this.noteMeasurementHost().appendChild(layoutContext);
|
|
4193
|
+
const measureContainer = this.document.createElement("div");
|
|
2422
4194
|
measureContainer.style.position = "absolute";
|
|
2423
4195
|
measureContainer.style.visibility = "hidden";
|
|
2424
4196
|
measureContainer.style.width = `${contentWidth}pt`;
|
|
2425
4197
|
measureContainer.style.left = "-9999px";
|
|
2426
4198
|
measureContainer.className = this.cssPrefix + "footnotes";
|
|
2427
|
-
|
|
2428
|
-
this.
|
|
2429
|
-
|
|
2430
|
-
|
|
2431
|
-
|
|
2432
|
-
|
|
4199
|
+
const hasContinuation = Boolean(existingPayload?.continuation?.remainingElements.length);
|
|
4200
|
+
this.appendFootnoteSeparator(measureContainer, hasContinuation);
|
|
4201
|
+
if (hasContinuation) {
|
|
4202
|
+
measureContainer.appendChild(this.createFootnoteContinuationWrapper(
|
|
4203
|
+
existingPayload.continuation
|
|
4204
|
+
));
|
|
4205
|
+
}
|
|
4206
|
+
const partialById = new Map(
|
|
4207
|
+
existingPayload?.partialFootnotes?.map((partial) => [partial.footnoteId, partial]) ?? []
|
|
4208
|
+
);
|
|
4209
|
+
for (const id of existingPayload?.footnoteIds ?? []) {
|
|
4210
|
+
this.checkpoint();
|
|
4211
|
+
const source = this.footnoteRegistry.get(id);
|
|
4212
|
+
if (!source) continue;
|
|
4213
|
+
const partial = partialById.get(id);
|
|
4214
|
+
measureContainer.appendChild(partial ? this.createPartialFootnoteItem(source, partial.fittingElements) : source.cloneNode(true));
|
|
4215
|
+
}
|
|
4216
|
+
const measuredPartial = this.createPartialFootnoteItem(attachedFootnote, []);
|
|
4217
|
+
const measuredContent = measuredPartial.querySelector(":scope > .footnote-content");
|
|
4218
|
+
if (!measuredContent) {
|
|
4219
|
+
layoutContext.remove();
|
|
4220
|
+
throw new Error("Footnote is missing its content shell");
|
|
4221
|
+
}
|
|
4222
|
+
measureContainer.appendChild(measuredPartial);
|
|
4223
|
+
this.noteMeasurementHost().appendChild(measureContainer);
|
|
4224
|
+
try {
|
|
4225
|
+
const footnoteContent = attachedFootnote.querySelector(".footnote-content");
|
|
4226
|
+
if (!footnoteContent) {
|
|
4227
|
+
return {
|
|
4228
|
+
fits: Array.from(attachedFootnote.children).map((el) => el.cloneNode(true)),
|
|
4229
|
+
overflow: []
|
|
4230
|
+
};
|
|
4231
|
+
}
|
|
4232
|
+
const children = Array.from(footnoteContent.children);
|
|
4233
|
+
const fits = [];
|
|
4234
|
+
let overflow = [];
|
|
4235
|
+
for (let i = 0; i < children.length; i++) {
|
|
4236
|
+
this.checkpoint();
|
|
4237
|
+
const child = children[i];
|
|
4238
|
+
const measuredCandidate = child.cloneNode(true);
|
|
4239
|
+
measuredContent.appendChild(measuredCandidate);
|
|
4240
|
+
const candidateFits = pxToPt(measureContainer.getBoundingClientRect().height) <= availableHeightPt;
|
|
4241
|
+
if (candidateFits) {
|
|
4242
|
+
fits.push(child.cloneNode(true));
|
|
4243
|
+
continue;
|
|
4244
|
+
}
|
|
4245
|
+
measuredCandidate.remove();
|
|
4246
|
+
const split = this.canRangeFragmentParagraph(child, true) ? this.splitParagraphAtLargestFit(child, (head) => {
|
|
4247
|
+
measuredContent.appendChild(head);
|
|
4248
|
+
try {
|
|
4249
|
+
return pxToPt(measureContainer.getBoundingClientRect().height) <= availableHeightPt;
|
|
4250
|
+
} finally {
|
|
4251
|
+
head.remove();
|
|
4252
|
+
}
|
|
4253
|
+
}, {
|
|
4254
|
+
// Both fallbacks cut where ordinary line breaking would not, so they
|
|
4255
|
+
// are only ever right for an element that owns the whole band: with
|
|
4256
|
+
// earlier siblings already packed here, a paragraph that will not
|
|
4257
|
+
// start belongs intact in the next note band.
|
|
4258
|
+
emergencyGraphemeBreaks: fits.length === 0 && forceProgress,
|
|
4259
|
+
forceFirstOnNoFit: fits.length === 0 && forceProgress
|
|
4260
|
+
}) : null;
|
|
4261
|
+
if (split?.kind === "split") {
|
|
4262
|
+
fits.push(split.head);
|
|
4263
|
+
split.tail.setAttribute(
|
|
4264
|
+
FOOTNOTE_SOURCE_POSITION_ATTR,
|
|
4265
|
+
i === 0 ? "first" : "later"
|
|
4266
|
+
);
|
|
4267
|
+
overflow = [split.tail, ...children.slice(i + 1).map((element, offset) => this.cloneFootnoteElementForContinuation(element, i + 1 + offset))];
|
|
4268
|
+
} else if (split?.kind === "no-fit") {
|
|
4269
|
+
overflow = children.slice(i).map((element, offset) => this.cloneFootnoteElementForContinuation(element, i + offset));
|
|
4270
|
+
} else if (fits.length === 0 && forceProgress) {
|
|
4271
|
+
fits.push(child.cloneNode(true));
|
|
4272
|
+
overflow = children.slice(i + 1).map((element, offset) => this.cloneFootnoteElementForContinuation(element, i + 1 + offset));
|
|
4273
|
+
} else {
|
|
4274
|
+
overflow = children.slice(i).map((element, offset) => this.cloneFootnoteElementForContinuation(element, i + offset));
|
|
4275
|
+
}
|
|
4276
|
+
break;
|
|
4277
|
+
}
|
|
4278
|
+
return { fits, overflow };
|
|
4279
|
+
} finally {
|
|
4280
|
+
measureContainer.remove();
|
|
4281
|
+
layoutContext.remove();
|
|
4282
|
+
}
|
|
2433
4283
|
}
|
|
2434
4284
|
/**
|
|
2435
4285
|
* Adds footnotes to a page container, including continuation content.
|
|
@@ -2442,12 +4292,11 @@ var Docxodus = (() => {
|
|
|
2442
4292
|
if (this.footnoteRegistry.size === 0 && !hasContinuation) {
|
|
2443
4293
|
return;
|
|
2444
4294
|
}
|
|
2445
|
-
const partialFootnoteIds = new Set(partialFootnotes?.map((p) => p.footnoteId) || []);
|
|
2446
4295
|
const maxFootnoteHeight = Math.min(
|
|
2447
4296
|
footnoteHeight,
|
|
2448
4297
|
bands.bodyHeight * MAX_FOOTNOTE_AREA_RATIO
|
|
2449
4298
|
);
|
|
2450
|
-
const footnotesDiv = document.createElement("div");
|
|
4299
|
+
const footnotesDiv = this.document.createElement("div");
|
|
2451
4300
|
footnotesDiv.className = `${this.cssPrefix}footnotes`;
|
|
2452
4301
|
footnotesDiv.style.position = "absolute";
|
|
2453
4302
|
footnotesDiv.style.bottom = `${dims.pageHeight - (bands.bodyTop + bands.bodyHeight)}pt`;
|
|
@@ -2456,35 +4305,18 @@ var Docxodus = (() => {
|
|
|
2456
4305
|
footnotesDiv.style.boxSizing = "border-box";
|
|
2457
4306
|
footnotesDiv.style.maxHeight = `${maxFootnoteHeight}pt`;
|
|
2458
4307
|
footnotesDiv.style.overflow = "hidden";
|
|
2459
|
-
|
|
2460
|
-
footnotesDiv.appendChild(hr);
|
|
4308
|
+
this.appendFootnoteSeparator(footnotesDiv, Boolean(hasContinuation));
|
|
2461
4309
|
if (hasContinuation) {
|
|
2462
|
-
|
|
2463
|
-
contWrapper.className = "footnote-continuation";
|
|
2464
|
-
for (const el of continuation.remainingElements) {
|
|
2465
|
-
contWrapper.appendChild(el.cloneNode(true));
|
|
2466
|
-
}
|
|
2467
|
-
footnotesDiv.appendChild(contWrapper);
|
|
4310
|
+
footnotesDiv.appendChild(this.createFootnoteContinuationWrapper(continuation));
|
|
2468
4311
|
}
|
|
2469
4312
|
for (const id of footnoteIds) {
|
|
2470
4313
|
const partial = partialFootnotes?.find((p) => p.footnoteId === id);
|
|
2471
4314
|
if (partial) {
|
|
2472
4315
|
const footnote = this.footnoteRegistry.get(id);
|
|
2473
4316
|
if (footnote) {
|
|
2474
|
-
|
|
2475
|
-
|
|
2476
|
-
|
|
2477
|
-
const numberSpan = footnote.querySelector(".footnote-number");
|
|
2478
|
-
if (numberSpan) {
|
|
2479
|
-
partialDiv.appendChild(numberSpan.cloneNode(true));
|
|
2480
|
-
}
|
|
2481
|
-
const contentSpan = document.createElement("span");
|
|
2482
|
-
contentSpan.className = "footnote-content";
|
|
2483
|
-
for (const el of partial.fittingElements) {
|
|
2484
|
-
contentSpan.appendChild(el.cloneNode(true));
|
|
2485
|
-
}
|
|
2486
|
-
partialDiv.appendChild(contentSpan);
|
|
2487
|
-
footnotesDiv.appendChild(partialDiv);
|
|
4317
|
+
footnotesDiv.appendChild(
|
|
4318
|
+
this.createPartialFootnoteItem(footnote, partial.fittingElements)
|
|
4319
|
+
);
|
|
2488
4320
|
}
|
|
2489
4321
|
} else {
|
|
2490
4322
|
const footnote = this.footnoteRegistry.get(id);
|
|
@@ -2495,30 +4327,26 @@ var Docxodus = (() => {
|
|
|
2495
4327
|
}
|
|
2496
4328
|
pageBox.appendChild(footnotesDiv);
|
|
2497
4329
|
}
|
|
2498
|
-
/**
|
|
2499
|
-
|
|
2500
|
-
*/
|
|
2501
|
-
selectHeader(sectionIndex, pageInSection, globalPageNumber) {
|
|
4330
|
+
/** Select the section's first/odd/even header from its one-based page position. */
|
|
4331
|
+
selectHeader(sectionIndex, pageInSection, displayedPageNumber) {
|
|
2502
4332
|
const sectionHf = this.hfRegistry.get(sectionIndex);
|
|
2503
4333
|
if (!sectionHf) return void 0;
|
|
2504
4334
|
if (pageInSection === 1 && sectionHf.headerFirst) {
|
|
2505
4335
|
return sectionHf.headerFirst;
|
|
2506
4336
|
}
|
|
2507
|
-
if (
|
|
4337
|
+
if (displayedPageNumber % 2 === 0 && sectionHf.headerEven) {
|
|
2508
4338
|
return sectionHf.headerEven;
|
|
2509
4339
|
}
|
|
2510
4340
|
return sectionHf.headerDefault;
|
|
2511
4341
|
}
|
|
2512
|
-
/**
|
|
2513
|
-
|
|
2514
|
-
*/
|
|
2515
|
-
selectFooter(sectionIndex, pageInSection, globalPageNumber) {
|
|
4342
|
+
/** Select the section's first/odd/even footer from its one-based page position. */
|
|
4343
|
+
selectFooter(sectionIndex, pageInSection, displayedPageNumber) {
|
|
2516
4344
|
const sectionHf = this.hfRegistry.get(sectionIndex);
|
|
2517
4345
|
if (!sectionHf) return void 0;
|
|
2518
4346
|
if (pageInSection === 1 && sectionHf.footerFirst) {
|
|
2519
4347
|
return sectionHf.footerFirst;
|
|
2520
4348
|
}
|
|
2521
|
-
if (
|
|
4349
|
+
if (displayedPageNumber % 2 === 0 && sectionHf.footerEven) {
|
|
2522
4350
|
return sectionHf.footerEven;
|
|
2523
4351
|
}
|
|
2524
4352
|
return sectionHf.footerDefault;
|
|
@@ -2533,7 +4361,7 @@ var Docxodus = (() => {
|
|
|
2533
4361
|
* Deterministic: it depends only on the section's page setup and the registry's pre-measured
|
|
2534
4362
|
* story heights, never on the page's content, which is what keeps it lazy-loading compatible.
|
|
2535
4363
|
*/
|
|
2536
|
-
getPageBands(dims, sectionIndex, pageInSection,
|
|
4364
|
+
getPageBands(dims, sectionIndex, pageInSection, displayedPageNumber) {
|
|
2537
4365
|
const sectionHf = this.hfRegistry.get(sectionIndex);
|
|
2538
4366
|
return resolvePageBands(
|
|
2539
4367
|
dims,
|
|
@@ -2542,14 +4370,14 @@ var Docxodus = (() => {
|
|
|
2542
4370
|
sectionHf?.headerEvenHeight,
|
|
2543
4371
|
sectionHf?.headerDefaultHeight,
|
|
2544
4372
|
pageInSection,
|
|
2545
|
-
|
|
4373
|
+
displayedPageNumber
|
|
2546
4374
|
),
|
|
2547
4375
|
this.selectStoryHeight(
|
|
2548
4376
|
sectionHf?.footerFirstHeight,
|
|
2549
4377
|
sectionHf?.footerEvenHeight,
|
|
2550
4378
|
sectionHf?.footerDefaultHeight,
|
|
2551
4379
|
pageInSection,
|
|
2552
|
-
|
|
4380
|
+
displayedPageNumber
|
|
2553
4381
|
)
|
|
2554
4382
|
);
|
|
2555
4383
|
}
|
|
@@ -2557,9 +4385,9 @@ var Docxodus = (() => {
|
|
|
2557
4385
|
* The measured height of the running story this page position selects, mirroring
|
|
2558
4386
|
* {@link selectHeader}/{@link selectFooter}. Zero when the page has no such story.
|
|
2559
4387
|
*/
|
|
2560
|
-
selectStoryHeight(first, even, fallback, pageInSection,
|
|
4388
|
+
selectStoryHeight(first, even, fallback, pageInSection, displayedPageNumber) {
|
|
2561
4389
|
if (pageInSection === 1 && first != null) return first;
|
|
2562
|
-
if (
|
|
4390
|
+
if (displayedPageNumber % 2 === 0 && even != null) return even;
|
|
2563
4391
|
return fallback ?? 0;
|
|
2564
4392
|
}
|
|
2565
4393
|
/**
|
|
@@ -2571,7 +4399,7 @@ var Docxodus = (() => {
|
|
|
2571
4399
|
* silently starts overlapping body text.
|
|
2572
4400
|
*/
|
|
2573
4401
|
measureHeaderFooterHeight(source, contentWidth) {
|
|
2574
|
-
const measureContainer = document.createElement("div");
|
|
4402
|
+
const measureContainer = this.document.createElement("div");
|
|
2575
4403
|
measureContainer.style.position = "absolute";
|
|
2576
4404
|
measureContainer.style.visibility = "hidden";
|
|
2577
4405
|
measureContainer.style.width = `${contentWidth}pt`;
|
|
@@ -2590,79 +4418,275 @@ var Docxodus = (() => {
|
|
|
2590
4418
|
* Implements a single-pass, forward-only algorithm that is compatible with future lazy loading.
|
|
2591
4419
|
* Supports footnote continuation - long footnotes can split across pages.
|
|
2592
4420
|
*/
|
|
2593
|
-
flowToPages(blocks, dims, startPageNumber, sectionIndex) {
|
|
4421
|
+
flowToPages(blocks, dims, startPageNumber, sectionIndex, sectionDimensions) {
|
|
2594
4422
|
const pages = [];
|
|
2595
4423
|
let currentContent = [];
|
|
2596
4424
|
let pageNumber = startPageNumber;
|
|
2597
|
-
let
|
|
2598
|
-
|
|
2599
|
-
|
|
2600
|
-
|
|
2601
|
-
|
|
2602
|
-
pageNumber
|
|
4425
|
+
let pageSectionIndex = sectionIndex;
|
|
4426
|
+
const sectionStartPages = /* @__PURE__ */ new Map([[sectionIndex, startPageNumber]]);
|
|
4427
|
+
const pageInSection = (owner = pageSectionIndex, physicalPage = pageNumber) => physicalPage - (sectionStartPages.get(owner) ?? physicalPage) + 1;
|
|
4428
|
+
const dimensionsFor = (owner = pageSectionIndex) => sectionDimensions.get(owner) ?? dims;
|
|
4429
|
+
const markSectionPlaced = (owner) => {
|
|
4430
|
+
if (!sectionStartPages.has(owner)) sectionStartPages.set(owner, pageNumber);
|
|
4431
|
+
};
|
|
4432
|
+
const previousBox = this.containerElement.querySelector(
|
|
4433
|
+
`.${this.cssPrefix}box:last-of-type`
|
|
4434
|
+
);
|
|
4435
|
+
let precedingDisplayedPageNumber = parseInt(
|
|
4436
|
+
previousBox?.dataset.displayedPageNumber ?? "0",
|
|
4437
|
+
10
|
|
4438
|
+
);
|
|
4439
|
+
const displayedPageNumber = (owner = pageSectionIndex, ownerPageInSection = pageInSection(owner), physicalPage = pageNumber) => {
|
|
4440
|
+
const numbering = this.pageNumbering.get(owner) ?? {};
|
|
4441
|
+
if (numbering.start !== void 0) {
|
|
4442
|
+
return numbering.start + ownerPageInSection - 1;
|
|
4443
|
+
}
|
|
4444
|
+
const ownerNumbering = this.pageNumbering.get(pageSectionIndex) ?? {};
|
|
4445
|
+
const currentPhysicalNumber = ownerNumbering.start !== void 0 ? ownerNumbering.start + pageInSection(pageSectionIndex) - 1 : precedingDisplayedPageNumber + 1;
|
|
4446
|
+
return currentPhysicalNumber + physicalPage - pageNumber;
|
|
4447
|
+
};
|
|
4448
|
+
let { bodyHeight: effectiveContentHeight } = this.getPageBands(
|
|
4449
|
+
dimensionsFor(),
|
|
4450
|
+
pageSectionIndex,
|
|
4451
|
+
pageInSection(),
|
|
4452
|
+
displayedPageNumber()
|
|
2603
4453
|
);
|
|
2604
4454
|
let remainingHeight = effectiveContentHeight;
|
|
2605
4455
|
let prevMarginBottomPt = 0;
|
|
2606
4456
|
let currentFootnoteIds = [];
|
|
4457
|
+
let currentPageHasFootnoteReference = false;
|
|
2607
4458
|
let currentFootnoteHeight = 0;
|
|
2608
4459
|
let currentContinuation = this.pendingFootnoteContinuation;
|
|
2609
4460
|
let nextPageContinuation = null;
|
|
4461
|
+
let currentContinuationPartitioned = false;
|
|
4462
|
+
let currentPageAdmitted = false;
|
|
2610
4463
|
let deferredFootnoteIds = [];
|
|
2611
4464
|
let currentPartialFootnotes = [];
|
|
2612
|
-
|
|
2613
|
-
|
|
2614
|
-
|
|
2615
|
-
|
|
4465
|
+
const adoptEmptyPageOwner = (owner) => {
|
|
4466
|
+
pageSectionIndex = owner;
|
|
4467
|
+
const bands = this.getPageBands(
|
|
4468
|
+
dimensionsFor(owner),
|
|
4469
|
+
owner,
|
|
4470
|
+
pageInSection(owner),
|
|
4471
|
+
displayedPageNumber(owner)
|
|
4472
|
+
);
|
|
4473
|
+
effectiveContentHeight = bands.bodyHeight;
|
|
4474
|
+
remainingHeight = effectiveContentHeight;
|
|
4475
|
+
};
|
|
4476
|
+
const admitCurrentPage = () => {
|
|
4477
|
+
if (currentPageAdmitted) return;
|
|
4478
|
+
this.admitPageAllocation();
|
|
4479
|
+
currentPageAdmitted = true;
|
|
4480
|
+
};
|
|
4481
|
+
const prepareCurrentContinuation = () => {
|
|
4482
|
+
if (currentContinuationPartitioned || (currentContinuation?.remainingElements.length ?? 0) === 0) return;
|
|
4483
|
+
admitCurrentPage();
|
|
4484
|
+
const ownedDimensions = dimensionsFor();
|
|
4485
|
+
const pageBands = this.getPageBands(
|
|
4486
|
+
ownedDimensions,
|
|
4487
|
+
pageSectionIndex,
|
|
4488
|
+
pageInSection(),
|
|
4489
|
+
displayedPageNumber()
|
|
4490
|
+
);
|
|
4491
|
+
const partition = this.splitContinuationForPage(
|
|
4492
|
+
currentContinuation,
|
|
4493
|
+
pageBands.bodyHeight * MAX_FOOTNOTE_AREA_RATIO,
|
|
4494
|
+
ownedDimensions.contentWidth
|
|
4495
|
+
);
|
|
4496
|
+
currentContinuation = partition.current;
|
|
4497
|
+
if (partition.overflow) nextPageContinuation = partition.overflow;
|
|
4498
|
+
currentContinuationPartitioned = true;
|
|
4499
|
+
currentFootnoteHeight = this.measureFootnotesHeight(
|
|
4500
|
+
currentFootnoteIds,
|
|
4501
|
+
ownedDimensions.contentWidth,
|
|
4502
|
+
currentContinuation,
|
|
4503
|
+
currentPartialFootnotes
|
|
4504
|
+
);
|
|
4505
|
+
};
|
|
4506
|
+
const finishPage = (nextPageSectionIndex = pageSectionIndex, forceEmptyPage = false) => {
|
|
2616
4507
|
const hasCurrentContinuation = (currentContinuation?.remainingElements.length ?? 0) > 0;
|
|
2617
|
-
if (currentContent.length === 0 && !hasCurrentContinuation)
|
|
4508
|
+
if (!forceEmptyPage && currentContent.length === 0 && currentFootnoteIds.length === 0 && !hasCurrentContinuation) {
|
|
4509
|
+
adoptEmptyPageOwner(nextPageSectionIndex);
|
|
4510
|
+
return;
|
|
4511
|
+
}
|
|
4512
|
+
prepareCurrentContinuation();
|
|
4513
|
+
admitCurrentPage();
|
|
4514
|
+
let pageContinuation = currentContinuation;
|
|
4515
|
+
const ownedPageInSection = pageInSection();
|
|
4516
|
+
const ownedDimensions = dimensionsFor();
|
|
4517
|
+
const ownedDisplayedPageNumber = displayedPageNumber(
|
|
4518
|
+
pageSectionIndex,
|
|
4519
|
+
ownedPageInSection
|
|
4520
|
+
);
|
|
4521
|
+
const pageBands = this.getPageBands(
|
|
4522
|
+
ownedDimensions,
|
|
4523
|
+
pageSectionIndex,
|
|
4524
|
+
ownedPageInSection,
|
|
4525
|
+
ownedDisplayedPageNumber
|
|
4526
|
+
);
|
|
4527
|
+
const maxFootnoteHeight = pageBands.bodyHeight * MAX_FOOTNOTE_AREA_RATIO;
|
|
4528
|
+
pageContinuation = currentContinuation;
|
|
4529
|
+
currentFootnoteHeight = this.measureFootnotesHeight(
|
|
4530
|
+
currentFootnoteIds,
|
|
4531
|
+
ownedDimensions.contentWidth,
|
|
4532
|
+
pageContinuation,
|
|
4533
|
+
currentPartialFootnotes
|
|
4534
|
+
);
|
|
4535
|
+
if (currentContent.length === 0 && currentFootnoteIds.length > 0 && currentPartialFootnotes.length === 0) {
|
|
4536
|
+
const fittingIds = [];
|
|
4537
|
+
for (let index = 0; index < currentFootnoteIds.length; index++) {
|
|
4538
|
+
const footnoteId = currentFootnoteIds[index];
|
|
4539
|
+
const candidateIds = [...fittingIds, footnoteId];
|
|
4540
|
+
const candidateHeight = this.measureFootnotesHeight(
|
|
4541
|
+
candidateIds,
|
|
4542
|
+
ownedDimensions.contentWidth,
|
|
4543
|
+
pageContinuation
|
|
4544
|
+
);
|
|
4545
|
+
const guardedCandidateHeight = candidateHeight + FOOTNOTE_MEASUREMENT_GUARD_PT;
|
|
4546
|
+
if (guardedCandidateHeight <= maxFootnoteHeight) {
|
|
4547
|
+
fittingIds.push(footnoteId);
|
|
4548
|
+
currentFootnoteHeight = guardedCandidateHeight;
|
|
4549
|
+
continue;
|
|
4550
|
+
}
|
|
4551
|
+
const hasPageContinuation = (pageContinuation?.remainingElements.length ?? 0) > 0;
|
|
4552
|
+
if (fittingIds.length === 0 && !hasPageContinuation) {
|
|
4553
|
+
const source = this.footnoteRegistry.get(footnoteId);
|
|
4554
|
+
const split = source ? this.splitFootnoteToFit(
|
|
4555
|
+
source,
|
|
4556
|
+
maxFootnoteHeight - FOOTNOTE_MEASUREMENT_GUARD_PT,
|
|
4557
|
+
ownedDimensions.contentWidth,
|
|
4558
|
+
true
|
|
4559
|
+
) : null;
|
|
4560
|
+
fittingIds.push(footnoteId);
|
|
4561
|
+
if (source && split && split.fits.length > 0 && split.overflow.length > 0) {
|
|
4562
|
+
currentPartialFootnotes.push({ footnoteId, fittingElements: split.fits });
|
|
4563
|
+
nextPageContinuation = {
|
|
4564
|
+
footnoteId,
|
|
4565
|
+
sourceAnchorId: source.dataset.sourceAnchorId,
|
|
4566
|
+
remainingElements: split.overflow
|
|
4567
|
+
};
|
|
4568
|
+
currentFootnoteHeight = maxFootnoteHeight;
|
|
4569
|
+
} else {
|
|
4570
|
+
currentFootnoteHeight = guardedCandidateHeight;
|
|
4571
|
+
}
|
|
4572
|
+
deferredFootnoteIds.push(...currentFootnoteIds.slice(index + 1));
|
|
4573
|
+
} else {
|
|
4574
|
+
deferredFootnoteIds.push(...currentFootnoteIds.slice(index));
|
|
4575
|
+
}
|
|
4576
|
+
break;
|
|
4577
|
+
}
|
|
4578
|
+
currentFootnoteIds = fittingIds;
|
|
4579
|
+
}
|
|
2618
4580
|
const page = this.createPage(
|
|
2619
|
-
|
|
4581
|
+
ownedDimensions,
|
|
2620
4582
|
pageNumber,
|
|
2621
|
-
|
|
4583
|
+
pageSectionIndex,
|
|
4584
|
+
ownedDisplayedPageNumber,
|
|
2622
4585
|
currentContent,
|
|
2623
|
-
|
|
4586
|
+
ownedPageInSection,
|
|
2624
4587
|
currentFootnoteIds,
|
|
2625
4588
|
currentFootnoteHeight,
|
|
2626
|
-
|
|
2627
|
-
currentPartialFootnotes.length > 0 ? currentPartialFootnotes : void 0
|
|
4589
|
+
pageContinuation,
|
|
4590
|
+
currentPartialFootnotes.length > 0 ? currentPartialFootnotes : void 0,
|
|
4591
|
+
false,
|
|
4592
|
+
currentPageAdmitted
|
|
2628
4593
|
);
|
|
2629
4594
|
pages.push(page);
|
|
4595
|
+
precedingDisplayedPageNumber = ownedDisplayedPageNumber;
|
|
2630
4596
|
pageNumber++;
|
|
2631
|
-
pageInSection++;
|
|
2632
4597
|
currentContent = [];
|
|
2633
|
-
|
|
4598
|
+
pageSectionIndex = nextPageSectionIndex;
|
|
4599
|
+
if (!sectionStartPages.has(pageSectionIndex)) {
|
|
4600
|
+
sectionStartPages.set(pageSectionIndex, pageNumber);
|
|
4601
|
+
}
|
|
4602
|
+
const newBands = this.getPageBands(
|
|
4603
|
+
dimensionsFor(),
|
|
4604
|
+
pageSectionIndex,
|
|
4605
|
+
pageInSection(),
|
|
4606
|
+
displayedPageNumber()
|
|
4607
|
+
);
|
|
2634
4608
|
effectiveContentHeight = newBands.bodyHeight;
|
|
2635
4609
|
remainingHeight = effectiveContentHeight;
|
|
2636
4610
|
prevMarginBottomPt = 0;
|
|
2637
4611
|
currentFootnoteIds = [];
|
|
4612
|
+
currentPageHasFootnoteReference = false;
|
|
2638
4613
|
currentPartialFootnotes = [];
|
|
2639
4614
|
currentContinuation = nextPageContinuation;
|
|
2640
4615
|
nextPageContinuation = null;
|
|
4616
|
+
currentContinuationPartitioned = false;
|
|
4617
|
+
currentPageAdmitted = false;
|
|
2641
4618
|
if (deferredFootnoteIds.length > 0) {
|
|
2642
4619
|
currentFootnoteIds = [...deferredFootnoteIds];
|
|
2643
4620
|
deferredFootnoteIds = [];
|
|
2644
4621
|
}
|
|
2645
|
-
|
|
2646
|
-
|
|
2647
|
-
} else {
|
|
2648
|
-
currentFootnoteHeight = 0;
|
|
2649
|
-
}
|
|
2650
|
-
if (currentFootnoteIds.length > 0) {
|
|
2651
|
-
currentFootnoteHeight += this.measureFootnotesHeight(
|
|
2652
|
-
currentFootnoteIds,
|
|
2653
|
-
dims.contentWidth,
|
|
2654
|
-
null
|
|
2655
|
-
);
|
|
2656
|
-
}
|
|
4622
|
+
const nextDimensions = dimensionsFor();
|
|
4623
|
+
currentFootnoteHeight = currentContinuation ? 0 : this.measureFootnotesHeight(currentFootnoteIds, nextDimensions.contentWidth);
|
|
2657
4624
|
};
|
|
4625
|
+
const pageHasPayload = () => currentContent.length > 0 || currentFootnoteIds.length > 0 || (currentContinuation?.remainingElements.length ?? 0) > 0;
|
|
2658
4626
|
for (let i = 0; i < blocks.length; i++) {
|
|
4627
|
+
this.checkpoint();
|
|
2659
4628
|
const block = blocks[i];
|
|
4629
|
+
const allBlockFootnoteIds = this.extractFootnoteRefs(block.element);
|
|
4630
|
+
if (block.sectionIndex !== pageSectionIndex && currentPageHasFootnoteReference && (!this.footnoteLayoutLikeWord8 || allBlockFootnoteIds.length > 0)) {
|
|
4631
|
+
finishPage(block.sectionIndex);
|
|
4632
|
+
}
|
|
4633
|
+
const pageIsEmpty = currentContent.length === 0 && currentFootnoteIds.length === 0 && (currentContinuation?.remainingElements.length ?? 0) === 0;
|
|
4634
|
+
if (pageIsEmpty && block.sectionIndex !== pageSectionIndex) {
|
|
4635
|
+
adoptEmptyPageOwner(block.sectionIndex);
|
|
4636
|
+
}
|
|
4637
|
+
prepareCurrentContinuation();
|
|
4638
|
+
if (currentFootnoteHeight > 0) {
|
|
4639
|
+
const ownerDimensions = dimensionsFor();
|
|
4640
|
+
const ownerBands = this.getPageBands(
|
|
4641
|
+
ownerDimensions,
|
|
4642
|
+
pageSectionIndex,
|
|
4643
|
+
pageInSection(),
|
|
4644
|
+
displayedPageNumber()
|
|
4645
|
+
);
|
|
4646
|
+
const ownerMaxFootnoteArea = ownerBands.bodyHeight * MAX_FOOTNOTE_AREA_RATIO;
|
|
4647
|
+
if (currentFootnoteHeight > ownerMaxFootnoteArea) {
|
|
4648
|
+
const fittingIds = [];
|
|
4649
|
+
let fittingHeight = 0;
|
|
4650
|
+
if ((currentContinuation?.remainingElements.length ?? 0) === 0 && currentPartialFootnotes.length === 0) {
|
|
4651
|
+
for (const footnoteId of currentFootnoteIds) {
|
|
4652
|
+
this.checkpoint();
|
|
4653
|
+
const candidateHeight = this.measureFootnotesHeight(
|
|
4654
|
+
[...fittingIds, footnoteId],
|
|
4655
|
+
ownerDimensions.contentWidth
|
|
4656
|
+
);
|
|
4657
|
+
if (candidateHeight + FOOTNOTE_MEASUREMENT_GUARD_PT > ownerMaxFootnoteArea) break;
|
|
4658
|
+
fittingIds.push(footnoteId);
|
|
4659
|
+
fittingHeight = candidateHeight;
|
|
4660
|
+
}
|
|
4661
|
+
}
|
|
4662
|
+
if (fittingIds.length > 0) {
|
|
4663
|
+
deferredFootnoteIds = [
|
|
4664
|
+
...currentFootnoteIds.slice(fittingIds.length),
|
|
4665
|
+
...deferredFootnoteIds
|
|
4666
|
+
];
|
|
4667
|
+
currentFootnoteIds = fittingIds;
|
|
4668
|
+
currentFootnoteHeight = fittingHeight;
|
|
4669
|
+
} else {
|
|
4670
|
+
finishPage(block.sectionIndex);
|
|
4671
|
+
i--;
|
|
4672
|
+
continue;
|
|
4673
|
+
}
|
|
4674
|
+
}
|
|
4675
|
+
}
|
|
4676
|
+
const blockDimensions = dimensionsFor(block.sectionIndex);
|
|
4677
|
+
const nextBlockPageInSection = pageInSection(block.sectionIndex, pageNumber + 1);
|
|
4678
|
+
const freshBlockPageBodyHeight = this.getPageBands(
|
|
4679
|
+
blockDimensions,
|
|
4680
|
+
block.sectionIndex,
|
|
4681
|
+
nextBlockPageInSection,
|
|
4682
|
+
displayedPageNumber(block.sectionIndex, nextBlockPageInSection, pageNumber + 1)
|
|
4683
|
+
).bodyHeight;
|
|
2660
4684
|
if (block.isPageBreak) {
|
|
2661
|
-
finishPage();
|
|
4685
|
+
finishPage(block.sectionIndex);
|
|
2662
4686
|
continue;
|
|
2663
4687
|
}
|
|
2664
4688
|
if (block.pageBreakBefore && currentContent.length > 0) {
|
|
2665
|
-
finishPage();
|
|
4689
|
+
finishPage(block.sectionIndex);
|
|
2666
4690
|
}
|
|
2667
4691
|
const previousBlock = blocks[i - 1];
|
|
2668
4692
|
const startsKeepChain = !previousBlock || !previousBlock.keepWithNext || previousBlock.isPageBreak || block.pageBreakBefore;
|
|
@@ -2678,7 +4702,7 @@ var Docxodus = (() => {
|
|
|
2678
4702
|
if (newChainFootnoteIds.length > 0 && this.footnoteRegistry.size > 0) {
|
|
2679
4703
|
const totalChainFootnoteHeight = this.measureFootnotesHeight(
|
|
2680
4704
|
[...currentFootnoteIds, ...newChainFootnoteIds],
|
|
2681
|
-
|
|
4705
|
+
blockDimensions.contentWidth,
|
|
2682
4706
|
currentContinuation
|
|
2683
4707
|
);
|
|
2684
4708
|
additionalChainFootnoteHeight = Math.max(
|
|
@@ -2690,61 +4714,68 @@ var Docxodus = (() => {
|
|
|
2690
4714
|
keepChain,
|
|
2691
4715
|
prevMarginBottomPt,
|
|
2692
4716
|
currentContent.length === 0,
|
|
2693
|
-
pageInSection
|
|
4717
|
+
pageInSection(block.sectionIndex)
|
|
2694
4718
|
) + additionalChainFootnoteHeight;
|
|
2695
4719
|
const currentAvailableHeight = remainingHeight - currentFootnoteHeight;
|
|
2696
4720
|
if (currentChainHeight > currentAvailableHeight) {
|
|
2697
4721
|
const nextPageBands = this.getPageBands(
|
|
2698
|
-
|
|
2699
|
-
sectionIndex,
|
|
2700
|
-
pageInSection + 1,
|
|
2701
|
-
|
|
4722
|
+
dimensionsFor(block.sectionIndex),
|
|
4723
|
+
block.sectionIndex,
|
|
4724
|
+
pageInSection(block.sectionIndex, pageNumber + 1),
|
|
4725
|
+
displayedPageNumber(
|
|
4726
|
+
block.sectionIndex,
|
|
4727
|
+
pageInSection(block.sectionIndex, pageNumber + 1),
|
|
4728
|
+
pageNumber + 1
|
|
4729
|
+
)
|
|
2702
4730
|
);
|
|
2703
4731
|
const freshChainBodyHeight = this.measureKeepWithNextChainBodyHeight(
|
|
2704
4732
|
keepChain,
|
|
2705
4733
|
0,
|
|
2706
4734
|
true,
|
|
2707
|
-
pageInSection + 1
|
|
4735
|
+
pageInSection(block.sectionIndex, pageNumber + 1)
|
|
2708
4736
|
);
|
|
2709
4737
|
const freshChainFootnoteHeight = this.measureFootnotesHeight(
|
|
2710
4738
|
newChainFootnoteIds,
|
|
2711
|
-
|
|
4739
|
+
blockDimensions.contentWidth,
|
|
2712
4740
|
nextPageContinuation
|
|
2713
4741
|
);
|
|
2714
4742
|
if (freshChainBodyHeight + freshChainFootnoteHeight <= nextPageBands.bodyHeight) {
|
|
2715
|
-
finishPage();
|
|
4743
|
+
finishPage(block.sectionIndex);
|
|
2716
4744
|
}
|
|
2717
4745
|
}
|
|
2718
4746
|
}
|
|
2719
4747
|
}
|
|
2720
|
-
const allBlockFootnoteIds = this.extractFootnoteRefs(block.element);
|
|
2721
4748
|
const newFootnoteIds = this.collectNewFootnoteIds([block], currentFootnoteIds);
|
|
4749
|
+
let combinedFootnoteHeight = currentFootnoteHeight;
|
|
2722
4750
|
let additionalFootnoteHeight = 0;
|
|
2723
4751
|
if (newFootnoteIds.length > 0 && this.footnoteRegistry.size > 0) {
|
|
2724
4752
|
const combinedFootnoteIds = [...currentFootnoteIds, ...newFootnoteIds];
|
|
2725
|
-
|
|
4753
|
+
combinedFootnoteHeight = this.measureFootnotesHeight(
|
|
2726
4754
|
combinedFootnoteIds,
|
|
2727
|
-
|
|
2728
|
-
currentContinuation
|
|
4755
|
+
blockDimensions.contentWidth,
|
|
4756
|
+
currentContinuation,
|
|
4757
|
+
currentPartialFootnotes
|
|
4758
|
+
);
|
|
4759
|
+
additionalFootnoteHeight = Math.max(
|
|
4760
|
+
0,
|
|
4761
|
+
combinedFootnoteHeight - currentFootnoteHeight
|
|
2729
4762
|
);
|
|
2730
|
-
additionalFootnoteHeight = totalFootnoteHeight - currentFootnoteHeight;
|
|
2731
4763
|
}
|
|
2732
4764
|
const isFirstOnPage = currentContent.length === 0;
|
|
2733
4765
|
const effectiveMarginTop = this.effectiveBlockMarginTop(
|
|
2734
4766
|
block,
|
|
2735
4767
|
prevMarginBottomPt,
|
|
2736
4768
|
isFirstOnPage,
|
|
2737
|
-
pageInSection
|
|
4769
|
+
pageInSection(block.sectionIndex)
|
|
2738
4770
|
);
|
|
2739
4771
|
const blockSpace = effectiveMarginTop + block.heightPt + additionalFootnoteHeight;
|
|
2740
4772
|
const effectiveRemainingHeight = remainingHeight - currentFootnoteHeight;
|
|
2741
4773
|
const bodyContentUsed = effectiveContentHeight - remainingHeight;
|
|
2742
4774
|
const maxFootnoteArea = effectiveContentHeight * MAX_FOOTNOTE_AREA_RATIO;
|
|
2743
|
-
const maxFootnoteExpansion = Math.max(0, maxFootnoteArea - currentFootnoteHeight);
|
|
2744
4775
|
if (blockSpace > effectiveRemainingHeight) {
|
|
2745
4776
|
const paragraphFragments = this.tryFragmentParagraph(
|
|
2746
4777
|
block,
|
|
2747
|
-
|
|
4778
|
+
blockDimensions,
|
|
2748
4779
|
effectiveRemainingHeight,
|
|
2749
4780
|
effectiveMarginTop
|
|
2750
4781
|
);
|
|
@@ -2754,40 +4785,74 @@ var Docxodus = (() => {
|
|
|
2754
4785
|
continue;
|
|
2755
4786
|
}
|
|
2756
4787
|
}
|
|
2757
|
-
if (blockSpace <= effectiveRemainingHeight) {
|
|
2758
|
-
|
|
4788
|
+
if (blockSpace <= effectiveRemainingHeight && (combinedFootnoteHeight === 0 || combinedFootnoteHeight + FOOTNOTE_MEASUREMENT_GUARD_PT <= maxFootnoteArea)) {
|
|
4789
|
+
markSectionPlaced(block.sectionIndex);
|
|
4790
|
+
currentContent.push(this.cloneBlockForPage(
|
|
4791
|
+
block,
|
|
4792
|
+
isFirstOnPage,
|
|
4793
|
+
pageInSection(block.sectionIndex)
|
|
4794
|
+
));
|
|
2759
4795
|
remainingHeight -= effectiveMarginTop + block.heightPt + block.marginBottomPt;
|
|
2760
4796
|
prevMarginBottomPt = block.marginBottomPt;
|
|
2761
4797
|
if (newFootnoteIds.length > 0) {
|
|
2762
4798
|
currentFootnoteIds.push(...newFootnoteIds);
|
|
2763
|
-
currentFootnoteHeight
|
|
4799
|
+
currentFootnoteHeight = combinedFootnoteHeight;
|
|
2764
4800
|
}
|
|
2765
|
-
|
|
4801
|
+
currentPageHasFootnoteReference ||= allBlockFootnoteIds.length > 0;
|
|
4802
|
+
} else if (block.heightPt + this.effectiveBlockMarginTop(
|
|
4803
|
+
block,
|
|
4804
|
+
0,
|
|
4805
|
+
true,
|
|
4806
|
+
pageInSection(block.sectionIndex, pageNumber + 1)
|
|
4807
|
+
) <= freshBlockPageBodyHeight) {
|
|
2766
4808
|
const blockSpaceWithoutFootnotes = effectiveMarginTop + block.heightPt;
|
|
2767
|
-
const minBodySpaceNeeded = bodyContentUsed + blockSpaceWithoutFootnotes + MIN_BODY_CONTENT_HEIGHT;
|
|
2768
|
-
const canExpandFootnotes = minBodySpaceNeeded <= effectiveContentHeight;
|
|
2769
4809
|
if (newFootnoteIds.length > 0 && blockSpaceWithoutFootnotes <= effectiveRemainingHeight) {
|
|
2770
|
-
|
|
4810
|
+
markSectionPlaced(block.sectionIndex);
|
|
4811
|
+
currentContent.push(this.cloneBlockForPage(
|
|
4812
|
+
block,
|
|
4813
|
+
isFirstOnPage,
|
|
4814
|
+
pageInSection(block.sectionIndex)
|
|
4815
|
+
));
|
|
2771
4816
|
remainingHeight -= effectiveMarginTop + block.heightPt + block.marginBottomPt;
|
|
2772
4817
|
prevMarginBottomPt = block.marginBottomPt;
|
|
2773
4818
|
const availableForFootnotes = Math.min(
|
|
2774
4819
|
maxFootnoteArea,
|
|
2775
4820
|
effectiveContentHeight - bodyContentUsed - blockSpaceWithoutFootnotes
|
|
2776
4821
|
);
|
|
2777
|
-
|
|
4822
|
+
const nextPageMaxFootnoteArea = freshBlockPageBodyHeight * MAX_FOOTNOTE_AREA_RATIO;
|
|
4823
|
+
const deferralCannotHelp = availableForFootnotes >= nextPageMaxFootnoteArea;
|
|
4824
|
+
for (let noteIndex = 0; noteIndex < newFootnoteIds.length; noteIndex++) {
|
|
4825
|
+
this.checkpoint();
|
|
4826
|
+
const footnoteId = newFootnoteIds[noteIndex];
|
|
2778
4827
|
const footnote = this.footnoteRegistry.get(footnoteId);
|
|
2779
4828
|
if (!footnote) continue;
|
|
2780
|
-
|
|
4829
|
+
if (nextPageContinuation) {
|
|
4830
|
+
deferredFootnoteIds.push(...newFootnoteIds.slice(noteIndex));
|
|
4831
|
+
break;
|
|
4832
|
+
}
|
|
4833
|
+
const candidateIds = [...currentFootnoteIds, footnoteId];
|
|
4834
|
+
const candidateHeight = this.measureFootnotesHeight(
|
|
4835
|
+
candidateIds,
|
|
4836
|
+
blockDimensions.contentWidth,
|
|
4837
|
+
currentContinuation,
|
|
4838
|
+
currentPartialFootnotes
|
|
4839
|
+
);
|
|
2781
4840
|
const spaceLeftForFootnotes = availableForFootnotes - currentFootnoteHeight;
|
|
2782
|
-
if (
|
|
4841
|
+
if (candidateHeight + FOOTNOTE_MEASUREMENT_GUARD_PT <= availableForFootnotes) {
|
|
2783
4842
|
currentFootnoteIds.push(footnoteId);
|
|
2784
|
-
currentFootnoteHeight
|
|
4843
|
+
currentFootnoteHeight = candidateHeight;
|
|
2785
4844
|
} else {
|
|
2786
4845
|
if (spaceLeftForFootnotes > 20) {
|
|
2787
4846
|
const { fits, overflow } = this.splitFootnoteToFit(
|
|
2788
4847
|
footnote,
|
|
2789
|
-
|
|
2790
|
-
|
|
4848
|
+
availableForFootnotes,
|
|
4849
|
+
blockDimensions.contentWidth,
|
|
4850
|
+
deferralCannotHelp,
|
|
4851
|
+
{
|
|
4852
|
+
footnoteIds: currentFootnoteIds,
|
|
4853
|
+
continuation: currentContinuation,
|
|
4854
|
+
partialFootnotes: currentPartialFootnotes
|
|
4855
|
+
}
|
|
2791
4856
|
);
|
|
2792
4857
|
if (fits.length > 0) {
|
|
2793
4858
|
currentFootnoteIds.push(footnoteId);
|
|
@@ -2798,78 +4863,70 @@ var Docxodus = (() => {
|
|
|
2798
4863
|
if (overflow.length > 0) {
|
|
2799
4864
|
nextPageContinuation = {
|
|
2800
4865
|
footnoteId,
|
|
4866
|
+
sourceAnchorId: footnote.dataset.sourceAnchorId,
|
|
2801
4867
|
remainingElements: overflow
|
|
2802
4868
|
};
|
|
2803
4869
|
}
|
|
2804
|
-
currentFootnoteHeight =
|
|
4870
|
+
currentFootnoteHeight = this.measureFootnotesHeight(
|
|
4871
|
+
currentFootnoteIds,
|
|
4872
|
+
blockDimensions.contentWidth,
|
|
4873
|
+
currentContinuation,
|
|
4874
|
+
currentPartialFootnotes
|
|
4875
|
+
);
|
|
4876
|
+
if (overflow.length > 0) {
|
|
4877
|
+
deferredFootnoteIds.push(...newFootnoteIds.slice(noteIndex + 1));
|
|
4878
|
+
break;
|
|
4879
|
+
}
|
|
2805
4880
|
} else {
|
|
2806
|
-
deferredFootnoteIds.push(
|
|
4881
|
+
deferredFootnoteIds.push(...newFootnoteIds.slice(noteIndex));
|
|
4882
|
+
break;
|
|
2807
4883
|
}
|
|
2808
4884
|
} else {
|
|
2809
|
-
deferredFootnoteIds.push(
|
|
4885
|
+
deferredFootnoteIds.push(...newFootnoteIds.slice(noteIndex));
|
|
4886
|
+
break;
|
|
2810
4887
|
}
|
|
2811
4888
|
}
|
|
2812
4889
|
}
|
|
2813
|
-
|
|
2814
|
-
const expandedFootnoteSpace = Math.min(maxFootnoteArea, additionalFootnoteHeight + currentFootnoteHeight);
|
|
2815
|
-
const bodySpaceAfterExpansion = effectiveContentHeight - expandedFootnoteSpace;
|
|
2816
|
-
if (blockSpaceWithoutFootnotes <= bodySpaceAfterExpansion - bodyContentUsed) {
|
|
2817
|
-
currentContent.push(this.cloneBlockForPage(block, isFirstOnPage, pageInSection));
|
|
2818
|
-
remainingHeight = bodySpaceAfterExpansion - bodyContentUsed - blockSpaceWithoutFootnotes;
|
|
2819
|
-
prevMarginBottomPt = block.marginBottomPt;
|
|
2820
|
-
currentFootnoteIds.push(...newFootnoteIds);
|
|
2821
|
-
currentFootnoteHeight = expandedFootnoteSpace;
|
|
2822
|
-
} else {
|
|
2823
|
-
finishPage();
|
|
2824
|
-
const newPageFootnoteHeight = allBlockFootnoteIds.length > 0 ? this.measureFootnotesHeight(allBlockFootnoteIds, dims.contentWidth, currentContinuation) : currentContinuation ? this.measureContinuationHeight(currentContinuation, dims.contentWidth) : 0;
|
|
2825
|
-
const newPageMarginTop = this.effectiveBlockMarginTop(
|
|
2826
|
-
block,
|
|
2827
|
-
0,
|
|
2828
|
-
true,
|
|
2829
|
-
pageInSection
|
|
2830
|
-
);
|
|
2831
|
-
const newPageSpace = newPageMarginTop + block.heightPt + block.marginBottomPt;
|
|
2832
|
-
currentContent.push(this.cloneBlockForPage(block, true, pageInSection));
|
|
2833
|
-
remainingHeight = effectiveContentHeight - newPageSpace;
|
|
2834
|
-
prevMarginBottomPt = block.marginBottomPt;
|
|
2835
|
-
currentFootnoteIds = [...currentFootnoteIds, ...allBlockFootnoteIds];
|
|
2836
|
-
currentFootnoteHeight = newPageFootnoteHeight;
|
|
2837
|
-
}
|
|
4890
|
+
currentPageHasFootnoteReference ||= allBlockFootnoteIds.length > 0;
|
|
2838
4891
|
} else {
|
|
2839
|
-
finishPage();
|
|
2840
|
-
|
|
2841
|
-
|
|
2842
|
-
block,
|
|
2843
|
-
0,
|
|
2844
|
-
true,
|
|
2845
|
-
pageInSection
|
|
2846
|
-
);
|
|
2847
|
-
const newPageSpace = newPageMarginTop + block.heightPt + block.marginBottomPt;
|
|
2848
|
-
currentContent.push(this.cloneBlockForPage(block, true, pageInSection));
|
|
2849
|
-
remainingHeight = effectiveContentHeight - newPageSpace;
|
|
2850
|
-
prevMarginBottomPt = block.marginBottomPt;
|
|
2851
|
-
currentFootnoteIds = [...currentFootnoteIds, ...allBlockFootnoteIds];
|
|
2852
|
-
currentFootnoteHeight = newPageFootnoteHeight;
|
|
4892
|
+
finishPage(block.sectionIndex, !pageHasPayload());
|
|
4893
|
+
i--;
|
|
4894
|
+
continue;
|
|
2853
4895
|
}
|
|
2854
4896
|
} else {
|
|
2855
|
-
const tableFragments = this.trySplitSimpleOversizedTable(
|
|
4897
|
+
const tableFragments = this.trySplitSimpleOversizedTable(
|
|
4898
|
+
block,
|
|
4899
|
+
dimensionsFor(block.sectionIndex),
|
|
4900
|
+
block.sectionIndex
|
|
4901
|
+
);
|
|
2856
4902
|
if (tableFragments) {
|
|
2857
4903
|
if (currentContent.length > 0 || currentContinuation) {
|
|
2858
|
-
finishPage();
|
|
4904
|
+
finishPage(block.sectionIndex);
|
|
2859
4905
|
}
|
|
2860
4906
|
blocks.splice(i, 1, ...tableFragments);
|
|
2861
4907
|
i--;
|
|
2862
4908
|
continue;
|
|
2863
4909
|
}
|
|
2864
|
-
if (
|
|
2865
|
-
finishPage();
|
|
4910
|
+
if (pageHasPayload()) {
|
|
4911
|
+
finishPage(block.sectionIndex);
|
|
4912
|
+
i--;
|
|
4913
|
+
continue;
|
|
2866
4914
|
}
|
|
2867
|
-
|
|
2868
|
-
|
|
2869
|
-
|
|
4915
|
+
markSectionPlaced(block.sectionIndex);
|
|
4916
|
+
currentContent.push(this.cloneBlockForPage(
|
|
4917
|
+
block,
|
|
4918
|
+
true,
|
|
4919
|
+
pageInSection(block.sectionIndex)
|
|
4920
|
+
));
|
|
4921
|
+
deferredFootnoteIds.push(...newFootnoteIds);
|
|
4922
|
+
currentPageHasFootnoteReference ||= allBlockFootnoteIds.length > 0;
|
|
4923
|
+
finishPage(block.sectionIndex);
|
|
2870
4924
|
}
|
|
2871
4925
|
}
|
|
2872
4926
|
finishPage();
|
|
4927
|
+
while (currentFootnoteIds.length > 0 || deferredFootnoteIds.length > 0 || (currentContinuation?.remainingElements.length ?? 0) > 0) {
|
|
4928
|
+
finishPage();
|
|
4929
|
+
}
|
|
2873
4930
|
this.pendingFootnoteContinuation = nextPageContinuation;
|
|
2874
4931
|
return pages;
|
|
2875
4932
|
}
|
|
@@ -2894,6 +4951,19 @@ var Docxodus = (() => {
|
|
|
2894
4951
|
if (el.getAttribute("contenteditable") !== null) el.setAttribute("contenteditable", "false");
|
|
2895
4952
|
}
|
|
2896
4953
|
}
|
|
4954
|
+
/** A repeated margin note is presentation, not a second bookmark/link target. */
|
|
4955
|
+
makeClonedMarginCommentInert(root) {
|
|
4956
|
+
this.makeClonedStoryInert(root);
|
|
4957
|
+
const nodes = [root, ...Array.from(root.querySelectorAll("*"))];
|
|
4958
|
+
for (const element of nodes) {
|
|
4959
|
+
element.removeAttribute("id");
|
|
4960
|
+
if (element.localName === "a" && element.getAttribute("href")?.startsWith("#")) {
|
|
4961
|
+
element.removeAttribute("href");
|
|
4962
|
+
element.setAttribute("aria-disabled", "true");
|
|
4963
|
+
element.tabIndex = -1;
|
|
4964
|
+
}
|
|
4965
|
+
}
|
|
4966
|
+
}
|
|
2897
4967
|
/**
|
|
2898
4968
|
* Resolves floating DrawingML objects after their anchor paragraphs have landed on a page.
|
|
2899
4969
|
*
|
|
@@ -2919,7 +4989,7 @@ var Docxodus = (() => {
|
|
|
2919
4989
|
const context = {
|
|
2920
4990
|
staticLeft: toPageX(staticRect.left),
|
|
2921
4991
|
staticTop: toPageY(staticRect.top),
|
|
2922
|
-
lineHeight: paragraph ? (parseFloat(getComputedStyle(paragraph).lineHeight) || staticRect.height) / pixelsPerPoint : staticRect.height / pixelsPerPoint,
|
|
4992
|
+
lineHeight: paragraph ? (parseFloat(this.view.getComputedStyle(paragraph).lineHeight) || staticRect.height) / pixelsPerPoint : staticRect.height / pixelsPerPoint,
|
|
2923
4993
|
paragraphLeft: toPageX(paragraphRect.left),
|
|
2924
4994
|
paragraphTop: toPageY(paragraphRect.top),
|
|
2925
4995
|
paragraphWidth: paragraphRect.width / pixelsPerPoint,
|
|
@@ -3049,36 +5119,48 @@ var Docxodus = (() => {
|
|
|
3049
5119
|
else if (alignment === "outside") fraction = pageNumber % 2 === 1 ? 1 : 0;
|
|
3050
5120
|
return reference.start + (reference.size - objectSize) * fraction;
|
|
3051
5121
|
}
|
|
5122
|
+
/** Charge a physical page before any page-owned partitioning or DOM allocation. */
|
|
5123
|
+
admitPageAllocation() {
|
|
5124
|
+
const prospectivePageCount = this.createdPageCount + 1;
|
|
5125
|
+
this.pageCountCheckpoint?.(prospectivePageCount);
|
|
5126
|
+
this.createdPageCount = prospectivePageCount;
|
|
5127
|
+
}
|
|
3052
5128
|
/**
|
|
3053
5129
|
* Creates a page container element.
|
|
3054
5130
|
*/
|
|
3055
|
-
createPage(dims, pageNumber, sectionIndex, content, pageInSection, footnoteIds = [], footnoteHeight = 0, continuation, partialFootnotes) {
|
|
3056
|
-
|
|
5131
|
+
createPage(dims, pageNumber, sectionIndex, displayedPageNumber, content, pageInSection, footnoteIds = [], footnoteHeight = 0, continuation, partialFootnotes, isSectionFiller = false, pageAlreadyAdmitted = false) {
|
|
5132
|
+
if (!pageAlreadyAdmitted) this.admitPageAllocation();
|
|
5133
|
+
const pageBox = this.document.createElement("div");
|
|
3057
5134
|
pageBox.className = `${this.cssPrefix}box`;
|
|
3058
5135
|
pageBox.style.width = `${dims.pageWidth}pt`;
|
|
3059
5136
|
pageBox.style.height = `${dims.pageHeight}pt`;
|
|
3060
5137
|
pageBox.style.overflow = "hidden";
|
|
3061
5138
|
pageBox.style.position = "relative";
|
|
3062
5139
|
if (this.scale !== 1) {
|
|
3063
|
-
|
|
3064
|
-
|
|
3065
|
-
|
|
3066
|
-
|
|
3067
|
-
|
|
3068
|
-
|
|
3069
|
-
|
|
3070
|
-
|
|
3071
|
-
|
|
5140
|
+
if (this.view.CSS?.supports("zoom", "1")) {
|
|
5141
|
+
pageBox.style.zoom = String(this.scale);
|
|
5142
|
+
} else {
|
|
5143
|
+
pageBox.style.transform = `scale(${this.scale})`;
|
|
5144
|
+
pageBox.style.transformOrigin = "top left";
|
|
5145
|
+
const heightReductionPt = dims.pageHeight * (1 - this.scale);
|
|
5146
|
+
const widthReductionPt = dims.pageWidth * (1 - this.scale);
|
|
5147
|
+
const heightReductionPx = ptToPx(heightReductionPt);
|
|
5148
|
+
const widthReductionPx = ptToPx(widthReductionPt);
|
|
5149
|
+
pageBox.style.marginRight = `-${widthReductionPx}px`;
|
|
5150
|
+
pageBox.style.marginBottom = `${this.pageGap - heightReductionPx}px`;
|
|
5151
|
+
}
|
|
3072
5152
|
}
|
|
3073
5153
|
pageBox.style.willChange = "transform";
|
|
3074
5154
|
pageBox.style.contain = "layout paint";
|
|
3075
5155
|
pageBox.dataset.pageNumber = String(pageNumber);
|
|
3076
5156
|
pageBox.dataset.sectionIndex = String(sectionIndex);
|
|
5157
|
+
pageBox.dataset.displayedPageNumber = String(displayedPageNumber);
|
|
5158
|
+
if (isSectionFiller) pageBox.dataset.sectionFiller = "true";
|
|
3077
5159
|
pageBox.dataset.pageInSection = String(pageInSection);
|
|
3078
|
-
const bands = this.getPageBands(dims, sectionIndex, pageInSection,
|
|
3079
|
-
const headerSource = this.selectHeader(sectionIndex, pageInSection,
|
|
5160
|
+
const bands = this.getPageBands(dims, sectionIndex, pageInSection, displayedPageNumber);
|
|
5161
|
+
const headerSource = isSectionFiller ? void 0 : this.selectHeader(sectionIndex, pageInSection, displayedPageNumber);
|
|
3080
5162
|
if (headerSource) {
|
|
3081
|
-
const headerDiv = document.createElement("div");
|
|
5163
|
+
const headerDiv = this.document.createElement("div");
|
|
3082
5164
|
headerDiv.className = `${this.cssPrefix}header`;
|
|
3083
5165
|
headerDiv.style.position = "absolute";
|
|
3084
5166
|
headerDiv.style.top = `${bands.headerTop}pt`;
|
|
@@ -3100,7 +5182,7 @@ var Docxodus = (() => {
|
|
|
3100
5182
|
}
|
|
3101
5183
|
const contentAreaTop = bands.bodyTop;
|
|
3102
5184
|
const contentAreaHeight = bands.bodyHeight;
|
|
3103
|
-
const contentArea = document.createElement("div");
|
|
5185
|
+
const contentArea = this.document.createElement("div");
|
|
3104
5186
|
contentArea.className = `${this.cssPrefix}content`;
|
|
3105
5187
|
contentArea.style.position = "absolute";
|
|
3106
5188
|
contentArea.style.top = `${contentAreaTop}pt`;
|
|
@@ -3113,12 +5195,46 @@ var Docxodus = (() => {
|
|
|
3113
5195
|
}
|
|
3114
5196
|
pageBox.appendChild(contentArea);
|
|
3115
5197
|
const hasContinuation = continuation && continuation.remainingElements.length > 0;
|
|
3116
|
-
if (footnoteIds.length > 0 || hasContinuation) {
|
|
5198
|
+
if (!isSectionFiller && (footnoteIds.length > 0 || hasContinuation)) {
|
|
3117
5199
|
this.addPageFootnotes(pageBox, footnoteIds, dims, bands, footnoteHeight, continuation, partialFootnotes);
|
|
3118
5200
|
}
|
|
3119
|
-
const
|
|
5201
|
+
const pageCommentIds = [];
|
|
5202
|
+
for (const marker of Array.from(
|
|
5203
|
+
pageBox.querySelectorAll("[data-comment-id]")
|
|
5204
|
+
)) {
|
|
5205
|
+
if (marker.closest(`.${this.cssPrefix}comment-margin`)) continue;
|
|
5206
|
+
const id = marker.dataset.commentId;
|
|
5207
|
+
if (id && this.commentMarginRegistry.has(id) && !pageCommentIds.includes(id)) {
|
|
5208
|
+
pageCommentIds.push(id);
|
|
5209
|
+
}
|
|
5210
|
+
}
|
|
5211
|
+
if (pageCommentIds.length > 0) {
|
|
5212
|
+
const marginColumn = this.document.createElement("aside");
|
|
5213
|
+
marginColumn.className = `${this.cssPrefix}comment-margin`;
|
|
5214
|
+
marginColumn.style.position = "absolute";
|
|
5215
|
+
marginColumn.style.top = `${contentAreaTop}pt`;
|
|
5216
|
+
marginColumn.style.left = `${dims.marginLeft + dims.contentWidth + 3}pt`;
|
|
5217
|
+
marginColumn.style.width = `${Math.max(12, dims.marginRight - 6)}pt`;
|
|
5218
|
+
marginColumn.style.maxHeight = `${contentAreaHeight}pt`;
|
|
5219
|
+
marginColumn.style.overflow = "hidden";
|
|
5220
|
+
marginColumn.style.boxSizing = "border-box";
|
|
5221
|
+
const placedThreadRoots = /* @__PURE__ */ new Set();
|
|
5222
|
+
for (const id of pageCommentIds) {
|
|
5223
|
+
const source = this.commentMarginRegistry.get(id);
|
|
5224
|
+
if (source) {
|
|
5225
|
+
const rootId = source.dataset.commentId ?? id;
|
|
5226
|
+
if (placedThreadRoots.has(rootId)) continue;
|
|
5227
|
+
placedThreadRoots.add(rootId);
|
|
5228
|
+
const clone = source.cloneNode(true);
|
|
5229
|
+
this.makeClonedMarginCommentInert(clone);
|
|
5230
|
+
marginColumn.appendChild(clone);
|
|
5231
|
+
}
|
|
5232
|
+
}
|
|
5233
|
+
pageBox.appendChild(marginColumn);
|
|
5234
|
+
}
|
|
5235
|
+
const footerSource = isSectionFiller ? void 0 : this.selectFooter(sectionIndex, pageInSection, displayedPageNumber);
|
|
3120
5236
|
if (footerSource) {
|
|
3121
|
-
const footerDiv = document.createElement("div");
|
|
5237
|
+
const footerDiv = this.document.createElement("div");
|
|
3122
5238
|
footerDiv.className = `${this.cssPrefix}footer`;
|
|
3123
5239
|
footerDiv.style.position = "absolute";
|
|
3124
5240
|
footerDiv.style.top = "auto";
|
|
@@ -3138,8 +5254,8 @@ var Docxodus = (() => {
|
|
|
3138
5254
|
}
|
|
3139
5255
|
pageBox.appendChild(footerDiv);
|
|
3140
5256
|
}
|
|
3141
|
-
if (this.showPageNumbers) {
|
|
3142
|
-
const pageNum = document.createElement("div");
|
|
5257
|
+
if (this.showPageNumbers && !isSectionFiller) {
|
|
5258
|
+
const pageNum = this.document.createElement("div");
|
|
3143
5259
|
pageNum.className = `${this.cssPrefix}number`;
|
|
3144
5260
|
pageNum.textContent = String(pageNumber);
|
|
3145
5261
|
pageBox.appendChild(pageNum);
|
|
@@ -3162,7 +5278,8 @@ var Docxodus = (() => {
|
|
|
3162
5278
|
}
|
|
3163
5279
|
};
|
|
3164
5280
|
function paginateHtml(html, container, options = {}) {
|
|
3165
|
-
const
|
|
5281
|
+
const ownerDocument = typeof container === "string" ? globalThis.document : container.ownerDocument;
|
|
5282
|
+
const containerEl = typeof container === "string" ? ownerDocument.getElementById(container) : container;
|
|
3166
5283
|
if (!containerEl) {
|
|
3167
5284
|
throw new Error("Container element not found");
|
|
3168
5285
|
}
|
|
@@ -6464,6 +8581,13 @@ var Docxodus = (() => {
|
|
|
6464
8581
|
function escapeInlineMarkdown(text) {
|
|
6465
8582
|
return text.replace(/([\\`*_[\]])/g, "\\$1");
|
|
6466
8583
|
}
|
|
8584
|
+
function escapeLeadingBlockMarkers(md) {
|
|
8585
|
+
return md.split("\n").map((line) => {
|
|
8586
|
+
if (/^[#>|]/.test(line) || /^[-+] /.test(line)) return "\\" + line;
|
|
8587
|
+
if (/^\d+\. /.test(line)) return line.replace(".", "\\.");
|
|
8588
|
+
return line;
|
|
8589
|
+
}).join("\n");
|
|
8590
|
+
}
|
|
6467
8591
|
function collectInlineSegments(node, out) {
|
|
6468
8592
|
node.childNodes.forEach((child) => {
|
|
6469
8593
|
if (isGeneratedChrome(child)) return;
|
|
@@ -6515,7 +8639,7 @@ var Docxodus = (() => {
|
|
|
6515
8639
|
merged.push({ ...s });
|
|
6516
8640
|
}
|
|
6517
8641
|
}
|
|
6518
|
-
return merged.map(segToMarkdown).join("").trim();
|
|
8642
|
+
return escapeLeadingBlockMarkers(merged.map(segToMarkdown).join("").trim());
|
|
6519
8643
|
}
|
|
6520
8644
|
function isListBlock(block) {
|
|
6521
8645
|
return !!block.querySelector(":scope > [data-list-marker]");
|
|
@@ -8550,17 +10674,19 @@ var Docxodus = (() => {
|
|
|
8550
10674
|
this.refreshAfter(block, idx, false);
|
|
8551
10675
|
}
|
|
8552
10676
|
// ─── Table row / column editing (active block must be inside a table cell) ──────────
|
|
8553
|
-
/** Run a table-structure op on the active cell
|
|
10677
|
+
/** Run a table-structure op on the active cell's canonical tc anchor and re-render. */
|
|
8554
10678
|
tableEdit(run) {
|
|
8555
10679
|
const block = this.activeBlock;
|
|
8556
|
-
|
|
10680
|
+
const cell = block?.closest("td, th");
|
|
10681
|
+
if (this.closed || !block || !cell || !block.closest("table")) return;
|
|
8557
10682
|
const unid = block.getAttribute("data-anchor");
|
|
8558
10683
|
if (!unid) return;
|
|
8559
|
-
|
|
8560
|
-
|
|
10684
|
+
const paragraphId = this.anchorIdOf(block);
|
|
10685
|
+
const cellId = this.anchorIdOf(cell);
|
|
10686
|
+
if (!paragraphId || !cellId) return;
|
|
8561
10687
|
const idx = this.blockIndex(block);
|
|
8562
|
-
|
|
8563
|
-
const res = this.parseEdit(run(
|
|
10688
|
+
this.syncBlock(block, paragraphId);
|
|
10689
|
+
const res = this.parseEdit(run(cellId));
|
|
8564
10690
|
if (!res.success) return;
|
|
8565
10691
|
this.refreshAfter(block, idx, false);
|
|
8566
10692
|
}
|
|
@@ -8713,6 +10839,59 @@ var Docxodus = (() => {
|
|
|
8713
10839
|
if (!res.success) return;
|
|
8714
10840
|
this.reconcile(idx, false);
|
|
8715
10841
|
}
|
|
10842
|
+
/**
|
|
10843
|
+
* Add a native Word comment on the current selection in the active block — or on the whole
|
|
10844
|
+
* block when the selection is collapsed (a null span comments the paragraph). The definition,
|
|
10845
|
+
* threading parts, and body-side range markers are written by the session's `AddComment`
|
|
10846
|
+
* (issue #300); the re-render shows the marker chrome the converter already draws. The
|
|
10847
|
+
* annotation type legal review runs on, finally authorable from the shipped surface
|
|
10848
|
+
* (issue #580).
|
|
10849
|
+
*/
|
|
10850
|
+
addComment(markdown = "New comment.", author = "Reviewer") {
|
|
10851
|
+
const block = this.activeBlock;
|
|
10852
|
+
if (this.closed || !block) return;
|
|
10853
|
+
const bridge = this.exports.DocxSessionBridge;
|
|
10854
|
+
if (!bridge.AddComment) return;
|
|
10855
|
+
let fullId = this.anchorIdOf(block);
|
|
10856
|
+
if (!fullId) return;
|
|
10857
|
+
const idx = this.blockIndex(block);
|
|
10858
|
+
const span = selectionSpanIn(block);
|
|
10859
|
+
fullId = this.syncBlock(block, fullId);
|
|
10860
|
+
const res = this.parseEdit(
|
|
10861
|
+
bridge.AddComment(
|
|
10862
|
+
this.handle,
|
|
10863
|
+
fullId,
|
|
10864
|
+
span ? JSON.stringify(span) : "",
|
|
10865
|
+
author,
|
|
10866
|
+
"",
|
|
10867
|
+
"",
|
|
10868
|
+
markdown
|
|
10869
|
+
)
|
|
10870
|
+
);
|
|
10871
|
+
if (!res.success) return;
|
|
10872
|
+
this.reconcile(idx, false);
|
|
10873
|
+
}
|
|
10874
|
+
/** The document's native comment threads (session truth), for review UIs — flat entries
|
|
10875
|
+
* with `parentAnchorId` linking replies and `resolved` carrying thread state. */
|
|
10876
|
+
listComments() {
|
|
10877
|
+
if (this.closed) return [];
|
|
10878
|
+
const bridge = this.exports.DocxSessionBridge;
|
|
10879
|
+
if (!bridge.ListComments) return [];
|
|
10880
|
+
try {
|
|
10881
|
+
return JSON.parse(bridge.ListComments(this.handle));
|
|
10882
|
+
} catch {
|
|
10883
|
+
return [];
|
|
10884
|
+
}
|
|
10885
|
+
}
|
|
10886
|
+
/** Resolve or reopen a comment thread by its `cmt` anchor id (from {@link listComments}).
|
|
10887
|
+
* Resolution is thread metadata (`commentsExtended`), not body markup, so no re-render is
|
|
10888
|
+
* needed — a review UI re-reads {@link listComments} for the new state. */
|
|
10889
|
+
setCommentResolved(commentAnchorId, resolved) {
|
|
10890
|
+
if (this.closed) return;
|
|
10891
|
+
const bridge = this.exports.DocxSessionBridge;
|
|
10892
|
+
if (!bridge.SetCommentResolved) return;
|
|
10893
|
+
this.parseEdit(bridge.SetCommentResolved(this.handle, commentAnchorId, resolved));
|
|
10894
|
+
}
|
|
8716
10895
|
applyParagraphFormat(op) {
|
|
8717
10896
|
const block = this.activeBlock;
|
|
8718
10897
|
if (this.closed || !block) return;
|
|
@@ -9240,8 +11419,8 @@ var Docxodus = (() => {
|
|
|
9240
11419
|
const backref = document.createElement("a");
|
|
9241
11420
|
backref.setAttribute("class", `${prefix}-backref`);
|
|
9242
11421
|
backref.setAttribute("contenteditable", "false");
|
|
9243
|
-
backref.textContent = "\u21A9";
|
|
9244
|
-
last.append(
|
|
11422
|
+
backref.textContent = " \u21A9";
|
|
11423
|
+
last.append(backref);
|
|
9245
11424
|
}
|
|
9246
11425
|
li.querySelectorAll("[data-anchor]").forEach((b) => this.wireBlock(b));
|
|
9247
11426
|
return li;
|
|
@@ -9350,7 +11529,7 @@ var Docxodus = (() => {
|
|
|
9350
11529
|
};
|
|
9351
11530
|
|
|
9352
11531
|
// src/ribbon-chrome.ts
|
|
9353
|
-
var RIBBON_STYLE_VERSION = "
|
|
11532
|
+
var RIBBON_STYLE_VERSION = "9";
|
|
9354
11533
|
var RIBBON_STYLE_ATTR = "data-docxodus-ribbon-styles";
|
|
9355
11534
|
var RIBBON_CSS = `
|
|
9356
11535
|
.dxr {
|
|
@@ -9413,6 +11592,27 @@ var Docxodus = (() => {
|
|
|
9413
11592
|
/* Touch devices get bigger hit targets everywhere the token is used. */
|
|
9414
11593
|
@media (pointer: coarse) { .dxr { --dxr-tap: 40px; } }
|
|
9415
11594
|
|
|
11595
|
+
/* \u2500\u2500 Scroll-edge affordances \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500
|
|
11596
|
+
The chrome's strips (title bar, tabs, panels, rail) scroll horizontally
|
|
11597
|
+
rather than squashing or wrapping \u2014 the compact rule is "no command is
|
|
11598
|
+
removed, the strip scrolls". But a hard clip mid-control reads as a broken
|
|
11599
|
+
layout, especially on a phone, so ribbon.ts measures every strip and stamps
|
|
11600
|
+
data-fade with the edges that hide more content ("l", "r", or both). The
|
|
11601
|
+
mask dissolves content at exactly those edges \u2014 the horizontal twin of
|
|
11602
|
+
.dxr-scroll's gradient veils \u2014 and lifts entirely once the strip fits, so
|
|
11603
|
+
nothing is ever faded unless there is more behind it. */
|
|
11604
|
+
.dxr-titlebar, .dxr-tabs, .dxr-panel, .dxr-rail { --dxr-fade-l: 0px; --dxr-fade-r: 0px; }
|
|
11605
|
+
.dxr [data-fade] {
|
|
11606
|
+
-webkit-mask-image: linear-gradient(to right,
|
|
11607
|
+
transparent, #000 var(--dxr-fade-l),
|
|
11608
|
+
#000 calc(100% - var(--dxr-fade-r)), transparent);
|
|
11609
|
+
mask-image: linear-gradient(to right,
|
|
11610
|
+
transparent, #000 var(--dxr-fade-l),
|
|
11611
|
+
#000 calc(100% - var(--dxr-fade-r)), transparent);
|
|
11612
|
+
}
|
|
11613
|
+
.dxr [data-fade~="l"] { --dxr-fade-l: 28px; }
|
|
11614
|
+
.dxr [data-fade~="r"] { --dxr-fade-r: 28px; }
|
|
11615
|
+
|
|
9416
11616
|
/* \u2500\u2500 Chrome shell \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 */
|
|
9417
11617
|
.dxr-chrome {
|
|
9418
11618
|
position: sticky;
|
|
@@ -9423,12 +11623,17 @@ var Docxodus = (() => {
|
|
|
9423
11623
|
border-bottom: 1px solid var(--dxr-rule);
|
|
9424
11624
|
}
|
|
9425
11625
|
|
|
11626
|
+
/* Scrolls in EVERY density (compact just tightens it): the fade affordance
|
|
11627
|
+
marks any overflowing strip as scrollable, so it must actually be. */
|
|
9426
11628
|
.dxr-titlebar {
|
|
9427
11629
|
display: flex;
|
|
9428
11630
|
align-items: center;
|
|
9429
11631
|
gap: 10px;
|
|
9430
11632
|
padding: 6px 10px 0;
|
|
11633
|
+
overflow-x: auto;
|
|
11634
|
+
scrollbar-width: none;
|
|
9431
11635
|
}
|
|
11636
|
+
.dxr-titlebar::-webkit-scrollbar { display: none; }
|
|
9432
11637
|
.dxr-brand {
|
|
9433
11638
|
display: flex;
|
|
9434
11639
|
align-items: baseline;
|
|
@@ -9798,6 +12003,25 @@ var Docxodus = (() => {
|
|
|
9798
12003
|
box-shadow: 0 1px 3px rgba(15, 23, 42, .08), 0 12px 24px rgba(15, 23, 42, .06);
|
|
9799
12004
|
}
|
|
9800
12005
|
|
|
12006
|
+
/* \u2500\u2500 Review: comment threads \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 */
|
|
12007
|
+
.dxr-threads {
|
|
12008
|
+
display: flex; flex-direction: column; gap: 4px;
|
|
12009
|
+
max-height: 92px; overflow-y: auto; min-width: 260px; padding-right: 2px;
|
|
12010
|
+
}
|
|
12011
|
+
.dxr-thread {
|
|
12012
|
+
display: flex; align-items: center; gap: 6px;
|
|
12013
|
+
font-size: 11px; line-height: 1.3; padding: 3px 6px;
|
|
12014
|
+
border: 1px solid var(--dxr-rule); border-radius: 4px; background: var(--dxr-chrome-sunk);
|
|
12015
|
+
}
|
|
12016
|
+
.dxr-thread[data-resolved] { opacity: 0.55; }
|
|
12017
|
+
.dxr-thread[data-reply] { margin-left: 14px; }
|
|
12018
|
+
.dxr-thread .dxr-tauthor { font-weight: 600; white-space: nowrap; }
|
|
12019
|
+
.dxr-thread .dxr-ttext {
|
|
12020
|
+
flex: 1; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; max-width: 220px;
|
|
12021
|
+
}
|
|
12022
|
+
.dxr-thread button { flex: none; }
|
|
12023
|
+
.dxr-group.dxr-grow { flex: 1; min-width: 0; }
|
|
12024
|
+
|
|
9801
12025
|
/* \u2500\u2500 Table size picker \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 */
|
|
9802
12026
|
.dxr-pop {
|
|
9803
12027
|
display: none;
|
|
@@ -9839,10 +12063,7 @@ var Docxodus = (() => {
|
|
|
9839
12063
|
.dxr[data-chrome="compact"] .dxr-titlebar {
|
|
9840
12064
|
gap: 6px;
|
|
9841
12065
|
padding: 5px 8px 0;
|
|
9842
|
-
overflow-x: auto;
|
|
9843
|
-
scrollbar-width: none;
|
|
9844
12066
|
}
|
|
9845
|
-
.dxr[data-chrome="compact"] .dxr-titlebar::-webkit-scrollbar { display: none; }
|
|
9846
12067
|
.dxr[data-chrome="compact"] .dxr-brandname { display: none; }
|
|
9847
12068
|
.dxr[data-chrome="compact"] .dxr-brand { flex: 0 1 auto; }
|
|
9848
12069
|
/* A filename clipped to "do..." tells you less than no filename at all, so it keeps a
|
|
@@ -10116,6 +12337,7 @@ var Docxodus = (() => {
|
|
|
10116
12337
|
<button type="button" class="dxr-tab" role="tab" data-tab="home" aria-selected="true">Home</button>
|
|
10117
12338
|
<button type="button" class="dxr-tab" role="tab" data-tab="insert" aria-selected="false">Insert</button>
|
|
10118
12339
|
<button type="button" class="dxr-tab" role="tab" data-tab="layout" aria-selected="false">Layout</button>
|
|
12340
|
+
<button type="button" class="dxr-tab" role="tab" data-tab="review" aria-selected="false">Review</button>
|
|
10119
12341
|
<button type="button" class="dxr-tab" role="tab" data-tab="table" data-contextual aria-selected="false" hidden>Table</button>
|
|
10120
12342
|
</div>
|
|
10121
12343
|
|
|
@@ -10281,6 +12503,31 @@ var Docxodus = (() => {
|
|
|
10281
12503
|
</div>
|
|
10282
12504
|
</div>
|
|
10283
12505
|
|
|
12506
|
+
<!-- REVIEW \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500
|
|
12507
|
+
Comment authoring over the session's native comment family (issue #580):
|
|
12508
|
+
the button comments the selection (the whole block when collapsed), and
|
|
12509
|
+
the thread list is live session truth with resolve/reopen per thread. -->
|
|
12510
|
+
<div class="dxr-panel" data-panel="review">
|
|
12511
|
+
<div class="dxr-group">
|
|
12512
|
+
<span class="dxr-glabel">Comments</span>
|
|
12513
|
+
<div class="dxr-row">
|
|
12514
|
+
<input data-dxr="commenttext" type="text" placeholder="Comment text…"
|
|
12515
|
+
title="The comment body; markdown is honored" />
|
|
12516
|
+
<button type="button" data-dxr="comment"
|
|
12517
|
+
title="Comment on the selection, or on the whole block when nothing is selected">💬 Comment</button>
|
|
12518
|
+
</div>
|
|
12519
|
+
<div class="dxr-row">
|
|
12520
|
+
<span class="dxr-note">Comments the selected text; the whole block when the caret is collapsed.</span>
|
|
12521
|
+
</div>
|
|
12522
|
+
</div>
|
|
12523
|
+
<div class="dxr-group dxr-grow">
|
|
12524
|
+
<span class="dxr-glabel">Threads</span>
|
|
12525
|
+
<div class="dxr-threads" data-dxr="threads" aria-live="polite">
|
|
12526
|
+
<span class="dxr-note">No comments yet.</span>
|
|
12527
|
+
</div>
|
|
12528
|
+
</div>
|
|
12529
|
+
</div>
|
|
12530
|
+
|
|
10284
12531
|
<!-- TABLE (contextual) \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500
|
|
10285
12532
|
Row/column editing lives here rather than in a floating toolbar: a docked
|
|
10286
12533
|
contextual tab cannot overlap the cell you are editing. -->
|
|
@@ -10482,6 +12729,8 @@ var Docxodus = (() => {
|
|
|
10482
12729
|
this.featureTimer = null;
|
|
10483
12730
|
this.featureIndex = 0;
|
|
10484
12731
|
this.resizeObserver = null;
|
|
12732
|
+
this.strips = [];
|
|
12733
|
+
this.stripObserver = null;
|
|
10485
12734
|
this.lastAnchorText = "";
|
|
10486
12735
|
this.selectionFrame = null;
|
|
10487
12736
|
this.options = options;
|
|
@@ -10512,6 +12761,7 @@ var Docxodus = (() => {
|
|
|
10512
12761
|
this.applyStaticOptions();
|
|
10513
12762
|
this.buildGrid();
|
|
10514
12763
|
this.wire();
|
|
12764
|
+
this.wireScrollFades();
|
|
10515
12765
|
this.applyChrome();
|
|
10516
12766
|
this.loader = this.createLoaderController();
|
|
10517
12767
|
if (this.loaderOptions) this.loader.show();
|
|
@@ -10591,6 +12841,38 @@ var Docxodus = (() => {
|
|
|
10591
12841
|
this.density = next;
|
|
10592
12842
|
this.element.dataset.chrome = next;
|
|
10593
12843
|
this.closePicker();
|
|
12844
|
+
this.syncAllStripFades();
|
|
12845
|
+
}
|
|
12846
|
+
// ── scroll-edge affordances ─────────────────────────────────────────────────
|
|
12847
|
+
/**
|
|
12848
|
+
* The chrome's strips scroll horizontally instead of squashing ("no command
|
|
12849
|
+
* is removed in compact"), so each one reports which edges hide more content
|
|
12850
|
+
* via `data-fade` and the stylesheet dissolves content at exactly those
|
|
12851
|
+
* edges. Without this a strip hard-clips mid-control and reads as broken.
|
|
12852
|
+
*/
|
|
12853
|
+
syncStripFade(strip) {
|
|
12854
|
+
const max = strip.scrollWidth - strip.clientWidth;
|
|
12855
|
+
const tokens = max <= 1 ? "" : `${strip.scrollLeft > 1 ? "l " : ""}${strip.scrollLeft < max - 1 ? "r" : ""}`.trim();
|
|
12856
|
+
if (tokens) strip.setAttribute("data-fade", tokens);
|
|
12857
|
+
else strip.removeAttribute("data-fade");
|
|
12858
|
+
}
|
|
12859
|
+
syncAllStripFades() {
|
|
12860
|
+
for (const strip of this.strips) this.syncStripFade(strip);
|
|
12861
|
+
}
|
|
12862
|
+
wireScrollFades() {
|
|
12863
|
+
this.strips = Array.from(
|
|
12864
|
+
this.element.querySelectorAll(".dxr-titlebar, .dxr-tabs, .dxr-panel, .dxr-rail")
|
|
12865
|
+
);
|
|
12866
|
+
if (typeof ResizeObserver !== "undefined") {
|
|
12867
|
+
this.stripObserver = new ResizeObserver((entries) => {
|
|
12868
|
+
for (const entry of entries) this.syncStripFade(entry.target);
|
|
12869
|
+
});
|
|
12870
|
+
}
|
|
12871
|
+
for (const strip of this.strips) {
|
|
12872
|
+
strip.addEventListener("scroll", () => this.syncStripFade(strip), { passive: true });
|
|
12873
|
+
this.stripObserver?.observe(strip);
|
|
12874
|
+
this.syncStripFade(strip);
|
|
12875
|
+
}
|
|
10594
12876
|
}
|
|
10595
12877
|
// ── status ──────────────────────────────────────────────────────────────────
|
|
10596
12878
|
/** Publish the lifecycle on the root, where CSS and host pages can key off it. */
|
|
@@ -10684,6 +12966,8 @@ var Docxodus = (() => {
|
|
|
10684
12966
|
doc.removeEventListener("mousedown", this.onDocumentMouseDown);
|
|
10685
12967
|
this.resizeObserver?.disconnect();
|
|
10686
12968
|
this.resizeObserver = null;
|
|
12969
|
+
this.stripObserver?.disconnect();
|
|
12970
|
+
this.stripObserver = null;
|
|
10687
12971
|
if (this.selectionFrame != null) cancelAnimationFrame(this.selectionFrame);
|
|
10688
12972
|
this.selectionFrame = null;
|
|
10689
12973
|
this.stopRotation();
|
|
@@ -10788,6 +13072,18 @@ var Docxodus = (() => {
|
|
|
10788
13072
|
if (this.live && style.value) this.run("style", () => this.live.setParagraphStyle(style.value));
|
|
10789
13073
|
style.value = "";
|
|
10790
13074
|
});
|
|
13075
|
+
const commentButton = this.control("comment");
|
|
13076
|
+
if (commentButton) {
|
|
13077
|
+
this.keepSelection(commentButton);
|
|
13078
|
+
commentButton.addEventListener("click", () => {
|
|
13079
|
+
if (!this.live) return;
|
|
13080
|
+
const input = this.control("commenttext");
|
|
13081
|
+
const text = input?.value.trim() || "New comment.";
|
|
13082
|
+
this.run("comment", () => this.live.addComment(text, this.options.revisionAuthor ?? "Reviewer"));
|
|
13083
|
+
if (input) input.value = "";
|
|
13084
|
+
this.renderThreads();
|
|
13085
|
+
});
|
|
13086
|
+
}
|
|
10791
13087
|
this.wireFileActions();
|
|
10792
13088
|
this.wireLayout();
|
|
10793
13089
|
this.wirePicker();
|
|
@@ -10910,6 +13206,54 @@ var Docxodus = (() => {
|
|
|
10910
13206
|
else panel.removeAttribute("data-active");
|
|
10911
13207
|
}
|
|
10912
13208
|
if (name === "layout") this.syncPageNumbering();
|
|
13209
|
+
if (name === "review") this.renderThreads();
|
|
13210
|
+
}
|
|
13211
|
+
/** Re-render the Review tab's thread list from session truth ({@link DocxEditor.listComments}):
|
|
13212
|
+
* one row per comment, replies indented under their thread root, resolve/reopen per root. */
|
|
13213
|
+
renderThreads() {
|
|
13214
|
+
const host = this.control("threads");
|
|
13215
|
+
if (!host || !this.live) return;
|
|
13216
|
+
const doc = this.element.ownerDocument ?? document;
|
|
13217
|
+
host.textContent = "";
|
|
13218
|
+
const comments = this.live.listComments();
|
|
13219
|
+
if (comments.length === 0) {
|
|
13220
|
+
const empty = doc.createElement("span");
|
|
13221
|
+
empty.className = "dxr-note";
|
|
13222
|
+
empty.textContent = "No comments yet.";
|
|
13223
|
+
host.appendChild(empty);
|
|
13224
|
+
return;
|
|
13225
|
+
}
|
|
13226
|
+
for (const entry of comments) {
|
|
13227
|
+
const row = doc.createElement("div");
|
|
13228
|
+
row.className = "dxr-thread";
|
|
13229
|
+
row.setAttribute("data-thread", entry.anchorId);
|
|
13230
|
+
if (entry.resolved) row.setAttribute("data-resolved", "");
|
|
13231
|
+
if (entry.parentAnchorId) row.setAttribute("data-reply", "");
|
|
13232
|
+
const author = doc.createElement("span");
|
|
13233
|
+
author.className = "dxr-tauthor";
|
|
13234
|
+
author.textContent = entry.author || "\u2014";
|
|
13235
|
+
row.appendChild(author);
|
|
13236
|
+
const text = doc.createElement("span");
|
|
13237
|
+
text.className = "dxr-ttext";
|
|
13238
|
+
text.textContent = entry.text;
|
|
13239
|
+
text.title = entry.text;
|
|
13240
|
+
row.appendChild(text);
|
|
13241
|
+
if (!entry.parentAnchorId) {
|
|
13242
|
+
const toggle = doc.createElement("button");
|
|
13243
|
+
toggle.type = "button";
|
|
13244
|
+
toggle.textContent = entry.resolved ? "Reopen" : "Resolve";
|
|
13245
|
+
toggle.title = entry.resolved ? "Reopen this comment thread" : "Mark this comment thread resolved";
|
|
13246
|
+
toggle.addEventListener("click", () => {
|
|
13247
|
+
if (!this.live) return;
|
|
13248
|
+
this.run(entry.resolved ? "reopen comment" : "resolve comment", () => this.live.setCommentResolved(entry.anchorId, !entry.resolved));
|
|
13249
|
+
this.renderThreads();
|
|
13250
|
+
});
|
|
13251
|
+
row.appendChild(toggle);
|
|
13252
|
+
}
|
|
13253
|
+
host.appendChild(row);
|
|
13254
|
+
}
|
|
13255
|
+
const panel = this.element.querySelector('.dxr-panel[data-panel="review"]');
|
|
13256
|
+
if (panel) this.syncStripFade(panel);
|
|
10913
13257
|
}
|
|
10914
13258
|
// ── selection-driven state ──────────────────────────────────────────────────
|
|
10915
13259
|
selectionElement() {
|
|
@@ -11212,6 +13556,55 @@ var Docxodus = (() => {
|
|
|
11212
13556
|
const buffer = await input.arrayBuffer();
|
|
11213
13557
|
return new Uint8Array(buffer);
|
|
11214
13558
|
}
|
|
13559
|
+
async function generatePackageManifest(document2) {
|
|
13560
|
+
const exports = ensureInitialized();
|
|
13561
|
+
const bytes = await toBytes(document2);
|
|
13562
|
+
await yieldToMain();
|
|
13563
|
+
return JSON.parse(
|
|
13564
|
+
exports.DocumentConverter.GeneratePackageManifest(bytes)
|
|
13565
|
+
);
|
|
13566
|
+
}
|
|
13567
|
+
async function verifyDeliverable(document2, baseline) {
|
|
13568
|
+
const exports = ensureInitialized();
|
|
13569
|
+
const bytes = await toBytes(document2);
|
|
13570
|
+
const baselineBytes = baseline === void 0 ? void 0 : await toBytes(baseline);
|
|
13571
|
+
await yieldToMain();
|
|
13572
|
+
return JSON.parse(
|
|
13573
|
+
baselineBytes === void 0 ? exports.DocumentConverter.VerifyDeliverable(bytes) : exports.DocumentConverter.VerifyDeliverableWithBaseline(
|
|
13574
|
+
baselineBytes,
|
|
13575
|
+
bytes
|
|
13576
|
+
)
|
|
13577
|
+
);
|
|
13578
|
+
}
|
|
13579
|
+
async function verifyDeliveryReceipt(receiptJson, artifacts) {
|
|
13580
|
+
const exports = ensureInitialized();
|
|
13581
|
+
const artifactsJson = artifacts === void 0 ? "" : JSON.stringify(
|
|
13582
|
+
Object.fromEntries(
|
|
13583
|
+
Object.entries(artifacts).map(([artifactId, bytes]) => [
|
|
13584
|
+
artifactId,
|
|
13585
|
+
bytesToBase64(bytes)
|
|
13586
|
+
])
|
|
13587
|
+
)
|
|
13588
|
+
);
|
|
13589
|
+
await yieldToMain();
|
|
13590
|
+
return JSON.parse(
|
|
13591
|
+
exports.DocumentConverter.VerifyDeliveryReceipt(receiptJson, artifactsJson)
|
|
13592
|
+
);
|
|
13593
|
+
}
|
|
13594
|
+
async function proveRedlineReversibility(baseline, intendedFinal, redline) {
|
|
13595
|
+
const exports = ensureInitialized();
|
|
13596
|
+
const baselineBytes = await toBytes(baseline);
|
|
13597
|
+
const intendedFinalBytes = await toBytes(intendedFinal);
|
|
13598
|
+
const redlineBytes = await toBytes(redline);
|
|
13599
|
+
await yieldToMain();
|
|
13600
|
+
return JSON.parse(
|
|
13601
|
+
exports.DocumentConverter.ProveRedlineReversibility(
|
|
13602
|
+
baselineBytes,
|
|
13603
|
+
intendedFinalBytes,
|
|
13604
|
+
redlineBytes
|
|
13605
|
+
)
|
|
13606
|
+
);
|
|
13607
|
+
}
|
|
11215
13608
|
async function renderBlockHtml(document2, anchorId, options) {
|
|
11216
13609
|
const exports = ensureInitialized();
|
|
11217
13610
|
const bytes = await toBytes(document2);
|
|
@@ -11295,22 +13688,18 @@ var Docxodus = (() => {
|
|
|
11295
13688
|
const modifiedBytes = await toBytes(modified);
|
|
11296
13689
|
await yieldToMain();
|
|
11297
13690
|
let result;
|
|
11298
|
-
|
|
11299
|
-
if (options?.detailThreshold !== void 0 || options?.caseInsensitive) {
|
|
13691
|
+
if (options?.caseInsensitive) {
|
|
11300
13692
|
result = exports.DocumentComparer.CompareDocumentsWithOptions(
|
|
11301
13693
|
originalBytes,
|
|
11302
13694
|
modifiedBytes,
|
|
11303
13695
|
options?.authorName ?? "Docxodus",
|
|
11304
|
-
options
|
|
11305
|
-
options?.caseInsensitive ?? false,
|
|
11306
|
-
engine
|
|
13696
|
+
options.caseInsensitive
|
|
11307
13697
|
);
|
|
11308
13698
|
} else {
|
|
11309
13699
|
result = exports.DocumentComparer.CompareDocuments(
|
|
11310
13700
|
originalBytes,
|
|
11311
13701
|
modifiedBytes,
|
|
11312
|
-
options?.authorName ?? "Docxodus"
|
|
11313
|
-
engine
|
|
13702
|
+
options?.authorName ?? "Docxodus"
|
|
11314
13703
|
);
|
|
11315
13704
|
}
|
|
11316
13705
|
if (result.length === 0) {
|
|
@@ -11324,25 +13713,21 @@ var Docxodus = (() => {
|
|
|
11324
13713
|
const modifiedBytes = await toBytes(modified);
|
|
11325
13714
|
await yieldToMain();
|
|
11326
13715
|
const renderTrackedChanges = options?.renderTrackedChanges ?? true;
|
|
11327
|
-
const engine = options?.engine ?? 1 /* DocxDiff */;
|
|
11328
13716
|
let result;
|
|
11329
|
-
if (options?.
|
|
13717
|
+
if (options?.caseInsensitive !== void 0) {
|
|
11330
13718
|
result = exports.DocumentComparer.CompareDocumentsToHtmlFull(
|
|
11331
13719
|
originalBytes,
|
|
11332
13720
|
modifiedBytes,
|
|
11333
13721
|
options?.authorName ?? "Docxodus",
|
|
11334
|
-
options
|
|
11335
|
-
|
|
11336
|
-
renderTrackedChanges,
|
|
11337
|
-
engine
|
|
13722
|
+
options.caseInsensitive,
|
|
13723
|
+
renderTrackedChanges
|
|
11338
13724
|
);
|
|
11339
13725
|
} else {
|
|
11340
13726
|
result = exports.DocumentComparer.CompareDocumentsToHtmlWithOptions(
|
|
11341
13727
|
originalBytes,
|
|
11342
13728
|
modifiedBytes,
|
|
11343
13729
|
options?.authorName ?? "Docxodus",
|
|
11344
|
-
renderTrackedChanges
|
|
11345
|
-
engine
|
|
13730
|
+
renderTrackedChanges
|
|
11346
13731
|
);
|
|
11347
13732
|
}
|
|
11348
13733
|
if (isErrorResponse(result)) {
|
|
@@ -11351,93 +13736,16 @@ var Docxodus = (() => {
|
|
|
11351
13736
|
}
|
|
11352
13737
|
return result;
|
|
11353
13738
|
}
|
|
11354
|
-
async function
|
|
11355
|
-
const exports = ensureInitialized();
|
|
11356
|
-
const originalBytes = await toBytes(original);
|
|
11357
|
-
const modifiedBytes = await toBytes(modified);
|
|
11358
|
-
await yieldToMain();
|
|
11359
|
-
const result = exports.DocumentComparer.CompareDocumentsWithLog(
|
|
11360
|
-
originalBytes,
|
|
11361
|
-
modifiedBytes,
|
|
11362
|
-
options?.authorName ?? "Docxodus",
|
|
11363
|
-
options?.detailThreshold ?? 0.15,
|
|
11364
|
-
options?.caseInsensitive ?? false
|
|
11365
|
-
);
|
|
11366
|
-
const parsed = JSON.parse(result);
|
|
11367
|
-
return {
|
|
11368
|
-
success: parsed.Success ?? parsed.success ?? false,
|
|
11369
|
-
document: parsed.DocumentBase64 || parsed.documentBase64 ? Uint8Array.from(atob(parsed.DocumentBase64 || parsed.documentBase64), (c) => c.charCodeAt(0)) : void 0,
|
|
11370
|
-
error: parsed.Error ?? parsed.error,
|
|
11371
|
-
log: convertLogEntries(parsed.Log || parsed.log || []),
|
|
11372
|
-
hasWarnings: parsed.HasWarnings ?? parsed.hasWarnings ?? false,
|
|
11373
|
-
hasErrors: parsed.HasErrors ?? parsed.hasErrors ?? false
|
|
11374
|
-
};
|
|
11375
|
-
}
|
|
11376
|
-
async function compareDocumentsToHtmlWithLog(original, modified, options) {
|
|
11377
|
-
const exports = ensureInitialized();
|
|
11378
|
-
const originalBytes = await toBytes(original);
|
|
11379
|
-
const modifiedBytes = await toBytes(modified);
|
|
11380
|
-
await yieldToMain();
|
|
11381
|
-
const result = exports.DocumentComparer.CompareDocumentsToHtmlWithLog(
|
|
11382
|
-
originalBytes,
|
|
11383
|
-
modifiedBytes,
|
|
11384
|
-
options?.authorName ?? "Docxodus",
|
|
11385
|
-
options?.detailThreshold ?? 0.15,
|
|
11386
|
-
options?.caseInsensitive ?? false,
|
|
11387
|
-
options?.renderTrackedChanges ?? true
|
|
11388
|
-
);
|
|
11389
|
-
const parsed = JSON.parse(result);
|
|
11390
|
-
return {
|
|
11391
|
-
success: parsed.Success ?? parsed.success ?? false,
|
|
11392
|
-
html: parsed.Html ?? parsed.html,
|
|
11393
|
-
error: parsed.Error ?? parsed.error,
|
|
11394
|
-
log: convertLogEntries(parsed.Log || parsed.log || []),
|
|
11395
|
-
hasWarnings: parsed.HasWarnings ?? parsed.hasWarnings ?? false,
|
|
11396
|
-
hasErrors: parsed.HasErrors ?? parsed.hasErrors ?? false
|
|
11397
|
-
};
|
|
11398
|
-
}
|
|
11399
|
-
function convertLogEntries(entries) {
|
|
11400
|
-
return entries.map((e) => ({
|
|
11401
|
-
level: e.Level ?? e.level ?? "Info",
|
|
11402
|
-
code: e.Code ?? e.code ?? "",
|
|
11403
|
-
message: e.Message ?? e.message ?? "",
|
|
11404
|
-
details: e.Details ?? e.details,
|
|
11405
|
-
location: e.Location ?? e.location
|
|
11406
|
-
}));
|
|
11407
|
-
}
|
|
11408
|
-
async function getRevisions(document2, options) {
|
|
13739
|
+
async function getRevisions(document2) {
|
|
11409
13740
|
const exports = ensureInitialized();
|
|
11410
13741
|
const bytes = await toBytes(document2);
|
|
11411
13742
|
await yieldToMain();
|
|
11412
|
-
const
|
|
11413
|
-
const moveSimilarityThreshold = options?.moveSimilarityThreshold ?? 0.8;
|
|
11414
|
-
const moveMinimumWordCount = options?.moveMinimumWordCount ?? 3;
|
|
11415
|
-
const caseInsensitive = options?.caseInsensitive ?? false;
|
|
11416
|
-
const result = exports.DocumentComparer.GetRevisionsJsonWithOptions(
|
|
11417
|
-
bytes,
|
|
11418
|
-
detectMoves,
|
|
11419
|
-
moveSimilarityThreshold,
|
|
11420
|
-
moveMinimumWordCount,
|
|
11421
|
-
caseInsensitive
|
|
11422
|
-
);
|
|
13743
|
+
const result = exports.DocumentComparer.GetRevisionsJson(bytes);
|
|
11423
13744
|
if (isErrorResponse(result)) {
|
|
11424
13745
|
const error = parseError(result);
|
|
11425
13746
|
throw new Error(`Failed to get revisions: ${error.error}`);
|
|
11426
13747
|
}
|
|
11427
|
-
|
|
11428
|
-
return (parsed.Revisions || parsed.revisions || []).map((r) => ({
|
|
11429
|
-
author: r.Author || r.author,
|
|
11430
|
-
date: r.Date || r.date,
|
|
11431
|
-
revisionType: r.RevisionType || r.revisionType,
|
|
11432
|
-
text: r.Text || r.text,
|
|
11433
|
-
moveGroupId: r.MoveGroupId ?? r.moveGroupId,
|
|
11434
|
-
isMoveSource: r.IsMoveSource ?? r.isMoveSource,
|
|
11435
|
-
formatChange: r.FormatChange || r.formatChange ? {
|
|
11436
|
-
oldProperties: r.FormatChange?.OldProperties || r.formatChange?.oldProperties,
|
|
11437
|
-
newProperties: r.FormatChange?.NewProperties || r.formatChange?.newProperties,
|
|
11438
|
-
changedPropertyNames: r.FormatChange?.ChangedPropertyNames || r.formatChange?.changedPropertyNames
|
|
11439
|
-
} : void 0
|
|
11440
|
-
}));
|
|
13748
|
+
return JSON.parse(result);
|
|
11441
13749
|
}
|
|
11442
13750
|
function docxDiffSettingsJson(settings) {
|
|
11443
13751
|
return settings ? JSON.stringify(settings) : "";
|
|
@@ -11472,20 +13780,61 @@ var Docxodus = (() => {
|
|
|
11472
13780
|
throw new Error(`Failed to get DocxDiff revisions: ${error.error}`);
|
|
11473
13781
|
}
|
|
11474
13782
|
const parsed = JSON.parse(result);
|
|
11475
|
-
return (parsed.revisions || parsed.Revisions || []).map(
|
|
11476
|
-
|
|
11477
|
-
|
|
11478
|
-
|
|
11479
|
-
|
|
11480
|
-
|
|
11481
|
-
|
|
11482
|
-
|
|
11483
|
-
|
|
11484
|
-
|
|
11485
|
-
|
|
11486
|
-
|
|
11487
|
-
|
|
11488
|
-
|
|
13783
|
+
return (parsed.revisions || parsed.Revisions || []).map(mapDocxDiffRevision);
|
|
13784
|
+
}
|
|
13785
|
+
async function docxDiffCompareProducts(left2, right2, settings, products) {
|
|
13786
|
+
const exports = ensureInitialized();
|
|
13787
|
+
const leftBytes = await toBytes(left2);
|
|
13788
|
+
const rightBytes = await toBytes(right2);
|
|
13789
|
+
await yieldToMain();
|
|
13790
|
+
const result = exports.DocxDiffBridge.CompareProductsJson(
|
|
13791
|
+
leftBytes,
|
|
13792
|
+
rightBytes,
|
|
13793
|
+
docxDiffSettingsJson(settings),
|
|
13794
|
+
products ? JSON.stringify(products) : ""
|
|
13795
|
+
);
|
|
13796
|
+
if (isErrorResponse(result)) {
|
|
13797
|
+
const error = parseError(result);
|
|
13798
|
+
throw new Error(`Failed to compare products: ${error.error}`);
|
|
13799
|
+
}
|
|
13800
|
+
const parsed = JSON.parse(result);
|
|
13801
|
+
return {
|
|
13802
|
+
redline: typeof parsed.redlineB64 === "string" ? Uint8Array.from(atob(parsed.redlineB64), (c) => c.charCodeAt(0)) : void 0,
|
|
13803
|
+
revisions: Array.isArray(parsed.revisions) ? parsed.revisions.map(mapDocxDiffRevision) : void 0,
|
|
13804
|
+
editScript: parsed.editScript !== void 0 ? parsed.editScript : void 0,
|
|
13805
|
+
semanticChanges: parsed.semanticChanges !== void 0 ? parsed.semanticChanges : void 0
|
|
13806
|
+
};
|
|
13807
|
+
}
|
|
13808
|
+
async function docxDiffCompareBatch(baseline, candidates, settings, products) {
|
|
13809
|
+
const exports = ensureInitialized();
|
|
13810
|
+
const baselineBytes = await toBytes(baseline);
|
|
13811
|
+
const payload = [];
|
|
13812
|
+
for (let i = 0; i < candidates.length; i++) {
|
|
13813
|
+
const bytes = await toBytes(candidates[i].document);
|
|
13814
|
+
payload.push({
|
|
13815
|
+
name: candidates[i].name ?? String(i),
|
|
13816
|
+
docB64: bytesToBase64(bytes)
|
|
13817
|
+
});
|
|
13818
|
+
}
|
|
13819
|
+
await yieldToMain();
|
|
13820
|
+
const result = exports.DocxDiffBridge.CompareBatchJson(
|
|
13821
|
+
baselineBytes,
|
|
13822
|
+
JSON.stringify(payload),
|
|
13823
|
+
docxDiffSettingsJson(settings),
|
|
13824
|
+
products ? JSON.stringify(products) : ""
|
|
13825
|
+
);
|
|
13826
|
+
if (isErrorResponse(result)) {
|
|
13827
|
+
const error = parseError(result);
|
|
13828
|
+
throw new Error(`Failed to compare batch: ${error.error}`);
|
|
13829
|
+
}
|
|
13830
|
+
const parsed = JSON.parse(result);
|
|
13831
|
+
return (parsed.results ?? []).map((entry) => ({
|
|
13832
|
+
name: String(entry.name ?? ""),
|
|
13833
|
+
error: typeof entry.error === "string" ? entry.error : void 0,
|
|
13834
|
+
redline: typeof entry.redlineB64 === "string" ? Uint8Array.from(atob(entry.redlineB64), (c) => c.charCodeAt(0)) : void 0,
|
|
13835
|
+
revisions: Array.isArray(entry.revisions) ? entry.revisions.map(mapDocxDiffRevision) : void 0,
|
|
13836
|
+
editScript: entry.editScript !== void 0 ? entry.editScript : void 0,
|
|
13837
|
+
semanticChanges: entry.semanticChanges !== void 0 ? entry.semanticChanges : void 0
|
|
11489
13838
|
}));
|
|
11490
13839
|
}
|
|
11491
13840
|
async function docxDiffGetEditScript(left2, right2, settings) {
|
|
@@ -11504,6 +13853,22 @@ var Docxodus = (() => {
|
|
|
11504
13853
|
}
|
|
11505
13854
|
return result;
|
|
11506
13855
|
}
|
|
13856
|
+
async function docxDiffGetSemanticChanges(left2, right2, settings) {
|
|
13857
|
+
const exports = ensureInitialized();
|
|
13858
|
+
const leftBytes = await toBytes(left2);
|
|
13859
|
+
const rightBytes = await toBytes(right2);
|
|
13860
|
+
await yieldToMain();
|
|
13861
|
+
const result = exports.DocxDiffBridge.GetSemanticChangesJson(
|
|
13862
|
+
leftBytes,
|
|
13863
|
+
rightBytes,
|
|
13864
|
+
docxDiffSettingsJson(settings)
|
|
13865
|
+
);
|
|
13866
|
+
if (isErrorResponse(result)) {
|
|
13867
|
+
const error = parseError(result);
|
|
13868
|
+
throw new Error(`Failed to get semantic changes: ${error.error}`);
|
|
13869
|
+
}
|
|
13870
|
+
return JSON.parse(result);
|
|
13871
|
+
}
|
|
11507
13872
|
async function docxDiffAcceptRevisions(redline) {
|
|
11508
13873
|
const exports = ensureInitialized();
|
|
11509
13874
|
const bytes = await toBytes(redline);
|
|
@@ -11757,6 +14122,7 @@ var Docxodus = (() => {
|
|
|
11757
14122
|
const parsed = JSON.parse(result);
|
|
11758
14123
|
const convertElement = (el) => ({
|
|
11759
14124
|
id: el.Id || el.id,
|
|
14125
|
+
anchorId: el.AnchorId || el.anchorId,
|
|
11760
14126
|
type: el.Type || el.type,
|
|
11761
14127
|
textPreview: el.TextPreview || el.textPreview,
|
|
11762
14128
|
index: el.Index ?? el.index,
|
|
@@ -11768,8 +14134,12 @@ var Docxodus = (() => {
|
|
|
11768
14134
|
});
|
|
11769
14135
|
const convertTableColumn = (col) => ({
|
|
11770
14136
|
tableId: col.TableId || col.tableId,
|
|
14137
|
+
anchorId: col.AnchorId || col.anchorId,
|
|
14138
|
+
tableAnchorId: col.TableAnchorId || col.tableAnchorId,
|
|
14139
|
+
isVirtual: col.IsVirtual ?? col.isVirtual ?? false,
|
|
11771
14140
|
columnIndex: col.ColumnIndex ?? col.columnIndex,
|
|
11772
14141
|
cellIds: col.CellIds || col.cellIds || [],
|
|
14142
|
+
cellAnchorIds: col.CellAnchorIds || col.cellAnchorIds || [],
|
|
11773
14143
|
rowCount: col.RowCount ?? col.rowCount
|
|
11774
14144
|
});
|
|
11775
14145
|
const root = convertElement(parsed.Root || parsed.root);
|
|
@@ -11832,7 +14202,8 @@ var Docxodus = (() => {
|
|
|
11832
14202
|
hasEndnotes: parsed.HasEndnotes ?? parsed.hasEndnotes,
|
|
11833
14203
|
hasTrackedChanges: parsed.HasTrackedChanges ?? parsed.hasTrackedChanges,
|
|
11834
14204
|
hasComments: parsed.HasComments ?? parsed.hasComments,
|
|
11835
|
-
estimatedPageCount: parsed.EstimatedPageCount ?? parsed.estimatedPageCount
|
|
14205
|
+
estimatedPageCount: parsed.EstimatedPageCount ?? parsed.estimatedPageCount,
|
|
14206
|
+
estimatedPageCountSource: parsed.EstimatedPageCountSource ?? parsed.estimatedPageCountSource ?? "heuristic"
|
|
11836
14207
|
};
|
|
11837
14208
|
}
|
|
11838
14209
|
async function exportToOpenContract(document2) {
|