docxodus 9.8.0 → 10.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 +178 -0
- 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 +244 -3
- package/dist/docxodus.worker.js.map +1 -1
- package/dist/editor.bundle.js +2024 -388
- 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 +2912 -437
- package/dist/embed.iife.js +2911 -436
- package/dist/export-assets.json +283 -0
- package/dist/export-browser.bundle.js +7990 -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 +62 -5
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +130 -16
- 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 +1937 -380
- package/dist/pagination.d.ts +277 -15
- package/dist/pagination.d.ts.map +1 -1
- package/dist/pagination.js +2055 -438
- package/dist/pagination.js.map +1 -1
- package/dist/react.d.ts +11 -4
- 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 +1 -1
- package/dist/ribbon-chrome.d.ts.map +1 -1
- package/dist/ribbon-chrome.js +46 -0
- package/dist/ribbon-chrome.js.map +1 -1
- package/dist/ribbon.js +70 -0
- package/dist/ribbon.js.map +1 -1
- package/dist/session.bundle.js +695 -29
- package/dist/session.d.ts +134 -19
- package/dist/session.d.ts.map +1 -1
- package/dist/session.js +562 -25
- package/dist/session.js.map +1 -1
- package/dist/types.d.ts +1207 -16
- package/dist/types.d.ts.map +1 -1
- 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.NonGeneric.wasm +0 -0
- package/dist/wasm/_framework/System.Collections.NonGeneric.wasm.br +0 -0
- package/dist/wasm/_framework/System.Collections.Specialized.wasm +0 -0
- package/dist/wasm/_framework/System.Collections.Specialized.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.ComponentModel.TypeConverter.wasm +0 -0
- package/dist/wasm/_framework/System.ComponentModel.TypeConverter.wasm.br +0 -0
- package/dist/wasm/_framework/System.ComponentModel.wasm +0 -0
- package/dist/wasm/_framework/System.ComponentModel.wasm.br +0 -0
- package/dist/wasm/_framework/System.Console.wasm +0 -0
- package/dist/wasm/_framework/System.Console.wasm.br +0 -0
- package/dist/wasm/_framework/System.Diagnostics.Process.wasm +0 -0
- package/dist/wasm/_framework/System.Diagnostics.Process.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.Expressions.wasm +0 -0
- package/dist/wasm/_framework/System.Linq.Expressions.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.Memory.wasm +0 -0
- package/dist/wasm/_framework/System.Memory.wasm.br +0 -0
- package/dist/wasm/_framework/System.Net.Http.wasm +0 -0
- package/dist/wasm/_framework/System.Net.Http.wasm.br +0 -0
- package/dist/wasm/_framework/System.Net.Primitives.wasm +0 -0
- package/dist/wasm/_framework/System.Net.Primitives.wasm.br +0 -0
- package/dist/wasm/_framework/System.ObjectModel.wasm +0 -0
- package/dist/wasm/_framework/System.ObjectModel.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/System.Xml.Linq.wasm +0 -0
- package/dist/wasm/_framework/System.Xml.Linq.wasm.br +0 -0
- package/dist/wasm/_framework/System.Xml.XDocument.wasm +0 -0
- package/dist/wasm/_framework/System.Xml.XDocument.wasm.br +0 -0
- package/dist/wasm/_framework/System.wasm +0 -0
- package/dist/wasm/_framework/System.wasm.br +0 -0
- package/dist/wasm/_framework/dotnet.boot.js +38 -38
- package/dist/wasm/_framework/dotnet.boot.js.br +0 -0
- package/dist/wasm/_framework/dotnet.js +1 -1
- package/dist/wasm/_framework/dotnet.js.br +0 -0
- package/dist/wasm/_framework/dotnet.native.js +3 -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/wasm/_framework/dotnet.runtime.js +1 -1
- package/dist/wasm/_framework/dotnet.runtime.js.br +0 -0
- package/dist/worker-proxy.bundle.js +196 -29
- package/dist/worker-proxy.d.ts +36 -3
- package/dist/worker-proxy.d.ts.map +1 -1
- package/dist/worker-proxy.js +186 -33
- package/dist/worker-proxy.js.map +1 -1
- package/package.json +24 -7
package/dist/embed.iife.js
CHANGED
|
@@ -154,6 +154,7 @@ var Docxodus = (() => {
|
|
|
154
154
|
addAnnotation: () => addAnnotation,
|
|
155
155
|
addAnnotationToHtml: () => addAnnotationToHtml,
|
|
156
156
|
addAnnotationWithTarget: () => addAnnotationWithTarget,
|
|
157
|
+
clearPageCitationHighlight: () => clearPageCitationHighlight,
|
|
157
158
|
compareDocuments: () => compareDocuments,
|
|
158
159
|
compareDocumentsToHtml: () => compareDocumentsToHtml,
|
|
159
160
|
compareDocumentsToHtmlWithLog: () => compareDocumentsToHtmlWithLog,
|
|
@@ -168,15 +169,18 @@ var Docxodus = (() => {
|
|
|
168
169
|
createEditor: () => createEditor,
|
|
169
170
|
createExternalAnnotationSet: () => createExternalAnnotationSet,
|
|
170
171
|
createRibbonEditor: () => createRibbonEditor,
|
|
172
|
+
createUnavailablePageMap: () => createUnavailablePageMap,
|
|
171
173
|
createViewer: () => createViewer,
|
|
172
174
|
docxDiffAcceptRevisions: () => docxDiffAcceptRevisions,
|
|
173
175
|
docxDiffCompare: () => docxDiffCompare,
|
|
176
|
+
docxDiffCompareProducts: () => docxDiffCompareProducts,
|
|
174
177
|
docxDiffConsolidate: () => docxDiffConsolidate,
|
|
175
178
|
docxDiffGetConflicts: () => docxDiffGetConflicts,
|
|
176
179
|
docxDiffGetConsolidatedEditScript: () => docxDiffGetConsolidatedEditScript,
|
|
177
180
|
docxDiffGetConsolidatedRevisions: () => docxDiffGetConsolidatedRevisions,
|
|
178
181
|
docxDiffGetEditScript: () => docxDiffGetEditScript,
|
|
179
182
|
docxDiffGetRevisions: () => docxDiffGetRevisions,
|
|
183
|
+
docxDiffGetSemanticChanges: () => docxDiffGetSemanticChanges,
|
|
180
184
|
docxDiffRejectRevisions: () => docxDiffRejectRevisions,
|
|
181
185
|
exportToOpenContract: () => exportToOpenContract,
|
|
182
186
|
findElementById: () => findElementById,
|
|
@@ -186,6 +190,7 @@ var Docxodus = (() => {
|
|
|
186
190
|
fitScale: () => fitScale,
|
|
187
191
|
generateAnnotationCss: () => generateAnnotationCss,
|
|
188
192
|
generateAnnotationVisibilityCss: () => generateAnnotationVisibilityCss,
|
|
193
|
+
generatePackageManifest: () => generatePackageManifest,
|
|
189
194
|
getAnnotations: () => getAnnotations,
|
|
190
195
|
getDocumentMetadata: () => getDocumentMetadata,
|
|
191
196
|
getDocumentStructure: () => getDocumentStructure,
|
|
@@ -205,10 +210,12 @@ var Docxodus = (() => {
|
|
|
205
210
|
isMoveDestination: () => isMoveDestination,
|
|
206
211
|
isMoveSource: () => isMoveSource,
|
|
207
212
|
mountRibbon: () => mountRibbon,
|
|
213
|
+
navigateToPageCitation: () => navigateToPageCitation,
|
|
208
214
|
openDocxSession: () => openDocxSession2,
|
|
209
215
|
paginateHtml: () => paginateHtml,
|
|
210
216
|
parseSectionDimensions: () => parseSectionDimensions,
|
|
211
217
|
projectAnnotationsOntoHtml: () => projectAnnotationsOntoHtml,
|
|
218
|
+
proveRedlineReversibility: () => proveRedlineReversibility,
|
|
212
219
|
ptToPx: () => ptToPx,
|
|
213
220
|
pxToPt: () => pxToPt,
|
|
214
221
|
removeAnnotation: () => removeAnnotation,
|
|
@@ -228,6 +235,8 @@ var Docxodus = (() => {
|
|
|
228
235
|
targetTableRow: () => targetTableRow,
|
|
229
236
|
toDocumentBytes: () => toDocumentBytes,
|
|
230
237
|
validateExternalAnnotations: () => validateExternalAnnotations,
|
|
238
|
+
verifyDeliverable: () => verifyDeliverable,
|
|
239
|
+
verifyDeliveryReceipt: () => verifyDeliveryReceipt,
|
|
231
240
|
wasmBasePath: () => wasmBasePath
|
|
232
241
|
});
|
|
233
242
|
|
|
@@ -480,7 +489,46 @@ var Docxodus = (() => {
|
|
|
480
489
|
}
|
|
481
490
|
|
|
482
491
|
// src/session.ts
|
|
483
|
-
|
|
492
|
+
function mutationBatchChangeSet(before, after, key) {
|
|
493
|
+
const beforeGroups = /* @__PURE__ */ new Map();
|
|
494
|
+
const afterGroups = /* @__PURE__ */ new Map();
|
|
495
|
+
const group = (items, target) => {
|
|
496
|
+
items.forEach((item, index) => {
|
|
497
|
+
const identity = key(item);
|
|
498
|
+
const indices = target.get(identity) ?? [];
|
|
499
|
+
indices.push(index);
|
|
500
|
+
target.set(identity, indices);
|
|
501
|
+
});
|
|
502
|
+
};
|
|
503
|
+
group(before, beforeGroups);
|
|
504
|
+
group(after, afterGroups);
|
|
505
|
+
const beforeMatched = before.map(() => false);
|
|
506
|
+
const afterMatched = after.map(() => false);
|
|
507
|
+
const modified = after.map(() => false);
|
|
508
|
+
for (const [identity, afterIndices] of afterGroups) {
|
|
509
|
+
const beforeIndices = beforeGroups.get(identity) ?? [];
|
|
510
|
+
for (const afterIndex of afterIndices) {
|
|
511
|
+
const beforeIndex = beforeIndices.find((index) => !beforeMatched[index] && JSON.stringify(before[index]) === JSON.stringify(after[afterIndex]));
|
|
512
|
+
if (beforeIndex === void 0) continue;
|
|
513
|
+
beforeMatched[beforeIndex] = true;
|
|
514
|
+
afterMatched[afterIndex] = true;
|
|
515
|
+
}
|
|
516
|
+
const remainingBefore = beforeIndices.filter((index) => !beforeMatched[index]);
|
|
517
|
+
const remainingAfter = afterIndices.filter((index) => !afterMatched[index]);
|
|
518
|
+
const modifiedCount = Math.min(remainingBefore.length, remainingAfter.length);
|
|
519
|
+
for (let index = 0; index < modifiedCount; index++) {
|
|
520
|
+
beforeMatched[remainingBefore[index]] = true;
|
|
521
|
+
afterMatched[remainingAfter[index]] = true;
|
|
522
|
+
modified[remainingAfter[index]] = true;
|
|
523
|
+
}
|
|
524
|
+
}
|
|
525
|
+
return {
|
|
526
|
+
added: after.filter((_, index) => !afterMatched[index]),
|
|
527
|
+
removed: before.filter((_, index) => !beforeMatched[index]),
|
|
528
|
+
modified: after.filter((_, index) => modified[index])
|
|
529
|
+
};
|
|
530
|
+
}
|
|
531
|
+
var DocxSession = class _DocxSession {
|
|
484
532
|
/** @internal */
|
|
485
533
|
constructor(handle, wasm) {
|
|
486
534
|
// ─── Raw escape hatch ────────────────────────────────────────────────
|
|
@@ -496,6 +544,336 @@ var Docxodus = (() => {
|
|
|
496
544
|
project() {
|
|
497
545
|
return JSON.parse(this.wasm.Project(this.handle));
|
|
498
546
|
}
|
|
547
|
+
/** Monotonic document version (0 at open; +1 per committed mutation/undo/redo). */
|
|
548
|
+
getVersion() {
|
|
549
|
+
return JSON.parse(this.wasm.GetVersion(this.handle)).version;
|
|
550
|
+
}
|
|
551
|
+
/**
|
|
552
|
+
* Verification manifest for the current logical checkpoint. Unsaved edits are included and
|
|
553
|
+
* the read does not mutate package bytes, caches, history, or the document version.
|
|
554
|
+
*/
|
|
555
|
+
getPackageManifest() {
|
|
556
|
+
return JSON.parse(this.wasm.GetPackageManifest(this.handle));
|
|
557
|
+
}
|
|
558
|
+
/** Register a browser-materialized PageMap without changing the document version. */
|
|
559
|
+
registerPageMap(pageMap, expectedRendererFingerprint) {
|
|
560
|
+
return JSON.parse(this.wasm.RegisterPageMap(
|
|
561
|
+
this.handle,
|
|
562
|
+
JSON.stringify(pageMap),
|
|
563
|
+
expectedRendererFingerprint ?? ""
|
|
564
|
+
));
|
|
565
|
+
}
|
|
566
|
+
getPageMapStatus(request) {
|
|
567
|
+
return JSON.parse(this.wasm.GetPageMapStatus(
|
|
568
|
+
this.handle,
|
|
569
|
+
request ? JSON.stringify(request) : ""
|
|
570
|
+
));
|
|
571
|
+
}
|
|
572
|
+
getPageCitation(anchorId, request) {
|
|
573
|
+
return JSON.parse(this.wasm.GetPageCitation(
|
|
574
|
+
this.handle,
|
|
575
|
+
anchorId,
|
|
576
|
+
JSON.stringify(request)
|
|
577
|
+
));
|
|
578
|
+
}
|
|
579
|
+
/** Evaluate optimistic guards without mutating or advancing the version. */
|
|
580
|
+
checkPreconditions(preconditions) {
|
|
581
|
+
return JSON.parse(
|
|
582
|
+
this.wasm.CheckPreconditions(this.handle, JSON.stringify(preconditions))
|
|
583
|
+
);
|
|
584
|
+
}
|
|
585
|
+
/**
|
|
586
|
+
* Guard any synchronous mutation. WASM calls are synchronous and single-threaded, so the
|
|
587
|
+
* check and callback form one uninterrupted client-side operation. Prefer a method's native
|
|
588
|
+
* `preconditions` option where it has one (notably replaceTextRange's match-count guard).
|
|
589
|
+
*/
|
|
590
|
+
runWithPreconditions(preconditions, mutation) {
|
|
591
|
+
const checked = this.checkPreconditions(preconditions);
|
|
592
|
+
return checked.success ? mutation() : checked;
|
|
593
|
+
}
|
|
594
|
+
/**
|
|
595
|
+
* Execute synchronous mutations atomically by default. Atomic success is one undo/version
|
|
596
|
+
* unit; any failed or thrown step restores the exact package and history checkpoint.
|
|
597
|
+
*/
|
|
598
|
+
executeBatch(steps, mode = "atomic") {
|
|
599
|
+
if (mode !== "atomic" && mode !== "best_effort") {
|
|
600
|
+
throw new RangeError(`unknown mutation batch mode: ${String(mode)}`);
|
|
601
|
+
}
|
|
602
|
+
const baseVersion = this.getVersion();
|
|
603
|
+
const observationWarnings = [];
|
|
604
|
+
const inspect = (label, read, fallback) => {
|
|
605
|
+
try {
|
|
606
|
+
return read();
|
|
607
|
+
} catch (error) {
|
|
608
|
+
observationWarnings.push(`${label} unavailable: ${error instanceof Error ? error.message : String(error)}`);
|
|
609
|
+
return fallback;
|
|
610
|
+
}
|
|
611
|
+
};
|
|
612
|
+
const beforeRevisions = inspect("Revision delta inspection", () => this.listRevisions(), []);
|
|
613
|
+
const beforeComments = inspect("Comment delta inspection", () => this.listComments(), []);
|
|
614
|
+
const beforeAnnotations = inspect("Annotation delta inspection", () => this.listAnnotations(), []);
|
|
615
|
+
const complete = (result) => {
|
|
616
|
+
try {
|
|
617
|
+
const revisionChanges = mutationBatchChangeSet(
|
|
618
|
+
beforeRevisions,
|
|
619
|
+
inspect("Revision delta inspection", () => this.listRevisions(), beforeRevisions),
|
|
620
|
+
(revision) => revision.id
|
|
621
|
+
);
|
|
622
|
+
const commentChanges = mutationBatchChangeSet(
|
|
623
|
+
beforeComments,
|
|
624
|
+
inspect("Comment delta inspection", () => this.listComments(), beforeComments),
|
|
625
|
+
(comment) => comment.anchorId
|
|
626
|
+
);
|
|
627
|
+
const annotationChanges = mutationBatchChangeSet(
|
|
628
|
+
beforeAnnotations,
|
|
629
|
+
inspect("Annotation delta inspection", () => this.listAnnotations(), beforeAnnotations),
|
|
630
|
+
(annotation) => annotation.id ?? ""
|
|
631
|
+
);
|
|
632
|
+
const resultVersion = inspect(
|
|
633
|
+
"Result version inspection",
|
|
634
|
+
() => this.getVersion(),
|
|
635
|
+
baseVersion
|
|
636
|
+
);
|
|
637
|
+
const warnings = [...observationWarnings];
|
|
638
|
+
if ([...revisionChanges.added, ...revisionChanges.modified].some((revision) => revision.date !== void 0 && revision.date !== null)) {
|
|
639
|
+
warnings.push("Tracked-revision date attributes may use the execution clock; compare revision ids, authors, types, text, and anchors across separate executions.");
|
|
640
|
+
}
|
|
641
|
+
if ([...commentChanges.added, ...commentChanges.modified].some((comment) => comment.date !== void 0 && comment.date !== null)) {
|
|
642
|
+
warnings.push("Comment date attributes may be generated from the execution clock; supply dates explicitly when byte-identical replay is required.");
|
|
643
|
+
}
|
|
644
|
+
if (annotationChanges.added.some((annotation) => annotation.created !== void 0 && annotation.created !== null)) {
|
|
645
|
+
warnings.push("Auto-generated annotation ids or creation timestamps are execution metadata; supply id and created explicitly when byte-identical replay is required.");
|
|
646
|
+
}
|
|
647
|
+
if (result.steps.some((step) => step.results.some((edit) => edit.created.length > 0))) {
|
|
648
|
+
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.");
|
|
649
|
+
}
|
|
650
|
+
if (mode === "best_effort" && !result.success) {
|
|
651
|
+
warnings.push("Best-effort execution retains every successful step despite later failures.");
|
|
652
|
+
}
|
|
653
|
+
let packageHash = null;
|
|
654
|
+
if (!this.wasm.GetPackageContentHash) {
|
|
655
|
+
warnings.push("This WASM bundle predates package equivalence hashes; packageHash is unavailable.");
|
|
656
|
+
} else {
|
|
657
|
+
try {
|
|
658
|
+
packageHash = this.wasm.GetPackageContentHash(this.handle);
|
|
659
|
+
} catch (error) {
|
|
660
|
+
warnings.push(`Package equivalence hash unavailable: ${error instanceof Error ? error.message : String(error)}`);
|
|
661
|
+
}
|
|
662
|
+
}
|
|
663
|
+
return {
|
|
664
|
+
...result,
|
|
665
|
+
preview: false,
|
|
666
|
+
baseVersion,
|
|
667
|
+
resultVersion,
|
|
668
|
+
packageHash,
|
|
669
|
+
revisionChanges,
|
|
670
|
+
commentChanges,
|
|
671
|
+
annotationChanges,
|
|
672
|
+
warnings,
|
|
673
|
+
html: null
|
|
674
|
+
};
|
|
675
|
+
} catch (error) {
|
|
676
|
+
return {
|
|
677
|
+
...result,
|
|
678
|
+
preview: false,
|
|
679
|
+
baseVersion,
|
|
680
|
+
resultVersion: inspect("Result version inspection", () => this.getVersion(), baseVersion),
|
|
681
|
+
packageHash: null,
|
|
682
|
+
revisionChanges: { added: [], removed: [], modified: [] },
|
|
683
|
+
commentChanges: { added: [], removed: [], modified: [] },
|
|
684
|
+
annotationChanges: { added: [], removed: [], modified: [] },
|
|
685
|
+
warnings: [
|
|
686
|
+
...observationWarnings,
|
|
687
|
+
`Batch receipt enrichment unavailable: ${error instanceof Error ? error.message : String(error)}`
|
|
688
|
+
],
|
|
689
|
+
html: null
|
|
690
|
+
};
|
|
691
|
+
}
|
|
692
|
+
};
|
|
693
|
+
const internalFailure = (value) => ({
|
|
694
|
+
success: false,
|
|
695
|
+
error: { code: "internal_error", message: value instanceof Error ? value.message : String(value) },
|
|
696
|
+
created: [],
|
|
697
|
+
removed: [],
|
|
698
|
+
modified: []
|
|
699
|
+
});
|
|
700
|
+
const run = (step) => {
|
|
701
|
+
try {
|
|
702
|
+
const value = step.mutation();
|
|
703
|
+
const results = Array.isArray(value) ? value : [value];
|
|
704
|
+
if (results.some((result) => result === null || typeof result !== "object" || typeof result.success !== "boolean")) {
|
|
705
|
+
return [internalFailure("batch mutation returned invalid edit results")];
|
|
706
|
+
}
|
|
707
|
+
return results;
|
|
708
|
+
} catch (error) {
|
|
709
|
+
return [internalFailure(error)];
|
|
710
|
+
}
|
|
711
|
+
};
|
|
712
|
+
const failureOf = (step, rolledBack) => ({
|
|
713
|
+
index: step.index,
|
|
714
|
+
tool: step.tool,
|
|
715
|
+
action: step.action,
|
|
716
|
+
error: step.results.find((result) => !result.success)?.error ?? { code: "internal_error", message: "batch step failed without an error" },
|
|
717
|
+
rolledBack
|
|
718
|
+
});
|
|
719
|
+
const preflightOne = (step) => {
|
|
720
|
+
try {
|
|
721
|
+
return step.preflight?.();
|
|
722
|
+
} catch (error) {
|
|
723
|
+
return internalFailure(error).error;
|
|
724
|
+
}
|
|
725
|
+
};
|
|
726
|
+
if (mode === "atomic") {
|
|
727
|
+
const preflight = steps.map(preflightOne);
|
|
728
|
+
const failedPreflight = preflight.findIndex((error) => error !== void 0);
|
|
729
|
+
if (failedPreflight >= 0) {
|
|
730
|
+
const source = steps[failedPreflight];
|
|
731
|
+
const failed2 = {
|
|
732
|
+
index: failedPreflight,
|
|
733
|
+
tool: source.tool,
|
|
734
|
+
action: source.action,
|
|
735
|
+
success: false,
|
|
736
|
+
rolledBack: true,
|
|
737
|
+
results: [{ success: false, error: preflight[failedPreflight], created: [], removed: [], modified: [] }]
|
|
738
|
+
};
|
|
739
|
+
return complete({
|
|
740
|
+
mode,
|
|
741
|
+
status: "failed",
|
|
742
|
+
success: false,
|
|
743
|
+
rolledBack: true,
|
|
744
|
+
steps: [failed2],
|
|
745
|
+
failure: failureOf(failed2, true)
|
|
746
|
+
});
|
|
747
|
+
}
|
|
748
|
+
const transaction = this.wasm.BeginTransaction(this.handle);
|
|
749
|
+
const completed2 = [];
|
|
750
|
+
try {
|
|
751
|
+
for (let index = 0; index < steps.length; index++) {
|
|
752
|
+
const source = steps[index];
|
|
753
|
+
const results = run(source);
|
|
754
|
+
const step = {
|
|
755
|
+
index,
|
|
756
|
+
tool: source.tool,
|
|
757
|
+
action: source.action,
|
|
758
|
+
success: results.every((result) => result.success),
|
|
759
|
+
rolledBack: false,
|
|
760
|
+
results
|
|
761
|
+
};
|
|
762
|
+
completed2.push(step);
|
|
763
|
+
if (!step.success) {
|
|
764
|
+
this.wasm.RollbackTransaction(transaction);
|
|
765
|
+
const rolledBack = completed2.map((value) => ({ ...value, rolledBack: true }));
|
|
766
|
+
const failed2 = rolledBack[rolledBack.length - 1];
|
|
767
|
+
return complete({
|
|
768
|
+
mode,
|
|
769
|
+
status: "failed",
|
|
770
|
+
success: false,
|
|
771
|
+
rolledBack: true,
|
|
772
|
+
steps: rolledBack,
|
|
773
|
+
failure: failureOf(failed2, true)
|
|
774
|
+
});
|
|
775
|
+
}
|
|
776
|
+
}
|
|
777
|
+
this.wasm.CommitTransaction(transaction);
|
|
778
|
+
return complete({ mode, status: "ok", success: true, rolledBack: false, steps: completed2 });
|
|
779
|
+
} catch (error) {
|
|
780
|
+
try {
|
|
781
|
+
this.wasm.RollbackTransaction(transaction);
|
|
782
|
+
} catch {
|
|
783
|
+
}
|
|
784
|
+
throw error;
|
|
785
|
+
}
|
|
786
|
+
}
|
|
787
|
+
const completed = steps.map((source, index) => {
|
|
788
|
+
const preflight = preflightOne(source);
|
|
789
|
+
const results = preflight ? [{ success: false, error: preflight, created: [], removed: [], modified: [] }] : run(source);
|
|
790
|
+
return {
|
|
791
|
+
index,
|
|
792
|
+
tool: source.tool,
|
|
793
|
+
action: source.action,
|
|
794
|
+
success: results.every((result) => result.success),
|
|
795
|
+
rolledBack: false,
|
|
796
|
+
results
|
|
797
|
+
};
|
|
798
|
+
});
|
|
799
|
+
const failed = completed.find((step) => !step.success);
|
|
800
|
+
return complete({
|
|
801
|
+
mode,
|
|
802
|
+
status: failed ? completed.some((step) => step.success) ? "partial" : "failed" : "ok",
|
|
803
|
+
success: failed === void 0,
|
|
804
|
+
rolledBack: false,
|
|
805
|
+
steps: completed,
|
|
806
|
+
failure: failed ? failureOf(failed, false) : void 0
|
|
807
|
+
});
|
|
808
|
+
}
|
|
809
|
+
/**
|
|
810
|
+
* Execute the same callback batch algorithm against a complete isolated package clone.
|
|
811
|
+
* Callbacks receive the shadow session explicitly; mutate that argument. The live session's
|
|
812
|
+
* package, caches, version, configuration, and undo/redo history are never execution targets.
|
|
813
|
+
*/
|
|
814
|
+
previewBatch(steps, mode = "atomic", options) {
|
|
815
|
+
if (mode !== "atomic" && mode !== "best_effort") {
|
|
816
|
+
throw new RangeError(`unknown mutation batch mode: ${String(mode)}`);
|
|
817
|
+
}
|
|
818
|
+
const htmlMode = options?.html ?? "none";
|
|
819
|
+
if (htmlMode !== "none" && htmlMode !== "scoped" && htmlMode !== "full") {
|
|
820
|
+
throw new RangeError(`unknown preview HTML mode: ${String(htmlMode)}`);
|
|
821
|
+
}
|
|
822
|
+
if (!this.wasm.OpenPreviewSession) {
|
|
823
|
+
throw new Error("This WASM bundle does not support isolated mutation previews.");
|
|
824
|
+
}
|
|
825
|
+
const shadow = new _DocxSession(this.wasm.OpenPreviewSession(this.handle), this.wasm);
|
|
826
|
+
try {
|
|
827
|
+
const result = shadow.executeBatch(
|
|
828
|
+
steps.map((step) => ({
|
|
829
|
+
tool: step.tool,
|
|
830
|
+
action: step.action,
|
|
831
|
+
mutation: () => step.mutation(shadow),
|
|
832
|
+
preflight: step.preflight ? () => step.preflight(shadow) : void 0
|
|
833
|
+
})),
|
|
834
|
+
mode
|
|
835
|
+
);
|
|
836
|
+
const warnings = [...result.warnings];
|
|
837
|
+
let html = null;
|
|
838
|
+
const unwrapRendered = (rendered) => {
|
|
839
|
+
if (rendered.trimStart().startsWith("{")) {
|
|
840
|
+
const envelope = JSON.parse(rendered);
|
|
841
|
+
if (envelope.error) {
|
|
842
|
+
warnings.push(`Preview HTML could not be generated: ${envelope.error}`);
|
|
843
|
+
return null;
|
|
844
|
+
}
|
|
845
|
+
}
|
|
846
|
+
return rendered;
|
|
847
|
+
};
|
|
848
|
+
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.";
|
|
849
|
+
try {
|
|
850
|
+
if (htmlMode === "scoped") {
|
|
851
|
+
if (!options?.htmlAnchorId) {
|
|
852
|
+
warnings.push("Scoped HTML was requested without htmlAnchorId; no HTML was generated.");
|
|
853
|
+
} else if (this.wasm.RenderPreviewBlockHtml) {
|
|
854
|
+
html = unwrapRendered(
|
|
855
|
+
this.wasm.RenderPreviewBlockHtml(shadow.handle, options.htmlAnchorId)
|
|
856
|
+
);
|
|
857
|
+
} else {
|
|
858
|
+
warnings.push(legacyProfileWarning);
|
|
859
|
+
html = shadow.renderBlock(options.htmlAnchorId);
|
|
860
|
+
}
|
|
861
|
+
} else if (htmlMode === "full") {
|
|
862
|
+
if (this.wasm.RenderPreviewHtml) {
|
|
863
|
+
html = unwrapRendered(this.wasm.RenderPreviewHtml(shadow.handle));
|
|
864
|
+
} else {
|
|
865
|
+
warnings.push(legacyProfileWarning);
|
|
866
|
+
html = unwrapRendered(this.wasm.RenderHtmlForReview ? this.wasm.RenderHtmlForReview(shadow.handle, "docx-", false, false, 1, true) : this.wasm.RenderHtml(shadow.handle, "docx-", false, false, 1));
|
|
867
|
+
}
|
|
868
|
+
}
|
|
869
|
+
} catch (error) {
|
|
870
|
+
warnings.push(`Preview HTML could not be generated: ${error instanceof Error ? error.message : String(error)}`);
|
|
871
|
+
}
|
|
872
|
+
return { ...result, preview: true, warnings, html };
|
|
873
|
+
} finally {
|
|
874
|
+
shadow.close();
|
|
875
|
+
}
|
|
876
|
+
}
|
|
499
877
|
/**
|
|
500
878
|
* Project a slice of the document keyed off an anchor — useful for showing
|
|
501
879
|
* one section to an LLM at a time without paying the cost of projecting the
|
|
@@ -511,9 +889,9 @@ var Docxodus = (() => {
|
|
|
511
889
|
*
|
|
512
890
|
* @see docs/architecture/docx_mutation_api.md
|
|
513
891
|
*/
|
|
514
|
-
projectAnchor(anchorId, depth = 2 /* SubtreeAndFollowingSiblings
|
|
892
|
+
projectAnchor(anchorId, depth = 2 /* SubtreeAndFollowingSiblings */, citation) {
|
|
515
893
|
return JSON.parse(
|
|
516
|
-
this.wasm.ProjectAnchor(this.handle, anchorId, depth)
|
|
894
|
+
citation ? this.wasm.ProjectAnchorWithCitations(this.handle, anchorId, depth, JSON.stringify(citation)) : this.wasm.ProjectAnchor(this.handle, anchorId, depth)
|
|
517
895
|
);
|
|
518
896
|
}
|
|
519
897
|
/**
|
|
@@ -537,11 +915,21 @@ var Docxodus = (() => {
|
|
|
537
915
|
return html;
|
|
538
916
|
}
|
|
539
917
|
// ─── Tier A: text CRUD ───────────────────────────────────────────────
|
|
540
|
-
replaceText(anchorId, markdown) {
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
return
|
|
918
|
+
replaceText(anchorId, markdown, preconditions) {
|
|
919
|
+
const apply = () => JSON.parse(
|
|
920
|
+
this.wasm.ReplaceText(this.handle, anchorId, markdown)
|
|
921
|
+
);
|
|
922
|
+
return preconditions ? this.runWithPreconditions(
|
|
923
|
+
{ ...preconditions, anchorId: preconditions.anchorId ?? anchorId },
|
|
924
|
+
apply
|
|
925
|
+
) : apply();
|
|
926
|
+
}
|
|
927
|
+
deleteBlock(anchorId, preconditions) {
|
|
928
|
+
const apply = () => JSON.parse(this.wasm.DeleteBlock(this.handle, anchorId));
|
|
929
|
+
return preconditions ? this.runWithPreconditions(
|
|
930
|
+
{ ...preconditions, anchorId: preconditions.anchorId ?? anchorId },
|
|
931
|
+
apply
|
|
932
|
+
) : apply();
|
|
545
933
|
}
|
|
546
934
|
/** Reorder one top-level paragraph/heading/list/table block relative to another. */
|
|
547
935
|
moveBlock(sourceAnchorId, targetAnchorId, position) {
|
|
@@ -596,7 +984,7 @@ var Docxodus = (() => {
|
|
|
596
984
|
}
|
|
597
985
|
/**
|
|
598
986
|
* 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
|
|
987
|
+
* cell markdown, and cell alignment. The returned `EditResult.created` lists canonical `tc`
|
|
600
988
|
* anchors (row-major), so each cell can then be addressed to fill/format.
|
|
601
989
|
*/
|
|
602
990
|
insertTable(anchorId, position, rows, cols, options) {
|
|
@@ -605,10 +993,26 @@ var Docxodus = (() => {
|
|
|
605
993
|
this.wasm.InsertTable(this.handle, anchorId, position, rows, cols, optionsJson)
|
|
606
994
|
);
|
|
607
995
|
}
|
|
996
|
+
/** Resolve a canonical `tbl` anchor to explicit table/row/column/cell identities. */
|
|
997
|
+
getTableMetadata(tableAnchorId) {
|
|
998
|
+
return JSON.parse(this.wasm.GetTableMetadata(this.handle, tableAnchorId));
|
|
999
|
+
}
|
|
1000
|
+
/** Resolve a canonical `tc` anchor to its zero-based table-grid coordinate and spans. */
|
|
1001
|
+
resolveTableCellAnchor(cellAnchorId) {
|
|
1002
|
+
return JSON.parse(
|
|
1003
|
+
this.wasm.ResolveTableCellAnchor(this.handle, cellAnchorId)
|
|
1004
|
+
);
|
|
1005
|
+
}
|
|
1006
|
+
/** Resolve a zero-based table-grid coordinate to the physical `tc` covering it. */
|
|
1007
|
+
resolveTableCellCoordinate(tableAnchorId, rowIndex, columnIndex) {
|
|
1008
|
+
return JSON.parse(
|
|
1009
|
+
this.wasm.ResolveTableCellCoordinate(this.handle, tableAnchorId, rowIndex, columnIndex)
|
|
1010
|
+
);
|
|
1011
|
+
}
|
|
608
1012
|
/**
|
|
609
|
-
* Table row/column editing, addressed by
|
|
610
|
-
* {@link insertTable}'s `created`
|
|
611
|
-
* empty (`created` lists
|
|
1013
|
+
* Table row/column editing, addressed by the canonical `tc` anchor returned from
|
|
1014
|
+
* {@link insertTable}'s `created` or table metadata. Insert clones the reference row/column's
|
|
1015
|
+
* widths and starts empty (`created` lists new `tc` anchors); delete of the last row/column removes
|
|
612
1016
|
* the whole table. All four are grid-aware: inserting across a merge extends it, deleting
|
|
613
1017
|
* through one narrows it, and deleting a vertical merge's lead row promotes the next row to
|
|
614
1018
|
* carry it — the grid is never left ragged.
|
|
@@ -648,7 +1052,7 @@ var Docxodus = (() => {
|
|
|
648
1052
|
return JSON.parse(this.wasm.UnmergeCells(this.handle, cellAnchorId));
|
|
649
1053
|
}
|
|
650
1054
|
/**
|
|
651
|
-
* Table styling, addressed by a
|
|
1055
|
+
* Table styling, addressed by a canonical `tc` anchor — the post-insert counterpart of
|
|
652
1056
|
* {@link insertTable}'s options (issue #315 Stage A). `setColumnWidths` retunes `w:tblGrid` +
|
|
653
1057
|
* every row's cell width (one positive twip value per column) and pins the table to fixed
|
|
654
1058
|
* layout, exactly as inserting with explicit `columnWidths` would.
|
|
@@ -688,6 +1092,19 @@ var Docxodus = (() => {
|
|
|
688
1092
|
this.wasm.SetRepeatHeaderRow(this.handle, cellAnchorId, repeat)
|
|
689
1093
|
);
|
|
690
1094
|
}
|
|
1095
|
+
/** Apply row layout options to the row containing the canonical cell anchor. */
|
|
1096
|
+
setTableRowOptions(cellAnchorId, options) {
|
|
1097
|
+
return JSON.parse(
|
|
1098
|
+
this.wasm.SetTableRowOptions(
|
|
1099
|
+
this.handle,
|
|
1100
|
+
cellAnchorId,
|
|
1101
|
+
options.repeatHeader ?? null,
|
|
1102
|
+
options.allowBreakAcrossPages ?? null,
|
|
1103
|
+
options.heightTwips ?? null,
|
|
1104
|
+
options.heightRule ?? "atLeast"
|
|
1105
|
+
)
|
|
1106
|
+
);
|
|
1107
|
+
}
|
|
691
1108
|
// ─── Headers / footers / page numbers ────────────────────────────────
|
|
692
1109
|
/**
|
|
693
1110
|
* Set the running header story for the section that owns `anchorId` (any body block in that
|
|
@@ -782,6 +1199,25 @@ var Docxodus = (() => {
|
|
|
782
1199
|
this.wasm.InsertFootnote(this.handle, anchorId, characterOffset, markdown)
|
|
783
1200
|
);
|
|
784
1201
|
}
|
|
1202
|
+
/**
|
|
1203
|
+
* Insert a Word-faithful internal cross-reference — a `REF` field targeting an existing
|
|
1204
|
+
* bookmark — at a character offset (issue #545). The field carries a cached result run
|
|
1205
|
+
* (the bookmarked text, or the target's auto-number under `referenceNumber`), so
|
|
1206
|
+
* renderers that do not recompute fields show a faithful snapshot and Word updates it
|
|
1207
|
+
* like a hand-authored cross-reference. A missing or incoherent bookmark fails with
|
|
1208
|
+
* `missing_bookmark_target`.
|
|
1209
|
+
*/
|
|
1210
|
+
insertCrossReference(anchorId, characterOffset, bookmarkName, options) {
|
|
1211
|
+
return JSON.parse(
|
|
1212
|
+
this.wasm.InsertCrossReference(
|
|
1213
|
+
this.handle,
|
|
1214
|
+
anchorId,
|
|
1215
|
+
characterOffset,
|
|
1216
|
+
bookmarkName,
|
|
1217
|
+
options ? JSON.stringify(options) : ""
|
|
1218
|
+
)
|
|
1219
|
+
);
|
|
1220
|
+
}
|
|
785
1221
|
/** Create an endnote — see {@link insertFootnote}; writes the endnotes part and a
|
|
786
1222
|
* `w:endnoteReference`, and the created definition anchor has kind `en`. */
|
|
787
1223
|
insertEndnote(anchorId, characterOffset, markdown) {
|
|
@@ -873,6 +1309,170 @@ var Docxodus = (() => {
|
|
|
873
1309
|
listComments() {
|
|
874
1310
|
return JSON.parse(this.wasm.ListComments(this.handle));
|
|
875
1311
|
}
|
|
1312
|
+
listHyperlinks(scopes = 63 /* All */) {
|
|
1313
|
+
return JSON.parse(this.wasm.ListHyperlinks(this.handle, scopes));
|
|
1314
|
+
}
|
|
1315
|
+
addHyperlink(anchorId, span, kind, target) {
|
|
1316
|
+
return JSON.parse(this.wasm.AddHyperlink(
|
|
1317
|
+
this.handle,
|
|
1318
|
+
anchorId,
|
|
1319
|
+
span.start,
|
|
1320
|
+
span.length,
|
|
1321
|
+
kind,
|
|
1322
|
+
target
|
|
1323
|
+
));
|
|
1324
|
+
}
|
|
1325
|
+
updateHyperlink(hyperlinkId, kind, target) {
|
|
1326
|
+
return JSON.parse(this.wasm.UpdateHyperlink(this.handle, hyperlinkId, kind, target));
|
|
1327
|
+
}
|
|
1328
|
+
removeHyperlink(hyperlinkId) {
|
|
1329
|
+
return JSON.parse(this.wasm.RemoveHyperlink(this.handle, hyperlinkId));
|
|
1330
|
+
}
|
|
1331
|
+
/** Versioned operational facts for native image inspection/mutation in this runtime. */
|
|
1332
|
+
getImageCapabilities() {
|
|
1333
|
+
return JSON.parse(this.wasm.GetImageCapabilities());
|
|
1334
|
+
}
|
|
1335
|
+
listImages(scopes = 63 /* All */) {
|
|
1336
|
+
return JSON.parse(this.wasm.ListImages(this.handle, scopes));
|
|
1337
|
+
}
|
|
1338
|
+
insertImage(anchorId, characterOffset, bytes, options = {}) {
|
|
1339
|
+
return JSON.parse(this.wasm.InsertImage(
|
|
1340
|
+
this.handle,
|
|
1341
|
+
anchorId,
|
|
1342
|
+
characterOffset,
|
|
1343
|
+
imageBytesToBase64(bytes),
|
|
1344
|
+
JSON.stringify(options)
|
|
1345
|
+
));
|
|
1346
|
+
}
|
|
1347
|
+
replaceImage(imageId, bytes) {
|
|
1348
|
+
return JSON.parse(this.wasm.ReplaceImage(
|
|
1349
|
+
this.handle,
|
|
1350
|
+
imageId,
|
|
1351
|
+
imageBytesToBase64(bytes)
|
|
1352
|
+
));
|
|
1353
|
+
}
|
|
1354
|
+
setImageDimensions(imageId, dimensions) {
|
|
1355
|
+
return JSON.parse(this.wasm.SetImageDimensions(
|
|
1356
|
+
this.handle,
|
|
1357
|
+
imageId,
|
|
1358
|
+
JSON.stringify(dimensions)
|
|
1359
|
+
));
|
|
1360
|
+
}
|
|
1361
|
+
setImageMetadata(imageId, altText, title) {
|
|
1362
|
+
return JSON.parse(this.wasm.SetImageMetadata(
|
|
1363
|
+
this.handle,
|
|
1364
|
+
imageId,
|
|
1365
|
+
altText,
|
|
1366
|
+
title
|
|
1367
|
+
));
|
|
1368
|
+
}
|
|
1369
|
+
setImageFloatingLayout(imageId, layout) {
|
|
1370
|
+
return JSON.parse(this.wasm.SetImageFloatingLayout(
|
|
1371
|
+
this.handle,
|
|
1372
|
+
imageId,
|
|
1373
|
+
JSON.stringify(layout)
|
|
1374
|
+
));
|
|
1375
|
+
}
|
|
1376
|
+
removeImage(imageId) {
|
|
1377
|
+
return JSON.parse(this.wasm.RemoveImage(this.handle, imageId));
|
|
1378
|
+
}
|
|
1379
|
+
/** Native Word structured-document tags, in outer-before-inner story order. */
|
|
1380
|
+
listContentControls(scopes = 63 /* All */) {
|
|
1381
|
+
return JSON.parse(this.wasm.ListContentControls(this.handle, scopes));
|
|
1382
|
+
}
|
|
1383
|
+
fillContentControlText(anchorId, text, options = {}) {
|
|
1384
|
+
return JSON.parse(this.wasm.FillContentControlText(
|
|
1385
|
+
this.handle,
|
|
1386
|
+
anchorId,
|
|
1387
|
+
text,
|
|
1388
|
+
JSON.stringify(options)
|
|
1389
|
+
));
|
|
1390
|
+
}
|
|
1391
|
+
fillContentControlRichText(anchorId, markdown, options = {}) {
|
|
1392
|
+
return JSON.parse(this.wasm.FillContentControlRichText(
|
|
1393
|
+
this.handle,
|
|
1394
|
+
anchorId,
|
|
1395
|
+
markdown,
|
|
1396
|
+
JSON.stringify(options)
|
|
1397
|
+
));
|
|
1398
|
+
}
|
|
1399
|
+
setContentControlChecked(anchorId, isChecked, options = {}) {
|
|
1400
|
+
return JSON.parse(this.wasm.SetContentControlChecked(
|
|
1401
|
+
this.handle,
|
|
1402
|
+
anchorId,
|
|
1403
|
+
isChecked,
|
|
1404
|
+
JSON.stringify(options)
|
|
1405
|
+
));
|
|
1406
|
+
}
|
|
1407
|
+
setContentControlDate(anchorId, value, displayText, options = {}) {
|
|
1408
|
+
const timestamp = value instanceof Date ? value.toISOString() : value;
|
|
1409
|
+
return JSON.parse(this.wasm.SetContentControlDate(
|
|
1410
|
+
this.handle,
|
|
1411
|
+
anchorId,
|
|
1412
|
+
timestamp,
|
|
1413
|
+
displayText ?? null,
|
|
1414
|
+
JSON.stringify(options)
|
|
1415
|
+
));
|
|
1416
|
+
}
|
|
1417
|
+
selectContentControlItem(anchorId, value, options = {}) {
|
|
1418
|
+
return JSON.parse(this.wasm.SelectContentControlItem(
|
|
1419
|
+
this.handle,
|
|
1420
|
+
anchorId,
|
|
1421
|
+
value,
|
|
1422
|
+
JSON.stringify(options)
|
|
1423
|
+
));
|
|
1424
|
+
}
|
|
1425
|
+
fillContentControlPicture(anchorId, bytes, options = {}) {
|
|
1426
|
+
return JSON.parse(this.wasm.FillContentControlPicture(
|
|
1427
|
+
this.handle,
|
|
1428
|
+
anchorId,
|
|
1429
|
+
imageBytesToBase64(bytes),
|
|
1430
|
+
JSON.stringify(options)
|
|
1431
|
+
));
|
|
1432
|
+
}
|
|
1433
|
+
addRepeatingSectionItem(sectionAnchorId, afterItemAnchorId, options = {}) {
|
|
1434
|
+
return JSON.parse(this.wasm.AddRepeatingSectionItem(
|
|
1435
|
+
this.handle,
|
|
1436
|
+
sectionAnchorId,
|
|
1437
|
+
afterItemAnchorId ?? "",
|
|
1438
|
+
JSON.stringify(options)
|
|
1439
|
+
));
|
|
1440
|
+
}
|
|
1441
|
+
removeRepeatingSectionItem(itemAnchorId) {
|
|
1442
|
+
return JSON.parse(this.wasm.RemoveRepeatingSectionItem(
|
|
1443
|
+
this.handle,
|
|
1444
|
+
itemAnchorId
|
|
1445
|
+
));
|
|
1446
|
+
}
|
|
1447
|
+
listBookmarks(scopes = 63 /* All */) {
|
|
1448
|
+
return JSON.parse(this.wasm.ListBookmarks(this.handle, scopes));
|
|
1449
|
+
}
|
|
1450
|
+
addBookmark(name, range) {
|
|
1451
|
+
return JSON.parse(this.wasm.AddBookmark(
|
|
1452
|
+
this.handle,
|
|
1453
|
+
name,
|
|
1454
|
+
range.startAnchorId,
|
|
1455
|
+
range.startOffset,
|
|
1456
|
+
range.endAnchorId,
|
|
1457
|
+
range.endOffset
|
|
1458
|
+
));
|
|
1459
|
+
}
|
|
1460
|
+
renameBookmark(name, newName) {
|
|
1461
|
+
return JSON.parse(this.wasm.RenameBookmark(this.handle, name, newName));
|
|
1462
|
+
}
|
|
1463
|
+
moveBookmark(name, range) {
|
|
1464
|
+
return JSON.parse(this.wasm.MoveBookmark(
|
|
1465
|
+
this.handle,
|
|
1466
|
+
name,
|
|
1467
|
+
range.startAnchorId,
|
|
1468
|
+
range.startOffset,
|
|
1469
|
+
range.endAnchorId,
|
|
1470
|
+
range.endOffset
|
|
1471
|
+
));
|
|
1472
|
+
}
|
|
1473
|
+
removeBookmark(name) {
|
|
1474
|
+
return JSON.parse(this.wasm.RemoveBookmark(this.handle, name));
|
|
1475
|
+
}
|
|
876
1476
|
// ─── Tracked revisions (issue #318) ──────────────────────────────────
|
|
877
1477
|
/** Markup-native tracked-revision listing, in document order across body, headers,
|
|
878
1478
|
* footers, footnotes, and endnotes. Ids are stable while the underlying markup
|
|
@@ -893,6 +1493,22 @@ var Docxodus = (() => {
|
|
|
893
1493
|
rejectRevision(revisionId) {
|
|
894
1494
|
return JSON.parse(this.wasm.RejectRevision(this.handle, revisionId));
|
|
895
1495
|
}
|
|
1496
|
+
/**
|
|
1497
|
+
* Accept every live revision as one undoable session mutation.
|
|
1498
|
+
*
|
|
1499
|
+
* Fails closed: an unsupported, malformed, or ambiguous registry entry aborts the whole
|
|
1500
|
+
* operation (`revisionUnsupported`/`revisionMalformed`/`revisionAmbiguous`) and nothing is
|
|
1501
|
+
* mutated. There is no force mode — call {@link listRevisions} and read each entry's
|
|
1502
|
+
* `diagnostic` to see what blocks it.
|
|
1503
|
+
*/
|
|
1504
|
+
acceptAllRevisions() {
|
|
1505
|
+
return JSON.parse(this.wasm.AcceptAllRevisions(this.handle));
|
|
1506
|
+
}
|
|
1507
|
+
/** Reject every live revision as one undoable session mutation. Fails closed exactly like
|
|
1508
|
+
* {@link acceptAllRevisions}. */
|
|
1509
|
+
rejectAllRevisions() {
|
|
1510
|
+
return JSON.parse(this.wasm.RejectAllRevisions(this.handle));
|
|
1511
|
+
}
|
|
896
1512
|
// ─── Tier C: formatting ──────────────────────────────────────────────
|
|
897
1513
|
applyFormat(anchorId, span, op) {
|
|
898
1514
|
const spanJson = span ? JSON.stringify(span) : "";
|
|
@@ -998,6 +1614,10 @@ var Docxodus = (() => {
|
|
|
998
1614
|
* replaces it with `replace`, preserving the surrounding run formatting that
|
|
999
1615
|
* the match didn't touch. Returns one `EditResult` per attempted match.
|
|
1000
1616
|
*
|
|
1617
|
+
* A `find` that matches nothing fails with a single `text_not_found` result
|
|
1618
|
+
* naming the anchor and the needle — never an empty array. Pass
|
|
1619
|
+
* `expectedMatchCount: 0` to assert absence as a successful no-op instead.
|
|
1620
|
+
*
|
|
1001
1621
|
* Run-formatting contract: the replacement text inherits the formatting of
|
|
1002
1622
|
* the FIRST run the match spanned. Middle/trailing runs keep their `w:rPr`
|
|
1003
1623
|
* but lose the slice of text the match consumed.
|
|
@@ -1174,10 +1794,16 @@ var Docxodus = (() => {
|
|
|
1174
1794
|
*
|
|
1175
1795
|
* @see docs/architecture/docx_mutation_api.md#findplaceholders
|
|
1176
1796
|
*/
|
|
1177
|
-
findPlaceholders(kinds = PlaceholderKinds.All, scope = 1, contextChars = 80, boundary = ContextBoundary.Char) {
|
|
1178
|
-
|
|
1179
|
-
this.
|
|
1180
|
-
|
|
1797
|
+
findPlaceholders(kinds = PlaceholderKinds.All, scope = 1, contextChars = 80, boundary = ContextBoundary.Char, citation) {
|
|
1798
|
+
const json = citation ? this.wasm.FindPlaceholdersWithCitations(
|
|
1799
|
+
this.handle,
|
|
1800
|
+
kinds,
|
|
1801
|
+
scope,
|
|
1802
|
+
contextChars,
|
|
1803
|
+
boundary,
|
|
1804
|
+
JSON.stringify(citation)
|
|
1805
|
+
) : this.wasm.FindPlaceholders(this.handle, kinds, scope, contextChars, boundary);
|
|
1806
|
+
return JSON.parse(json);
|
|
1181
1807
|
}
|
|
1182
1808
|
/**
|
|
1183
1809
|
* Returns a snapshot of edit-state introspection signals — placeholder counts,
|
|
@@ -1200,6 +1826,23 @@ var Docxodus = (() => {
|
|
|
1200
1826
|
}
|
|
1201
1827
|
return raw;
|
|
1202
1828
|
}
|
|
1829
|
+
/**
|
|
1830
|
+
* Return stable semantic changes from the package opened for this session to
|
|
1831
|
+
* its current state. Requires `captureInitialProjection` (enabled by default).
|
|
1832
|
+
*/
|
|
1833
|
+
getSemanticChanges() {
|
|
1834
|
+
return JSON.parse(this.wasm.GetSemanticChanges(this.handle));
|
|
1835
|
+
}
|
|
1836
|
+
/**
|
|
1837
|
+
* Run the default deliverable gate on this session's normal clean-save checkpoint.
|
|
1838
|
+
* With initial projection capture enabled (the default), exact opening bytes are the
|
|
1839
|
+
* baseline used for dispositions and semantic/package deltas.
|
|
1840
|
+
*/
|
|
1841
|
+
verifyDeliverable() {
|
|
1842
|
+
return JSON.parse(
|
|
1843
|
+
this.wasm.VerifyDeliverable(this.handle)
|
|
1844
|
+
);
|
|
1845
|
+
}
|
|
1203
1846
|
// ─── Annotation-based anchor discovery (#132) ────────────────────────
|
|
1204
1847
|
/**
|
|
1205
1848
|
* Resolves an annotation's range to the block-level markdown anchors covering
|
|
@@ -1215,8 +1858,9 @@ var Docxodus = (() => {
|
|
|
1215
1858
|
*
|
|
1216
1859
|
* @see docs/architecture/docx_mutation_api.md#findbyannotation
|
|
1217
1860
|
*/
|
|
1218
|
-
findByAnnotation(annotationId) {
|
|
1219
|
-
|
|
1861
|
+
findByAnnotation(annotationId, citation) {
|
|
1862
|
+
const json = citation ? this.wasm.FindByAnnotationWithCitations(this.handle, annotationId, JSON.stringify(citation)) : this.wasm.FindByAnnotation(this.handle, annotationId);
|
|
1863
|
+
return JSON.parse(json);
|
|
1220
1864
|
}
|
|
1221
1865
|
/**
|
|
1222
1866
|
* Finds every annotation whose `labelId` matches and resolves each of their
|
|
@@ -1225,8 +1869,9 @@ var Docxodus = (() => {
|
|
|
1225
1869
|
* annotations on different paragraphs become three entries). Annotations
|
|
1226
1870
|
* whose bookmark resolves to no anchors are omitted from the result.
|
|
1227
1871
|
*/
|
|
1228
|
-
findByLabel(labelId) {
|
|
1229
|
-
|
|
1872
|
+
findByLabel(labelId, citation) {
|
|
1873
|
+
const json = citation ? this.wasm.FindByLabelWithCitations(this.handle, labelId, JSON.stringify(citation)) : this.wasm.FindByLabel(this.handle, labelId);
|
|
1874
|
+
return JSON.parse(json);
|
|
1230
1875
|
}
|
|
1231
1876
|
/**
|
|
1232
1877
|
* Resolves any bookmark in the main document part (Docxodus-managed or
|
|
@@ -1234,8 +1879,9 @@ var Docxodus = (() => {
|
|
|
1234
1879
|
* order. Empty when the bookmark name is unknown. Use this for raw bookmark
|
|
1235
1880
|
* names that didn't come from the annotation system.
|
|
1236
1881
|
*/
|
|
1237
|
-
findByBookmark(bookmarkName) {
|
|
1238
|
-
|
|
1882
|
+
findByBookmark(bookmarkName, citation) {
|
|
1883
|
+
const json = citation ? this.wasm.FindByBookmarkWithCitations(this.handle, bookmarkName, JSON.stringify(citation)) : this.wasm.FindByBookmark(this.handle, bookmarkName);
|
|
1884
|
+
return JSON.parse(json);
|
|
1239
1885
|
}
|
|
1240
1886
|
// ─── Text/kind-based anchor discovery (#171) ─────────────────────────
|
|
1241
1887
|
/**
|
|
@@ -1280,15 +1926,25 @@ var Docxodus = (() => {
|
|
|
1280
1926
|
);
|
|
1281
1927
|
}
|
|
1282
1928
|
/**
|
|
1283
|
-
* Return every anchor of the given `kind`
|
|
1284
|
-
* `"
|
|
1929
|
+
* Return every anchor of the given `kind` — one of `"p"`, `"h"`, `"li"`,
|
|
1930
|
+
* `"tbl"`, `"tr"`, `"tc"`, `"col"`, `"sdt"`, `"sec"`, `"fn"`, `"en"`,
|
|
1931
|
+
* `"cmt"`, `"unk"` — in document order. Matching is an exact string comparison;
|
|
1932
|
+
* in particular the row and cell tokens are `"tr"` and `"tc"`, never
|
|
1933
|
+
* `"row"`/`"cell"`. Unknown strings are not parsed or rejected: they simply
|
|
1934
|
+
* return an empty array. `"img"` and `"drw"` likewise return empty because the
|
|
1935
|
+
* projection never assigns those reserved kinds; address images through the
|
|
1936
|
+
* image surface instead. Reads the projection's anchor
|
|
1285
1937
|
* index directly — no text scan. Pass `scope` (e.g. `"body"`) to restrict to
|
|
1286
1938
|
* a single part; omit it to span all scopes.
|
|
1287
1939
|
*/
|
|
1288
|
-
findByKind(kind, scope) {
|
|
1289
|
-
|
|
1290
|
-
this.
|
|
1291
|
-
|
|
1940
|
+
findByKind(kind, scope, citation) {
|
|
1941
|
+
const json = citation ? this.wasm.FindByKindWithCitations(
|
|
1942
|
+
this.handle,
|
|
1943
|
+
kind,
|
|
1944
|
+
scope ?? "",
|
|
1945
|
+
JSON.stringify(citation)
|
|
1946
|
+
) : this.wasm.FindByKind(this.handle, kind, scope ?? "");
|
|
1947
|
+
return JSON.parse(json);
|
|
1292
1948
|
}
|
|
1293
1949
|
/**
|
|
1294
1950
|
* Look up a single anchor's preview info — `{ id, kind, scope, textPreview }`.
|
|
@@ -1342,6 +1998,18 @@ var Docxodus = (() => {
|
|
|
1342
1998
|
const raw = this.wasm.GetSectionInfo(this.handle, anchorId);
|
|
1343
1999
|
return JSON.parse(raw);
|
|
1344
2000
|
}
|
|
2001
|
+
/** Enumerate the document's explicit style catalog with resolved high-signal properties. */
|
|
2002
|
+
listStyles() {
|
|
2003
|
+
return JSON.parse(this.wasm.ListStyles(this.handle));
|
|
2004
|
+
}
|
|
2005
|
+
/** Inspect direct and effective paragraph/run formatting for one paragraph anchor. */
|
|
2006
|
+
getFormatting(anchorId) {
|
|
2007
|
+
return JSON.parse(this.wasm.GetFormatting(this.handle, anchorId));
|
|
2008
|
+
}
|
|
2009
|
+
/** Enumerate text-bearing runs as mutation-compatible anchor/span pairs. */
|
|
2010
|
+
listInlineSpans(anchorId) {
|
|
2011
|
+
return JSON.parse(this.wasm.ListInlineSpans(this.handle, anchorId));
|
|
2012
|
+
}
|
|
1345
2013
|
/**
|
|
1346
2014
|
* Enumerates every annotation persisted in the document. Lets an agent prime
|
|
1347
2015
|
* itself with "here are the labeled regions you can target" before committing
|
|
@@ -1406,6 +2074,13 @@ var Docxodus = (() => {
|
|
|
1406
2074
|
this.close();
|
|
1407
2075
|
}
|
|
1408
2076
|
};
|
|
2077
|
+
function imageBytesToBase64(bytes) {
|
|
2078
|
+
let binary = "";
|
|
2079
|
+
for (let offset = 0; offset < bytes.length; offset += 32768) {
|
|
2080
|
+
binary += String.fromCharCode(...bytes.subarray(offset, offset + 32768));
|
|
2081
|
+
}
|
|
2082
|
+
return globalThis.btoa(binary);
|
|
2083
|
+
}
|
|
1409
2084
|
function openDocxSession(bytes, wasmExports2, settings) {
|
|
1410
2085
|
const bridge = wasmExports2.DocxSessionBridge;
|
|
1411
2086
|
const handle = bridge.OpenSession(bytes, settings ? JSON.stringify(settings) : "");
|
|
@@ -1457,17 +2132,41 @@ var Docxodus = (() => {
|
|
|
1457
2132
|
function ptToPx(pt) {
|
|
1458
2133
|
return pt / 0.75;
|
|
1459
2134
|
}
|
|
2135
|
+
function finiteDataNumber(value, fallback) {
|
|
2136
|
+
if (value === void 0 || value.trim() === "") return fallback;
|
|
2137
|
+
const parsed = Number(value);
|
|
2138
|
+
return Number.isFinite(parsed) ? parsed : fallback;
|
|
2139
|
+
}
|
|
2140
|
+
function positiveDataNumber(value, fallback) {
|
|
2141
|
+
const parsed = finiteDataNumber(value, fallback);
|
|
2142
|
+
return parsed > 0 ? parsed : fallback;
|
|
2143
|
+
}
|
|
2144
|
+
function nonNegativeDataNumber(value, fallback) {
|
|
2145
|
+
const parsed = finiteDataNumber(value, fallback);
|
|
2146
|
+
return parsed >= 0 ? parsed : fallback;
|
|
2147
|
+
}
|
|
2148
|
+
function positiveFinite(value, fallback) {
|
|
2149
|
+
return Number.isFinite(value) && value > 0 ? value : fallback;
|
|
2150
|
+
}
|
|
1460
2151
|
function parseSectionDimensions(section) {
|
|
1461
|
-
const pageWidth =
|
|
1462
|
-
const pageHeight =
|
|
1463
|
-
const
|
|
1464
|
-
const
|
|
1465
|
-
const
|
|
1466
|
-
const
|
|
1467
|
-
const
|
|
1468
|
-
const
|
|
1469
|
-
const
|
|
1470
|
-
const
|
|
2152
|
+
const pageWidth = positiveDataNumber(section.dataset.pageWidth, DEFAULT_PAGE_WIDTH);
|
|
2153
|
+
const pageHeight = positiveDataNumber(section.dataset.pageHeight, DEFAULT_PAGE_HEIGHT);
|
|
2154
|
+
const marginTop = finiteDataNumber(section.dataset.marginTop, DEFAULT_MARGIN);
|
|
2155
|
+
const marginRight = finiteDataNumber(section.dataset.marginRight, DEFAULT_MARGIN);
|
|
2156
|
+
const marginBottom = finiteDataNumber(section.dataset.marginBottom, DEFAULT_MARGIN);
|
|
2157
|
+
const marginLeft = finiteDataNumber(section.dataset.marginLeft, DEFAULT_MARGIN);
|
|
2158
|
+
const derivedContentWidth = positiveFinite(pageWidth - marginLeft - marginRight, pageWidth);
|
|
2159
|
+
const derivedContentHeight = positiveFinite(pageHeight - marginTop - marginBottom, pageHeight);
|
|
2160
|
+
const contentWidth = positiveDataNumber(section.dataset.contentWidth, derivedContentWidth);
|
|
2161
|
+
const contentHeight = positiveDataNumber(section.dataset.contentHeight, derivedContentHeight);
|
|
2162
|
+
const headerDistance = nonNegativeDataNumber(
|
|
2163
|
+
section.dataset.headerHeight,
|
|
2164
|
+
DEFAULT_HEADER_FOOTER_DISTANCE
|
|
2165
|
+
);
|
|
2166
|
+
const footerDistance = nonNegativeDataNumber(
|
|
2167
|
+
section.dataset.footerHeight,
|
|
2168
|
+
DEFAULT_HEADER_FOOTER_DISTANCE
|
|
2169
|
+
);
|
|
1471
2170
|
return {
|
|
1472
2171
|
pageWidth,
|
|
1473
2172
|
pageHeight,
|
|
@@ -1544,8 +2243,96 @@ var Docxodus = (() => {
|
|
|
1544
2243
|
}
|
|
1545
2244
|
|
|
1546
2245
|
// src/pagination.ts
|
|
2246
|
+
function samePageBox(a, b) {
|
|
2247
|
+
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;
|
|
2248
|
+
}
|
|
2249
|
+
function createUnavailablePageMap(documentVersion, rendererFingerprint, mode = "continuous") {
|
|
2250
|
+
if (!rendererFingerprint) throw new Error("rendererFingerprint must be non-empty");
|
|
2251
|
+
return {
|
|
2252
|
+
schemaVersion: 1,
|
|
2253
|
+
mode,
|
|
2254
|
+
availability: "unavailable",
|
|
2255
|
+
documentVersion,
|
|
2256
|
+
rendererFingerprint,
|
|
2257
|
+
pages: [],
|
|
2258
|
+
fragments: []
|
|
2259
|
+
};
|
|
2260
|
+
}
|
|
2261
|
+
var activePageCitationHighlights = /* @__PURE__ */ new WeakMap();
|
|
2262
|
+
function clearPageCitationHighlight(root) {
|
|
2263
|
+
const active = activePageCitationHighlights.get(root);
|
|
2264
|
+
if (!active) return;
|
|
2265
|
+
if (active.highlightClass && active.addedClass) {
|
|
2266
|
+
active.target.classList.remove(active.highlightClass);
|
|
2267
|
+
}
|
|
2268
|
+
for (const style of active.inlineStyles ?? []) {
|
|
2269
|
+
if (style.value) {
|
|
2270
|
+
active.target.style.setProperty(style.name, style.value, style.priority);
|
|
2271
|
+
} else {
|
|
2272
|
+
active.target.style.removeProperty(style.name);
|
|
2273
|
+
}
|
|
2274
|
+
}
|
|
2275
|
+
activePageCitationHighlights.delete(root);
|
|
2276
|
+
}
|
|
2277
|
+
function navigateToPageCitation(root, citation, options = {}) {
|
|
2278
|
+
clearPageCitationHighlight(root);
|
|
2279
|
+
if (citation.availability !== "available" || citation.fragments.length === 0) {
|
|
2280
|
+
return { navigated: false, unavailableReason: "citation_unavailable" };
|
|
2281
|
+
}
|
|
2282
|
+
const byAttribute = (name, value, within = root) => {
|
|
2283
|
+
for (const node of Array.from(within.querySelectorAll(`[${name}]`))) {
|
|
2284
|
+
if (node.getAttribute(name) === value) return node;
|
|
2285
|
+
}
|
|
2286
|
+
return null;
|
|
2287
|
+
};
|
|
2288
|
+
const fragment = citation.fragments[0];
|
|
2289
|
+
let target = byAttribute("data-page-fragment-id", fragment.fragmentId);
|
|
2290
|
+
if (!target) {
|
|
2291
|
+
const page = byAttribute("data-page-number", String(fragment.pageNumber));
|
|
2292
|
+
if (page) target = byAttribute("data-source-anchor-id", citation.anchorId, page) ?? page;
|
|
2293
|
+
}
|
|
2294
|
+
if (!target) {
|
|
2295
|
+
return {
|
|
2296
|
+
navigated: false,
|
|
2297
|
+
pageNumber: fragment.pageNumber,
|
|
2298
|
+
fragmentId: fragment.fragmentId,
|
|
2299
|
+
unavailableReason: "fragment_not_found"
|
|
2300
|
+
};
|
|
2301
|
+
}
|
|
2302
|
+
if (options.highlightClass) {
|
|
2303
|
+
const addedClass = !target.classList.contains(options.highlightClass);
|
|
2304
|
+
target.classList.add(options.highlightClass);
|
|
2305
|
+
activePageCitationHighlights.set(root, {
|
|
2306
|
+
target,
|
|
2307
|
+
highlightClass: options.highlightClass,
|
|
2308
|
+
addedClass
|
|
2309
|
+
});
|
|
2310
|
+
} else if (options.highlight !== false) {
|
|
2311
|
+
const names = ["outline", "outline-offset", "background-color"];
|
|
2312
|
+
const inlineStyles = names.map((name) => ({
|
|
2313
|
+
name,
|
|
2314
|
+
value: target.style.getPropertyValue(name),
|
|
2315
|
+
priority: target.style.getPropertyPriority(name)
|
|
2316
|
+
}));
|
|
2317
|
+
target.style.setProperty("outline", "3px solid #f4b400", "important");
|
|
2318
|
+
target.style.setProperty("outline-offset", "2px", "important");
|
|
2319
|
+
target.style.setProperty("background-color", "rgba(255, 235, 59, .18)", "important");
|
|
2320
|
+
activePageCitationHighlights.set(root, { target, inlineStyles });
|
|
2321
|
+
}
|
|
2322
|
+
target.scrollIntoView({
|
|
2323
|
+
behavior: options.behavior ?? "smooth",
|
|
2324
|
+
block: options.block ?? "center"
|
|
2325
|
+
});
|
|
2326
|
+
return {
|
|
2327
|
+
navigated: true,
|
|
2328
|
+
target,
|
|
2329
|
+
pageNumber: fragment.pageNumber,
|
|
2330
|
+
fragmentId: fragment.fragmentId
|
|
2331
|
+
};
|
|
2332
|
+
}
|
|
1547
2333
|
var MAX_FOOTNOTE_AREA_RATIO = 0.6;
|
|
1548
|
-
var
|
|
2334
|
+
var FOOTNOTE_MEASUREMENT_GUARD_PT = 2;
|
|
2335
|
+
var FOOTNOTE_SOURCE_POSITION_ATTR = "data-pagination-footnote-source-position";
|
|
1549
2336
|
var PaginationEngine = class {
|
|
1550
2337
|
/**
|
|
1551
2338
|
* Creates a new pagination engine.
|
|
@@ -1555,24 +2342,46 @@ var Docxodus = (() => {
|
|
|
1555
2342
|
* @param options - Pagination options
|
|
1556
2343
|
*/
|
|
1557
2344
|
constructor(staging, container, options = {}) {
|
|
2345
|
+
this.createdPageCount = 0;
|
|
2346
|
+
this.footnoteSeparator = null;
|
|
2347
|
+
this.footnoteContinuationSeparator = null;
|
|
2348
|
+
this.footnoteLayoutLikeWord8 = false;
|
|
1558
2349
|
this.pendingFootnoteContinuation = null;
|
|
1559
2350
|
/** Per-section `w:pgNumType` (start / format), read off the section wrappers. */
|
|
1560
2351
|
this.pageNumbering = /* @__PURE__ */ new Map();
|
|
1561
|
-
this.
|
|
1562
|
-
this.
|
|
2352
|
+
this.lastPages = [];
|
|
2353
|
+
this.expectedPageMapAnchorIds = /* @__PURE__ */ new Set();
|
|
2354
|
+
this.state = "ready";
|
|
2355
|
+
const ownerDocument = typeof staging !== "string" ? staging.ownerDocument : typeof container !== "string" ? container.ownerDocument : globalThis.document;
|
|
2356
|
+
this.stagingElement = typeof staging === "string" ? ownerDocument.getElementById(staging) : staging;
|
|
2357
|
+
this.containerElement = typeof container === "string" ? ownerDocument.getElementById(container) : container;
|
|
1563
2358
|
if (!this.stagingElement) {
|
|
1564
2359
|
throw new Error("Staging element not found");
|
|
1565
2360
|
}
|
|
1566
2361
|
if (!this.containerElement) {
|
|
1567
2362
|
throw new Error("Container element not found");
|
|
1568
2363
|
}
|
|
2364
|
+
if (this.stagingElement.ownerDocument !== this.containerElement.ownerDocument) {
|
|
2365
|
+
throw new Error("Staging and container elements must belong to the same document");
|
|
2366
|
+
}
|
|
2367
|
+
const view = ownerDocument.defaultView;
|
|
2368
|
+
if (!view) {
|
|
2369
|
+
throw new Error("Pagination requires an attached document with a defaultView");
|
|
2370
|
+
}
|
|
2371
|
+
this.document = ownerDocument;
|
|
2372
|
+
this.view = view;
|
|
1569
2373
|
this.scale = options.scale ?? 1;
|
|
1570
2374
|
this.cssPrefix = options.cssPrefix ?? "page-";
|
|
1571
2375
|
this.showPageNumbers = options.showPageNumbers ?? true;
|
|
1572
2376
|
this.pageGap = options.pageGap ?? 20;
|
|
1573
2377
|
this.fragmentParagraphs = options.fragmentParagraphs ?? false;
|
|
2378
|
+
this.deferFragmentIdentities = options.deferFragmentIdentities ?? false;
|
|
2379
|
+
this.cancellationCheckpoint = options.checkCancellation;
|
|
2380
|
+
this.pageCountCheckpoint = options.checkPageCount;
|
|
2381
|
+
this.layoutToken = options.layoutToken;
|
|
1574
2382
|
this.hfRegistry = /* @__PURE__ */ new Map();
|
|
1575
2383
|
this.footnoteRegistry = /* @__PURE__ */ new Map();
|
|
2384
|
+
this.commentMarginRegistry = /* @__PURE__ */ new Map();
|
|
1576
2385
|
}
|
|
1577
2386
|
/**
|
|
1578
2387
|
* Runs the pagination process.
|
|
@@ -1580,31 +2389,469 @@ var Docxodus = (() => {
|
|
|
1580
2389
|
* @returns PaginationResult with page information
|
|
1581
2390
|
*/
|
|
1582
2391
|
paginate() {
|
|
1583
|
-
|
|
1584
|
-
|
|
1585
|
-
|
|
1586
|
-
|
|
1587
|
-
|
|
1588
|
-
"
|
|
1589
|
-
|
|
1590
|
-
|
|
1591
|
-
|
|
1592
|
-
|
|
1593
|
-
|
|
1594
|
-
|
|
1595
|
-
|
|
1596
|
-
|
|
1597
|
-
this.
|
|
1598
|
-
this.
|
|
1599
|
-
|
|
1600
|
-
|
|
1601
|
-
|
|
1602
|
-
|
|
1603
|
-
|
|
2392
|
+
this.checkpoint();
|
|
2393
|
+
if (this.state !== "ready") {
|
|
2394
|
+
throw new Error(`PaginationEngine is one-shot and is already ${this.state}`);
|
|
2395
|
+
}
|
|
2396
|
+
if (!this.stagingElement.isConnected || !this.containerElement.isConnected) {
|
|
2397
|
+
throw new Error("Pagination requires staging and container elements attached to their document");
|
|
2398
|
+
}
|
|
2399
|
+
if (this.document.documentElement.getBoundingClientRect().width <= 0) {
|
|
2400
|
+
throw new Error("Pagination requires a browsing context with non-zero layout");
|
|
2401
|
+
}
|
|
2402
|
+
this.state = "running";
|
|
2403
|
+
try {
|
|
2404
|
+
const pages = [];
|
|
2405
|
+
let pageNumber = 1;
|
|
2406
|
+
this.hfRegistry = this.parseHeaderFooterRegistry();
|
|
2407
|
+
this.footnoteRegistry = this.parseFootnoteRegistry();
|
|
2408
|
+
({
|
|
2409
|
+
normal: this.footnoteSeparator,
|
|
2410
|
+
continuation: this.footnoteContinuationSeparator
|
|
2411
|
+
} = this.parseFootnoteSeparators());
|
|
2412
|
+
this.commentMarginRegistry = this.parseCommentMarginRegistry();
|
|
2413
|
+
this.footnoteLayoutLikeWord8 = this.stagingElement.dataset.footnoteLayoutLikeWord8 === "true";
|
|
2414
|
+
const sections = this.stagingElement.querySelectorAll(
|
|
2415
|
+
"[data-section-index]"
|
|
2416
|
+
);
|
|
2417
|
+
this.pageNumbering = this.parsePageNumbering(sections);
|
|
2418
|
+
const sectionsToProcess = sections.length > 0 ? Array.from(sections) : [this.stagingElement];
|
|
2419
|
+
this.expectedPageMapAnchorIds = /* @__PURE__ */ new Set();
|
|
2420
|
+
const referencedFootnoteIds = /* @__PURE__ */ new Set();
|
|
2421
|
+
const referencedCommentIds = /* @__PURE__ */ new Set();
|
|
2422
|
+
for (const section of sectionsToProcess) {
|
|
2423
|
+
this.checkpoint();
|
|
2424
|
+
this.collectExpectedSourceAnchors(section, this.expectedPageMapAnchorIds, true);
|
|
2425
|
+
for (const reference of Array.from(section.querySelectorAll("[data-footnote-id]"))) {
|
|
2426
|
+
this.checkpoint();
|
|
2427
|
+
if (reference.closest("#pagination-footnote-registry, #pagination-hf-registry")) continue;
|
|
2428
|
+
const id = reference.dataset.footnoteId;
|
|
2429
|
+
if (id) referencedFootnoteIds.add(id);
|
|
2430
|
+
}
|
|
2431
|
+
for (const reference of Array.from(section.querySelectorAll("[data-comment-id]"))) {
|
|
2432
|
+
this.checkpoint();
|
|
2433
|
+
if (reference.closest(
|
|
2434
|
+
"#pagination-comment-margin-registry, #pagination-footnote-registry, #pagination-hf-registry"
|
|
2435
|
+
)) continue;
|
|
2436
|
+
const id = reference.dataset.commentId;
|
|
2437
|
+
if (id) referencedCommentIds.add(id);
|
|
2438
|
+
}
|
|
2439
|
+
}
|
|
2440
|
+
for (const id of referencedFootnoteIds) {
|
|
2441
|
+
this.checkpoint();
|
|
2442
|
+
const source = this.footnoteRegistry.get(id);
|
|
2443
|
+
if (!source) continue;
|
|
2444
|
+
this.collectExpectedSourceAnchors(source, this.expectedPageMapAnchorIds);
|
|
2445
|
+
for (const reference of Array.from(
|
|
2446
|
+
source.querySelectorAll("[data-comment-id]")
|
|
2447
|
+
)) {
|
|
2448
|
+
this.checkpoint();
|
|
2449
|
+
const commentId = reference.dataset.commentId;
|
|
2450
|
+
if (commentId) referencedCommentIds.add(commentId);
|
|
2451
|
+
}
|
|
2452
|
+
}
|
|
2453
|
+
for (const id of referencedCommentIds) {
|
|
2454
|
+
this.checkpoint();
|
|
2455
|
+
const source = this.commentMarginRegistry.get(id);
|
|
2456
|
+
if (source) this.collectExpectedSourceAnchors(source, this.expectedPageMapAnchorIds);
|
|
2457
|
+
}
|
|
2458
|
+
const runs = [];
|
|
2459
|
+
for (const section of sectionsToProcess) {
|
|
2460
|
+
this.checkpoint();
|
|
2461
|
+
const dims = parseSectionDimensions(section);
|
|
2462
|
+
const previous = runs[runs.length - 1];
|
|
2463
|
+
if (previous && section.dataset.sectionType === "continuous" && samePageBox(previous.dims, dims)) {
|
|
2464
|
+
previous.sections.push(section);
|
|
2465
|
+
previous.sectionDimensions.set(
|
|
2466
|
+
parseInt(section.dataset.sectionIndex || "0", 10),
|
|
2467
|
+
dims
|
|
2468
|
+
);
|
|
2469
|
+
} else {
|
|
2470
|
+
const sectionIndex = parseInt(section.dataset.sectionIndex || "0", 10);
|
|
2471
|
+
runs.push({
|
|
2472
|
+
sections: [section],
|
|
2473
|
+
sectionIndex,
|
|
2474
|
+
sectionType: section.dataset.sectionType ?? "nextPage",
|
|
2475
|
+
dims,
|
|
2476
|
+
sectionDimensions: /* @__PURE__ */ new Map([[sectionIndex, dims]])
|
|
2477
|
+
});
|
|
2478
|
+
}
|
|
2479
|
+
}
|
|
2480
|
+
for (const run of runs) {
|
|
2481
|
+
this.checkpoint();
|
|
2482
|
+
const needsParityFiller = pages.length > 0 && (run.sectionType === "oddPage" && pageNumber % 2 === 0 || run.sectionType === "evenPage" && pageNumber % 2 === 1);
|
|
2483
|
+
if (needsParityFiller) {
|
|
2484
|
+
const precedingPage = pages[pages.length - 1];
|
|
2485
|
+
const precedingPageInSection = parseInt(
|
|
2486
|
+
precedingPage.element.dataset.pageInSection ?? "1",
|
|
2487
|
+
10
|
|
2488
|
+
);
|
|
2489
|
+
const precedingDisplayedPageNumber = parseInt(
|
|
2490
|
+
precedingPage.element.dataset.displayedPageNumber ?? String(precedingPage.pageNumber),
|
|
2491
|
+
10
|
|
2492
|
+
);
|
|
2493
|
+
const filler = this.createPage(
|
|
2494
|
+
precedingPage.dimensions,
|
|
2495
|
+
pageNumber,
|
|
2496
|
+
precedingPage.sectionIndex,
|
|
2497
|
+
precedingDisplayedPageNumber + 1,
|
|
2498
|
+
[],
|
|
2499
|
+
precedingPageInSection + 1,
|
|
2500
|
+
[],
|
|
2501
|
+
0,
|
|
2502
|
+
null,
|
|
2503
|
+
void 0,
|
|
2504
|
+
true
|
|
2505
|
+
);
|
|
2506
|
+
pages.push(filler);
|
|
2507
|
+
pageNumber++;
|
|
2508
|
+
}
|
|
2509
|
+
this.stagingElement.style.visibility = "hidden";
|
|
2510
|
+
this.stagingElement.style.position = "absolute";
|
|
2511
|
+
this.stagingElement.style.left = "-9999px";
|
|
2512
|
+
this.stagingElement.style.display = "block";
|
|
2513
|
+
const blocks = [];
|
|
2514
|
+
for (const section of run.sections) {
|
|
2515
|
+
this.checkpoint();
|
|
2516
|
+
const sectionIndex = parseInt(section.dataset.sectionIndex || "0", 10);
|
|
2517
|
+
const sectionDims = run.sectionDimensions.get(sectionIndex) ?? run.dims;
|
|
2518
|
+
section.style.width = `${sectionDims.contentWidth}pt`;
|
|
2519
|
+
const columnCount = parseInt(section.dataset.cols || "1", 10);
|
|
2520
|
+
if (columnCount > 1) {
|
|
2521
|
+
const gap = parseFloat(section.dataset.colGap || "");
|
|
2522
|
+
blocks.push(...this.buildColumnBlocks(
|
|
2523
|
+
section,
|
|
2524
|
+
sectionDims,
|
|
2525
|
+
sectionIndex,
|
|
2526
|
+
columnCount,
|
|
2527
|
+
Number.isFinite(gap) ? gap : 36
|
|
2528
|
+
));
|
|
2529
|
+
} else {
|
|
2530
|
+
blocks.push(...this.measureBlocks(section, sectionDims, sectionIndex));
|
|
2531
|
+
}
|
|
2532
|
+
}
|
|
2533
|
+
const sectionPages = this.flowToPages(
|
|
2534
|
+
blocks,
|
|
2535
|
+
run.dims,
|
|
2536
|
+
pageNumber,
|
|
2537
|
+
run.sectionIndex,
|
|
2538
|
+
run.sectionDimensions
|
|
2539
|
+
);
|
|
2540
|
+
this.checkpoint();
|
|
2541
|
+
pages.push(...sectionPages);
|
|
2542
|
+
pageNumber += sectionPages.length;
|
|
2543
|
+
}
|
|
2544
|
+
this.stagingElement.style.display = "none";
|
|
2545
|
+
this.substitutePageNumberFields(pages.length);
|
|
2546
|
+
for (const page of pages) {
|
|
2547
|
+
this.checkpoint();
|
|
2548
|
+
if (page.element.dataset.sectionFiller === "true") continue;
|
|
2549
|
+
const pageInSection = parseInt(page.element.dataset.pageInSection || "1", 10);
|
|
2550
|
+
const displayedPageNumber = parseInt(
|
|
2551
|
+
page.element.dataset.displayedPageNumber || String(page.pageNumber),
|
|
2552
|
+
10
|
|
2553
|
+
);
|
|
2554
|
+
const header = this.selectHeader(page.sectionIndex, pageInSection, displayedPageNumber);
|
|
2555
|
+
const footer = this.selectFooter(page.sectionIndex, pageInSection, displayedPageNumber);
|
|
2556
|
+
if (header) this.collectExpectedSourceAnchors(header, this.expectedPageMapAnchorIds);
|
|
2557
|
+
if (footer) this.collectExpectedSourceAnchors(footer, this.expectedPageMapAnchorIds);
|
|
2558
|
+
}
|
|
2559
|
+
this.qualifyPageFragments(pages);
|
|
2560
|
+
this.transferVisibleFragmentTargets();
|
|
2561
|
+
if (!this.deferFragmentIdentities) this.normalizeVisiblePageFragments(pages);
|
|
2562
|
+
this.lastPages = pages;
|
|
2563
|
+
const result = {
|
|
2564
|
+
totalPages: pages.length,
|
|
2565
|
+
pages,
|
|
2566
|
+
pageMap: this.layoutToken ? this.materializePageMap(
|
|
2567
|
+
this.layoutToken.documentVersion,
|
|
2568
|
+
this.layoutToken.rendererFingerprint
|
|
2569
|
+
) : void 0
|
|
2570
|
+
};
|
|
2571
|
+
this.state = "complete";
|
|
2572
|
+
return result;
|
|
2573
|
+
} catch (error) {
|
|
2574
|
+
this.state = "failed";
|
|
2575
|
+
throw error;
|
|
2576
|
+
}
|
|
2577
|
+
}
|
|
2578
|
+
checkpoint() {
|
|
2579
|
+
this.cancellationCheckpoint?.();
|
|
2580
|
+
}
|
|
2581
|
+
/**
|
|
2582
|
+
* Normalize visible fragment identities after a caller applies final standalone styles. This
|
|
2583
|
+
* deliberately runs before the stability barrier; materializePageMap is read-only so PageMap
|
|
2584
|
+
* measurement cannot mutate a tree after it was declared stable.
|
|
2585
|
+
*/
|
|
2586
|
+
normalizePageMapFragmentIdentities() {
|
|
2587
|
+
if (this.lastPages.length === 0) {
|
|
2588
|
+
throw new Error("paginate() must complete before fragment identities can be normalized");
|
|
2589
|
+
}
|
|
2590
|
+
this.normalizeVisiblePageFragments(this.lastPages);
|
|
2591
|
+
}
|
|
2592
|
+
/**
|
|
2593
|
+
* Materialize the last completed browser layout as portable page-relative point geometry.
|
|
2594
|
+
* The caller supplies both invalidation tokens; this engine never guesses a document version
|
|
2595
|
+
* or renderer fingerprint.
|
|
2596
|
+
*/
|
|
2597
|
+
materializePageMap(documentVersion, rendererFingerprint) {
|
|
2598
|
+
if (!Number.isSafeInteger(documentVersion) || documentVersion < 0) {
|
|
2599
|
+
throw new Error("documentVersion must be a non-negative safe integer");
|
|
2600
|
+
}
|
|
2601
|
+
if (!rendererFingerprint) throw new Error("rendererFingerprint must be non-empty");
|
|
2602
|
+
if (this.lastPages.length === 0) throw new Error("paginate() must complete before materializePageMap()");
|
|
2603
|
+
const pages = this.lastPages.map((page) => ({
|
|
2604
|
+
pageNumber: page.pageNumber,
|
|
2605
|
+
pageInSection: parseInt(page.element.dataset.pageInSection || "1", 10),
|
|
2606
|
+
width: page.dimensions.pageWidth,
|
|
2607
|
+
height: page.dimensions.pageHeight,
|
|
2608
|
+
sectionIndex: page.sectionIndex,
|
|
2609
|
+
pageName: `docxodus-section-${page.sectionIndex}`
|
|
2610
|
+
}));
|
|
2611
|
+
const fragments = [];
|
|
2612
|
+
const requiredAnchorIds = new Set(this.expectedPageMapAnchorIds);
|
|
2613
|
+
if (requiredAnchorIds.size === 0) {
|
|
2614
|
+
throw new Error("cannot publish an available PageMap without canonical source inventory");
|
|
2615
|
+
}
|
|
2616
|
+
const measuredAnchorIds = /* @__PURE__ */ new Set();
|
|
2617
|
+
const emittedFragmentCounts = /* @__PURE__ */ new Map();
|
|
2618
|
+
for (const page of this.lastPages) {
|
|
2619
|
+
this.checkpoint();
|
|
2620
|
+
const pageRect = page.element.getBoundingClientRect();
|
|
2621
|
+
if (pageRect.width <= 0 || pageRect.height <= 0) {
|
|
2622
|
+
throw new Error(`page ${page.pageNumber} has no measurable geometry`);
|
|
2623
|
+
}
|
|
2624
|
+
const pointPerRenderedX = page.dimensions.pageWidth / pageRect.width;
|
|
2625
|
+
const pointPerRenderedY = page.dimensions.pageHeight / pageRect.height;
|
|
2626
|
+
const nodes = page.element.querySelectorAll("[data-source-anchor-id]");
|
|
2627
|
+
for (const element of Array.from(nodes)) {
|
|
2628
|
+
this.checkpoint();
|
|
2629
|
+
if (this.isDeliberatelyUnrenderedSource(element, page.element)) continue;
|
|
2630
|
+
const anchorId = element.dataset.sourceAnchorId;
|
|
2631
|
+
if (!anchorId || !element.dataset.pageFragmentId || !Number.isInteger(parseInt(element.dataset.fragmentIndex || "", 10))) {
|
|
2632
|
+
throw new Error(`page ${page.pageNumber} contains an unqualified source anchor`);
|
|
2633
|
+
}
|
|
2634
|
+
const rect = element.getBoundingClientRect();
|
|
2635
|
+
const style = this.view.getComputedStyle(element);
|
|
2636
|
+
const deliberatelyHidden = style.display === "none" || style.visibility === "hidden";
|
|
2637
|
+
if (deliberatelyHidden) continue;
|
|
2638
|
+
requiredAnchorIds.add(anchorId);
|
|
2639
|
+
const visibleRect = this.intersectWithClippingAncestors(element, page.element, pageRect, rect);
|
|
2640
|
+
const left2 = visibleRect.left;
|
|
2641
|
+
const top2 = visibleRect.top;
|
|
2642
|
+
const right2 = visibleRect.right;
|
|
2643
|
+
const bottom2 = visibleRect.bottom;
|
|
2644
|
+
if (rect.width <= 0 || rect.height <= 0 || right2 <= left2 || bottom2 <= top2) {
|
|
2645
|
+
continue;
|
|
2646
|
+
}
|
|
2647
|
+
measuredAnchorIds.add(anchorId);
|
|
2648
|
+
const fragmentIndex = emittedFragmentCounts.get(anchorId) ?? 0;
|
|
2649
|
+
emittedFragmentCounts.set(anchorId, fragmentIndex + 1);
|
|
2650
|
+
const fragmentId = `p${page.pageNumber}-f${fragmentIndex}-${anchorId}`;
|
|
2651
|
+
if (element.dataset.fragmentIndex !== String(fragmentIndex) || element.dataset.pageFragmentId !== fragmentId || element.dataset.pageNumber !== String(page.pageNumber)) {
|
|
2652
|
+
throw new Error(
|
|
2653
|
+
`page ${page.pageNumber} fragment identity changed after final-tree normalization`
|
|
2654
|
+
);
|
|
2655
|
+
}
|
|
2656
|
+
fragments.push({
|
|
2657
|
+
fragmentId,
|
|
2658
|
+
anchorId,
|
|
2659
|
+
fragmentIndex,
|
|
2660
|
+
pageNumber: page.pageNumber,
|
|
2661
|
+
geometry: {
|
|
2662
|
+
x: (left2 - pageRect.left) * pointPerRenderedX,
|
|
2663
|
+
y: (top2 - pageRect.top) * pointPerRenderedY,
|
|
2664
|
+
width: (right2 - left2) * pointPerRenderedX,
|
|
2665
|
+
height: (bottom2 - top2) * pointPerRenderedY
|
|
2666
|
+
},
|
|
2667
|
+
story: this.storyForCanonicalAnchor(anchorId),
|
|
2668
|
+
inTableCell: element.matches("td,th") || element.closest("td,th") !== null
|
|
2669
|
+
});
|
|
2670
|
+
}
|
|
2671
|
+
}
|
|
2672
|
+
const missingAnchor = Array.from(requiredAnchorIds).find((id) => !measuredAnchorIds.has(id));
|
|
2673
|
+
if (missingAnchor) {
|
|
2674
|
+
throw new Error(`source anchor ${missingAnchor} has no measurable fragment in the paginated layout`);
|
|
2675
|
+
}
|
|
2676
|
+
return {
|
|
2677
|
+
schemaVersion: 1,
|
|
2678
|
+
mode: "paginated",
|
|
2679
|
+
availability: "available",
|
|
2680
|
+
documentVersion,
|
|
2681
|
+
rendererFingerprint,
|
|
2682
|
+
pages,
|
|
2683
|
+
fragments
|
|
2684
|
+
};
|
|
2685
|
+
}
|
|
2686
|
+
normalizeVisiblePageFragments(pages) {
|
|
2687
|
+
const emittedFragmentCounts = /* @__PURE__ */ new Map();
|
|
2688
|
+
for (const page of pages) {
|
|
2689
|
+
this.checkpoint();
|
|
2690
|
+
const pageRect = page.element.getBoundingClientRect();
|
|
2691
|
+
for (const element of Array.from(
|
|
2692
|
+
page.element.querySelectorAll("[data-source-anchor-id]")
|
|
2693
|
+
)) {
|
|
2694
|
+
this.checkpoint();
|
|
2695
|
+
if (this.isDeliberatelyUnrenderedSource(element, page.element)) continue;
|
|
2696
|
+
const anchorId = element.dataset.sourceAnchorId;
|
|
2697
|
+
if (!anchorId) continue;
|
|
2698
|
+
const style = this.view.getComputedStyle(element);
|
|
2699
|
+
if (style.display === "none" || style.visibility === "hidden") continue;
|
|
2700
|
+
const rect = element.getBoundingClientRect();
|
|
2701
|
+
const visible = this.intersectWithClippingAncestors(element, page.element, pageRect, rect);
|
|
2702
|
+
if (rect.width <= 0 || rect.height <= 0 || visible.right <= visible.left || visible.bottom <= visible.top) continue;
|
|
2703
|
+
const fragmentIndex = emittedFragmentCounts.get(anchorId) ?? 0;
|
|
2704
|
+
emittedFragmentCounts.set(anchorId, fragmentIndex + 1);
|
|
2705
|
+
element.dataset.pageNumber = String(page.pageNumber);
|
|
2706
|
+
element.dataset.fragmentIndex = String(fragmentIndex);
|
|
2707
|
+
element.dataset.pageFragmentId = `p${page.pageNumber}-f${fragmentIndex}-${anchorId}`;
|
|
2708
|
+
}
|
|
2709
|
+
}
|
|
2710
|
+
}
|
|
2711
|
+
/**
|
|
2712
|
+
* Intersect an element with every ancestor that establishes an overflow clip before the page
|
|
2713
|
+
* root. getBoundingClientRect() reports layout outside those clips, which is not rendered and
|
|
2714
|
+
* therefore must not satisfy PageMap completeness or inflate portable geometry.
|
|
2715
|
+
*/
|
|
2716
|
+
intersectWithClippingAncestors(element, page, pageRect, rect) {
|
|
2717
|
+
let left2 = Math.max(rect.left, pageRect.left);
|
|
2718
|
+
let top2 = Math.max(rect.top, pageRect.top);
|
|
2719
|
+
let right2 = Math.min(rect.right, pageRect.right);
|
|
2720
|
+
let bottom2 = Math.min(rect.bottom, pageRect.bottom);
|
|
2721
|
+
const clips = (value) => value === "hidden" || value === "clip" || value === "scroll" || value === "auto";
|
|
2722
|
+
for (let ancestor = element.parentElement; ancestor && ancestor !== page; ancestor = ancestor.parentElement) {
|
|
2723
|
+
const style = this.view.getComputedStyle(ancestor);
|
|
2724
|
+
const clipsX = clips(style.overflowX);
|
|
2725
|
+
const clipsY = clips(style.overflowY);
|
|
2726
|
+
if (!clipsX && !clipsY) continue;
|
|
2727
|
+
const ancestorRect = ancestor.getBoundingClientRect();
|
|
2728
|
+
if (clipsX) {
|
|
2729
|
+
left2 = Math.max(left2, ancestorRect.left);
|
|
2730
|
+
right2 = Math.min(right2, ancestorRect.right);
|
|
2731
|
+
}
|
|
2732
|
+
if (clipsY) {
|
|
2733
|
+
top2 = Math.max(top2, ancestorRect.top);
|
|
2734
|
+
bottom2 = Math.min(bottom2, ancestorRect.bottom);
|
|
2735
|
+
}
|
|
2736
|
+
}
|
|
2737
|
+
return { left: left2, top: top2, right: right2, bottom: bottom2 };
|
|
2738
|
+
}
|
|
2739
|
+
storyForCanonicalAnchor(anchorId) {
|
|
2740
|
+
const first = anchorId.indexOf(":");
|
|
2741
|
+
const second = first < 0 ? -1 : anchorId.indexOf(":", first + 1);
|
|
2742
|
+
const scope = first >= 0 && second > first ? anchorId.slice(first + 1, second) : "body";
|
|
2743
|
+
if (scope.startsWith("hdr")) return "header";
|
|
2744
|
+
if (scope.startsWith("ftr")) return "footer";
|
|
2745
|
+
if (scope === "fn") return "footnote";
|
|
2746
|
+
if (scope === "en") return "endnote";
|
|
2747
|
+
if (scope === "cmt") return "comment";
|
|
2748
|
+
return "body";
|
|
2749
|
+
}
|
|
2750
|
+
/**
|
|
2751
|
+
* Add canonical IDs from an addressable source subtree to the pre-pagination inventory.
|
|
2752
|
+
* Registry wrappers are excluded when scanning staging because selectable registry contents are
|
|
2753
|
+
* inventoried separately. Producers may explicitly mark content that has no visual substrate
|
|
2754
|
+
* with `data-page-map-exclude="true"`; native hidden semantics carry the same signal.
|
|
2755
|
+
*/
|
|
2756
|
+
collectExpectedSourceAnchors(source, destination, excludeRegistries = false) {
|
|
2757
|
+
const candidates = source.matches("[data-source-anchor-id]") ? [source] : [];
|
|
2758
|
+
candidates.push(...Array.from(source.querySelectorAll("[data-source-anchor-id]")));
|
|
2759
|
+
for (const element of candidates) {
|
|
2760
|
+
if (excludeRegistries && element.closest(
|
|
2761
|
+
"#pagination-hf-registry, #pagination-footnote-registry, #pagination-comment-margin-registry"
|
|
2762
|
+
)) {
|
|
2763
|
+
continue;
|
|
2764
|
+
}
|
|
2765
|
+
if (this.isZeroHeightExplicitBreakCarrier(element)) {
|
|
2766
|
+
element.dataset.pageMapExclude = "true";
|
|
2767
|
+
}
|
|
2768
|
+
if (this.isDeliberatelyUnrenderedSource(element, source)) continue;
|
|
2769
|
+
const anchorId = element.dataset.sourceAnchorId;
|
|
2770
|
+
if (anchorId) destination.add(anchorId);
|
|
2771
|
+
}
|
|
2772
|
+
}
|
|
2773
|
+
isZeroHeightExplicitBreakCarrier(element) {
|
|
2774
|
+
const next = element.nextElementSibling;
|
|
2775
|
+
const bounds = element.getBoundingClientRect();
|
|
2776
|
+
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);
|
|
2777
|
+
}
|
|
2778
|
+
isDeliberatelyUnrenderedSource(element, sourceRoot) {
|
|
2779
|
+
for (let current = element; current; current = current.parentElement) {
|
|
2780
|
+
const zeroHeightExplicitBreakCarrier = current === element && this.isZeroHeightExplicitBreakCarrier(current);
|
|
2781
|
+
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") {
|
|
2782
|
+
return true;
|
|
2783
|
+
}
|
|
2784
|
+
if (current === sourceRoot) break;
|
|
2785
|
+
}
|
|
2786
|
+
return false;
|
|
2787
|
+
}
|
|
2788
|
+
/**
|
|
2789
|
+
* Keep exactly one active bare-Unid editor anchor per source block. Presentation clones use
|
|
2790
|
+
* canonical source identity plus page/fragment qualification instead.
|
|
2791
|
+
*/
|
|
2792
|
+
qualifyPageFragments(pages) {
|
|
2793
|
+
const fragmentCounts = /* @__PURE__ */ new Map();
|
|
2794
|
+
const activeCanonicalIds = /* @__PURE__ */ new Set();
|
|
2795
|
+
const activeBareAnchorIds = /* @__PURE__ */ new Set();
|
|
2796
|
+
const makeInactive = (element) => {
|
|
2797
|
+
element.removeAttribute("data-anchor");
|
|
2798
|
+
element.removeAttribute("data-committed-text");
|
|
2799
|
+
if (element.hasAttribute("contenteditable")) element.setAttribute("contenteditable", "false");
|
|
2800
|
+
};
|
|
2801
|
+
for (const page of pages) {
|
|
2802
|
+
const nodes = page.element.querySelectorAll("[data-source-anchor-id]");
|
|
2803
|
+
for (const element of Array.from(nodes)) {
|
|
2804
|
+
const anchorId = element.dataset.sourceAnchorId;
|
|
2805
|
+
if (!anchorId) continue;
|
|
2806
|
+
const fragmentIndex = fragmentCounts.get(anchorId) ?? 0;
|
|
2807
|
+
fragmentCounts.set(anchorId, fragmentIndex + 1);
|
|
2808
|
+
element.dataset.pageNumber = String(page.pageNumber);
|
|
2809
|
+
element.dataset.fragmentIndex = String(fragmentIndex);
|
|
2810
|
+
element.dataset.pageFragmentId = `p${page.pageNumber}-f${fragmentIndex}-${anchorId}`;
|
|
2811
|
+
const story = this.storyForCanonicalAnchor(anchorId);
|
|
2812
|
+
const mayOwnActiveEditorAnchor = story === "body" || story === "comment" || story === "footnote" || story === "endnote";
|
|
2813
|
+
if (element.hasAttribute("data-anchor")) {
|
|
2814
|
+
const bareAnchorId = element.dataset.anchor;
|
|
2815
|
+
if (mayOwnActiveEditorAnchor && !activeCanonicalIds.has(anchorId) && !activeBareAnchorIds.has(bareAnchorId)) {
|
|
2816
|
+
activeCanonicalIds.add(anchorId);
|
|
2817
|
+
activeBareAnchorIds.add(bareAnchorId);
|
|
2818
|
+
} else {
|
|
2819
|
+
makeInactive(element);
|
|
2820
|
+
}
|
|
2821
|
+
}
|
|
2822
|
+
}
|
|
2823
|
+
}
|
|
2824
|
+
const activeStagingCanonicalIds = /* @__PURE__ */ new Set();
|
|
2825
|
+
for (const element of Array.from(
|
|
2826
|
+
this.stagingElement.querySelectorAll("[data-source-anchor-id][data-anchor]")
|
|
2827
|
+
)) {
|
|
2828
|
+
const anchorId = element.dataset.sourceAnchorId;
|
|
2829
|
+
if (!anchorId) continue;
|
|
2830
|
+
const bareAnchorId = element.dataset.anchor;
|
|
2831
|
+
if (activeCanonicalIds.has(anchorId) || activeStagingCanonicalIds.has(anchorId) || activeBareAnchorIds.has(bareAnchorId)) {
|
|
2832
|
+
makeInactive(element);
|
|
2833
|
+
} else {
|
|
2834
|
+
activeStagingCanonicalIds.add(anchorId);
|
|
2835
|
+
activeBareAnchorIds.add(bareAnchorId);
|
|
2836
|
+
}
|
|
2837
|
+
}
|
|
2838
|
+
}
|
|
2839
|
+
/**
|
|
2840
|
+
* Page flow clones source blocks while the hidden staging tree stays in the document. Any HTML
|
|
2841
|
+
* fragment target copied into a visible page would therefore resolve to its earlier hidden
|
|
2842
|
+
* source. Transfer target ownership to the page presentation after flow is complete; registry
|
|
2843
|
+
* and wrapper IDs that have no visible counterpart remain available to pagination internals.
|
|
2844
|
+
*/
|
|
2845
|
+
transferVisibleFragmentTargets() {
|
|
2846
|
+
const visibleIds = new Set(Array.from(
|
|
2847
|
+
this.containerElement.querySelectorAll("[id]")
|
|
2848
|
+
).map((element) => element.id).filter(Boolean));
|
|
2849
|
+
if (visibleIds.size === 0) return;
|
|
2850
|
+
for (const source of Array.from(
|
|
2851
|
+
this.stagingElement.querySelectorAll("[id]")
|
|
2852
|
+
)) {
|
|
2853
|
+
if (visibleIds.has(source.id)) source.removeAttribute("id");
|
|
1604
2854
|
}
|
|
1605
|
-
this.stagingElement.style.display = "none";
|
|
1606
|
-
this.substitutePageNumberFields(pages.length);
|
|
1607
|
-
return { totalPages: pages.length, pages };
|
|
1608
2855
|
}
|
|
1609
2856
|
/** Read each section's `w:pgNumType` off its wrapper (see {@link SectionPageNumbering}). */
|
|
1610
2857
|
parsePageNumbering(sections) {
|
|
@@ -1648,9 +2895,8 @@ var Docxodus = (() => {
|
|
|
1648
2895
|
if (markers.length === 0) continue;
|
|
1649
2896
|
const sectionIndex = parseInt(box.dataset.sectionIndex || "0", 10);
|
|
1650
2897
|
const pageNumber = parseInt(box.dataset.pageNumber || "1", 10);
|
|
1651
|
-
const pageInSection = parseInt(box.dataset.pageInSection || "1", 10);
|
|
1652
2898
|
const numbering = this.pageNumbering.get(sectionIndex) ?? {};
|
|
1653
|
-
const displayed =
|
|
2899
|
+
const displayed = parseInt(box.dataset.displayedPageNumber || String(pageNumber), 10);
|
|
1654
2900
|
for (const marker of Array.from(markers)) {
|
|
1655
2901
|
const kind = marker.dataset.field;
|
|
1656
2902
|
if (kind !== "PAGE" && kind !== "NUMPAGES") continue;
|
|
@@ -1662,17 +2908,26 @@ var Docxodus = (() => {
|
|
|
1662
2908
|
/**
|
|
1663
2909
|
* Measures all content blocks in a section.
|
|
1664
2910
|
*/
|
|
1665
|
-
measureBlocks(section, dims) {
|
|
2911
|
+
measureBlocks(section, dims, sectionIndex) {
|
|
1666
2912
|
const blocks = [];
|
|
1667
2913
|
const children = Array.from(section.children);
|
|
1668
2914
|
for (const child of children) {
|
|
2915
|
+
this.checkpoint();
|
|
1669
2916
|
if (child.dataset.sectionIndex !== void 0) {
|
|
1670
|
-
const
|
|
2917
|
+
const nestedSectionIndex = parseInt(child.dataset.sectionIndex || String(sectionIndex), 10);
|
|
2918
|
+
const nestedBlocks = this.measureBlocks(child, dims, nestedSectionIndex);
|
|
1671
2919
|
blocks.push(...nestedBlocks);
|
|
1672
2920
|
continue;
|
|
1673
2921
|
}
|
|
2922
|
+
if (child.matches("section.endnotes")) {
|
|
2923
|
+
const endnoteBlocks = this.measureSafeEndnoteBlocks(child, dims, sectionIndex);
|
|
2924
|
+
if (endnoteBlocks) {
|
|
2925
|
+
blocks.push(...endnoteBlocks);
|
|
2926
|
+
continue;
|
|
2927
|
+
}
|
|
2928
|
+
}
|
|
1674
2929
|
const rect = child.getBoundingClientRect();
|
|
1675
|
-
const style =
|
|
2930
|
+
const style = this.view.getComputedStyle(child);
|
|
1676
2931
|
const marginTopPx = parseFloat(style.marginTop) || 0;
|
|
1677
2932
|
const marginBottomPx = parseFloat(style.marginBottom) || 0;
|
|
1678
2933
|
const heightPt = pxToPt(rect.height);
|
|
@@ -1681,14 +2936,166 @@ var Docxodus = (() => {
|
|
|
1681
2936
|
const isPageBreak = child.dataset.pageBreak === "true" || child.classList.contains(`${this.cssPrefix}break`);
|
|
1682
2937
|
blocks.push({
|
|
1683
2938
|
element: child,
|
|
2939
|
+
sectionIndex,
|
|
1684
2940
|
heightPt,
|
|
1685
2941
|
marginTopPt,
|
|
1686
2942
|
marginBottomPt,
|
|
1687
2943
|
keepWithNext: child.dataset.keepWithNext === "true",
|
|
1688
2944
|
keepLines: child.dataset.keepLines === "true",
|
|
1689
2945
|
pageBreakBefore: child.dataset.pageBreakBefore === "true",
|
|
1690
|
-
isPageBreak
|
|
2946
|
+
isPageBreak,
|
|
2947
|
+
isWordParagraph: this.isWordParagraphElement(child)
|
|
2948
|
+
});
|
|
2949
|
+
}
|
|
2950
|
+
return blocks;
|
|
2951
|
+
}
|
|
2952
|
+
/**
|
|
2953
|
+
* Flatten the converter's `section.endnotes > ol > li > p` presentation into
|
|
2954
|
+
* ordinary paragraph blocks. This preserves paragraph formatting and canonical
|
|
2955
|
+
* p:en/en:en identities while allowing the existing paragraph fragmenter to
|
|
2956
|
+
* split a long endnote across page boundaries.
|
|
2957
|
+
*/
|
|
2958
|
+
measureSafeEndnoteBlocks(section, dims, sectionIndex) {
|
|
2959
|
+
const sectionChildren = Array.from(section.children);
|
|
2960
|
+
const list = sectionChildren.find((child) => child.tagName === "OL");
|
|
2961
|
+
if (!list || sectionChildren.some((child) => child.tagName !== "HR" && child !== list) || Array.from(list.children).some((child) => child.tagName !== "LI")) {
|
|
2962
|
+
return null;
|
|
2963
|
+
}
|
|
2964
|
+
const items = Array.from(list.children);
|
|
2965
|
+
if (items.length === 0 || items.some(
|
|
2966
|
+
(item) => item.children.length === 0 || Array.from(item.children).some((child) => child.tagName !== "P")
|
|
2967
|
+
)) {
|
|
2968
|
+
return null;
|
|
2969
|
+
}
|
|
2970
|
+
const blocks = [];
|
|
2971
|
+
const sectionStyle = this.view.getComputedStyle(section);
|
|
2972
|
+
for (const rule of sectionChildren.filter((child) => child.tagName === "HR")) {
|
|
2973
|
+
this.checkpoint();
|
|
2974
|
+
const clonedRule = rule.cloneNode(true);
|
|
2975
|
+
if (blocks.length === 0) clonedRule.style.marginTop = sectionStyle.marginTop;
|
|
2976
|
+
blocks.push(this.measureElement(clonedRule, dims, sectionIndex));
|
|
2977
|
+
}
|
|
2978
|
+
const listStyle = this.view.getComputedStyle(list);
|
|
2979
|
+
for (let itemIndex = 0; itemIndex < items.length; itemIndex++) {
|
|
2980
|
+
this.checkpoint();
|
|
2981
|
+
const item = items[itemIndex];
|
|
2982
|
+
const ownerAnchorId = item.dataset.sourceAnchorId;
|
|
2983
|
+
const paragraphs = Array.from(item.children);
|
|
2984
|
+
for (let paragraphIndex = 0; paragraphIndex < paragraphs.length; paragraphIndex++) {
|
|
2985
|
+
this.checkpoint();
|
|
2986
|
+
const paragraph = paragraphs[paragraphIndex];
|
|
2987
|
+
if (!this.hasRangeFragmentSafeLayout(paragraph)) {
|
|
2988
|
+
return null;
|
|
2989
|
+
}
|
|
2990
|
+
const clone = paragraph.cloneNode(true);
|
|
2991
|
+
clone.dataset.paginationSafeEndnote = "true";
|
|
2992
|
+
clone.style.fontSize ||= sectionStyle.fontSize;
|
|
2993
|
+
clone.style.lineHeight ||= sectionStyle.lineHeight;
|
|
2994
|
+
clone.style.paddingLeft ||= listStyle.paddingLeft;
|
|
2995
|
+
if (ownerAnchorId && !Array.from(
|
|
2996
|
+
clone.querySelectorAll("[data-source-anchor-id]")
|
|
2997
|
+
).some((node) => node.dataset.sourceAnchorId === ownerAnchorId)) {
|
|
2998
|
+
const owner = this.document.createElement("span");
|
|
2999
|
+
owner.dataset.sourceAnchorId = ownerAnchorId;
|
|
3000
|
+
while (clone.firstChild) owner.appendChild(clone.firstChild);
|
|
3001
|
+
clone.appendChild(owner);
|
|
3002
|
+
}
|
|
3003
|
+
if (paragraphIndex === 0) {
|
|
3004
|
+
const itemId = item.id;
|
|
3005
|
+
if (itemId) {
|
|
3006
|
+
if (clone.id && clone.id !== itemId) return null;
|
|
3007
|
+
clone.id = itemId;
|
|
3008
|
+
}
|
|
3009
|
+
const value = parseInt(item.getAttribute("value") || String(itemIndex + 1), 10);
|
|
3010
|
+
const marker = this.formatOrderedListMarker(
|
|
3011
|
+
Number.isFinite(value) ? value : itemIndex + 1,
|
|
3012
|
+
listStyle.listStyleType
|
|
3013
|
+
);
|
|
3014
|
+
clone.insertBefore(this.document.createTextNode(`${marker}. `), clone.firstChild);
|
|
3015
|
+
}
|
|
3016
|
+
blocks.push(this.measureElement(clone, dims, sectionIndex));
|
|
3017
|
+
}
|
|
3018
|
+
}
|
|
3019
|
+
return blocks;
|
|
3020
|
+
}
|
|
3021
|
+
/** Render the CSS ordered-list formats emitted by the converter after an endnote is flattened. */
|
|
3022
|
+
formatOrderedListMarker(value, listStyleType) {
|
|
3023
|
+
const format = (() => {
|
|
3024
|
+
switch (listStyleType) {
|
|
3025
|
+
case "lower-roman":
|
|
3026
|
+
return "lowerRoman";
|
|
3027
|
+
case "upper-roman":
|
|
3028
|
+
return "upperRoman";
|
|
3029
|
+
case "lower-alpha":
|
|
3030
|
+
case "lower-latin":
|
|
3031
|
+
return "lowerLetter";
|
|
3032
|
+
case "upper-alpha":
|
|
3033
|
+
case "upper-latin":
|
|
3034
|
+
return "upperLetter";
|
|
3035
|
+
default:
|
|
3036
|
+
return "decimal";
|
|
3037
|
+
}
|
|
3038
|
+
})();
|
|
3039
|
+
return formatPageNumber(value, format);
|
|
3040
|
+
}
|
|
3041
|
+
/**
|
|
3042
|
+
* Flows a multi-column (`w:cols`) section's children into CSS-multicol container
|
|
3043
|
+
* blocks. Word lays such a section out as N columns inside the same body extent;
|
|
3044
|
+
* a balanced `column-count` container reproduces that geometry, and the paginator
|
|
3045
|
+
* then places each container as one ordinary measured block. A container grows
|
|
3046
|
+
* greedily until its balanced height would exceed the smallest page body available
|
|
3047
|
+
* to the section, so a long columned section still splits across pages at block
|
|
3048
|
+
* boundaries. Each child lands in exactly one container, so anchors never
|
|
3049
|
+
* duplicate. An explicit page break child passes through as its own block, which
|
|
3050
|
+
* ends the current container and lets the normal flow logic turn the page.
|
|
3051
|
+
*/
|
|
3052
|
+
buildColumnBlocks(section, dims, sectionIndex, columnCount, columnGapPt) {
|
|
3053
|
+
const children = Array.from(section.children);
|
|
3054
|
+
const blocks = [];
|
|
3055
|
+
const maxFragmentHeight = this.smallestEffectiveContentHeight(dims, sectionIndex);
|
|
3056
|
+
const isBreak = (child) => child.dataset.pageBreak === "true" || child.classList.contains(`${this.cssPrefix}break`);
|
|
3057
|
+
const makeContainer = (slice) => {
|
|
3058
|
+
const container = this.document.createElement("div");
|
|
3059
|
+
container.style.columnCount = String(columnCount);
|
|
3060
|
+
container.style.columnGap = `${columnGapPt}pt`;
|
|
3061
|
+
for (const child of slice) {
|
|
3062
|
+
container.appendChild(child.cloneNode(true));
|
|
3063
|
+
}
|
|
3064
|
+
return container;
|
|
3065
|
+
};
|
|
3066
|
+
let start2 = 0;
|
|
3067
|
+
while (start2 < children.length) {
|
|
3068
|
+
this.checkpoint();
|
|
3069
|
+
if (isBreak(children[start2])) {
|
|
3070
|
+
blocks.push(this.measureElement(children[start2], dims, sectionIndex));
|
|
3071
|
+
start2++;
|
|
3072
|
+
continue;
|
|
3073
|
+
}
|
|
3074
|
+
let end = start2 + 1;
|
|
3075
|
+
let container = makeContainer(children.slice(start2, end));
|
|
3076
|
+
let measured = this.measureElement(container, dims, sectionIndex);
|
|
3077
|
+
while (end < children.length && !isBreak(children[end])) {
|
|
3078
|
+
this.checkpoint();
|
|
3079
|
+
const candidate = makeContainer(children.slice(start2, end + 1));
|
|
3080
|
+
const candidateMeasured = this.measureElement(candidate, dims, sectionIndex);
|
|
3081
|
+
if (candidateMeasured.heightPt > maxFragmentHeight) break;
|
|
3082
|
+
container = candidate;
|
|
3083
|
+
measured = candidateMeasured;
|
|
3084
|
+
end++;
|
|
3085
|
+
}
|
|
3086
|
+
blocks.push({
|
|
3087
|
+
element: container,
|
|
3088
|
+
sectionIndex,
|
|
3089
|
+
heightPt: measured.heightPt,
|
|
3090
|
+
marginTopPt: measured.marginTopPt,
|
|
3091
|
+
marginBottomPt: measured.marginBottomPt,
|
|
3092
|
+
keepWithNext: false,
|
|
3093
|
+
keepLines: false,
|
|
3094
|
+
pageBreakBefore: false,
|
|
3095
|
+
isPageBreak: false,
|
|
3096
|
+
isWordParagraph: false
|
|
1691
3097
|
});
|
|
3098
|
+
start2 = end;
|
|
1692
3099
|
}
|
|
1693
3100
|
return blocks;
|
|
1694
3101
|
}
|
|
@@ -1697,8 +3104,8 @@ var Docxodus = (() => {
|
|
|
1697
3104
|
* This is intentionally DOM-based: table row heights cannot be inferred from individual
|
|
1698
3105
|
* rows because wrapping and collapsed borders change the height of a fragment.
|
|
1699
3106
|
*/
|
|
1700
|
-
measureElement(element, dims) {
|
|
1701
|
-
const measurementHost = document.createElement("div");
|
|
3107
|
+
measureElement(element, dims, sectionIndex) {
|
|
3108
|
+
const measurementHost = this.document.createElement("div");
|
|
1702
3109
|
measurementHost.style.position = "absolute";
|
|
1703
3110
|
measurementHost.style.visibility = "hidden";
|
|
1704
3111
|
measurementHost.style.left = "-9999px";
|
|
@@ -1707,16 +3114,18 @@ var Docxodus = (() => {
|
|
|
1707
3114
|
measurementHost.appendChild(measuredElement);
|
|
1708
3115
|
this.stagingElement.appendChild(measurementHost);
|
|
1709
3116
|
const rect = measuredElement.getBoundingClientRect();
|
|
1710
|
-
const style =
|
|
3117
|
+
const style = this.view.getComputedStyle(measuredElement);
|
|
1711
3118
|
const measured = {
|
|
1712
3119
|
element,
|
|
3120
|
+
sectionIndex,
|
|
1713
3121
|
heightPt: pxToPt(rect.height),
|
|
1714
3122
|
marginTopPt: pxToPt(parseFloat(style.marginTop) || 0),
|
|
1715
3123
|
marginBottomPt: pxToPt(parseFloat(style.marginBottom) || 0),
|
|
1716
3124
|
keepWithNext: element.dataset.keepWithNext === "true",
|
|
1717
3125
|
keepLines: element.dataset.keepLines === "true",
|
|
1718
3126
|
pageBreakBefore: element.dataset.pageBreakBefore === "true",
|
|
1719
|
-
isPageBreak: element.dataset.pageBreak === "true" || element.classList.contains(`${this.cssPrefix}break`)
|
|
3127
|
+
isPageBreak: element.dataset.pageBreak === "true" || element.classList.contains(`${this.cssPrefix}break`),
|
|
3128
|
+
isWordParagraph: this.isWordParagraphElement(element)
|
|
1720
3129
|
};
|
|
1721
3130
|
this.stagingElement.removeChild(measurementHost);
|
|
1722
3131
|
return measured;
|
|
@@ -1748,10 +3157,15 @@ var Docxodus = (() => {
|
|
|
1748
3157
|
* collapsed-margin rules as normal block placement. The trailing margin is
|
|
1749
3158
|
* intentionally excluded, matching the individual block fit check.
|
|
1750
3159
|
*/
|
|
1751
|
-
measureKeepWithNextChainBodyHeight(chain, previousMarginBottomPt, isFirstOnPage) {
|
|
3160
|
+
measureKeepWithNextChainBodyHeight(chain, previousMarginBottomPt, isFirstOnPage, pageInSection) {
|
|
1752
3161
|
const firstBlock = chain[0];
|
|
1753
3162
|
if (!firstBlock) return 0;
|
|
1754
|
-
const firstMarginTop =
|
|
3163
|
+
const firstMarginTop = this.effectiveBlockMarginTop(
|
|
3164
|
+
firstBlock,
|
|
3165
|
+
previousMarginBottomPt,
|
|
3166
|
+
isFirstOnPage,
|
|
3167
|
+
pageInSection
|
|
3168
|
+
);
|
|
1755
3169
|
let bodyHeight = firstMarginTop + firstBlock.heightPt;
|
|
1756
3170
|
for (let index = 1; index < chain.length; index++) {
|
|
1757
3171
|
const previousBlock = chain[index - 1];
|
|
@@ -1760,6 +3174,35 @@ var Docxodus = (() => {
|
|
|
1760
3174
|
}
|
|
1761
3175
|
return bodyHeight;
|
|
1762
3176
|
}
|
|
3177
|
+
/** Word paragraphs render as `p`, or as `h1`–`h6` when their style has an outline level. */
|
|
3178
|
+
isWordParagraphElement(element) {
|
|
3179
|
+
return element.tagName === "P" || /^H[1-6]$/.test(element.tagName);
|
|
3180
|
+
}
|
|
3181
|
+
shouldSuppressPageTopSpacing(block, isFirstOnPage, pageInSection) {
|
|
3182
|
+
return isFirstOnPage && pageInSection > 1 && block.isWordParagraph === true;
|
|
3183
|
+
}
|
|
3184
|
+
/**
|
|
3185
|
+
* Resolves the part of a block's top margin that consumes the current page.
|
|
3186
|
+
*
|
|
3187
|
+
* In Word's native DOCX layout, paragraph space-before is suppressed when a paragraph is the
|
|
3188
|
+
* first body block on a later page of the SAME section. The first page of a document/section is
|
|
3189
|
+
* the exception and keeps its spacing. Tables and other block margins are not paragraph spacing,
|
|
3190
|
+
* so they continue to use the ordinary CSS collapsing rule.
|
|
3191
|
+
*/
|
|
3192
|
+
effectiveBlockMarginTop(block, previousMarginBottomPt, isFirstOnPage, pageInSection) {
|
|
3193
|
+
if (this.shouldSuppressPageTopSpacing(block, isFirstOnPage, pageInSection)) {
|
|
3194
|
+
return 0;
|
|
3195
|
+
}
|
|
3196
|
+
return isFirstOnPage ? block.marginTopPt : Math.max(block.marginTopPt, previousMarginBottomPt) - previousMarginBottomPt;
|
|
3197
|
+
}
|
|
3198
|
+
/** Clone a source block with the same page-top spacing decision used by the height budget. */
|
|
3199
|
+
cloneBlockForPage(block, isFirstOnPage, pageInSection) {
|
|
3200
|
+
const clone = block.element.cloneNode(true);
|
|
3201
|
+
if (this.shouldSuppressPageTopSpacing(block, isFirstOnPage, pageInSection)) {
|
|
3202
|
+
clone.style.setProperty("margin-top", "0", "important");
|
|
3203
|
+
}
|
|
3204
|
+
return clone;
|
|
3205
|
+
}
|
|
1763
3206
|
/**
|
|
1764
3207
|
* Finds footnote references introduced by a sequence of blocks, preserving
|
|
1765
3208
|
* document order and excluding references already assigned to the page.
|
|
@@ -1768,6 +3211,7 @@ var Docxodus = (() => {
|
|
|
1768
3211
|
const knownIds = new Set(existingFootnoteIds);
|
|
1769
3212
|
const newIds = [];
|
|
1770
3213
|
for (const block of blocks) {
|
|
3214
|
+
this.checkpoint();
|
|
1771
3215
|
for (const id of this.extractFootnoteRefs(block.element)) {
|
|
1772
3216
|
if (!knownIds.has(id)) {
|
|
1773
3217
|
knownIds.add(id);
|
|
@@ -1785,8 +3229,8 @@ var Docxodus = (() => {
|
|
|
1785
3229
|
smallestEffectiveContentHeight(dims, sectionIndex) {
|
|
1786
3230
|
return Math.min(
|
|
1787
3231
|
this.getPageBands(dims, sectionIndex, 1, 1).bodyHeight,
|
|
1788
|
-
this.getPageBands(dims, sectionIndex, 2,
|
|
1789
|
-
this.getPageBands(dims, sectionIndex,
|
|
3232
|
+
this.getPageBands(dims, sectionIndex, 2, 2).bodyHeight,
|
|
3233
|
+
this.getPageBands(dims, sectionIndex, 3, 3).bodyHeight
|
|
1790
3234
|
);
|
|
1791
3235
|
}
|
|
1792
3236
|
/**
|
|
@@ -1824,10 +3268,11 @@ var Docxodus = (() => {
|
|
|
1824
3268
|
if (wrapper.tagName !== "DIV" || wrapper.children.length !== 1 || block.keepWithNext || block.keepLines || block.pageBreakBefore || block.isPageBreak) {
|
|
1825
3269
|
return null;
|
|
1826
3270
|
}
|
|
1827
|
-
const
|
|
1828
|
-
if (!
|
|
3271
|
+
const tableElement = wrapper.firstElementChild;
|
|
3272
|
+
if (!tableElement || tableElement.localName !== "table") {
|
|
1829
3273
|
return null;
|
|
1830
3274
|
}
|
|
3275
|
+
const table = tableElement;
|
|
1831
3276
|
const body = table.tBodies.length === 1 ? table.tBodies[0] : null;
|
|
1832
3277
|
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]")) {
|
|
1833
3278
|
return null;
|
|
@@ -1841,8 +3286,10 @@ var Docxodus = (() => {
|
|
|
1841
3286
|
const groups = [];
|
|
1842
3287
|
let start2 = 0;
|
|
1843
3288
|
while (start2 < rows.length) {
|
|
3289
|
+
this.checkpoint();
|
|
1844
3290
|
let end = start2;
|
|
1845
3291
|
while (end < rows.length) {
|
|
3292
|
+
this.checkpoint();
|
|
1846
3293
|
const candidate = this.createSimpleTableFragment(
|
|
1847
3294
|
wrapper,
|
|
1848
3295
|
table,
|
|
@@ -1850,7 +3297,7 @@ var Docxodus = (() => {
|
|
|
1850
3297
|
rows.slice(start2, end + 1),
|
|
1851
3298
|
start2 === 0
|
|
1852
3299
|
);
|
|
1853
|
-
const measured = this.measureElement(candidate, dims);
|
|
3300
|
+
const measured = this.measureElement(candidate, dims, block.sectionIndex);
|
|
1854
3301
|
if (measured.heightPt > maximumFragmentHeight) {
|
|
1855
3302
|
break;
|
|
1856
3303
|
}
|
|
@@ -1882,7 +3329,7 @@ var Docxodus = (() => {
|
|
|
1882
3329
|
if (!isLast) {
|
|
1883
3330
|
fragment.style.setProperty("margin-bottom", "0", "important");
|
|
1884
3331
|
}
|
|
1885
|
-
const measured = this.measureElement(fragment, dims);
|
|
3332
|
+
const measured = this.measureElement(fragment, dims, block.sectionIndex);
|
|
1886
3333
|
if (measured.heightPt + measured.marginTopPt + measured.marginBottomPt > minimumContentHeight) {
|
|
1887
3334
|
return null;
|
|
1888
3335
|
}
|
|
@@ -1897,35 +3344,198 @@ var Docxodus = (() => {
|
|
|
1897
3344
|
return fragments;
|
|
1898
3345
|
}
|
|
1899
3346
|
/**
|
|
1900
|
-
*
|
|
1901
|
-
*
|
|
1902
|
-
*
|
|
1903
|
-
|
|
1904
|
-
|
|
1905
|
-
|
|
1906
|
-
|
|
3347
|
+
* DOM endpoints that can finish a paragraph fragment. The flattened UTF-16
|
|
3348
|
+
* offsets are checked against the browser's Unicode grapheme segmenter, so a
|
|
3349
|
+
* formatting-run boundary can never bisect a surrogate pair, combining
|
|
3350
|
+
* sequence, or joined emoji. NBSP/word-joiner boundaries remain indivisible.
|
|
3351
|
+
* PAGE/NUMPAGES field results are atomic because splitting their marker would
|
|
3352
|
+
* make later substitution duplicate or replace only half of the field.
|
|
3353
|
+
*/
|
|
3354
|
+
paragraphFragmentEndpoints(paragraph, emergencyGraphemeBreaks = false) {
|
|
3355
|
+
const textNodes = [];
|
|
3356
|
+
const textStarts = /* @__PURE__ */ new Map();
|
|
3357
|
+
const lastTextByField = /* @__PURE__ */ new Map();
|
|
3358
|
+
const walker = this.document.createTreeWalker(paragraph, this.view.NodeFilter.SHOW_TEXT);
|
|
3359
|
+
const flattenedChunks = [];
|
|
3360
|
+
let flattenedLength = 0;
|
|
1907
3361
|
let textNode;
|
|
1908
3362
|
while (textNode = walker.nextNode()) {
|
|
1909
|
-
|
|
1910
|
-
|
|
1911
|
-
|
|
3363
|
+
this.checkpoint();
|
|
3364
|
+
textStarts.set(textNode, flattenedLength);
|
|
3365
|
+
textNodes.push(textNode);
|
|
3366
|
+
flattenedChunks.push(textNode.data);
|
|
3367
|
+
flattenedLength += textNode.data.length;
|
|
3368
|
+
let field = textNode.parentElement?.closest("[data-field]") ?? null;
|
|
3369
|
+
while (field?.parentElement?.closest("[data-field]")) {
|
|
3370
|
+
field = field.parentElement.closest("[data-field]");
|
|
3371
|
+
}
|
|
3372
|
+
if (field && paragraph.contains(field)) lastTextByField.set(field, textNode);
|
|
3373
|
+
}
|
|
3374
|
+
const flattenedText = flattenedChunks.join("");
|
|
3375
|
+
if (!this.hasVisibleText(flattenedText)) return [];
|
|
3376
|
+
const invisible = /[\s\u200B-\u200F\uFEFF]/;
|
|
3377
|
+
let firstVisibleOffset = -1;
|
|
3378
|
+
let lastVisibleOffset = -1;
|
|
3379
|
+
for (let index = 0; index < flattenedText.length; index++) {
|
|
3380
|
+
if (index % 4096 === 0) this.checkpoint();
|
|
3381
|
+
if (invisible.test(flattenedText[index])) continue;
|
|
3382
|
+
if (firstVisibleOffset < 0) firstVisibleOffset = index;
|
|
3383
|
+
lastVisibleOffset = index;
|
|
3384
|
+
}
|
|
3385
|
+
const graphemeBoundaries = this.graphemeBoundaryOffsets(flattenedText);
|
|
3386
|
+
const candidates = [];
|
|
3387
|
+
const addCandidate = (candidate) => {
|
|
3388
|
+
const { textOffset } = candidate;
|
|
3389
|
+
if (textOffset <= 0 || textOffset >= flattenedText.length) return;
|
|
3390
|
+
if (textOffset <= firstVisibleOffset || textOffset > lastVisibleOffset) return;
|
|
3391
|
+
if (this.isNonBreakingTextBoundary(flattenedText, textOffset)) return;
|
|
3392
|
+
if (!this.isLegalParagraphLineBoundary(paragraph, flattenedText, textOffset)) return;
|
|
3393
|
+
if (graphemeBoundaries) {
|
|
3394
|
+
if (!graphemeBoundaries.has(textOffset)) return;
|
|
3395
|
+
} else if (!this.isConservativeFallbackTextBoundary(flattenedText, textOffset)) {
|
|
3396
|
+
return;
|
|
3397
|
+
}
|
|
3398
|
+
candidates.push(candidate);
|
|
3399
|
+
};
|
|
3400
|
+
for (const node of textNodes) {
|
|
3401
|
+
this.checkpoint();
|
|
3402
|
+
const start2 = textStarts.get(node);
|
|
3403
|
+
const atomic = node.parentElement?.closest(
|
|
3404
|
+
"[data-field], [data-list-marker], a[data-comment-id]"
|
|
3405
|
+
);
|
|
3406
|
+
if (atomic && paragraph.contains(atomic)) continue;
|
|
3407
|
+
const whitespace = /[\u0009-\u000D\u0020]+/g;
|
|
1912
3408
|
let match;
|
|
1913
|
-
|
|
1914
|
-
|
|
3409
|
+
let matchesSinceCheckpoint = 0;
|
|
3410
|
+
while ((match = whitespace.exec(node.data)) !== null) {
|
|
3411
|
+
if (++matchesSinceCheckpoint % 256 === 0) this.checkpoint();
|
|
3412
|
+
const offset = match.index + match[0].length;
|
|
3413
|
+
addCandidate({ node, offset, textOffset: start2 + offset, priority: 0 });
|
|
3414
|
+
}
|
|
3415
|
+
addCandidate({
|
|
3416
|
+
node,
|
|
3417
|
+
offset: node.data.length,
|
|
3418
|
+
textOffset: start2 + node.data.length,
|
|
3419
|
+
priority: 0
|
|
3420
|
+
});
|
|
3421
|
+
if (emergencyGraphemeBreaks) {
|
|
3422
|
+
for (let offset = 1; offset < node.data.length; offset++) {
|
|
3423
|
+
if (offset % 256 === 0) this.checkpoint();
|
|
3424
|
+
addCandidate({
|
|
3425
|
+
node,
|
|
3426
|
+
offset,
|
|
3427
|
+
textOffset: start2 + offset,
|
|
3428
|
+
priority: -1
|
|
3429
|
+
});
|
|
3430
|
+
}
|
|
1915
3431
|
}
|
|
1916
|
-
|
|
1917
|
-
|
|
3432
|
+
}
|
|
3433
|
+
for (const field of Array.from(paragraph.querySelectorAll("[data-field]"))) {
|
|
3434
|
+
this.checkpoint();
|
|
3435
|
+
if (field.parentElement?.closest("[data-field]")) continue;
|
|
3436
|
+
const last = lastTextByField.get(field);
|
|
3437
|
+
const parent = field.parentNode;
|
|
3438
|
+
if (!last || !parent) continue;
|
|
3439
|
+
const childIndex = Array.prototype.indexOf.call(parent.childNodes, field);
|
|
3440
|
+
if (childIndex < 0) continue;
|
|
3441
|
+
addCandidate({
|
|
3442
|
+
node: parent,
|
|
3443
|
+
offset: childIndex + 1,
|
|
3444
|
+
textOffset: textStarts.get(last) + last.data.length,
|
|
3445
|
+
priority: 1
|
|
3446
|
+
});
|
|
3447
|
+
}
|
|
3448
|
+
candidates.sort((left2, right2) => left2.textOffset - right2.textOffset || right2.priority - left2.priority);
|
|
3449
|
+
const endpoints = [];
|
|
3450
|
+
for (const candidate of candidates) {
|
|
3451
|
+
if (endpoints[endpoints.length - 1]?.textOffset !== candidate.textOffset) {
|
|
3452
|
+
endpoints.push(candidate);
|
|
1918
3453
|
}
|
|
1919
3454
|
}
|
|
1920
3455
|
return endpoints;
|
|
1921
3456
|
}
|
|
3457
|
+
/** Browser-native UAX #29 boundaries; null keeps older runtimes conservative. */
|
|
3458
|
+
graphemeBoundaryOffsets(text) {
|
|
3459
|
+
const SegmenterConstructor = this.view.Intl.Segmenter;
|
|
3460
|
+
if (!SegmenterConstructor) return null;
|
|
3461
|
+
const boundaries = /* @__PURE__ */ new Set([0, text.length]);
|
|
3462
|
+
let segmentCount = 0;
|
|
3463
|
+
for (const part of new SegmenterConstructor("en", { granularity: "grapheme" }).segment(text)) {
|
|
3464
|
+
if (++segmentCount % 256 === 0) this.checkpoint();
|
|
3465
|
+
boundaries.add(part.index);
|
|
3466
|
+
boundaries.add(part.index + part.segment.length);
|
|
3467
|
+
}
|
|
3468
|
+
return boundaries;
|
|
3469
|
+
}
|
|
3470
|
+
/**
|
|
3471
|
+
* Conservative UAX #14/CSS wrapping opportunities used for synthetic page
|
|
3472
|
+
* boundaries. In particular, a formatting-run boundary is not itself a word
|
|
3473
|
+
* boundary, and Japanese opening/closing punctuation stays with its pair.
|
|
3474
|
+
* Arbitrary grapheme breaks are admitted only when the paragraph's CSS asks
|
|
3475
|
+
* the browser to wrap anywhere.
|
|
3476
|
+
*/
|
|
3477
|
+
isLegalParagraphLineBoundary(paragraph, text, offset) {
|
|
3478
|
+
const lastCodeUnit = text.charCodeAt(offset - 1);
|
|
3479
|
+
const beforeOffset = lastCodeUnit >= 56320 && lastCodeUnit <= 57343 ? Math.max(0, offset - 2) : offset - 1;
|
|
3480
|
+
const beforeCodePoint = text.codePointAt(beforeOffset);
|
|
3481
|
+
const afterCodePoint = text.codePointAt(offset);
|
|
3482
|
+
const before = beforeCodePoint === void 0 ? "" : String.fromCodePoint(beforeCodePoint);
|
|
3483
|
+
const after = afterCodePoint === void 0 ? "" : String.fromCodePoint(afterCodePoint);
|
|
3484
|
+
if (/^[\u0009-\u000D\u0020]$/.test(before) || /^[\u0009-\u000D\u0020]$/.test(after)) return true;
|
|
3485
|
+
if (/[-\u00AD\u2010]$/u.test(before)) return true;
|
|
3486
|
+
const style = this.view.getComputedStyle(paragraph);
|
|
3487
|
+
if (style.wordBreak === "break-all" || style.overflowWrap === "anywhere" || style.overflowWrap === "break-word") return true;
|
|
3488
|
+
const eastAsian = /[\p{Script=Han}\p{Script=Hiragana}\p{Script=Katakana}\p{Script=Hangul}]/u;
|
|
3489
|
+
if (!eastAsian.test(before) && !eastAsian.test(after)) return false;
|
|
3490
|
+
const opening = "([{<\u2018\u201C\u3008\u300A\u300C\u300E\u3010\u3014\u3016\u3018\u301A\uFF08\uFF3B\uFF5B";
|
|
3491
|
+
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";
|
|
3492
|
+
return !opening.includes(before) && !closing.includes(after);
|
|
3493
|
+
}
|
|
3494
|
+
/** Never fragment across characters whose line-break meaning is explicitly non-breaking. */
|
|
3495
|
+
isNonBreakingTextBoundary(text, offset) {
|
|
3496
|
+
const codePointBefore = (index) => {
|
|
3497
|
+
if (index <= 0) return void 0;
|
|
3498
|
+
const last = text.charCodeAt(index - 1);
|
|
3499
|
+
const start2 = last >= 56320 && last <= 57343 ? index - 2 : index - 1;
|
|
3500
|
+
return text.codePointAt(Math.max(0, start2));
|
|
3501
|
+
};
|
|
3502
|
+
const nonBreaking = /* @__PURE__ */ new Set([
|
|
3503
|
+
160,
|
|
3504
|
+
8206,
|
|
3505
|
+
8207,
|
|
3506
|
+
8209,
|
|
3507
|
+
8234,
|
|
3508
|
+
8235,
|
|
3509
|
+
8236,
|
|
3510
|
+
8237,
|
|
3511
|
+
8238,
|
|
3512
|
+
8239,
|
|
3513
|
+
8288,
|
|
3514
|
+
8294,
|
|
3515
|
+
8295,
|
|
3516
|
+
8296,
|
|
3517
|
+
8297,
|
|
3518
|
+
65279
|
|
3519
|
+
]);
|
|
3520
|
+
return nonBreaking.has(codePointBefore(offset) ?? -1) || nonBreaking.has(text.codePointAt(offset) ?? -1);
|
|
3521
|
+
}
|
|
3522
|
+
/**
|
|
3523
|
+
* Without Intl.Segmenter, admit only an all-ASCII boundary. This still
|
|
3524
|
+
* fragments ordinary prose while refusing to guess about Unicode clusters.
|
|
3525
|
+
*/
|
|
3526
|
+
isConservativeFallbackTextBoundary(text, offset) {
|
|
3527
|
+
return text.charCodeAt(offset - 1) <= 127 && text.charCodeAt(offset) <= 127;
|
|
3528
|
+
}
|
|
1922
3529
|
/**
|
|
1923
3530
|
* Whether a range contains visible text after ignoring bidi/zero-width marks.
|
|
1924
3531
|
* Paragraph fragmentation deliberately excludes non-textual descendants, so
|
|
1925
3532
|
* this is enough to reject empty head or tail fragments.
|
|
1926
3533
|
*/
|
|
1927
3534
|
hasVisibleFragmentText(fragment) {
|
|
1928
|
-
return (fragment.textContent || "")
|
|
3535
|
+
return this.hasVisibleText(fragment.textContent || "");
|
|
3536
|
+
}
|
|
3537
|
+
hasVisibleText(text) {
|
|
3538
|
+
return text.replace(/[\u200B-\u200F\uFEFF]/g, "").trim().length > 0;
|
|
1929
3539
|
}
|
|
1930
3540
|
/**
|
|
1931
3541
|
* Phase one only handles text-like paragraph descendants. Objects, explicit
|
|
@@ -1934,13 +3544,25 @@ var Docxodus = (() => {
|
|
|
1934
3544
|
* fallback instead of risking broken content or duplicate anchors.
|
|
1935
3545
|
*/
|
|
1936
3546
|
canFragmentParagraph(block) {
|
|
1937
|
-
if (!this.fragmentParagraphs
|
|
3547
|
+
if (!this.fragmentParagraphs) {
|
|
1938
3548
|
return false;
|
|
1939
3549
|
}
|
|
1940
3550
|
const paragraph = block.element;
|
|
1941
3551
|
if (block.keepWithNext || block.keepLines || block.pageBreakBefore || block.isPageBreak || paragraph.dataset.widowControl === "true" || paragraph.hasAttribute("contenteditable")) {
|
|
1942
3552
|
return false;
|
|
1943
3553
|
}
|
|
3554
|
+
return this.canRangeFragmentParagraph(paragraph);
|
|
3555
|
+
}
|
|
3556
|
+
/**
|
|
3557
|
+
* Shared structural gate for body and note paragraph fragmentation. Footnote
|
|
3558
|
+
* first paragraphs are inline beside their marker, so that one known layout
|
|
3559
|
+
* context may opt into an inline root while retaining every descendant and
|
|
3560
|
+
* break-safety restriction used for body text.
|
|
3561
|
+
*/
|
|
3562
|
+
canRangeFragmentParagraph(paragraph, allowInlineRoot = false) {
|
|
3563
|
+
if (paragraph.tagName !== "P" || paragraph.dataset.keepWithNext === "true" || paragraph.dataset.keepLines === "true" || paragraph.dataset.pageBreakBefore === "true" || paragraph.dataset.widowControl === "true" || paragraph.hasAttribute("contenteditable")) {
|
|
3564
|
+
return false;
|
|
3565
|
+
}
|
|
1944
3566
|
const unsupportedDescendants = [
|
|
1945
3567
|
"br",
|
|
1946
3568
|
"img",
|
|
@@ -1971,21 +3593,27 @@ var Docxodus = (() => {
|
|
|
1971
3593
|
"fieldset",
|
|
1972
3594
|
"details",
|
|
1973
3595
|
"[data-footnote-id]",
|
|
1974
|
-
"[data-list-marker]",
|
|
1975
|
-
"[data-anchor]",
|
|
1976
|
-
"[id]",
|
|
1977
3596
|
"[contenteditable]"
|
|
1978
3597
|
].join(", ");
|
|
1979
3598
|
if (paragraph.querySelector(unsupportedDescendants)) {
|
|
1980
3599
|
return false;
|
|
1981
3600
|
}
|
|
1982
|
-
const
|
|
1983
|
-
|
|
3601
|
+
const isValidatedEndnote = paragraph.dataset.paginationSafeEndnote === "true";
|
|
3602
|
+
return isValidatedEndnote || this.hasRangeFragmentSafeLayout(paragraph, allowInlineRoot);
|
|
3603
|
+
}
|
|
3604
|
+
/**
|
|
3605
|
+
* A range clone preserves nested inline formatting exactly. Anything that establishes its own
|
|
3606
|
+
* box/layout context is deferred until a future fragmenter can model it accurately. Callers
|
|
3607
|
+
* must invoke this while the paragraph is attached to the styled document.
|
|
3608
|
+
*/
|
|
3609
|
+
hasRangeFragmentSafeLayout(paragraph, allowInlineRoot = false) {
|
|
3610
|
+
const paragraphStyle = this.view.getComputedStyle(paragraph);
|
|
3611
|
+
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") {
|
|
1984
3612
|
return false;
|
|
1985
3613
|
}
|
|
1986
3614
|
for (const descendant of Array.from(paragraph.querySelectorAll("*"))) {
|
|
1987
|
-
const style =
|
|
1988
|
-
if (style.display !== "inline" || style.position !== "static" || style.float !== "none" || style.whiteSpace !== "normal") {
|
|
3615
|
+
const style = this.view.getComputedStyle(descendant);
|
|
3616
|
+
if (style.display !== "inline" || style.position !== "static" || style.float !== "none" || style.whiteSpace !== "normal" && style.whiteSpace !== "pre-wrap") {
|
|
1989
3617
|
return false;
|
|
1990
3618
|
}
|
|
1991
3619
|
}
|
|
@@ -2012,58 +3640,122 @@ var Docxodus = (() => {
|
|
|
2012
3640
|
return fragment;
|
|
2013
3641
|
}
|
|
2014
3642
|
/**
|
|
2015
|
-
*
|
|
2016
|
-
*
|
|
2017
|
-
*
|
|
3643
|
+
* Range clones repeat an inline ancestor when the split lands inside it. Keep
|
|
3644
|
+
* semantic wrappers (links, comments, formatting) on both sides, but retain a
|
|
3645
|
+
* duplicated HTML/editor identity only on the leading fragment. Targets that
|
|
3646
|
+
* occur wholly after the split are absent from `head` and remain on `tail`.
|
|
2018
3647
|
*/
|
|
2019
|
-
|
|
2020
|
-
|
|
2021
|
-
|
|
2022
|
-
|
|
2023
|
-
|
|
2024
|
-
const
|
|
2025
|
-
|
|
2026
|
-
|
|
2027
|
-
}
|
|
2028
|
-
|
|
2029
|
-
|
|
2030
|
-
|
|
2031
|
-
|
|
2032
|
-
|
|
2033
|
-
|
|
2034
|
-
|
|
2035
|
-
|
|
2036
|
-
|
|
2037
|
-
|
|
2038
|
-
|
|
2039
|
-
|
|
2040
|
-
|
|
3648
|
+
reconcileParagraphFragmentIdentities(head, tail) {
|
|
3649
|
+
const elements = (root, selector2) => [
|
|
3650
|
+
...root.matches(selector2) ? [root] : [],
|
|
3651
|
+
...Array.from(root.querySelectorAll(selector2))
|
|
3652
|
+
];
|
|
3653
|
+
const headIds = new Set(elements(head, "[id]").map((element) => element.id));
|
|
3654
|
+
for (const element of elements(tail, "[id]")) {
|
|
3655
|
+
if (headIds.has(element.id)) element.removeAttribute("id");
|
|
3656
|
+
}
|
|
3657
|
+
const headAnchors = new Set(elements(head, "[data-anchor]").map((element) => element.dataset.anchor).filter((anchor) => Boolean(anchor)));
|
|
3658
|
+
for (const element of elements(tail, "[data-anchor]")) {
|
|
3659
|
+
if (!element.dataset.anchor || !headAnchors.has(element.dataset.anchor)) continue;
|
|
3660
|
+
element.removeAttribute("data-anchor");
|
|
3661
|
+
element.removeAttribute("data-committed-text");
|
|
3662
|
+
}
|
|
3663
|
+
}
|
|
3664
|
+
/**
|
|
3665
|
+
* Range-clone the largest safe prefix accepted by `fits`. The measurement
|
|
3666
|
+
* policy stays with the caller, allowing body blocks and note bands to share
|
|
3667
|
+
* one DOM fragmenter while measuring in their respective layout contexts.
|
|
3668
|
+
*/
|
|
3669
|
+
splitParagraphAtLargestFit(paragraph, fits, options = {}) {
|
|
3670
|
+
const largestFit = (endpoints2) => {
|
|
3671
|
+
const lastEndpoint = endpoints2[endpoints2.length - 1];
|
|
3672
|
+
if (lastEndpoint) {
|
|
3673
|
+
const lastRange = this.document.createRange();
|
|
3674
|
+
lastRange.setStart(paragraph, 0);
|
|
3675
|
+
lastRange.setEnd(lastEndpoint.node, lastEndpoint.offset);
|
|
3676
|
+
const lastContents = lastRange.cloneContents();
|
|
3677
|
+
if (this.hasVisibleFragmentText(lastContents)) {
|
|
3678
|
+
const lastHead = this.createParagraphFragment(paragraph, lastRange, true, false);
|
|
3679
|
+
if (fits(lastHead)) return { endpoint: lastEndpoint, head: lastHead };
|
|
3680
|
+
}
|
|
2041
3681
|
}
|
|
2042
|
-
|
|
2043
|
-
|
|
2044
|
-
|
|
2045
|
-
|
|
2046
|
-
|
|
2047
|
-
|
|
2048
|
-
|
|
3682
|
+
let low = 0;
|
|
3683
|
+
let high = endpoints2.length - 2;
|
|
3684
|
+
let best2 = null;
|
|
3685
|
+
while (low <= high) {
|
|
3686
|
+
this.checkpoint();
|
|
3687
|
+
const middle = Math.floor((low + high) / 2);
|
|
3688
|
+
const endpoint = endpoints2[middle];
|
|
3689
|
+
const headRange = this.document.createRange();
|
|
3690
|
+
headRange.setStart(paragraph, 0);
|
|
3691
|
+
headRange.setEnd(endpoint.node, endpoint.offset);
|
|
3692
|
+
const headContents = headRange.cloneContents();
|
|
3693
|
+
if (!this.hasVisibleFragmentText(headContents)) {
|
|
3694
|
+
low = middle + 1;
|
|
3695
|
+
continue;
|
|
3696
|
+
}
|
|
3697
|
+
const head = this.createParagraphFragment(paragraph, headRange, true, false);
|
|
3698
|
+
if (fits(head)) {
|
|
3699
|
+
best2 = { endpoint, head };
|
|
3700
|
+
low = middle + 1;
|
|
3701
|
+
} else {
|
|
3702
|
+
high = middle - 1;
|
|
3703
|
+
}
|
|
2049
3704
|
}
|
|
3705
|
+
return best2;
|
|
3706
|
+
};
|
|
3707
|
+
let endpoints = this.paragraphFragmentEndpoints(paragraph);
|
|
3708
|
+
let best = largestFit(endpoints);
|
|
3709
|
+
if (!best && options.emergencyGraphemeBreaks) {
|
|
3710
|
+
endpoints = this.paragraphFragmentEndpoints(paragraph, true);
|
|
3711
|
+
best = largestFit(endpoints);
|
|
3712
|
+
}
|
|
3713
|
+
if (!best && options.forceFirstOnNoFit && endpoints.length > 0) {
|
|
3714
|
+
const endpoint = endpoints[0];
|
|
3715
|
+
const headRange = this.document.createRange();
|
|
3716
|
+
headRange.setStart(paragraph, 0);
|
|
3717
|
+
headRange.setEnd(endpoint.node, endpoint.offset);
|
|
3718
|
+
best = {
|
|
3719
|
+
endpoint,
|
|
3720
|
+
head: this.createParagraphFragment(paragraph, headRange, true, false)
|
|
3721
|
+
};
|
|
2050
3722
|
}
|
|
2051
3723
|
if (!best) {
|
|
2052
|
-
return
|
|
3724
|
+
return endpoints.length > 0 ? { kind: "no-fit" } : { kind: "indivisible" };
|
|
2053
3725
|
}
|
|
2054
|
-
const tailRange = document.createRange();
|
|
3726
|
+
const tailRange = this.document.createRange();
|
|
2055
3727
|
tailRange.setStart(best.endpoint.node, best.endpoint.offset);
|
|
2056
3728
|
tailRange.setEnd(paragraph, paragraph.childNodes.length);
|
|
2057
3729
|
const tailContents = tailRange.cloneContents();
|
|
2058
|
-
if (!this.hasVisibleFragmentText(tailContents)) {
|
|
3730
|
+
if (!this.hasVisibleFragmentText(tailContents)) return { kind: "indivisible" };
|
|
3731
|
+
const tail = this.createParagraphFragment(paragraph, tailRange, false, true);
|
|
3732
|
+
this.reconcileParagraphFragmentIdentities(best.head, tail);
|
|
3733
|
+
return {
|
|
3734
|
+
kind: "split",
|
|
3735
|
+
head: best.head,
|
|
3736
|
+
tail
|
|
3737
|
+
};
|
|
3738
|
+
}
|
|
3739
|
+
/**
|
|
3740
|
+
* Splits a simple paragraph at the largest DOM Range endpoint that fits the
|
|
3741
|
+
* currently available body space. The caller then processes the tail normally,
|
|
3742
|
+
* allowing it to fragment again on later pages when necessary.
|
|
3743
|
+
*/
|
|
3744
|
+
tryFragmentParagraph(block, dims, availableHeightPt, effectiveMarginTopPt) {
|
|
3745
|
+
if (!this.canFragmentParagraph(block) || availableHeightPt <= effectiveMarginTopPt) {
|
|
2059
3746
|
return null;
|
|
2060
3747
|
}
|
|
2061
|
-
const
|
|
2062
|
-
|
|
3748
|
+
const split = this.splitParagraphAtLargestFit(block.element, (head) => {
|
|
3749
|
+
const measured = this.measureElement(head, dims, block.sectionIndex);
|
|
3750
|
+
return effectiveMarginTopPt + measured.heightPt <= availableHeightPt;
|
|
3751
|
+
});
|
|
3752
|
+
if (split.kind !== "split") return null;
|
|
3753
|
+
const headMeasured = this.measureElement(split.head, dims, block.sectionIndex);
|
|
3754
|
+
const tailMeasured = this.measureElement(split.tail, dims, block.sectionIndex);
|
|
2063
3755
|
return [
|
|
2064
3756
|
{
|
|
2065
|
-
...
|
|
2066
|
-
element:
|
|
3757
|
+
...headMeasured,
|
|
3758
|
+
element: split.head,
|
|
2067
3759
|
keepWithNext: false,
|
|
2068
3760
|
keepLines: false,
|
|
2069
3761
|
pageBreakBefore: false,
|
|
@@ -2071,7 +3763,7 @@ var Docxodus = (() => {
|
|
|
2071
3763
|
},
|
|
2072
3764
|
{
|
|
2073
3765
|
...tailMeasured,
|
|
2074
|
-
element: tail,
|
|
3766
|
+
element: split.tail,
|
|
2075
3767
|
keepWithNext: false,
|
|
2076
3768
|
keepLines: false,
|
|
2077
3769
|
pageBreakBefore: false,
|
|
@@ -2142,7 +3834,7 @@ var Docxodus = (() => {
|
|
|
2142
3834
|
const registry = /* @__PURE__ */ new Map();
|
|
2143
3835
|
const registryEl = this.stagingElement.querySelector("#pagination-footnote-registry");
|
|
2144
3836
|
if (!registryEl) return registry;
|
|
2145
|
-
const entries = Array.from(registryEl.
|
|
3837
|
+
const entries = Array.from(registryEl.children).filter((entry) => entry instanceof this.view.HTMLElement && entry.hasAttribute("data-footnote-id"));
|
|
2146
3838
|
for (const entry of entries) {
|
|
2147
3839
|
const footnoteId = entry.dataset.footnoteId;
|
|
2148
3840
|
if (footnoteId) {
|
|
@@ -2151,6 +3843,68 @@ var Docxodus = (() => {
|
|
|
2151
3843
|
}
|
|
2152
3844
|
return registry;
|
|
2153
3845
|
}
|
|
3846
|
+
/** Read Word's optional normal and continuation separator stories. */
|
|
3847
|
+
parseFootnoteSeparators() {
|
|
3848
|
+
const registry = this.stagingElement.querySelector("#pagination-footnote-registry");
|
|
3849
|
+
const clone = (kind) => {
|
|
3850
|
+
const source = Array.from(registry?.children ?? []).find(
|
|
3851
|
+
(entry) => entry instanceof this.view.HTMLElement && entry.getAttribute("data-footnote-separator") === kind
|
|
3852
|
+
);
|
|
3853
|
+
return source?.cloneNode(true);
|
|
3854
|
+
};
|
|
3855
|
+
return {
|
|
3856
|
+
normal: clone("normal") ?? null,
|
|
3857
|
+
continuation: clone("continuation") ?? null
|
|
3858
|
+
};
|
|
3859
|
+
}
|
|
3860
|
+
/** Append the exact separator story selected for this initial/continued note band. */
|
|
3861
|
+
appendFootnoteSeparator(container, continuation) {
|
|
3862
|
+
const kind = continuation ? "continuation" : "normal";
|
|
3863
|
+
const source = continuation ? this.footnoteContinuationSeparator : this.footnoteSeparator;
|
|
3864
|
+
if (!source) {
|
|
3865
|
+
const fallback = this.document.createElement("hr");
|
|
3866
|
+
fallback.dataset.footnoteSeparator = kind;
|
|
3867
|
+
container.appendChild(fallback);
|
|
3868
|
+
return;
|
|
3869
|
+
}
|
|
3870
|
+
const clone = source.cloneNode(true);
|
|
3871
|
+
for (const element of [clone, ...Array.from(clone.querySelectorAll("*"))]) {
|
|
3872
|
+
element.removeAttribute("id");
|
|
3873
|
+
element.removeAttribute("data-anchor");
|
|
3874
|
+
element.removeAttribute("data-committed-text");
|
|
3875
|
+
element.removeAttribute("data-source-anchor-id");
|
|
3876
|
+
element.removeAttribute("data-page-fragment-id");
|
|
3877
|
+
element.removeAttribute("data-fragment-index");
|
|
3878
|
+
element.removeAttribute("data-footnote-id");
|
|
3879
|
+
element.removeAttribute("data-comment-id");
|
|
3880
|
+
element.removeAttribute("name");
|
|
3881
|
+
if (element instanceof this.view.HTMLAnchorElement && element.getAttribute("href")?.startsWith("#")) {
|
|
3882
|
+
element.removeAttribute("href");
|
|
3883
|
+
}
|
|
3884
|
+
element.setAttribute("contenteditable", "false");
|
|
3885
|
+
}
|
|
3886
|
+
container.appendChild(clone);
|
|
3887
|
+
}
|
|
3888
|
+
/** Parses the hidden source notes used to render paginated margin comments. */
|
|
3889
|
+
parseCommentMarginRegistry() {
|
|
3890
|
+
const registry = /* @__PURE__ */ new Map();
|
|
3891
|
+
const registryEl = this.stagingElement.querySelector(
|
|
3892
|
+
"#pagination-comment-margin-registry"
|
|
3893
|
+
);
|
|
3894
|
+
if (!registryEl) return registry;
|
|
3895
|
+
for (const entry of Array.from(
|
|
3896
|
+
registryEl.querySelectorAll("[data-comment-id]")
|
|
3897
|
+
)) {
|
|
3898
|
+
const commentId = entry.dataset.commentId;
|
|
3899
|
+
if (!commentId) continue;
|
|
3900
|
+
let root = entry;
|
|
3901
|
+
for (let parent = root.parentElement?.closest("[data-comment-id]"); parent && registryEl.contains(parent); parent = root.parentElement?.closest("[data-comment-id]")) {
|
|
3902
|
+
root = parent;
|
|
3903
|
+
}
|
|
3904
|
+
registry.set(commentId, root.cloneNode(true));
|
|
3905
|
+
}
|
|
3906
|
+
return registry;
|
|
3907
|
+
}
|
|
2154
3908
|
/**
|
|
2155
3909
|
* Extracts footnote reference IDs from an element.
|
|
2156
3910
|
*/
|
|
@@ -2165,6 +3919,92 @@ var Docxodus = (() => {
|
|
|
2165
3919
|
}
|
|
2166
3920
|
return ids;
|
|
2167
3921
|
}
|
|
3922
|
+
/** Clone a note child for the continuation queue with its original selector position. */
|
|
3923
|
+
cloneFootnoteElementForContinuation(element, sourceIndex) {
|
|
3924
|
+
const clone = element.cloneNode(true);
|
|
3925
|
+
clone.setAttribute(
|
|
3926
|
+
FOOTNOTE_SOURCE_POSITION_ATTR,
|
|
3927
|
+
sourceIndex === 0 ? "first" : "later"
|
|
3928
|
+
);
|
|
3929
|
+
return clone;
|
|
3930
|
+
}
|
|
3931
|
+
/** Remove identities that belong only to the initial registry presentation shell. */
|
|
3932
|
+
makeContinuationShellInert(element) {
|
|
3933
|
+
element.removeAttribute("id");
|
|
3934
|
+
element.removeAttribute("data-anchor");
|
|
3935
|
+
element.removeAttribute("data-committed-text");
|
|
3936
|
+
}
|
|
3937
|
+
/**
|
|
3938
|
+
* Build the exact continuation shape shared by measurement and paint.
|
|
3939
|
+
*
|
|
3940
|
+
* Keep the source `.footnote-item > .footnote-content` ancestry: document
|
|
3941
|
+
* CSS, inherited direction/language, and custom classes frequently target
|
|
3942
|
+
* those shells. Only the number and initial HTML/editor identities are
|
|
3943
|
+
* omitted. A hidden sentinel preserves `p:not(:first-of-type)` for a page
|
|
3944
|
+
* whose first carried element was a later source paragraph.
|
|
3945
|
+
*/
|
|
3946
|
+
createFootnoteContinuationWrapper(continuation, elements = continuation.remainingElements) {
|
|
3947
|
+
const source = this.footnoteRegistry.get(continuation.footnoteId);
|
|
3948
|
+
const wrapper = source ? source.cloneNode(false) : this.document.createElement("div");
|
|
3949
|
+
this.makeContinuationShellInert(wrapper);
|
|
3950
|
+
wrapper.classList.add("footnote-continuation");
|
|
3951
|
+
wrapper.dataset.footnoteId = continuation.footnoteId;
|
|
3952
|
+
if (continuation.sourceAnchorId) {
|
|
3953
|
+
wrapper.dataset.sourceAnchorId = continuation.sourceAnchorId;
|
|
3954
|
+
}
|
|
3955
|
+
const sourceContent = source?.querySelector(".footnote-content");
|
|
3956
|
+
const content = sourceContent ? sourceContent.cloneNode(false) : this.document.createElement("span");
|
|
3957
|
+
this.makeContinuationShellInert(content);
|
|
3958
|
+
if (!sourceContent) content.className = "footnote-content";
|
|
3959
|
+
const first = elements[0];
|
|
3960
|
+
if (first?.tagName === "P" && first.getAttribute(FOOTNOTE_SOURCE_POSITION_ATTR) === "later") {
|
|
3961
|
+
const sentinel = this.document.createElement("p");
|
|
3962
|
+
sentinel.className = "footnote-continuation-position-sentinel";
|
|
3963
|
+
sentinel.setAttribute("aria-hidden", "true");
|
|
3964
|
+
sentinel.style.setProperty("display", "none", "important");
|
|
3965
|
+
content.appendChild(sentinel);
|
|
3966
|
+
}
|
|
3967
|
+
for (let index = 0; index < elements.length; index++) {
|
|
3968
|
+
this.checkpoint();
|
|
3969
|
+
const element = elements[index];
|
|
3970
|
+
const clone = element.cloneNode(true);
|
|
3971
|
+
clone.removeAttribute(FOOTNOTE_SOURCE_POSITION_ATTR);
|
|
3972
|
+
if (index === 0 && clone.tagName === "P") {
|
|
3973
|
+
clone.style.setProperty("display", "block", "important");
|
|
3974
|
+
}
|
|
3975
|
+
content.appendChild(clone);
|
|
3976
|
+
}
|
|
3977
|
+
wrapper.appendChild(content);
|
|
3978
|
+
return wrapper;
|
|
3979
|
+
}
|
|
3980
|
+
/** Build the exact partial-note item shape shared by measurement and paint. */
|
|
3981
|
+
createPartialFootnoteItem(footnote, fittingElements) {
|
|
3982
|
+
const item = footnote.cloneNode(false);
|
|
3983
|
+
const number = footnote.querySelector(".footnote-number");
|
|
3984
|
+
if (number) item.appendChild(number.cloneNode(true));
|
|
3985
|
+
const sourceContent = footnote.querySelector(".footnote-content");
|
|
3986
|
+
const content = sourceContent ? sourceContent.cloneNode(false) : this.document.createElement("span");
|
|
3987
|
+
if (!sourceContent) content.className = "footnote-content";
|
|
3988
|
+
for (const element of fittingElements) {
|
|
3989
|
+
this.checkpoint();
|
|
3990
|
+
content.appendChild(element.cloneNode(true));
|
|
3991
|
+
}
|
|
3992
|
+
item.appendChild(content);
|
|
3993
|
+
return item;
|
|
3994
|
+
}
|
|
3995
|
+
/**
|
|
3996
|
+
* Where a note measurement tree is attached.
|
|
3997
|
+
*
|
|
3998
|
+
* A note band is reserved from a measurement and then painted; if the two
|
|
3999
|
+
* happen under different inherited typography the painted notes overflow the
|
|
4000
|
+
* reserve, which is the invisible clipping this engine exists to avoid. The
|
|
4001
|
+
* registry lives in the staging tree but a band paints inside the output
|
|
4002
|
+
* container, so measure there. The host is never a page box, so a page's
|
|
4003
|
+
* `zoom` cannot scale a reserve that is accounted for in unscaled points.
|
|
4004
|
+
*/
|
|
4005
|
+
noteMeasurementHost() {
|
|
4006
|
+
return this.containerElement.isConnected ? this.containerElement : this.stagingElement;
|
|
4007
|
+
}
|
|
2168
4008
|
/**
|
|
2169
4009
|
* Measures the height of footnotes for given IDs (in points).
|
|
2170
4010
|
* Creates a temporary container to measure the footnotes.
|
|
@@ -2172,140 +4012,246 @@ var Docxodus = (() => {
|
|
|
2172
4012
|
* @param contentWidth - Width for measurement
|
|
2173
4013
|
* @param continuation - Optional continuation content to include first
|
|
2174
4014
|
*/
|
|
2175
|
-
measureFootnotesHeight(footnoteIds, contentWidth, continuation) {
|
|
4015
|
+
measureFootnotesHeight(footnoteIds, contentWidth, continuation, partialFootnotes) {
|
|
2176
4016
|
const hasContinuation = continuation && continuation.remainingElements.length > 0;
|
|
2177
|
-
if (footnoteIds.length === 0 && !hasContinuation
|
|
4017
|
+
if (footnoteIds.length === 0 && !hasContinuation) {
|
|
4018
|
+
return 0;
|
|
4019
|
+
}
|
|
4020
|
+
if (this.footnoteRegistry.size === 0 && !hasContinuation) {
|
|
2178
4021
|
return 0;
|
|
2179
4022
|
}
|
|
2180
|
-
const measureContainer = document.createElement("div");
|
|
4023
|
+
const measureContainer = this.document.createElement("div");
|
|
2181
4024
|
measureContainer.style.position = "absolute";
|
|
2182
4025
|
measureContainer.style.visibility = "hidden";
|
|
2183
4026
|
measureContainer.style.width = `${contentWidth}pt`;
|
|
2184
4027
|
measureContainer.style.left = "-9999px";
|
|
2185
4028
|
measureContainer.className = this.cssPrefix + "footnotes";
|
|
2186
|
-
|
|
2187
|
-
measureContainer.appendChild(hr);
|
|
4029
|
+
this.appendFootnoteSeparator(measureContainer, Boolean(hasContinuation));
|
|
2188
4030
|
if (hasContinuation) {
|
|
2189
|
-
|
|
2190
|
-
contWrapper.className = "footnote-continuation";
|
|
2191
|
-
for (const el of continuation.remainingElements) {
|
|
2192
|
-
contWrapper.appendChild(el.cloneNode(true));
|
|
2193
|
-
}
|
|
2194
|
-
measureContainer.appendChild(contWrapper);
|
|
4031
|
+
measureContainer.appendChild(this.createFootnoteContinuationWrapper(continuation));
|
|
2195
4032
|
}
|
|
4033
|
+
const partialById = new Map(
|
|
4034
|
+
partialFootnotes?.map((partial) => [partial.footnoteId, partial]) ?? []
|
|
4035
|
+
);
|
|
2196
4036
|
for (const id of footnoteIds) {
|
|
4037
|
+
this.checkpoint();
|
|
2197
4038
|
const footnote = this.footnoteRegistry.get(id);
|
|
2198
4039
|
if (footnote) {
|
|
2199
|
-
|
|
4040
|
+
const partial = partialById.get(id);
|
|
4041
|
+
measureContainer.appendChild(partial ? this.createPartialFootnoteItem(footnote, partial.fittingElements) : footnote.cloneNode(true));
|
|
2200
4042
|
}
|
|
2201
4043
|
}
|
|
2202
|
-
this.
|
|
2203
|
-
|
|
2204
|
-
|
|
2205
|
-
|
|
2206
|
-
|
|
4044
|
+
this.noteMeasurementHost().appendChild(measureContainer);
|
|
4045
|
+
try {
|
|
4046
|
+
return pxToPt(measureContainer.getBoundingClientRect().height);
|
|
4047
|
+
} finally {
|
|
4048
|
+
measureContainer.remove();
|
|
4049
|
+
}
|
|
2207
4050
|
}
|
|
2208
4051
|
/**
|
|
2209
|
-
*
|
|
4052
|
+
* Partition a continuation for one page's note band, preferring complete children
|
|
4053
|
+
* and range-fragmenting an eligible paragraph when necessary. Always advances by
|
|
4054
|
+
* at least one element so an indivisible oversized paragraph follows the established
|
|
4055
|
+
* clipped fallback without trapping pagination in a loop.
|
|
2210
4056
|
*/
|
|
2211
|
-
|
|
2212
|
-
|
|
2213
|
-
|
|
2214
|
-
|
|
2215
|
-
const measureContainer = document.createElement("div");
|
|
4057
|
+
splitContinuationForPage(continuation, availableHeightPt, contentWidth) {
|
|
4058
|
+
const fitting = [];
|
|
4059
|
+
let remaining = [];
|
|
4060
|
+
const measureContainer = this.document.createElement("div");
|
|
2216
4061
|
measureContainer.style.position = "absolute";
|
|
2217
4062
|
measureContainer.style.visibility = "hidden";
|
|
2218
4063
|
measureContainer.style.width = `${contentWidth}pt`;
|
|
2219
4064
|
measureContainer.style.left = "-9999px";
|
|
2220
4065
|
measureContainer.className = this.cssPrefix + "footnotes";
|
|
2221
|
-
|
|
2222
|
-
|
|
2223
|
-
|
|
2224
|
-
|
|
2225
|
-
|
|
2226
|
-
|
|
2227
|
-
|
|
2228
|
-
|
|
2229
|
-
|
|
2230
|
-
|
|
2231
|
-
|
|
2232
|
-
|
|
2233
|
-
|
|
2234
|
-
|
|
2235
|
-
|
|
2236
|
-
|
|
2237
|
-
|
|
2238
|
-
|
|
2239
|
-
|
|
2240
|
-
|
|
2241
|
-
|
|
2242
|
-
|
|
2243
|
-
|
|
2244
|
-
|
|
2245
|
-
|
|
2246
|
-
|
|
2247
|
-
|
|
2248
|
-
|
|
2249
|
-
|
|
2250
|
-
|
|
2251
|
-
|
|
2252
|
-
|
|
2253
|
-
|
|
2254
|
-
|
|
2255
|
-
|
|
2256
|
-
|
|
2257
|
-
|
|
2258
|
-
|
|
2259
|
-
|
|
2260
|
-
|
|
2261
|
-
|
|
2262
|
-
|
|
2263
|
-
|
|
2264
|
-
|
|
2265
|
-
|
|
2266
|
-
|
|
2267
|
-
|
|
2268
|
-
|
|
2269
|
-
|
|
2270
|
-
|
|
2271
|
-
|
|
2272
|
-
|
|
2273
|
-
|
|
2274
|
-
|
|
2275
|
-
|
|
2276
|
-
|
|
2277
|
-
|
|
2278
|
-
|
|
2279
|
-
|
|
2280
|
-
|
|
2281
|
-
|
|
2282
|
-
|
|
2283
|
-
|
|
2284
|
-
|
|
4066
|
+
this.appendFootnoteSeparator(measureContainer, true);
|
|
4067
|
+
const wrapper = this.createFootnoteContinuationWrapper(continuation, []);
|
|
4068
|
+
const content = wrapper.querySelector(":scope > .footnote-content");
|
|
4069
|
+
if (!content) {
|
|
4070
|
+
throw new Error("Footnote continuation is missing its content shell");
|
|
4071
|
+
}
|
|
4072
|
+
measureContainer.appendChild(wrapper);
|
|
4073
|
+
this.noteMeasurementHost().appendChild(measureContainer);
|
|
4074
|
+
try {
|
|
4075
|
+
for (let index = 0; index < continuation.remainingElements.length; index++) {
|
|
4076
|
+
this.checkpoint();
|
|
4077
|
+
const element = continuation.remainingElements[index];
|
|
4078
|
+
const sourcePosition = element.getAttribute(FOOTNOTE_SOURCE_POSITION_ATTR);
|
|
4079
|
+
if (fitting.length === 0 && element.tagName === "P" && element.getAttribute(FOOTNOTE_SOURCE_POSITION_ATTR) === "later") {
|
|
4080
|
+
const sentinel = this.document.createElement("p");
|
|
4081
|
+
sentinel.className = "footnote-continuation-position-sentinel";
|
|
4082
|
+
sentinel.setAttribute("aria-hidden", "true");
|
|
4083
|
+
sentinel.style.setProperty("display", "none", "important");
|
|
4084
|
+
content.appendChild(sentinel);
|
|
4085
|
+
}
|
|
4086
|
+
const candidate = element.cloneNode(true);
|
|
4087
|
+
candidate.removeAttribute(FOOTNOTE_SOURCE_POSITION_ATTR);
|
|
4088
|
+
if (fitting.length === 0 && candidate.tagName === "P") {
|
|
4089
|
+
candidate.style.setProperty("display", "block", "important");
|
|
4090
|
+
}
|
|
4091
|
+
content.appendChild(candidate);
|
|
4092
|
+
const candidateHeight = pxToPt(measureContainer.getBoundingClientRect().height);
|
|
4093
|
+
if (candidateHeight <= availableHeightPt) {
|
|
4094
|
+
fitting.push(element);
|
|
4095
|
+
continue;
|
|
4096
|
+
}
|
|
4097
|
+
const canSplitCandidate = candidate.tagName === "P" && this.canRangeFragmentParagraph(candidate);
|
|
4098
|
+
candidate.remove();
|
|
4099
|
+
let split = null;
|
|
4100
|
+
if (canSplitCandidate) {
|
|
4101
|
+
split = this.splitParagraphAtLargestFit(candidate, (head) => {
|
|
4102
|
+
content.appendChild(head);
|
|
4103
|
+
try {
|
|
4104
|
+
return pxToPt(measureContainer.getBoundingClientRect().height) <= availableHeightPt;
|
|
4105
|
+
} finally {
|
|
4106
|
+
head.remove();
|
|
4107
|
+
}
|
|
4108
|
+
}, {
|
|
4109
|
+
// Both fallbacks below cut where ordinary line breaking would not,
|
|
4110
|
+
// so they are only ever right for an element that owns the whole
|
|
4111
|
+
// band: if this page already carries earlier content, a paragraph
|
|
4112
|
+
// that will not start here belongs intact in the next note band.
|
|
4113
|
+
emergencyGraphemeBreaks: fitting.length === 0,
|
|
4114
|
+
// A continuation page owns the full note band. If even one legal
|
|
4115
|
+
// grapheme is taller than it, clip only that unit and keep draining.
|
|
4116
|
+
forceFirstOnNoFit: fitting.length === 0
|
|
4117
|
+
});
|
|
4118
|
+
}
|
|
4119
|
+
if (split?.kind === "split") {
|
|
4120
|
+
if (sourcePosition) {
|
|
4121
|
+
split.head.setAttribute(FOOTNOTE_SOURCE_POSITION_ATTR, sourcePosition);
|
|
4122
|
+
split.tail.setAttribute(FOOTNOTE_SOURCE_POSITION_ATTR, sourcePosition);
|
|
4123
|
+
}
|
|
4124
|
+
fitting.push(split.head);
|
|
4125
|
+
remaining = [split.tail, ...continuation.remainingElements.slice(index + 1)];
|
|
4126
|
+
} else if (fitting.length === 0) {
|
|
4127
|
+
fitting.push(element);
|
|
4128
|
+
remaining = continuation.remainingElements.slice(index + 1);
|
|
4129
|
+
} else {
|
|
4130
|
+
remaining = continuation.remainingElements.slice(index);
|
|
2285
4131
|
}
|
|
2286
4132
|
break;
|
|
2287
4133
|
}
|
|
4134
|
+
} finally {
|
|
4135
|
+
measureContainer.remove();
|
|
2288
4136
|
}
|
|
2289
|
-
return {
|
|
4137
|
+
return {
|
|
4138
|
+
current: {
|
|
4139
|
+
footnoteId: continuation.footnoteId,
|
|
4140
|
+
sourceAnchorId: continuation.sourceAnchorId,
|
|
4141
|
+
remainingElements: fitting
|
|
4142
|
+
},
|
|
4143
|
+
overflow: remaining.length > 0 ? {
|
|
4144
|
+
footnoteId: continuation.footnoteId,
|
|
4145
|
+
sourceAnchorId: continuation.sourceAnchorId,
|
|
4146
|
+
remainingElements: remaining
|
|
4147
|
+
} : null
|
|
4148
|
+
};
|
|
2290
4149
|
}
|
|
2291
4150
|
/**
|
|
2292
|
-
*
|
|
4151
|
+
* Splits a footnote element into parts that fit within the available height.
|
|
4152
|
+
* Returns the elements that fit and the elements that need to continue.
|
|
2293
4153
|
*/
|
|
2294
|
-
|
|
2295
|
-
const
|
|
2296
|
-
|
|
2297
|
-
|
|
4154
|
+
splitFootnoteToFit(footnoteElement, availableHeightPt, contentWidth, forceProgress = false, existingPayload) {
|
|
4155
|
+
const layoutContext = this.document.createElement("div");
|
|
4156
|
+
layoutContext.style.position = "absolute";
|
|
4157
|
+
layoutContext.style.visibility = "hidden";
|
|
4158
|
+
layoutContext.style.width = `${contentWidth}pt`;
|
|
4159
|
+
layoutContext.style.left = "-9999px";
|
|
4160
|
+
layoutContext.className = this.cssPrefix + "footnotes";
|
|
4161
|
+
this.appendFootnoteSeparator(layoutContext, false);
|
|
4162
|
+
const attachedFootnote = footnoteElement.cloneNode(true);
|
|
4163
|
+
layoutContext.appendChild(attachedFootnote);
|
|
4164
|
+
this.noteMeasurementHost().appendChild(layoutContext);
|
|
4165
|
+
const measureContainer = this.document.createElement("div");
|
|
2298
4166
|
measureContainer.style.position = "absolute";
|
|
2299
4167
|
measureContainer.style.visibility = "hidden";
|
|
2300
4168
|
measureContainer.style.width = `${contentWidth}pt`;
|
|
2301
4169
|
measureContainer.style.left = "-9999px";
|
|
2302
4170
|
measureContainer.className = this.cssPrefix + "footnotes";
|
|
2303
|
-
|
|
2304
|
-
this.
|
|
2305
|
-
|
|
2306
|
-
|
|
2307
|
-
|
|
2308
|
-
|
|
4171
|
+
const hasContinuation = Boolean(existingPayload?.continuation?.remainingElements.length);
|
|
4172
|
+
this.appendFootnoteSeparator(measureContainer, hasContinuation);
|
|
4173
|
+
if (hasContinuation) {
|
|
4174
|
+
measureContainer.appendChild(this.createFootnoteContinuationWrapper(
|
|
4175
|
+
existingPayload.continuation
|
|
4176
|
+
));
|
|
4177
|
+
}
|
|
4178
|
+
const partialById = new Map(
|
|
4179
|
+
existingPayload?.partialFootnotes?.map((partial) => [partial.footnoteId, partial]) ?? []
|
|
4180
|
+
);
|
|
4181
|
+
for (const id of existingPayload?.footnoteIds ?? []) {
|
|
4182
|
+
this.checkpoint();
|
|
4183
|
+
const source = this.footnoteRegistry.get(id);
|
|
4184
|
+
if (!source) continue;
|
|
4185
|
+
const partial = partialById.get(id);
|
|
4186
|
+
measureContainer.appendChild(partial ? this.createPartialFootnoteItem(source, partial.fittingElements) : source.cloneNode(true));
|
|
4187
|
+
}
|
|
4188
|
+
const measuredPartial = this.createPartialFootnoteItem(attachedFootnote, []);
|
|
4189
|
+
const measuredContent = measuredPartial.querySelector(":scope > .footnote-content");
|
|
4190
|
+
if (!measuredContent) {
|
|
4191
|
+
layoutContext.remove();
|
|
4192
|
+
throw new Error("Footnote is missing its content shell");
|
|
4193
|
+
}
|
|
4194
|
+
measureContainer.appendChild(measuredPartial);
|
|
4195
|
+
this.noteMeasurementHost().appendChild(measureContainer);
|
|
4196
|
+
try {
|
|
4197
|
+
const footnoteContent = attachedFootnote.querySelector(".footnote-content");
|
|
4198
|
+
if (!footnoteContent) {
|
|
4199
|
+
return {
|
|
4200
|
+
fits: Array.from(attachedFootnote.children).map((el) => el.cloneNode(true)),
|
|
4201
|
+
overflow: []
|
|
4202
|
+
};
|
|
4203
|
+
}
|
|
4204
|
+
const children = Array.from(footnoteContent.children);
|
|
4205
|
+
const fits = [];
|
|
4206
|
+
let overflow = [];
|
|
4207
|
+
for (let i = 0; i < children.length; i++) {
|
|
4208
|
+
this.checkpoint();
|
|
4209
|
+
const child = children[i];
|
|
4210
|
+
const measuredCandidate = child.cloneNode(true);
|
|
4211
|
+
measuredContent.appendChild(measuredCandidate);
|
|
4212
|
+
const candidateFits = pxToPt(measureContainer.getBoundingClientRect().height) <= availableHeightPt;
|
|
4213
|
+
if (candidateFits) {
|
|
4214
|
+
fits.push(child.cloneNode(true));
|
|
4215
|
+
continue;
|
|
4216
|
+
}
|
|
4217
|
+
measuredCandidate.remove();
|
|
4218
|
+
const split = this.canRangeFragmentParagraph(child, true) ? this.splitParagraphAtLargestFit(child, (head) => {
|
|
4219
|
+
measuredContent.appendChild(head);
|
|
4220
|
+
try {
|
|
4221
|
+
return pxToPt(measureContainer.getBoundingClientRect().height) <= availableHeightPt;
|
|
4222
|
+
} finally {
|
|
4223
|
+
head.remove();
|
|
4224
|
+
}
|
|
4225
|
+
}, {
|
|
4226
|
+
// Both fallbacks cut where ordinary line breaking would not, so they
|
|
4227
|
+
// are only ever right for an element that owns the whole band: with
|
|
4228
|
+
// earlier siblings already packed here, a paragraph that will not
|
|
4229
|
+
// start belongs intact in the next note band.
|
|
4230
|
+
emergencyGraphemeBreaks: fits.length === 0 && forceProgress,
|
|
4231
|
+
forceFirstOnNoFit: fits.length === 0 && forceProgress
|
|
4232
|
+
}) : null;
|
|
4233
|
+
if (split?.kind === "split") {
|
|
4234
|
+
fits.push(split.head);
|
|
4235
|
+
split.tail.setAttribute(
|
|
4236
|
+
FOOTNOTE_SOURCE_POSITION_ATTR,
|
|
4237
|
+
i === 0 ? "first" : "later"
|
|
4238
|
+
);
|
|
4239
|
+
overflow = [split.tail, ...children.slice(i + 1).map((element, offset) => this.cloneFootnoteElementForContinuation(element, i + 1 + offset))];
|
|
4240
|
+
} else if (split?.kind === "no-fit") {
|
|
4241
|
+
overflow = children.slice(i).map((element, offset) => this.cloneFootnoteElementForContinuation(element, i + offset));
|
|
4242
|
+
} else if (fits.length === 0 && forceProgress) {
|
|
4243
|
+
fits.push(child.cloneNode(true));
|
|
4244
|
+
overflow = children.slice(i + 1).map((element, offset) => this.cloneFootnoteElementForContinuation(element, i + 1 + offset));
|
|
4245
|
+
} else {
|
|
4246
|
+
overflow = children.slice(i).map((element, offset) => this.cloneFootnoteElementForContinuation(element, i + offset));
|
|
4247
|
+
}
|
|
4248
|
+
break;
|
|
4249
|
+
}
|
|
4250
|
+
return { fits, overflow };
|
|
4251
|
+
} finally {
|
|
4252
|
+
measureContainer.remove();
|
|
4253
|
+
layoutContext.remove();
|
|
4254
|
+
}
|
|
2309
4255
|
}
|
|
2310
4256
|
/**
|
|
2311
4257
|
* Adds footnotes to a page container, including continuation content.
|
|
@@ -2318,12 +4264,11 @@ var Docxodus = (() => {
|
|
|
2318
4264
|
if (this.footnoteRegistry.size === 0 && !hasContinuation) {
|
|
2319
4265
|
return;
|
|
2320
4266
|
}
|
|
2321
|
-
const partialFootnoteIds = new Set(partialFootnotes?.map((p) => p.footnoteId) || []);
|
|
2322
4267
|
const maxFootnoteHeight = Math.min(
|
|
2323
4268
|
footnoteHeight,
|
|
2324
4269
|
bands.bodyHeight * MAX_FOOTNOTE_AREA_RATIO
|
|
2325
4270
|
);
|
|
2326
|
-
const footnotesDiv = document.createElement("div");
|
|
4271
|
+
const footnotesDiv = this.document.createElement("div");
|
|
2327
4272
|
footnotesDiv.className = `${this.cssPrefix}footnotes`;
|
|
2328
4273
|
footnotesDiv.style.position = "absolute";
|
|
2329
4274
|
footnotesDiv.style.bottom = `${dims.pageHeight - (bands.bodyTop + bands.bodyHeight)}pt`;
|
|
@@ -2332,35 +4277,18 @@ var Docxodus = (() => {
|
|
|
2332
4277
|
footnotesDiv.style.boxSizing = "border-box";
|
|
2333
4278
|
footnotesDiv.style.maxHeight = `${maxFootnoteHeight}pt`;
|
|
2334
4279
|
footnotesDiv.style.overflow = "hidden";
|
|
2335
|
-
|
|
2336
|
-
footnotesDiv.appendChild(hr);
|
|
4280
|
+
this.appendFootnoteSeparator(footnotesDiv, Boolean(hasContinuation));
|
|
2337
4281
|
if (hasContinuation) {
|
|
2338
|
-
|
|
2339
|
-
contWrapper.className = "footnote-continuation";
|
|
2340
|
-
for (const el of continuation.remainingElements) {
|
|
2341
|
-
contWrapper.appendChild(el.cloneNode(true));
|
|
2342
|
-
}
|
|
2343
|
-
footnotesDiv.appendChild(contWrapper);
|
|
4282
|
+
footnotesDiv.appendChild(this.createFootnoteContinuationWrapper(continuation));
|
|
2344
4283
|
}
|
|
2345
4284
|
for (const id of footnoteIds) {
|
|
2346
4285
|
const partial = partialFootnotes?.find((p) => p.footnoteId === id);
|
|
2347
4286
|
if (partial) {
|
|
2348
4287
|
const footnote = this.footnoteRegistry.get(id);
|
|
2349
4288
|
if (footnote) {
|
|
2350
|
-
|
|
2351
|
-
|
|
2352
|
-
|
|
2353
|
-
const numberSpan = footnote.querySelector(".footnote-number");
|
|
2354
|
-
if (numberSpan) {
|
|
2355
|
-
partialDiv.appendChild(numberSpan.cloneNode(true));
|
|
2356
|
-
}
|
|
2357
|
-
const contentSpan = document.createElement("span");
|
|
2358
|
-
contentSpan.className = "footnote-content";
|
|
2359
|
-
for (const el of partial.fittingElements) {
|
|
2360
|
-
contentSpan.appendChild(el.cloneNode(true));
|
|
2361
|
-
}
|
|
2362
|
-
partialDiv.appendChild(contentSpan);
|
|
2363
|
-
footnotesDiv.appendChild(partialDiv);
|
|
4289
|
+
footnotesDiv.appendChild(
|
|
4290
|
+
this.createPartialFootnoteItem(footnote, partial.fittingElements)
|
|
4291
|
+
);
|
|
2364
4292
|
}
|
|
2365
4293
|
} else {
|
|
2366
4294
|
const footnote = this.footnoteRegistry.get(id);
|
|
@@ -2371,30 +4299,26 @@ var Docxodus = (() => {
|
|
|
2371
4299
|
}
|
|
2372
4300
|
pageBox.appendChild(footnotesDiv);
|
|
2373
4301
|
}
|
|
2374
|
-
/**
|
|
2375
|
-
|
|
2376
|
-
*/
|
|
2377
|
-
selectHeader(sectionIndex, pageInSection, globalPageNumber) {
|
|
4302
|
+
/** Select the section's first/odd/even header from its one-based page position. */
|
|
4303
|
+
selectHeader(sectionIndex, pageInSection, displayedPageNumber) {
|
|
2378
4304
|
const sectionHf = this.hfRegistry.get(sectionIndex);
|
|
2379
4305
|
if (!sectionHf) return void 0;
|
|
2380
4306
|
if (pageInSection === 1 && sectionHf.headerFirst) {
|
|
2381
4307
|
return sectionHf.headerFirst;
|
|
2382
4308
|
}
|
|
2383
|
-
if (
|
|
4309
|
+
if (displayedPageNumber % 2 === 0 && sectionHf.headerEven) {
|
|
2384
4310
|
return sectionHf.headerEven;
|
|
2385
4311
|
}
|
|
2386
4312
|
return sectionHf.headerDefault;
|
|
2387
4313
|
}
|
|
2388
|
-
/**
|
|
2389
|
-
|
|
2390
|
-
*/
|
|
2391
|
-
selectFooter(sectionIndex, pageInSection, globalPageNumber) {
|
|
4314
|
+
/** Select the section's first/odd/even footer from its one-based page position. */
|
|
4315
|
+
selectFooter(sectionIndex, pageInSection, displayedPageNumber) {
|
|
2392
4316
|
const sectionHf = this.hfRegistry.get(sectionIndex);
|
|
2393
4317
|
if (!sectionHf) return void 0;
|
|
2394
4318
|
if (pageInSection === 1 && sectionHf.footerFirst) {
|
|
2395
4319
|
return sectionHf.footerFirst;
|
|
2396
4320
|
}
|
|
2397
|
-
if (
|
|
4321
|
+
if (displayedPageNumber % 2 === 0 && sectionHf.footerEven) {
|
|
2398
4322
|
return sectionHf.footerEven;
|
|
2399
4323
|
}
|
|
2400
4324
|
return sectionHf.footerDefault;
|
|
@@ -2409,7 +4333,7 @@ var Docxodus = (() => {
|
|
|
2409
4333
|
* Deterministic: it depends only on the section's page setup and the registry's pre-measured
|
|
2410
4334
|
* story heights, never on the page's content, which is what keeps it lazy-loading compatible.
|
|
2411
4335
|
*/
|
|
2412
|
-
getPageBands(dims, sectionIndex, pageInSection,
|
|
4336
|
+
getPageBands(dims, sectionIndex, pageInSection, displayedPageNumber) {
|
|
2413
4337
|
const sectionHf = this.hfRegistry.get(sectionIndex);
|
|
2414
4338
|
return resolvePageBands(
|
|
2415
4339
|
dims,
|
|
@@ -2418,14 +4342,14 @@ var Docxodus = (() => {
|
|
|
2418
4342
|
sectionHf?.headerEvenHeight,
|
|
2419
4343
|
sectionHf?.headerDefaultHeight,
|
|
2420
4344
|
pageInSection,
|
|
2421
|
-
|
|
4345
|
+
displayedPageNumber
|
|
2422
4346
|
),
|
|
2423
4347
|
this.selectStoryHeight(
|
|
2424
4348
|
sectionHf?.footerFirstHeight,
|
|
2425
4349
|
sectionHf?.footerEvenHeight,
|
|
2426
4350
|
sectionHf?.footerDefaultHeight,
|
|
2427
4351
|
pageInSection,
|
|
2428
|
-
|
|
4352
|
+
displayedPageNumber
|
|
2429
4353
|
)
|
|
2430
4354
|
);
|
|
2431
4355
|
}
|
|
@@ -2433,9 +4357,9 @@ var Docxodus = (() => {
|
|
|
2433
4357
|
* The measured height of the running story this page position selects, mirroring
|
|
2434
4358
|
* {@link selectHeader}/{@link selectFooter}. Zero when the page has no such story.
|
|
2435
4359
|
*/
|
|
2436
|
-
selectStoryHeight(first, even, fallback, pageInSection,
|
|
4360
|
+
selectStoryHeight(first, even, fallback, pageInSection, displayedPageNumber) {
|
|
2437
4361
|
if (pageInSection === 1 && first != null) return first;
|
|
2438
|
-
if (
|
|
4362
|
+
if (displayedPageNumber % 2 === 0 && even != null) return even;
|
|
2439
4363
|
return fallback ?? 0;
|
|
2440
4364
|
}
|
|
2441
4365
|
/**
|
|
@@ -2447,7 +4371,7 @@ var Docxodus = (() => {
|
|
|
2447
4371
|
* silently starts overlapping body text.
|
|
2448
4372
|
*/
|
|
2449
4373
|
measureHeaderFooterHeight(source, contentWidth) {
|
|
2450
|
-
const measureContainer = document.createElement("div");
|
|
4374
|
+
const measureContainer = this.document.createElement("div");
|
|
2451
4375
|
measureContainer.style.position = "absolute";
|
|
2452
4376
|
measureContainer.style.visibility = "hidden";
|
|
2453
4377
|
measureContainer.style.width = `${contentWidth}pt`;
|
|
@@ -2466,79 +4390,275 @@ var Docxodus = (() => {
|
|
|
2466
4390
|
* Implements a single-pass, forward-only algorithm that is compatible with future lazy loading.
|
|
2467
4391
|
* Supports footnote continuation - long footnotes can split across pages.
|
|
2468
4392
|
*/
|
|
2469
|
-
flowToPages(blocks, dims, startPageNumber, sectionIndex) {
|
|
4393
|
+
flowToPages(blocks, dims, startPageNumber, sectionIndex, sectionDimensions) {
|
|
2470
4394
|
const pages = [];
|
|
2471
4395
|
let currentContent = [];
|
|
2472
4396
|
let pageNumber = startPageNumber;
|
|
2473
|
-
let
|
|
4397
|
+
let pageSectionIndex = sectionIndex;
|
|
4398
|
+
const sectionStartPages = /* @__PURE__ */ new Map([[sectionIndex, startPageNumber]]);
|
|
4399
|
+
const pageInSection = (owner = pageSectionIndex, physicalPage = pageNumber) => physicalPage - (sectionStartPages.get(owner) ?? physicalPage) + 1;
|
|
4400
|
+
const dimensionsFor = (owner = pageSectionIndex) => sectionDimensions.get(owner) ?? dims;
|
|
4401
|
+
const markSectionPlaced = (owner) => {
|
|
4402
|
+
if (!sectionStartPages.has(owner)) sectionStartPages.set(owner, pageNumber);
|
|
4403
|
+
};
|
|
4404
|
+
const previousBox = this.containerElement.querySelector(
|
|
4405
|
+
`.${this.cssPrefix}box:last-of-type`
|
|
4406
|
+
);
|
|
4407
|
+
let precedingDisplayedPageNumber = parseInt(
|
|
4408
|
+
previousBox?.dataset.displayedPageNumber ?? "0",
|
|
4409
|
+
10
|
|
4410
|
+
);
|
|
4411
|
+
const displayedPageNumber = (owner = pageSectionIndex, ownerPageInSection = pageInSection(owner), physicalPage = pageNumber) => {
|
|
4412
|
+
const numbering = this.pageNumbering.get(owner) ?? {};
|
|
4413
|
+
if (numbering.start !== void 0) {
|
|
4414
|
+
return numbering.start + ownerPageInSection - 1;
|
|
4415
|
+
}
|
|
4416
|
+
const ownerNumbering = this.pageNumbering.get(pageSectionIndex) ?? {};
|
|
4417
|
+
const currentPhysicalNumber = ownerNumbering.start !== void 0 ? ownerNumbering.start + pageInSection(pageSectionIndex) - 1 : precedingDisplayedPageNumber + 1;
|
|
4418
|
+
return currentPhysicalNumber + physicalPage - pageNumber;
|
|
4419
|
+
};
|
|
2474
4420
|
let { bodyHeight: effectiveContentHeight } = this.getPageBands(
|
|
2475
|
-
|
|
2476
|
-
|
|
2477
|
-
pageInSection,
|
|
2478
|
-
|
|
4421
|
+
dimensionsFor(),
|
|
4422
|
+
pageSectionIndex,
|
|
4423
|
+
pageInSection(),
|
|
4424
|
+
displayedPageNumber()
|
|
2479
4425
|
);
|
|
2480
4426
|
let remainingHeight = effectiveContentHeight;
|
|
2481
4427
|
let prevMarginBottomPt = 0;
|
|
2482
4428
|
let currentFootnoteIds = [];
|
|
4429
|
+
let currentPageHasFootnoteReference = false;
|
|
2483
4430
|
let currentFootnoteHeight = 0;
|
|
2484
4431
|
let currentContinuation = this.pendingFootnoteContinuation;
|
|
2485
4432
|
let nextPageContinuation = null;
|
|
4433
|
+
let currentContinuationPartitioned = false;
|
|
4434
|
+
let currentPageAdmitted = false;
|
|
2486
4435
|
let deferredFootnoteIds = [];
|
|
2487
4436
|
let currentPartialFootnotes = [];
|
|
2488
|
-
|
|
2489
|
-
|
|
2490
|
-
|
|
2491
|
-
|
|
4437
|
+
const adoptEmptyPageOwner = (owner) => {
|
|
4438
|
+
pageSectionIndex = owner;
|
|
4439
|
+
const bands = this.getPageBands(
|
|
4440
|
+
dimensionsFor(owner),
|
|
4441
|
+
owner,
|
|
4442
|
+
pageInSection(owner),
|
|
4443
|
+
displayedPageNumber(owner)
|
|
4444
|
+
);
|
|
4445
|
+
effectiveContentHeight = bands.bodyHeight;
|
|
4446
|
+
remainingHeight = effectiveContentHeight;
|
|
4447
|
+
};
|
|
4448
|
+
const admitCurrentPage = () => {
|
|
4449
|
+
if (currentPageAdmitted) return;
|
|
4450
|
+
this.admitPageAllocation();
|
|
4451
|
+
currentPageAdmitted = true;
|
|
4452
|
+
};
|
|
4453
|
+
const prepareCurrentContinuation = () => {
|
|
4454
|
+
if (currentContinuationPartitioned || (currentContinuation?.remainingElements.length ?? 0) === 0) return;
|
|
4455
|
+
admitCurrentPage();
|
|
4456
|
+
const ownedDimensions = dimensionsFor();
|
|
4457
|
+
const pageBands = this.getPageBands(
|
|
4458
|
+
ownedDimensions,
|
|
4459
|
+
pageSectionIndex,
|
|
4460
|
+
pageInSection(),
|
|
4461
|
+
displayedPageNumber()
|
|
4462
|
+
);
|
|
4463
|
+
const partition = this.splitContinuationForPage(
|
|
4464
|
+
currentContinuation,
|
|
4465
|
+
pageBands.bodyHeight * MAX_FOOTNOTE_AREA_RATIO,
|
|
4466
|
+
ownedDimensions.contentWidth
|
|
4467
|
+
);
|
|
4468
|
+
currentContinuation = partition.current;
|
|
4469
|
+
if (partition.overflow) nextPageContinuation = partition.overflow;
|
|
4470
|
+
currentContinuationPartitioned = true;
|
|
4471
|
+
currentFootnoteHeight = this.measureFootnotesHeight(
|
|
4472
|
+
currentFootnoteIds,
|
|
4473
|
+
ownedDimensions.contentWidth,
|
|
4474
|
+
currentContinuation,
|
|
4475
|
+
currentPartialFootnotes
|
|
4476
|
+
);
|
|
4477
|
+
};
|
|
4478
|
+
const finishPage = (nextPageSectionIndex = pageSectionIndex, forceEmptyPage = false) => {
|
|
2492
4479
|
const hasCurrentContinuation = (currentContinuation?.remainingElements.length ?? 0) > 0;
|
|
2493
|
-
if (currentContent.length === 0 && !hasCurrentContinuation)
|
|
4480
|
+
if (!forceEmptyPage && currentContent.length === 0 && currentFootnoteIds.length === 0 && !hasCurrentContinuation) {
|
|
4481
|
+
adoptEmptyPageOwner(nextPageSectionIndex);
|
|
4482
|
+
return;
|
|
4483
|
+
}
|
|
4484
|
+
prepareCurrentContinuation();
|
|
4485
|
+
admitCurrentPage();
|
|
4486
|
+
let pageContinuation = currentContinuation;
|
|
4487
|
+
const ownedPageInSection = pageInSection();
|
|
4488
|
+
const ownedDimensions = dimensionsFor();
|
|
4489
|
+
const ownedDisplayedPageNumber = displayedPageNumber(
|
|
4490
|
+
pageSectionIndex,
|
|
4491
|
+
ownedPageInSection
|
|
4492
|
+
);
|
|
4493
|
+
const pageBands = this.getPageBands(
|
|
4494
|
+
ownedDimensions,
|
|
4495
|
+
pageSectionIndex,
|
|
4496
|
+
ownedPageInSection,
|
|
4497
|
+
ownedDisplayedPageNumber
|
|
4498
|
+
);
|
|
4499
|
+
const maxFootnoteHeight = pageBands.bodyHeight * MAX_FOOTNOTE_AREA_RATIO;
|
|
4500
|
+
pageContinuation = currentContinuation;
|
|
4501
|
+
currentFootnoteHeight = this.measureFootnotesHeight(
|
|
4502
|
+
currentFootnoteIds,
|
|
4503
|
+
ownedDimensions.contentWidth,
|
|
4504
|
+
pageContinuation,
|
|
4505
|
+
currentPartialFootnotes
|
|
4506
|
+
);
|
|
4507
|
+
if (currentContent.length === 0 && currentFootnoteIds.length > 0 && currentPartialFootnotes.length === 0) {
|
|
4508
|
+
const fittingIds = [];
|
|
4509
|
+
for (let index = 0; index < currentFootnoteIds.length; index++) {
|
|
4510
|
+
const footnoteId = currentFootnoteIds[index];
|
|
4511
|
+
const candidateIds = [...fittingIds, footnoteId];
|
|
4512
|
+
const candidateHeight = this.measureFootnotesHeight(
|
|
4513
|
+
candidateIds,
|
|
4514
|
+
ownedDimensions.contentWidth,
|
|
4515
|
+
pageContinuation
|
|
4516
|
+
);
|
|
4517
|
+
const guardedCandidateHeight = candidateHeight + FOOTNOTE_MEASUREMENT_GUARD_PT;
|
|
4518
|
+
if (guardedCandidateHeight <= maxFootnoteHeight) {
|
|
4519
|
+
fittingIds.push(footnoteId);
|
|
4520
|
+
currentFootnoteHeight = guardedCandidateHeight;
|
|
4521
|
+
continue;
|
|
4522
|
+
}
|
|
4523
|
+
const hasPageContinuation = (pageContinuation?.remainingElements.length ?? 0) > 0;
|
|
4524
|
+
if (fittingIds.length === 0 && !hasPageContinuation) {
|
|
4525
|
+
const source = this.footnoteRegistry.get(footnoteId);
|
|
4526
|
+
const split = source ? this.splitFootnoteToFit(
|
|
4527
|
+
source,
|
|
4528
|
+
maxFootnoteHeight - FOOTNOTE_MEASUREMENT_GUARD_PT,
|
|
4529
|
+
ownedDimensions.contentWidth,
|
|
4530
|
+
true
|
|
4531
|
+
) : null;
|
|
4532
|
+
fittingIds.push(footnoteId);
|
|
4533
|
+
if (source && split && split.fits.length > 0 && split.overflow.length > 0) {
|
|
4534
|
+
currentPartialFootnotes.push({ footnoteId, fittingElements: split.fits });
|
|
4535
|
+
nextPageContinuation = {
|
|
4536
|
+
footnoteId,
|
|
4537
|
+
sourceAnchorId: source.dataset.sourceAnchorId,
|
|
4538
|
+
remainingElements: split.overflow
|
|
4539
|
+
};
|
|
4540
|
+
currentFootnoteHeight = maxFootnoteHeight;
|
|
4541
|
+
} else {
|
|
4542
|
+
currentFootnoteHeight = guardedCandidateHeight;
|
|
4543
|
+
}
|
|
4544
|
+
deferredFootnoteIds.push(...currentFootnoteIds.slice(index + 1));
|
|
4545
|
+
} else {
|
|
4546
|
+
deferredFootnoteIds.push(...currentFootnoteIds.slice(index));
|
|
4547
|
+
}
|
|
4548
|
+
break;
|
|
4549
|
+
}
|
|
4550
|
+
currentFootnoteIds = fittingIds;
|
|
4551
|
+
}
|
|
2494
4552
|
const page = this.createPage(
|
|
2495
|
-
|
|
4553
|
+
ownedDimensions,
|
|
2496
4554
|
pageNumber,
|
|
2497
|
-
|
|
4555
|
+
pageSectionIndex,
|
|
4556
|
+
ownedDisplayedPageNumber,
|
|
2498
4557
|
currentContent,
|
|
2499
|
-
|
|
4558
|
+
ownedPageInSection,
|
|
2500
4559
|
currentFootnoteIds,
|
|
2501
4560
|
currentFootnoteHeight,
|
|
2502
|
-
|
|
2503
|
-
currentPartialFootnotes.length > 0 ? currentPartialFootnotes : void 0
|
|
4561
|
+
pageContinuation,
|
|
4562
|
+
currentPartialFootnotes.length > 0 ? currentPartialFootnotes : void 0,
|
|
4563
|
+
false,
|
|
4564
|
+
currentPageAdmitted
|
|
2504
4565
|
);
|
|
2505
4566
|
pages.push(page);
|
|
4567
|
+
precedingDisplayedPageNumber = ownedDisplayedPageNumber;
|
|
2506
4568
|
pageNumber++;
|
|
2507
|
-
pageInSection++;
|
|
2508
4569
|
currentContent = [];
|
|
2509
|
-
|
|
4570
|
+
pageSectionIndex = nextPageSectionIndex;
|
|
4571
|
+
if (!sectionStartPages.has(pageSectionIndex)) {
|
|
4572
|
+
sectionStartPages.set(pageSectionIndex, pageNumber);
|
|
4573
|
+
}
|
|
4574
|
+
const newBands = this.getPageBands(
|
|
4575
|
+
dimensionsFor(),
|
|
4576
|
+
pageSectionIndex,
|
|
4577
|
+
pageInSection(),
|
|
4578
|
+
displayedPageNumber()
|
|
4579
|
+
);
|
|
2510
4580
|
effectiveContentHeight = newBands.bodyHeight;
|
|
2511
4581
|
remainingHeight = effectiveContentHeight;
|
|
2512
4582
|
prevMarginBottomPt = 0;
|
|
2513
4583
|
currentFootnoteIds = [];
|
|
4584
|
+
currentPageHasFootnoteReference = false;
|
|
2514
4585
|
currentPartialFootnotes = [];
|
|
2515
4586
|
currentContinuation = nextPageContinuation;
|
|
2516
4587
|
nextPageContinuation = null;
|
|
4588
|
+
currentContinuationPartitioned = false;
|
|
4589
|
+
currentPageAdmitted = false;
|
|
2517
4590
|
if (deferredFootnoteIds.length > 0) {
|
|
2518
4591
|
currentFootnoteIds = [...deferredFootnoteIds];
|
|
2519
4592
|
deferredFootnoteIds = [];
|
|
2520
4593
|
}
|
|
2521
|
-
|
|
2522
|
-
|
|
2523
|
-
} else {
|
|
2524
|
-
currentFootnoteHeight = 0;
|
|
2525
|
-
}
|
|
2526
|
-
if (currentFootnoteIds.length > 0) {
|
|
2527
|
-
currentFootnoteHeight += this.measureFootnotesHeight(
|
|
2528
|
-
currentFootnoteIds,
|
|
2529
|
-
dims.contentWidth,
|
|
2530
|
-
null
|
|
2531
|
-
);
|
|
2532
|
-
}
|
|
4594
|
+
const nextDimensions = dimensionsFor();
|
|
4595
|
+
currentFootnoteHeight = currentContinuation ? 0 : this.measureFootnotesHeight(currentFootnoteIds, nextDimensions.contentWidth);
|
|
2533
4596
|
};
|
|
4597
|
+
const pageHasPayload = () => currentContent.length > 0 || currentFootnoteIds.length > 0 || (currentContinuation?.remainingElements.length ?? 0) > 0;
|
|
2534
4598
|
for (let i = 0; i < blocks.length; i++) {
|
|
4599
|
+
this.checkpoint();
|
|
2535
4600
|
const block = blocks[i];
|
|
4601
|
+
const allBlockFootnoteIds = this.extractFootnoteRefs(block.element);
|
|
4602
|
+
if (block.sectionIndex !== pageSectionIndex && currentPageHasFootnoteReference && (!this.footnoteLayoutLikeWord8 || allBlockFootnoteIds.length > 0)) {
|
|
4603
|
+
finishPage(block.sectionIndex);
|
|
4604
|
+
}
|
|
4605
|
+
const pageIsEmpty = currentContent.length === 0 && currentFootnoteIds.length === 0 && (currentContinuation?.remainingElements.length ?? 0) === 0;
|
|
4606
|
+
if (pageIsEmpty && block.sectionIndex !== pageSectionIndex) {
|
|
4607
|
+
adoptEmptyPageOwner(block.sectionIndex);
|
|
4608
|
+
}
|
|
4609
|
+
prepareCurrentContinuation();
|
|
4610
|
+
if (currentFootnoteHeight > 0) {
|
|
4611
|
+
const ownerDimensions = dimensionsFor();
|
|
4612
|
+
const ownerBands = this.getPageBands(
|
|
4613
|
+
ownerDimensions,
|
|
4614
|
+
pageSectionIndex,
|
|
4615
|
+
pageInSection(),
|
|
4616
|
+
displayedPageNumber()
|
|
4617
|
+
);
|
|
4618
|
+
const ownerMaxFootnoteArea = ownerBands.bodyHeight * MAX_FOOTNOTE_AREA_RATIO;
|
|
4619
|
+
if (currentFootnoteHeight > ownerMaxFootnoteArea) {
|
|
4620
|
+
const fittingIds = [];
|
|
4621
|
+
let fittingHeight = 0;
|
|
4622
|
+
if ((currentContinuation?.remainingElements.length ?? 0) === 0 && currentPartialFootnotes.length === 0) {
|
|
4623
|
+
for (const footnoteId of currentFootnoteIds) {
|
|
4624
|
+
this.checkpoint();
|
|
4625
|
+
const candidateHeight = this.measureFootnotesHeight(
|
|
4626
|
+
[...fittingIds, footnoteId],
|
|
4627
|
+
ownerDimensions.contentWidth
|
|
4628
|
+
);
|
|
4629
|
+
if (candidateHeight + FOOTNOTE_MEASUREMENT_GUARD_PT > ownerMaxFootnoteArea) break;
|
|
4630
|
+
fittingIds.push(footnoteId);
|
|
4631
|
+
fittingHeight = candidateHeight;
|
|
4632
|
+
}
|
|
4633
|
+
}
|
|
4634
|
+
if (fittingIds.length > 0) {
|
|
4635
|
+
deferredFootnoteIds = [
|
|
4636
|
+
...currentFootnoteIds.slice(fittingIds.length),
|
|
4637
|
+
...deferredFootnoteIds
|
|
4638
|
+
];
|
|
4639
|
+
currentFootnoteIds = fittingIds;
|
|
4640
|
+
currentFootnoteHeight = fittingHeight;
|
|
4641
|
+
} else {
|
|
4642
|
+
finishPage(block.sectionIndex);
|
|
4643
|
+
i--;
|
|
4644
|
+
continue;
|
|
4645
|
+
}
|
|
4646
|
+
}
|
|
4647
|
+
}
|
|
4648
|
+
const blockDimensions = dimensionsFor(block.sectionIndex);
|
|
4649
|
+
const nextBlockPageInSection = pageInSection(block.sectionIndex, pageNumber + 1);
|
|
4650
|
+
const freshBlockPageBodyHeight = this.getPageBands(
|
|
4651
|
+
blockDimensions,
|
|
4652
|
+
block.sectionIndex,
|
|
4653
|
+
nextBlockPageInSection,
|
|
4654
|
+
displayedPageNumber(block.sectionIndex, nextBlockPageInSection, pageNumber + 1)
|
|
4655
|
+
).bodyHeight;
|
|
2536
4656
|
if (block.isPageBreak) {
|
|
2537
|
-
finishPage();
|
|
4657
|
+
finishPage(block.sectionIndex);
|
|
2538
4658
|
continue;
|
|
2539
4659
|
}
|
|
2540
4660
|
if (block.pageBreakBefore && currentContent.length > 0) {
|
|
2541
|
-
finishPage();
|
|
4661
|
+
finishPage(block.sectionIndex);
|
|
2542
4662
|
}
|
|
2543
4663
|
const previousBlock = blocks[i - 1];
|
|
2544
4664
|
const startsKeepChain = !previousBlock || !previousBlock.keepWithNext || previousBlock.isPageBreak || block.pageBreakBefore;
|
|
@@ -2554,7 +4674,7 @@ var Docxodus = (() => {
|
|
|
2554
4674
|
if (newChainFootnoteIds.length > 0 && this.footnoteRegistry.size > 0) {
|
|
2555
4675
|
const totalChainFootnoteHeight = this.measureFootnotesHeight(
|
|
2556
4676
|
[...currentFootnoteIds, ...newChainFootnoteIds],
|
|
2557
|
-
|
|
4677
|
+
blockDimensions.contentWidth,
|
|
2558
4678
|
currentContinuation
|
|
2559
4679
|
);
|
|
2560
4680
|
additionalChainFootnoteHeight = Math.max(
|
|
@@ -2565,58 +4685,69 @@ var Docxodus = (() => {
|
|
|
2565
4685
|
const currentChainHeight = this.measureKeepWithNextChainBodyHeight(
|
|
2566
4686
|
keepChain,
|
|
2567
4687
|
prevMarginBottomPt,
|
|
2568
|
-
currentContent.length === 0
|
|
4688
|
+
currentContent.length === 0,
|
|
4689
|
+
pageInSection(block.sectionIndex)
|
|
2569
4690
|
) + additionalChainFootnoteHeight;
|
|
2570
4691
|
const currentAvailableHeight = remainingHeight - currentFootnoteHeight;
|
|
2571
4692
|
if (currentChainHeight > currentAvailableHeight) {
|
|
2572
4693
|
const nextPageBands = this.getPageBands(
|
|
2573
|
-
|
|
2574
|
-
sectionIndex,
|
|
2575
|
-
pageInSection + 1,
|
|
2576
|
-
|
|
4694
|
+
dimensionsFor(block.sectionIndex),
|
|
4695
|
+
block.sectionIndex,
|
|
4696
|
+
pageInSection(block.sectionIndex, pageNumber + 1),
|
|
4697
|
+
displayedPageNumber(
|
|
4698
|
+
block.sectionIndex,
|
|
4699
|
+
pageInSection(block.sectionIndex, pageNumber + 1),
|
|
4700
|
+
pageNumber + 1
|
|
4701
|
+
)
|
|
2577
4702
|
);
|
|
2578
4703
|
const freshChainBodyHeight = this.measureKeepWithNextChainBodyHeight(
|
|
2579
4704
|
keepChain,
|
|
2580
4705
|
0,
|
|
2581
|
-
true
|
|
4706
|
+
true,
|
|
4707
|
+
pageInSection(block.sectionIndex, pageNumber + 1)
|
|
2582
4708
|
);
|
|
2583
4709
|
const freshChainFootnoteHeight = this.measureFootnotesHeight(
|
|
2584
4710
|
newChainFootnoteIds,
|
|
2585
|
-
|
|
4711
|
+
blockDimensions.contentWidth,
|
|
2586
4712
|
nextPageContinuation
|
|
2587
4713
|
);
|
|
2588
4714
|
if (freshChainBodyHeight + freshChainFootnoteHeight <= nextPageBands.bodyHeight) {
|
|
2589
|
-
finishPage();
|
|
4715
|
+
finishPage(block.sectionIndex);
|
|
2590
4716
|
}
|
|
2591
4717
|
}
|
|
2592
4718
|
}
|
|
2593
4719
|
}
|
|
2594
|
-
const allBlockFootnoteIds = this.extractFootnoteRefs(block.element);
|
|
2595
4720
|
const newFootnoteIds = this.collectNewFootnoteIds([block], currentFootnoteIds);
|
|
4721
|
+
let combinedFootnoteHeight = currentFootnoteHeight;
|
|
2596
4722
|
let additionalFootnoteHeight = 0;
|
|
2597
4723
|
if (newFootnoteIds.length > 0 && this.footnoteRegistry.size > 0) {
|
|
2598
4724
|
const combinedFootnoteIds = [...currentFootnoteIds, ...newFootnoteIds];
|
|
2599
|
-
|
|
4725
|
+
combinedFootnoteHeight = this.measureFootnotesHeight(
|
|
2600
4726
|
combinedFootnoteIds,
|
|
2601
|
-
|
|
2602
|
-
currentContinuation
|
|
4727
|
+
blockDimensions.contentWidth,
|
|
4728
|
+
currentContinuation,
|
|
4729
|
+
currentPartialFootnotes
|
|
4730
|
+
);
|
|
4731
|
+
additionalFootnoteHeight = Math.max(
|
|
4732
|
+
0,
|
|
4733
|
+
combinedFootnoteHeight - currentFootnoteHeight
|
|
2603
4734
|
);
|
|
2604
|
-
additionalFootnoteHeight = totalFootnoteHeight - currentFootnoteHeight;
|
|
2605
4735
|
}
|
|
2606
4736
|
const isFirstOnPage = currentContent.length === 0;
|
|
2607
|
-
|
|
2608
|
-
|
|
2609
|
-
|
|
2610
|
-
|
|
4737
|
+
const effectiveMarginTop = this.effectiveBlockMarginTop(
|
|
4738
|
+
block,
|
|
4739
|
+
prevMarginBottomPt,
|
|
4740
|
+
isFirstOnPage,
|
|
4741
|
+
pageInSection(block.sectionIndex)
|
|
4742
|
+
);
|
|
2611
4743
|
const blockSpace = effectiveMarginTop + block.heightPt + additionalFootnoteHeight;
|
|
2612
4744
|
const effectiveRemainingHeight = remainingHeight - currentFootnoteHeight;
|
|
2613
4745
|
const bodyContentUsed = effectiveContentHeight - remainingHeight;
|
|
2614
4746
|
const maxFootnoteArea = effectiveContentHeight * MAX_FOOTNOTE_AREA_RATIO;
|
|
2615
|
-
const maxFootnoteExpansion = Math.max(0, maxFootnoteArea - currentFootnoteHeight);
|
|
2616
4747
|
if (blockSpace > effectiveRemainingHeight) {
|
|
2617
4748
|
const paragraphFragments = this.tryFragmentParagraph(
|
|
2618
4749
|
block,
|
|
2619
|
-
|
|
4750
|
+
blockDimensions,
|
|
2620
4751
|
effectiveRemainingHeight,
|
|
2621
4752
|
effectiveMarginTop
|
|
2622
4753
|
);
|
|
@@ -2626,40 +4757,74 @@ var Docxodus = (() => {
|
|
|
2626
4757
|
continue;
|
|
2627
4758
|
}
|
|
2628
4759
|
}
|
|
2629
|
-
if (blockSpace <= effectiveRemainingHeight) {
|
|
2630
|
-
|
|
4760
|
+
if (blockSpace <= effectiveRemainingHeight && (combinedFootnoteHeight === 0 || combinedFootnoteHeight + FOOTNOTE_MEASUREMENT_GUARD_PT <= maxFootnoteArea)) {
|
|
4761
|
+
markSectionPlaced(block.sectionIndex);
|
|
4762
|
+
currentContent.push(this.cloneBlockForPage(
|
|
4763
|
+
block,
|
|
4764
|
+
isFirstOnPage,
|
|
4765
|
+
pageInSection(block.sectionIndex)
|
|
4766
|
+
));
|
|
2631
4767
|
remainingHeight -= effectiveMarginTop + block.heightPt + block.marginBottomPt;
|
|
2632
4768
|
prevMarginBottomPt = block.marginBottomPt;
|
|
2633
4769
|
if (newFootnoteIds.length > 0) {
|
|
2634
4770
|
currentFootnoteIds.push(...newFootnoteIds);
|
|
2635
|
-
currentFootnoteHeight
|
|
4771
|
+
currentFootnoteHeight = combinedFootnoteHeight;
|
|
2636
4772
|
}
|
|
2637
|
-
|
|
4773
|
+
currentPageHasFootnoteReference ||= allBlockFootnoteIds.length > 0;
|
|
4774
|
+
} else if (block.heightPt + this.effectiveBlockMarginTop(
|
|
4775
|
+
block,
|
|
4776
|
+
0,
|
|
4777
|
+
true,
|
|
4778
|
+
pageInSection(block.sectionIndex, pageNumber + 1)
|
|
4779
|
+
) <= freshBlockPageBodyHeight) {
|
|
2638
4780
|
const blockSpaceWithoutFootnotes = effectiveMarginTop + block.heightPt;
|
|
2639
|
-
const minBodySpaceNeeded = bodyContentUsed + blockSpaceWithoutFootnotes + MIN_BODY_CONTENT_HEIGHT;
|
|
2640
|
-
const canExpandFootnotes = minBodySpaceNeeded <= effectiveContentHeight;
|
|
2641
4781
|
if (newFootnoteIds.length > 0 && blockSpaceWithoutFootnotes <= effectiveRemainingHeight) {
|
|
2642
|
-
|
|
4782
|
+
markSectionPlaced(block.sectionIndex);
|
|
4783
|
+
currentContent.push(this.cloneBlockForPage(
|
|
4784
|
+
block,
|
|
4785
|
+
isFirstOnPage,
|
|
4786
|
+
pageInSection(block.sectionIndex)
|
|
4787
|
+
));
|
|
2643
4788
|
remainingHeight -= effectiveMarginTop + block.heightPt + block.marginBottomPt;
|
|
2644
4789
|
prevMarginBottomPt = block.marginBottomPt;
|
|
2645
4790
|
const availableForFootnotes = Math.min(
|
|
2646
4791
|
maxFootnoteArea,
|
|
2647
4792
|
effectiveContentHeight - bodyContentUsed - blockSpaceWithoutFootnotes
|
|
2648
4793
|
);
|
|
2649
|
-
|
|
4794
|
+
const nextPageMaxFootnoteArea = freshBlockPageBodyHeight * MAX_FOOTNOTE_AREA_RATIO;
|
|
4795
|
+
const deferralCannotHelp = availableForFootnotes >= nextPageMaxFootnoteArea;
|
|
4796
|
+
for (let noteIndex = 0; noteIndex < newFootnoteIds.length; noteIndex++) {
|
|
4797
|
+
this.checkpoint();
|
|
4798
|
+
const footnoteId = newFootnoteIds[noteIndex];
|
|
2650
4799
|
const footnote = this.footnoteRegistry.get(footnoteId);
|
|
2651
4800
|
if (!footnote) continue;
|
|
2652
|
-
|
|
4801
|
+
if (nextPageContinuation) {
|
|
4802
|
+
deferredFootnoteIds.push(...newFootnoteIds.slice(noteIndex));
|
|
4803
|
+
break;
|
|
4804
|
+
}
|
|
4805
|
+
const candidateIds = [...currentFootnoteIds, footnoteId];
|
|
4806
|
+
const candidateHeight = this.measureFootnotesHeight(
|
|
4807
|
+
candidateIds,
|
|
4808
|
+
blockDimensions.contentWidth,
|
|
4809
|
+
currentContinuation,
|
|
4810
|
+
currentPartialFootnotes
|
|
4811
|
+
);
|
|
2653
4812
|
const spaceLeftForFootnotes = availableForFootnotes - currentFootnoteHeight;
|
|
2654
|
-
if (
|
|
4813
|
+
if (candidateHeight + FOOTNOTE_MEASUREMENT_GUARD_PT <= availableForFootnotes) {
|
|
2655
4814
|
currentFootnoteIds.push(footnoteId);
|
|
2656
|
-
currentFootnoteHeight
|
|
4815
|
+
currentFootnoteHeight = candidateHeight;
|
|
2657
4816
|
} else {
|
|
2658
4817
|
if (spaceLeftForFootnotes > 20) {
|
|
2659
4818
|
const { fits, overflow } = this.splitFootnoteToFit(
|
|
2660
4819
|
footnote,
|
|
2661
|
-
|
|
2662
|
-
|
|
4820
|
+
availableForFootnotes,
|
|
4821
|
+
blockDimensions.contentWidth,
|
|
4822
|
+
deferralCannotHelp,
|
|
4823
|
+
{
|
|
4824
|
+
footnoteIds: currentFootnoteIds,
|
|
4825
|
+
continuation: currentContinuation,
|
|
4826
|
+
partialFootnotes: currentPartialFootnotes
|
|
4827
|
+
}
|
|
2663
4828
|
);
|
|
2664
4829
|
if (fits.length > 0) {
|
|
2665
4830
|
currentFootnoteIds.push(footnoteId);
|
|
@@ -2670,66 +4835,70 @@ var Docxodus = (() => {
|
|
|
2670
4835
|
if (overflow.length > 0) {
|
|
2671
4836
|
nextPageContinuation = {
|
|
2672
4837
|
footnoteId,
|
|
4838
|
+
sourceAnchorId: footnote.dataset.sourceAnchorId,
|
|
2673
4839
|
remainingElements: overflow
|
|
2674
4840
|
};
|
|
2675
4841
|
}
|
|
2676
|
-
currentFootnoteHeight =
|
|
4842
|
+
currentFootnoteHeight = this.measureFootnotesHeight(
|
|
4843
|
+
currentFootnoteIds,
|
|
4844
|
+
blockDimensions.contentWidth,
|
|
4845
|
+
currentContinuation,
|
|
4846
|
+
currentPartialFootnotes
|
|
4847
|
+
);
|
|
4848
|
+
if (overflow.length > 0) {
|
|
4849
|
+
deferredFootnoteIds.push(...newFootnoteIds.slice(noteIndex + 1));
|
|
4850
|
+
break;
|
|
4851
|
+
}
|
|
2677
4852
|
} else {
|
|
2678
|
-
deferredFootnoteIds.push(
|
|
4853
|
+
deferredFootnoteIds.push(...newFootnoteIds.slice(noteIndex));
|
|
4854
|
+
break;
|
|
2679
4855
|
}
|
|
2680
4856
|
} else {
|
|
2681
|
-
deferredFootnoteIds.push(
|
|
4857
|
+
deferredFootnoteIds.push(...newFootnoteIds.slice(noteIndex));
|
|
4858
|
+
break;
|
|
2682
4859
|
}
|
|
2683
4860
|
}
|
|
2684
4861
|
}
|
|
2685
|
-
|
|
2686
|
-
const expandedFootnoteSpace = Math.min(maxFootnoteArea, additionalFootnoteHeight + currentFootnoteHeight);
|
|
2687
|
-
const bodySpaceAfterExpansion = effectiveContentHeight - expandedFootnoteSpace;
|
|
2688
|
-
if (blockSpaceWithoutFootnotes <= bodySpaceAfterExpansion - bodyContentUsed) {
|
|
2689
|
-
currentContent.push(block.element.cloneNode(true));
|
|
2690
|
-
remainingHeight = bodySpaceAfterExpansion - bodyContentUsed - blockSpaceWithoutFootnotes;
|
|
2691
|
-
prevMarginBottomPt = block.marginBottomPt;
|
|
2692
|
-
currentFootnoteIds.push(...newFootnoteIds);
|
|
2693
|
-
currentFootnoteHeight = expandedFootnoteSpace;
|
|
2694
|
-
} else {
|
|
2695
|
-
finishPage();
|
|
2696
|
-
const newPageFootnoteHeight = allBlockFootnoteIds.length > 0 ? this.measureFootnotesHeight(allBlockFootnoteIds, dims.contentWidth, currentContinuation) : currentContinuation ? this.measureContinuationHeight(currentContinuation, dims.contentWidth) : 0;
|
|
2697
|
-
const newPageSpace = block.marginTopPt + block.heightPt + block.marginBottomPt;
|
|
2698
|
-
currentContent.push(block.element.cloneNode(true));
|
|
2699
|
-
remainingHeight = effectiveContentHeight - newPageSpace;
|
|
2700
|
-
prevMarginBottomPt = block.marginBottomPt;
|
|
2701
|
-
currentFootnoteIds = [...currentFootnoteIds, ...allBlockFootnoteIds];
|
|
2702
|
-
currentFootnoteHeight = newPageFootnoteHeight;
|
|
2703
|
-
}
|
|
4862
|
+
currentPageHasFootnoteReference ||= allBlockFootnoteIds.length > 0;
|
|
2704
4863
|
} else {
|
|
2705
|
-
finishPage();
|
|
2706
|
-
|
|
2707
|
-
|
|
2708
|
-
currentContent.push(block.element.cloneNode(true));
|
|
2709
|
-
remainingHeight = effectiveContentHeight - newPageSpace;
|
|
2710
|
-
prevMarginBottomPt = block.marginBottomPt;
|
|
2711
|
-
currentFootnoteIds = [...currentFootnoteIds, ...allBlockFootnoteIds];
|
|
2712
|
-
currentFootnoteHeight = newPageFootnoteHeight;
|
|
4864
|
+
finishPage(block.sectionIndex, !pageHasPayload());
|
|
4865
|
+
i--;
|
|
4866
|
+
continue;
|
|
2713
4867
|
}
|
|
2714
4868
|
} else {
|
|
2715
|
-
const tableFragments = this.trySplitSimpleOversizedTable(
|
|
4869
|
+
const tableFragments = this.trySplitSimpleOversizedTable(
|
|
4870
|
+
block,
|
|
4871
|
+
dimensionsFor(block.sectionIndex),
|
|
4872
|
+
block.sectionIndex
|
|
4873
|
+
);
|
|
2716
4874
|
if (tableFragments) {
|
|
2717
4875
|
if (currentContent.length > 0 || currentContinuation) {
|
|
2718
|
-
finishPage();
|
|
4876
|
+
finishPage(block.sectionIndex);
|
|
2719
4877
|
}
|
|
2720
4878
|
blocks.splice(i, 1, ...tableFragments);
|
|
2721
4879
|
i--;
|
|
2722
4880
|
continue;
|
|
2723
4881
|
}
|
|
2724
|
-
if (
|
|
2725
|
-
finishPage();
|
|
4882
|
+
if (pageHasPayload()) {
|
|
4883
|
+
finishPage(block.sectionIndex);
|
|
4884
|
+
i--;
|
|
4885
|
+
continue;
|
|
2726
4886
|
}
|
|
2727
|
-
|
|
2728
|
-
|
|
2729
|
-
|
|
4887
|
+
markSectionPlaced(block.sectionIndex);
|
|
4888
|
+
currentContent.push(this.cloneBlockForPage(
|
|
4889
|
+
block,
|
|
4890
|
+
true,
|
|
4891
|
+
pageInSection(block.sectionIndex)
|
|
4892
|
+
));
|
|
4893
|
+
deferredFootnoteIds.push(...newFootnoteIds);
|
|
4894
|
+
currentPageHasFootnoteReference ||= allBlockFootnoteIds.length > 0;
|
|
4895
|
+
finishPage(block.sectionIndex);
|
|
2730
4896
|
}
|
|
2731
4897
|
}
|
|
2732
4898
|
finishPage();
|
|
4899
|
+
while (currentFootnoteIds.length > 0 || deferredFootnoteIds.length > 0 || (currentContinuation?.remainingElements.length ?? 0) > 0) {
|
|
4900
|
+
finishPage();
|
|
4901
|
+
}
|
|
2733
4902
|
this.pendingFootnoteContinuation = nextPageContinuation;
|
|
2734
4903
|
return pages;
|
|
2735
4904
|
}
|
|
@@ -2754,6 +4923,19 @@ var Docxodus = (() => {
|
|
|
2754
4923
|
if (el.getAttribute("contenteditable") !== null) el.setAttribute("contenteditable", "false");
|
|
2755
4924
|
}
|
|
2756
4925
|
}
|
|
4926
|
+
/** A repeated margin note is presentation, not a second bookmark/link target. */
|
|
4927
|
+
makeClonedMarginCommentInert(root) {
|
|
4928
|
+
this.makeClonedStoryInert(root);
|
|
4929
|
+
const nodes = [root, ...Array.from(root.querySelectorAll("*"))];
|
|
4930
|
+
for (const element of nodes) {
|
|
4931
|
+
element.removeAttribute("id");
|
|
4932
|
+
if (element.localName === "a" && element.getAttribute("href")?.startsWith("#")) {
|
|
4933
|
+
element.removeAttribute("href");
|
|
4934
|
+
element.setAttribute("aria-disabled", "true");
|
|
4935
|
+
element.tabIndex = -1;
|
|
4936
|
+
}
|
|
4937
|
+
}
|
|
4938
|
+
}
|
|
2757
4939
|
/**
|
|
2758
4940
|
* Resolves floating DrawingML objects after their anchor paragraphs have landed on a page.
|
|
2759
4941
|
*
|
|
@@ -2779,7 +4961,7 @@ var Docxodus = (() => {
|
|
|
2779
4961
|
const context = {
|
|
2780
4962
|
staticLeft: toPageX(staticRect.left),
|
|
2781
4963
|
staticTop: toPageY(staticRect.top),
|
|
2782
|
-
lineHeight: paragraph ? (parseFloat(getComputedStyle(paragraph).lineHeight) || staticRect.height) / pixelsPerPoint : staticRect.height / pixelsPerPoint,
|
|
4964
|
+
lineHeight: paragraph ? (parseFloat(this.view.getComputedStyle(paragraph).lineHeight) || staticRect.height) / pixelsPerPoint : staticRect.height / pixelsPerPoint,
|
|
2783
4965
|
paragraphLeft: toPageX(paragraphRect.left),
|
|
2784
4966
|
paragraphTop: toPageY(paragraphRect.top),
|
|
2785
4967
|
paragraphWidth: paragraphRect.width / pixelsPerPoint,
|
|
@@ -2909,36 +5091,48 @@ var Docxodus = (() => {
|
|
|
2909
5091
|
else if (alignment === "outside") fraction = pageNumber % 2 === 1 ? 1 : 0;
|
|
2910
5092
|
return reference.start + (reference.size - objectSize) * fraction;
|
|
2911
5093
|
}
|
|
5094
|
+
/** Charge a physical page before any page-owned partitioning or DOM allocation. */
|
|
5095
|
+
admitPageAllocation() {
|
|
5096
|
+
const prospectivePageCount = this.createdPageCount + 1;
|
|
5097
|
+
this.pageCountCheckpoint?.(prospectivePageCount);
|
|
5098
|
+
this.createdPageCount = prospectivePageCount;
|
|
5099
|
+
}
|
|
2912
5100
|
/**
|
|
2913
5101
|
* Creates a page container element.
|
|
2914
5102
|
*/
|
|
2915
|
-
createPage(dims, pageNumber, sectionIndex, content, pageInSection, footnoteIds = [], footnoteHeight = 0, continuation, partialFootnotes) {
|
|
2916
|
-
|
|
5103
|
+
createPage(dims, pageNumber, sectionIndex, displayedPageNumber, content, pageInSection, footnoteIds = [], footnoteHeight = 0, continuation, partialFootnotes, isSectionFiller = false, pageAlreadyAdmitted = false) {
|
|
5104
|
+
if (!pageAlreadyAdmitted) this.admitPageAllocation();
|
|
5105
|
+
const pageBox = this.document.createElement("div");
|
|
2917
5106
|
pageBox.className = `${this.cssPrefix}box`;
|
|
2918
5107
|
pageBox.style.width = `${dims.pageWidth}pt`;
|
|
2919
5108
|
pageBox.style.height = `${dims.pageHeight}pt`;
|
|
2920
5109
|
pageBox.style.overflow = "hidden";
|
|
2921
5110
|
pageBox.style.position = "relative";
|
|
2922
5111
|
if (this.scale !== 1) {
|
|
2923
|
-
|
|
2924
|
-
|
|
2925
|
-
|
|
2926
|
-
|
|
2927
|
-
|
|
2928
|
-
|
|
2929
|
-
|
|
2930
|
-
|
|
2931
|
-
|
|
5112
|
+
if (this.view.CSS?.supports("zoom", "1")) {
|
|
5113
|
+
pageBox.style.zoom = String(this.scale);
|
|
5114
|
+
} else {
|
|
5115
|
+
pageBox.style.transform = `scale(${this.scale})`;
|
|
5116
|
+
pageBox.style.transformOrigin = "top left";
|
|
5117
|
+
const heightReductionPt = dims.pageHeight * (1 - this.scale);
|
|
5118
|
+
const widthReductionPt = dims.pageWidth * (1 - this.scale);
|
|
5119
|
+
const heightReductionPx = ptToPx(heightReductionPt);
|
|
5120
|
+
const widthReductionPx = ptToPx(widthReductionPt);
|
|
5121
|
+
pageBox.style.marginRight = `-${widthReductionPx}px`;
|
|
5122
|
+
pageBox.style.marginBottom = `${this.pageGap - heightReductionPx}px`;
|
|
5123
|
+
}
|
|
2932
5124
|
}
|
|
2933
5125
|
pageBox.style.willChange = "transform";
|
|
2934
5126
|
pageBox.style.contain = "layout paint";
|
|
2935
5127
|
pageBox.dataset.pageNumber = String(pageNumber);
|
|
2936
5128
|
pageBox.dataset.sectionIndex = String(sectionIndex);
|
|
5129
|
+
pageBox.dataset.displayedPageNumber = String(displayedPageNumber);
|
|
5130
|
+
if (isSectionFiller) pageBox.dataset.sectionFiller = "true";
|
|
2937
5131
|
pageBox.dataset.pageInSection = String(pageInSection);
|
|
2938
|
-
const bands = this.getPageBands(dims, sectionIndex, pageInSection,
|
|
2939
|
-
const headerSource = this.selectHeader(sectionIndex, pageInSection,
|
|
5132
|
+
const bands = this.getPageBands(dims, sectionIndex, pageInSection, displayedPageNumber);
|
|
5133
|
+
const headerSource = isSectionFiller ? void 0 : this.selectHeader(sectionIndex, pageInSection, displayedPageNumber);
|
|
2940
5134
|
if (headerSource) {
|
|
2941
|
-
const headerDiv = document.createElement("div");
|
|
5135
|
+
const headerDiv = this.document.createElement("div");
|
|
2942
5136
|
headerDiv.className = `${this.cssPrefix}header`;
|
|
2943
5137
|
headerDiv.style.position = "absolute";
|
|
2944
5138
|
headerDiv.style.top = `${bands.headerTop}pt`;
|
|
@@ -2960,7 +5154,7 @@ var Docxodus = (() => {
|
|
|
2960
5154
|
}
|
|
2961
5155
|
const contentAreaTop = bands.bodyTop;
|
|
2962
5156
|
const contentAreaHeight = bands.bodyHeight;
|
|
2963
|
-
const contentArea = document.createElement("div");
|
|
5157
|
+
const contentArea = this.document.createElement("div");
|
|
2964
5158
|
contentArea.className = `${this.cssPrefix}content`;
|
|
2965
5159
|
contentArea.style.position = "absolute";
|
|
2966
5160
|
contentArea.style.top = `${contentAreaTop}pt`;
|
|
@@ -2973,12 +5167,46 @@ var Docxodus = (() => {
|
|
|
2973
5167
|
}
|
|
2974
5168
|
pageBox.appendChild(contentArea);
|
|
2975
5169
|
const hasContinuation = continuation && continuation.remainingElements.length > 0;
|
|
2976
|
-
if (footnoteIds.length > 0 || hasContinuation) {
|
|
5170
|
+
if (!isSectionFiller && (footnoteIds.length > 0 || hasContinuation)) {
|
|
2977
5171
|
this.addPageFootnotes(pageBox, footnoteIds, dims, bands, footnoteHeight, continuation, partialFootnotes);
|
|
2978
5172
|
}
|
|
2979
|
-
const
|
|
5173
|
+
const pageCommentIds = [];
|
|
5174
|
+
for (const marker of Array.from(
|
|
5175
|
+
pageBox.querySelectorAll("[data-comment-id]")
|
|
5176
|
+
)) {
|
|
5177
|
+
if (marker.closest(`.${this.cssPrefix}comment-margin`)) continue;
|
|
5178
|
+
const id = marker.dataset.commentId;
|
|
5179
|
+
if (id && this.commentMarginRegistry.has(id) && !pageCommentIds.includes(id)) {
|
|
5180
|
+
pageCommentIds.push(id);
|
|
5181
|
+
}
|
|
5182
|
+
}
|
|
5183
|
+
if (pageCommentIds.length > 0) {
|
|
5184
|
+
const marginColumn = this.document.createElement("aside");
|
|
5185
|
+
marginColumn.className = `${this.cssPrefix}comment-margin`;
|
|
5186
|
+
marginColumn.style.position = "absolute";
|
|
5187
|
+
marginColumn.style.top = `${contentAreaTop}pt`;
|
|
5188
|
+
marginColumn.style.left = `${dims.marginLeft + dims.contentWidth + 3}pt`;
|
|
5189
|
+
marginColumn.style.width = `${Math.max(12, dims.marginRight - 6)}pt`;
|
|
5190
|
+
marginColumn.style.maxHeight = `${contentAreaHeight}pt`;
|
|
5191
|
+
marginColumn.style.overflow = "hidden";
|
|
5192
|
+
marginColumn.style.boxSizing = "border-box";
|
|
5193
|
+
const placedThreadRoots = /* @__PURE__ */ new Set();
|
|
5194
|
+
for (const id of pageCommentIds) {
|
|
5195
|
+
const source = this.commentMarginRegistry.get(id);
|
|
5196
|
+
if (source) {
|
|
5197
|
+
const rootId = source.dataset.commentId ?? id;
|
|
5198
|
+
if (placedThreadRoots.has(rootId)) continue;
|
|
5199
|
+
placedThreadRoots.add(rootId);
|
|
5200
|
+
const clone = source.cloneNode(true);
|
|
5201
|
+
this.makeClonedMarginCommentInert(clone);
|
|
5202
|
+
marginColumn.appendChild(clone);
|
|
5203
|
+
}
|
|
5204
|
+
}
|
|
5205
|
+
pageBox.appendChild(marginColumn);
|
|
5206
|
+
}
|
|
5207
|
+
const footerSource = isSectionFiller ? void 0 : this.selectFooter(sectionIndex, pageInSection, displayedPageNumber);
|
|
2980
5208
|
if (footerSource) {
|
|
2981
|
-
const footerDiv = document.createElement("div");
|
|
5209
|
+
const footerDiv = this.document.createElement("div");
|
|
2982
5210
|
footerDiv.className = `${this.cssPrefix}footer`;
|
|
2983
5211
|
footerDiv.style.position = "absolute";
|
|
2984
5212
|
footerDiv.style.top = "auto";
|
|
@@ -2998,8 +5226,8 @@ var Docxodus = (() => {
|
|
|
2998
5226
|
}
|
|
2999
5227
|
pageBox.appendChild(footerDiv);
|
|
3000
5228
|
}
|
|
3001
|
-
if (this.showPageNumbers) {
|
|
3002
|
-
const pageNum = document.createElement("div");
|
|
5229
|
+
if (this.showPageNumbers && !isSectionFiller) {
|
|
5230
|
+
const pageNum = this.document.createElement("div");
|
|
3003
5231
|
pageNum.className = `${this.cssPrefix}number`;
|
|
3004
5232
|
pageNum.textContent = String(pageNumber);
|
|
3005
5233
|
pageBox.appendChild(pageNum);
|
|
@@ -3022,7 +5250,8 @@ var Docxodus = (() => {
|
|
|
3022
5250
|
}
|
|
3023
5251
|
};
|
|
3024
5252
|
function paginateHtml(html, container, options = {}) {
|
|
3025
|
-
const
|
|
5253
|
+
const ownerDocument = typeof container === "string" ? globalThis.document : container.ownerDocument;
|
|
5254
|
+
const containerEl = typeof container === "string" ? ownerDocument.getElementById(container) : container;
|
|
3026
5255
|
if (!containerEl) {
|
|
3027
5256
|
throw new Error("Container element not found");
|
|
3028
5257
|
}
|
|
@@ -6324,6 +8553,13 @@ var Docxodus = (() => {
|
|
|
6324
8553
|
function escapeInlineMarkdown(text) {
|
|
6325
8554
|
return text.replace(/([\\`*_[\]])/g, "\\$1");
|
|
6326
8555
|
}
|
|
8556
|
+
function escapeLeadingBlockMarkers(md) {
|
|
8557
|
+
return md.split("\n").map((line) => {
|
|
8558
|
+
if (/^[#>|]/.test(line) || /^[-+] /.test(line)) return "\\" + line;
|
|
8559
|
+
if (/^\d+\. /.test(line)) return line.replace(".", "\\.");
|
|
8560
|
+
return line;
|
|
8561
|
+
}).join("\n");
|
|
8562
|
+
}
|
|
6327
8563
|
function collectInlineSegments(node, out) {
|
|
6328
8564
|
node.childNodes.forEach((child) => {
|
|
6329
8565
|
if (isGeneratedChrome(child)) return;
|
|
@@ -6375,7 +8611,7 @@ var Docxodus = (() => {
|
|
|
6375
8611
|
merged.push({ ...s });
|
|
6376
8612
|
}
|
|
6377
8613
|
}
|
|
6378
|
-
return merged.map(segToMarkdown).join("").trim();
|
|
8614
|
+
return escapeLeadingBlockMarkers(merged.map(segToMarkdown).join("").trim());
|
|
6379
8615
|
}
|
|
6380
8616
|
function isListBlock(block) {
|
|
6381
8617
|
return !!block.querySelector(":scope > [data-list-marker]");
|
|
@@ -8410,17 +10646,19 @@ var Docxodus = (() => {
|
|
|
8410
10646
|
this.refreshAfter(block, idx, false);
|
|
8411
10647
|
}
|
|
8412
10648
|
// ─── Table row / column editing (active block must be inside a table cell) ──────────
|
|
8413
|
-
/** Run a table-structure op on the active cell
|
|
10649
|
+
/** Run a table-structure op on the active cell's canonical tc anchor and re-render. */
|
|
8414
10650
|
tableEdit(run) {
|
|
8415
10651
|
const block = this.activeBlock;
|
|
8416
|
-
|
|
10652
|
+
const cell = block?.closest("td, th");
|
|
10653
|
+
if (this.closed || !block || !cell || !block.closest("table")) return;
|
|
8417
10654
|
const unid = block.getAttribute("data-anchor");
|
|
8418
10655
|
if (!unid) return;
|
|
8419
|
-
|
|
8420
|
-
|
|
10656
|
+
const paragraphId = this.anchorIdOf(block);
|
|
10657
|
+
const cellId = this.anchorIdOf(cell);
|
|
10658
|
+
if (!paragraphId || !cellId) return;
|
|
8421
10659
|
const idx = this.blockIndex(block);
|
|
8422
|
-
|
|
8423
|
-
const res = this.parseEdit(run(
|
|
10660
|
+
this.syncBlock(block, paragraphId);
|
|
10661
|
+
const res = this.parseEdit(run(cellId));
|
|
8424
10662
|
if (!res.success) return;
|
|
8425
10663
|
this.refreshAfter(block, idx, false);
|
|
8426
10664
|
}
|
|
@@ -8573,6 +10811,59 @@ var Docxodus = (() => {
|
|
|
8573
10811
|
if (!res.success) return;
|
|
8574
10812
|
this.reconcile(idx, false);
|
|
8575
10813
|
}
|
|
10814
|
+
/**
|
|
10815
|
+
* Add a native Word comment on the current selection in the active block — or on the whole
|
|
10816
|
+
* block when the selection is collapsed (a null span comments the paragraph). The definition,
|
|
10817
|
+
* threading parts, and body-side range markers are written by the session's `AddComment`
|
|
10818
|
+
* (issue #300); the re-render shows the marker chrome the converter already draws. The
|
|
10819
|
+
* annotation type legal review runs on, finally authorable from the shipped surface
|
|
10820
|
+
* (issue #580).
|
|
10821
|
+
*/
|
|
10822
|
+
addComment(markdown = "New comment.", author = "Reviewer") {
|
|
10823
|
+
const block = this.activeBlock;
|
|
10824
|
+
if (this.closed || !block) return;
|
|
10825
|
+
const bridge = this.exports.DocxSessionBridge;
|
|
10826
|
+
if (!bridge.AddComment) return;
|
|
10827
|
+
let fullId = this.anchorIdOf(block);
|
|
10828
|
+
if (!fullId) return;
|
|
10829
|
+
const idx = this.blockIndex(block);
|
|
10830
|
+
const span = selectionSpanIn(block);
|
|
10831
|
+
fullId = this.syncBlock(block, fullId);
|
|
10832
|
+
const res = this.parseEdit(
|
|
10833
|
+
bridge.AddComment(
|
|
10834
|
+
this.handle,
|
|
10835
|
+
fullId,
|
|
10836
|
+
span ? JSON.stringify(span) : "",
|
|
10837
|
+
author,
|
|
10838
|
+
"",
|
|
10839
|
+
"",
|
|
10840
|
+
markdown
|
|
10841
|
+
)
|
|
10842
|
+
);
|
|
10843
|
+
if (!res.success) return;
|
|
10844
|
+
this.reconcile(idx, false);
|
|
10845
|
+
}
|
|
10846
|
+
/** The document's native comment threads (session truth), for review UIs — flat entries
|
|
10847
|
+
* with `parentAnchorId` linking replies and `resolved` carrying thread state. */
|
|
10848
|
+
listComments() {
|
|
10849
|
+
if (this.closed) return [];
|
|
10850
|
+
const bridge = this.exports.DocxSessionBridge;
|
|
10851
|
+
if (!bridge.ListComments) return [];
|
|
10852
|
+
try {
|
|
10853
|
+
return JSON.parse(bridge.ListComments(this.handle));
|
|
10854
|
+
} catch {
|
|
10855
|
+
return [];
|
|
10856
|
+
}
|
|
10857
|
+
}
|
|
10858
|
+
/** Resolve or reopen a comment thread by its `cmt` anchor id (from {@link listComments}).
|
|
10859
|
+
* Resolution is thread metadata (`commentsExtended`), not body markup, so no re-render is
|
|
10860
|
+
* needed — a review UI re-reads {@link listComments} for the new state. */
|
|
10861
|
+
setCommentResolved(commentAnchorId, resolved) {
|
|
10862
|
+
if (this.closed) return;
|
|
10863
|
+
const bridge = this.exports.DocxSessionBridge;
|
|
10864
|
+
if (!bridge.SetCommentResolved) return;
|
|
10865
|
+
this.parseEdit(bridge.SetCommentResolved(this.handle, commentAnchorId, resolved));
|
|
10866
|
+
}
|
|
8576
10867
|
applyParagraphFormat(op) {
|
|
8577
10868
|
const block = this.activeBlock;
|
|
8578
10869
|
if (this.closed || !block) return;
|
|
@@ -9100,8 +11391,8 @@ var Docxodus = (() => {
|
|
|
9100
11391
|
const backref = document.createElement("a");
|
|
9101
11392
|
backref.setAttribute("class", `${prefix}-backref`);
|
|
9102
11393
|
backref.setAttribute("contenteditable", "false");
|
|
9103
|
-
backref.textContent = "\u21A9";
|
|
9104
|
-
last.append(
|
|
11394
|
+
backref.textContent = " \u21A9";
|
|
11395
|
+
last.append(backref);
|
|
9105
11396
|
}
|
|
9106
11397
|
li.querySelectorAll("[data-anchor]").forEach((b) => this.wireBlock(b));
|
|
9107
11398
|
return li;
|
|
@@ -9254,6 +11545,7 @@ var Docxodus = (() => {
|
|
|
9254
11545
|
color: var(--dxr-ink);
|
|
9255
11546
|
background: var(--dxr-desk);
|
|
9256
11547
|
-webkit-text-size-adjust: 100%;
|
|
11548
|
+
text-size-adjust: 100%;
|
|
9257
11549
|
}
|
|
9258
11550
|
.dxr *, .dxr *::before, .dxr *::after { box-sizing: border-box; }
|
|
9259
11551
|
|
|
@@ -9657,6 +11949,25 @@ var Docxodus = (() => {
|
|
|
9657
11949
|
box-shadow: 0 1px 3px rgba(15, 23, 42, .08), 0 12px 24px rgba(15, 23, 42, .06);
|
|
9658
11950
|
}
|
|
9659
11951
|
|
|
11952
|
+
/* \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 */
|
|
11953
|
+
.dxr-threads {
|
|
11954
|
+
display: flex; flex-direction: column; gap: 4px;
|
|
11955
|
+
max-height: 92px; overflow-y: auto; min-width: 260px; padding-right: 2px;
|
|
11956
|
+
}
|
|
11957
|
+
.dxr-thread {
|
|
11958
|
+
display: flex; align-items: center; gap: 6px;
|
|
11959
|
+
font-size: 11px; line-height: 1.3; padding: 3px 6px;
|
|
11960
|
+
border: 1px solid var(--dxr-rule); border-radius: 4px; background: var(--dxr-chrome-sunk);
|
|
11961
|
+
}
|
|
11962
|
+
.dxr-thread[data-resolved] { opacity: 0.55; }
|
|
11963
|
+
.dxr-thread[data-reply] { margin-left: 14px; }
|
|
11964
|
+
.dxr-thread .dxr-tauthor { font-weight: 600; white-space: nowrap; }
|
|
11965
|
+
.dxr-thread .dxr-ttext {
|
|
11966
|
+
flex: 1; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; max-width: 220px;
|
|
11967
|
+
}
|
|
11968
|
+
.dxr-thread button { flex: none; }
|
|
11969
|
+
.dxr-group.dxr-grow { flex: 1; min-width: 0; }
|
|
11970
|
+
|
|
9660
11971
|
/* \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 */
|
|
9661
11972
|
.dxr-pop {
|
|
9662
11973
|
display: none;
|
|
@@ -9975,6 +12286,7 @@ var Docxodus = (() => {
|
|
|
9975
12286
|
<button type="button" class="dxr-tab" role="tab" data-tab="home" aria-selected="true">Home</button>
|
|
9976
12287
|
<button type="button" class="dxr-tab" role="tab" data-tab="insert" aria-selected="false">Insert</button>
|
|
9977
12288
|
<button type="button" class="dxr-tab" role="tab" data-tab="layout" aria-selected="false">Layout</button>
|
|
12289
|
+
<button type="button" class="dxr-tab" role="tab" data-tab="review" aria-selected="false">Review</button>
|
|
9978
12290
|
<button type="button" class="dxr-tab" role="tab" data-tab="table" data-contextual aria-selected="false" hidden>Table</button>
|
|
9979
12291
|
</div>
|
|
9980
12292
|
|
|
@@ -10140,6 +12452,31 @@ var Docxodus = (() => {
|
|
|
10140
12452
|
</div>
|
|
10141
12453
|
</div>
|
|
10142
12454
|
|
|
12455
|
+
<!-- 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
|
|
12456
|
+
Comment authoring over the session's native comment family (issue #580):
|
|
12457
|
+
the button comments the selection (the whole block when collapsed), and
|
|
12458
|
+
the thread list is live session truth with resolve/reopen per thread. -->
|
|
12459
|
+
<div class="dxr-panel" data-panel="review">
|
|
12460
|
+
<div class="dxr-group">
|
|
12461
|
+
<span class="dxr-glabel">Comments</span>
|
|
12462
|
+
<div class="dxr-row">
|
|
12463
|
+
<input data-dxr="commenttext" type="text" placeholder="Comment text…"
|
|
12464
|
+
title="The comment body; markdown is honored" />
|
|
12465
|
+
<button type="button" data-dxr="comment"
|
|
12466
|
+
title="Comment on the selection, or on the whole block when nothing is selected">💬 Comment</button>
|
|
12467
|
+
</div>
|
|
12468
|
+
<div class="dxr-row">
|
|
12469
|
+
<span class="dxr-note">Comments the selected text; the whole block when the caret is collapsed.</span>
|
|
12470
|
+
</div>
|
|
12471
|
+
</div>
|
|
12472
|
+
<div class="dxr-group dxr-grow">
|
|
12473
|
+
<span class="dxr-glabel">Threads</span>
|
|
12474
|
+
<div class="dxr-threads" data-dxr="threads" aria-live="polite">
|
|
12475
|
+
<span class="dxr-note">No comments yet.</span>
|
|
12476
|
+
</div>
|
|
12477
|
+
</div>
|
|
12478
|
+
</div>
|
|
12479
|
+
|
|
10143
12480
|
<!-- 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
|
|
10144
12481
|
Row/column editing lives here rather than in a floating toolbar: a docked
|
|
10145
12482
|
contextual tab cannot overlap the cell you are editing. -->
|
|
@@ -10647,6 +12984,18 @@ var Docxodus = (() => {
|
|
|
10647
12984
|
if (this.live && style.value) this.run("style", () => this.live.setParagraphStyle(style.value));
|
|
10648
12985
|
style.value = "";
|
|
10649
12986
|
});
|
|
12987
|
+
const commentButton = this.control("comment");
|
|
12988
|
+
if (commentButton) {
|
|
12989
|
+
this.keepSelection(commentButton);
|
|
12990
|
+
commentButton.addEventListener("click", () => {
|
|
12991
|
+
if (!this.live) return;
|
|
12992
|
+
const input = this.control("commenttext");
|
|
12993
|
+
const text = input?.value.trim() || "New comment.";
|
|
12994
|
+
this.run("comment", () => this.live.addComment(text, this.options.revisionAuthor ?? "Reviewer"));
|
|
12995
|
+
if (input) input.value = "";
|
|
12996
|
+
this.renderThreads();
|
|
12997
|
+
});
|
|
12998
|
+
}
|
|
10650
12999
|
this.wireFileActions();
|
|
10651
13000
|
this.wireLayout();
|
|
10652
13001
|
this.wirePicker();
|
|
@@ -10769,6 +13118,52 @@ var Docxodus = (() => {
|
|
|
10769
13118
|
else panel.removeAttribute("data-active");
|
|
10770
13119
|
}
|
|
10771
13120
|
if (name === "layout") this.syncPageNumbering();
|
|
13121
|
+
if (name === "review") this.renderThreads();
|
|
13122
|
+
}
|
|
13123
|
+
/** Re-render the Review tab's thread list from session truth ({@link DocxEditor.listComments}):
|
|
13124
|
+
* one row per comment, replies indented under their thread root, resolve/reopen per root. */
|
|
13125
|
+
renderThreads() {
|
|
13126
|
+
const host = this.control("threads");
|
|
13127
|
+
if (!host || !this.live) return;
|
|
13128
|
+
const doc = this.element.ownerDocument ?? document;
|
|
13129
|
+
host.textContent = "";
|
|
13130
|
+
const comments = this.live.listComments();
|
|
13131
|
+
if (comments.length === 0) {
|
|
13132
|
+
const empty = doc.createElement("span");
|
|
13133
|
+
empty.className = "dxr-note";
|
|
13134
|
+
empty.textContent = "No comments yet.";
|
|
13135
|
+
host.appendChild(empty);
|
|
13136
|
+
return;
|
|
13137
|
+
}
|
|
13138
|
+
for (const entry of comments) {
|
|
13139
|
+
const row = doc.createElement("div");
|
|
13140
|
+
row.className = "dxr-thread";
|
|
13141
|
+
row.setAttribute("data-thread", entry.anchorId);
|
|
13142
|
+
if (entry.resolved) row.setAttribute("data-resolved", "");
|
|
13143
|
+
if (entry.parentAnchorId) row.setAttribute("data-reply", "");
|
|
13144
|
+
const author = doc.createElement("span");
|
|
13145
|
+
author.className = "dxr-tauthor";
|
|
13146
|
+
author.textContent = entry.author || "\u2014";
|
|
13147
|
+
row.appendChild(author);
|
|
13148
|
+
const text = doc.createElement("span");
|
|
13149
|
+
text.className = "dxr-ttext";
|
|
13150
|
+
text.textContent = entry.text;
|
|
13151
|
+
text.title = entry.text;
|
|
13152
|
+
row.appendChild(text);
|
|
13153
|
+
if (!entry.parentAnchorId) {
|
|
13154
|
+
const toggle = doc.createElement("button");
|
|
13155
|
+
toggle.type = "button";
|
|
13156
|
+
toggle.textContent = entry.resolved ? "Reopen" : "Resolve";
|
|
13157
|
+
toggle.title = entry.resolved ? "Reopen this comment thread" : "Mark this comment thread resolved";
|
|
13158
|
+
toggle.addEventListener("click", () => {
|
|
13159
|
+
if (!this.live) return;
|
|
13160
|
+
this.run(entry.resolved ? "reopen comment" : "resolve comment", () => this.live.setCommentResolved(entry.anchorId, !entry.resolved));
|
|
13161
|
+
this.renderThreads();
|
|
13162
|
+
});
|
|
13163
|
+
row.appendChild(toggle);
|
|
13164
|
+
}
|
|
13165
|
+
host.appendChild(row);
|
|
13166
|
+
}
|
|
10772
13167
|
}
|
|
10773
13168
|
// ── selection-driven state ──────────────────────────────────────────────────
|
|
10774
13169
|
selectionElement() {
|
|
@@ -11071,6 +13466,55 @@ var Docxodus = (() => {
|
|
|
11071
13466
|
const buffer = await input.arrayBuffer();
|
|
11072
13467
|
return new Uint8Array(buffer);
|
|
11073
13468
|
}
|
|
13469
|
+
async function generatePackageManifest(document2) {
|
|
13470
|
+
const exports = ensureInitialized();
|
|
13471
|
+
const bytes = await toBytes(document2);
|
|
13472
|
+
await yieldToMain();
|
|
13473
|
+
return JSON.parse(
|
|
13474
|
+
exports.DocumentConverter.GeneratePackageManifest(bytes)
|
|
13475
|
+
);
|
|
13476
|
+
}
|
|
13477
|
+
async function verifyDeliverable(document2, baseline) {
|
|
13478
|
+
const exports = ensureInitialized();
|
|
13479
|
+
const bytes = await toBytes(document2);
|
|
13480
|
+
const baselineBytes = baseline === void 0 ? void 0 : await toBytes(baseline);
|
|
13481
|
+
await yieldToMain();
|
|
13482
|
+
return JSON.parse(
|
|
13483
|
+
baselineBytes === void 0 ? exports.DocumentConverter.VerifyDeliverable(bytes) : exports.DocumentConverter.VerifyDeliverableWithBaseline(
|
|
13484
|
+
baselineBytes,
|
|
13485
|
+
bytes
|
|
13486
|
+
)
|
|
13487
|
+
);
|
|
13488
|
+
}
|
|
13489
|
+
async function verifyDeliveryReceipt(receiptJson, artifacts) {
|
|
13490
|
+
const exports = ensureInitialized();
|
|
13491
|
+
const artifactsJson = artifacts === void 0 ? "" : JSON.stringify(
|
|
13492
|
+
Object.fromEntries(
|
|
13493
|
+
Object.entries(artifacts).map(([artifactId, bytes]) => [
|
|
13494
|
+
artifactId,
|
|
13495
|
+
bytesToBase64(bytes)
|
|
13496
|
+
])
|
|
13497
|
+
)
|
|
13498
|
+
);
|
|
13499
|
+
await yieldToMain();
|
|
13500
|
+
return JSON.parse(
|
|
13501
|
+
exports.DocumentConverter.VerifyDeliveryReceipt(receiptJson, artifactsJson)
|
|
13502
|
+
);
|
|
13503
|
+
}
|
|
13504
|
+
async function proveRedlineReversibility(baseline, intendedFinal, redline) {
|
|
13505
|
+
const exports = ensureInitialized();
|
|
13506
|
+
const baselineBytes = await toBytes(baseline);
|
|
13507
|
+
const intendedFinalBytes = await toBytes(intendedFinal);
|
|
13508
|
+
const redlineBytes = await toBytes(redline);
|
|
13509
|
+
await yieldToMain();
|
|
13510
|
+
return JSON.parse(
|
|
13511
|
+
exports.DocumentConverter.ProveRedlineReversibility(
|
|
13512
|
+
baselineBytes,
|
|
13513
|
+
intendedFinalBytes,
|
|
13514
|
+
redlineBytes
|
|
13515
|
+
)
|
|
13516
|
+
);
|
|
13517
|
+
}
|
|
11074
13518
|
async function renderBlockHtml(document2, anchorId, options) {
|
|
11075
13519
|
const exports = ensureInitialized();
|
|
11076
13520
|
const bytes = await toBytes(document2);
|
|
@@ -11331,21 +13775,30 @@ var Docxodus = (() => {
|
|
|
11331
13775
|
throw new Error(`Failed to get DocxDiff revisions: ${error.error}`);
|
|
11332
13776
|
}
|
|
11333
13777
|
const parsed = JSON.parse(result);
|
|
11334
|
-
return (parsed.revisions || parsed.Revisions || []).map(
|
|
11335
|
-
|
|
11336
|
-
|
|
11337
|
-
|
|
11338
|
-
|
|
11339
|
-
|
|
11340
|
-
|
|
11341
|
-
|
|
11342
|
-
|
|
11343
|
-
|
|
11344
|
-
|
|
11345
|
-
|
|
11346
|
-
|
|
11347
|
-
|
|
11348
|
-
|
|
13778
|
+
return (parsed.revisions || parsed.Revisions || []).map(mapDocxDiffRevision);
|
|
13779
|
+
}
|
|
13780
|
+
async function docxDiffCompareProducts(left2, right2, settings, products) {
|
|
13781
|
+
const exports = ensureInitialized();
|
|
13782
|
+
const leftBytes = await toBytes(left2);
|
|
13783
|
+
const rightBytes = await toBytes(right2);
|
|
13784
|
+
await yieldToMain();
|
|
13785
|
+
const result = exports.DocxDiffBridge.CompareProductsJson(
|
|
13786
|
+
leftBytes,
|
|
13787
|
+
rightBytes,
|
|
13788
|
+
docxDiffSettingsJson(settings),
|
|
13789
|
+
products ? JSON.stringify(products) : ""
|
|
13790
|
+
);
|
|
13791
|
+
if (isErrorResponse(result)) {
|
|
13792
|
+
const error = parseError(result);
|
|
13793
|
+
throw new Error(`Failed to compare products: ${error.error}`);
|
|
13794
|
+
}
|
|
13795
|
+
const parsed = JSON.parse(result);
|
|
13796
|
+
return {
|
|
13797
|
+
redline: typeof parsed.redlineB64 === "string" ? Uint8Array.from(atob(parsed.redlineB64), (c) => c.charCodeAt(0)) : void 0,
|
|
13798
|
+
revisions: Array.isArray(parsed.revisions) ? parsed.revisions.map(mapDocxDiffRevision) : void 0,
|
|
13799
|
+
editScript: parsed.editScript !== void 0 ? parsed.editScript : void 0,
|
|
13800
|
+
semanticChanges: parsed.semanticChanges !== void 0 ? parsed.semanticChanges : void 0
|
|
13801
|
+
};
|
|
11349
13802
|
}
|
|
11350
13803
|
async function docxDiffGetEditScript(left2, right2, settings) {
|
|
11351
13804
|
const exports = ensureInitialized();
|
|
@@ -11363,6 +13816,22 @@ var Docxodus = (() => {
|
|
|
11363
13816
|
}
|
|
11364
13817
|
return result;
|
|
11365
13818
|
}
|
|
13819
|
+
async function docxDiffGetSemanticChanges(left2, right2, settings) {
|
|
13820
|
+
const exports = ensureInitialized();
|
|
13821
|
+
const leftBytes = await toBytes(left2);
|
|
13822
|
+
const rightBytes = await toBytes(right2);
|
|
13823
|
+
await yieldToMain();
|
|
13824
|
+
const result = exports.DocxDiffBridge.GetSemanticChangesJson(
|
|
13825
|
+
leftBytes,
|
|
13826
|
+
rightBytes,
|
|
13827
|
+
docxDiffSettingsJson(settings)
|
|
13828
|
+
);
|
|
13829
|
+
if (isErrorResponse(result)) {
|
|
13830
|
+
const error = parseError(result);
|
|
13831
|
+
throw new Error(`Failed to get semantic changes: ${error.error}`);
|
|
13832
|
+
}
|
|
13833
|
+
return JSON.parse(result);
|
|
13834
|
+
}
|
|
11366
13835
|
async function docxDiffAcceptRevisions(redline) {
|
|
11367
13836
|
const exports = ensureInitialized();
|
|
11368
13837
|
const bytes = await toBytes(redline);
|
|
@@ -11616,6 +14085,7 @@ var Docxodus = (() => {
|
|
|
11616
14085
|
const parsed = JSON.parse(result);
|
|
11617
14086
|
const convertElement = (el) => ({
|
|
11618
14087
|
id: el.Id || el.id,
|
|
14088
|
+
anchorId: el.AnchorId || el.anchorId,
|
|
11619
14089
|
type: el.Type || el.type,
|
|
11620
14090
|
textPreview: el.TextPreview || el.textPreview,
|
|
11621
14091
|
index: el.Index ?? el.index,
|
|
@@ -11627,8 +14097,12 @@ var Docxodus = (() => {
|
|
|
11627
14097
|
});
|
|
11628
14098
|
const convertTableColumn = (col) => ({
|
|
11629
14099
|
tableId: col.TableId || col.tableId,
|
|
14100
|
+
anchorId: col.AnchorId || col.anchorId,
|
|
14101
|
+
tableAnchorId: col.TableAnchorId || col.tableAnchorId,
|
|
14102
|
+
isVirtual: col.IsVirtual ?? col.isVirtual ?? false,
|
|
11630
14103
|
columnIndex: col.ColumnIndex ?? col.columnIndex,
|
|
11631
14104
|
cellIds: col.CellIds || col.cellIds || [],
|
|
14105
|
+
cellAnchorIds: col.CellAnchorIds || col.cellAnchorIds || [],
|
|
11632
14106
|
rowCount: col.RowCount ?? col.rowCount
|
|
11633
14107
|
});
|
|
11634
14108
|
const root = convertElement(parsed.Root || parsed.root);
|
|
@@ -11691,7 +14165,8 @@ var Docxodus = (() => {
|
|
|
11691
14165
|
hasEndnotes: parsed.HasEndnotes ?? parsed.hasEndnotes,
|
|
11692
14166
|
hasTrackedChanges: parsed.HasTrackedChanges ?? parsed.hasTrackedChanges,
|
|
11693
14167
|
hasComments: parsed.HasComments ?? parsed.hasComments,
|
|
11694
|
-
estimatedPageCount: parsed.EstimatedPageCount ?? parsed.estimatedPageCount
|
|
14168
|
+
estimatedPageCount: parsed.EstimatedPageCount ?? parsed.estimatedPageCount,
|
|
14169
|
+
estimatedPageCountSource: parsed.EstimatedPageCountSource ?? parsed.estimatedPageCountSource ?? "heuristic"
|
|
11695
14170
|
};
|
|
11696
14171
|
}
|
|
11697
14172
|
async function exportToOpenContract(document2) {
|