docxodus 9.9.0 → 11.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +182 -4
- package/dist/canonical.d.ts +7 -0
- package/dist/canonical.d.ts.map +1 -0
- package/dist/canonical.js +56 -0
- package/dist/canonical.js.map +1 -0
- package/dist/docxodus.worker.d.ts +2 -1
- package/dist/docxodus.worker.d.ts.map +1 -1
- package/dist/docxodus.worker.js +250 -41
- package/dist/docxodus.worker.js.map +1 -1
- package/dist/editor.bundle.js +1980 -423
- package/dist/editor.d.ts +22 -2
- package/dist/editor.d.ts.map +1 -1
- package/dist/editor.js +94 -9
- package/dist/editor.js.map +1 -1
- package/dist/embed.bundle.js +2968 -597
- package/dist/embed.d.ts +1 -1
- package/dist/embed.iife.js +2969 -598
- package/dist/embed.js +1 -1
- package/dist/export-assets.json +277 -0
- package/dist/export-browser.bundle.js +7989 -0
- package/dist/export-browser.d.ts +266 -0
- package/dist/export-browser.d.ts.map +1 -0
- package/dist/export-browser.js +2915 -0
- package/dist/export-browser.js.map +1 -0
- package/dist/export-resource-limits-v1.json +57 -0
- package/dist/font-contract.d.ts +150 -0
- package/dist/font-contract.d.ts.map +1 -0
- package/dist/font-contract.js +59 -0
- package/dist/font-contract.js.map +1 -0
- package/dist/font-runtime.d.ts +33 -0
- package/dist/font-runtime.d.ts.map +1 -0
- package/dist/font-runtime.js +1100 -0
- package/dist/font-runtime.js.map +1 -0
- package/dist/index.d.ts +83 -65
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +194 -149
- package/dist/index.js.map +1 -1
- package/dist/page-geometry.d.ts +8 -1
- package/dist/page-geometry.d.ts.map +1 -1
- package/dist/page-geometry.js +37 -11
- package/dist/page-geometry.js.map +1 -1
- package/dist/pagination.bundle.js +1827 -410
- package/dist/pagination.d.ts +249 -15
- package/dist/pagination.d.ts.map +1 -1
- package/dist/pagination.js +1942 -453
- package/dist/pagination.js.map +1 -1
- package/dist/react.d.ts +13 -6
- package/dist/react.d.ts.map +1 -1
- package/dist/react.js +38 -5
- package/dist/react.js.map +1 -1
- package/dist/render-report-v2.schema.json +1581 -0
- package/dist/ribbon-chrome.d.ts +2 -2
- package/dist/ribbon-chrome.d.ts.map +1 -1
- package/dist/ribbon-chrome.js +72 -4
- package/dist/ribbon-chrome.js.map +1 -1
- package/dist/ribbon.js +119 -0
- package/dist/ribbon.js.map +1 -1
- package/dist/session.bundle.js +747 -29
- package/dist/session.d.ts +159 -19
- package/dist/session.d.ts.map +1 -1
- package/dist/session.js +593 -25
- package/dist/session.js.map +1 -1
- package/dist/types.d.ts +1287 -238
- package/dist/types.d.ts.map +1 -1
- package/dist/types.js +9 -75
- package/dist/types.js.map +1 -1
- package/dist/wasm/_framework/DocumentFormat.OpenXml.Framework.wasm +0 -0
- package/dist/wasm/_framework/DocumentFormat.OpenXml.Framework.wasm.br +0 -0
- package/dist/wasm/_framework/DocumentFormat.OpenXml.wasm +0 -0
- package/dist/wasm/_framework/DocumentFormat.OpenXml.wasm.br +0 -0
- package/dist/wasm/_framework/Docxodus.wasm +0 -0
- package/dist/wasm/_framework/Docxodus.wasm.br +0 -0
- package/dist/wasm/_framework/DocxodusWasm.wasm +0 -0
- package/dist/wasm/_framework/DocxodusWasm.wasm.br +0 -0
- package/dist/wasm/_framework/System.Collections.Concurrent.wasm +0 -0
- package/dist/wasm/_framework/System.Collections.Concurrent.wasm.br +0 -0
- package/dist/wasm/_framework/System.Collections.Immutable.wasm +0 -0
- package/dist/wasm/_framework/System.Collections.Immutable.wasm.br +0 -0
- package/dist/wasm/_framework/System.Collections.wasm +0 -0
- package/dist/wasm/_framework/System.Collections.wasm.br +0 -0
- package/dist/wasm/_framework/System.ComponentModel.Primitives.wasm +0 -0
- package/dist/wasm/_framework/System.ComponentModel.Primitives.wasm.br +0 -0
- package/dist/wasm/_framework/System.IO.Compression.wasm +0 -0
- package/dist/wasm/_framework/System.IO.Compression.wasm.br +0 -0
- package/dist/wasm/_framework/System.IO.Packaging.wasm +0 -0
- package/dist/wasm/_framework/System.IO.Packaging.wasm.br +0 -0
- package/dist/wasm/_framework/System.IO.Pipelines.wasm +0 -0
- package/dist/wasm/_framework/System.IO.Pipelines.wasm.br +0 -0
- package/dist/wasm/_framework/System.Linq.wasm +0 -0
- package/dist/wasm/_framework/System.Linq.wasm.br +0 -0
- package/dist/wasm/_framework/System.Private.CoreLib.wasm +0 -0
- package/dist/wasm/_framework/System.Private.CoreLib.wasm.br +0 -0
- package/dist/wasm/_framework/System.Private.Uri.wasm +0 -0
- package/dist/wasm/_framework/System.Private.Uri.wasm.br +0 -0
- package/dist/wasm/_framework/System.Private.Xml.Linq.wasm +0 -0
- package/dist/wasm/_framework/System.Private.Xml.Linq.wasm.br +0 -0
- package/dist/wasm/_framework/System.Private.Xml.wasm +0 -0
- package/dist/wasm/_framework/System.Private.Xml.wasm.br +0 -0
- package/dist/wasm/_framework/System.Runtime.InteropServices.JavaScript.wasm +0 -0
- package/dist/wasm/_framework/System.Runtime.InteropServices.JavaScript.wasm.br +0 -0
- package/dist/wasm/_framework/System.Runtime.wasm +0 -0
- package/dist/wasm/_framework/System.Runtime.wasm.br +0 -0
- package/dist/wasm/_framework/System.Security.Cryptography.wasm +0 -0
- package/dist/wasm/_framework/System.Security.Cryptography.wasm.br +0 -0
- package/dist/wasm/_framework/System.Text.Encodings.Web.wasm +0 -0
- package/dist/wasm/_framework/System.Text.Encodings.Web.wasm.br +0 -0
- package/dist/wasm/_framework/System.Text.Json.wasm +0 -0
- package/dist/wasm/_framework/System.Text.Json.wasm.br +0 -0
- package/dist/wasm/_framework/System.Text.RegularExpressions.wasm +0 -0
- package/dist/wasm/_framework/System.Text.RegularExpressions.wasm.br +0 -0
- package/dist/wasm/_framework/dotnet.boot.js +34 -40
- package/dist/wasm/_framework/dotnet.boot.js.br +0 -0
- package/dist/wasm/_framework/dotnet.native.js +39 -3
- package/dist/wasm/_framework/dotnet.native.js.br +0 -0
- package/dist/wasm/_framework/dotnet.native.wasm +0 -0
- package/dist/wasm/_framework/dotnet.native.wasm.br +0 -0
- package/dist/worker-proxy.bundle.js +198 -32
- package/dist/worker-proxy.d.ts +37 -4
- package/dist/worker-proxy.d.ts.map +1 -1
- package/dist/worker-proxy.js +187 -35
- package/dist/worker-proxy.js.map +1 -1
- package/package.json +22 -6
- package/dist/wasm/_framework/System.Diagnostics.Process.wasm +0 -0
- package/dist/wasm/_framework/System.Diagnostics.Process.wasm.br +0 -0
package/dist/embed.bundle.js
CHANGED
|
@@ -167,11 +167,6 @@ var RevisionType = /* @__PURE__ */ ((RevisionType2) => {
|
|
|
167
167
|
RevisionType2["FormatChanged"] = "FormatChanged";
|
|
168
168
|
return RevisionType2;
|
|
169
169
|
})(RevisionType || {});
|
|
170
|
-
var ComparisonEngine = /* @__PURE__ */ ((ComparisonEngine2) => {
|
|
171
|
-
ComparisonEngine2[ComparisonEngine2["WmlComparer"] = 0] = "WmlComparer";
|
|
172
|
-
ComparisonEngine2[ComparisonEngine2["DocxDiff"] = 1] = "DocxDiff";
|
|
173
|
-
return ComparisonEngine2;
|
|
174
|
-
})(ComparisonEngine || {});
|
|
175
170
|
var CommentRenderMode = /* @__PURE__ */ ((CommentRenderMode2) => {
|
|
176
171
|
CommentRenderMode2[CommentRenderMode2["Disabled"] = -1] = "Disabled";
|
|
177
172
|
CommentRenderMode2[CommentRenderMode2["EndnoteStyle"] = 0] = "EndnoteStyle";
|
|
@@ -208,30 +203,16 @@ var ConflictResolution = /* @__PURE__ */ ((ConflictResolution2) => {
|
|
|
208
203
|
return ConflictResolution2;
|
|
209
204
|
})(ConflictResolution || {});
|
|
210
205
|
function isInsertion(revision) {
|
|
211
|
-
return revision.
|
|
206
|
+
return revision.family === "content_insert";
|
|
212
207
|
}
|
|
213
208
|
function isDeletion(revision) {
|
|
214
|
-
return revision.
|
|
209
|
+
return revision.family === "content_delete";
|
|
215
210
|
}
|
|
216
211
|
function isMove(revision) {
|
|
217
|
-
return revision.
|
|
212
|
+
return revision.family === "move";
|
|
218
213
|
}
|
|
219
214
|
function isFormatChange(revision) {
|
|
220
|
-
return revision.
|
|
221
|
-
}
|
|
222
|
-
function isMoveSource(revision) {
|
|
223
|
-
return isMove(revision) && revision.isMoveSource === true;
|
|
224
|
-
}
|
|
225
|
-
function isMoveDestination(revision) {
|
|
226
|
-
return isMove(revision) && revision.isMoveSource === false;
|
|
227
|
-
}
|
|
228
|
-
function findMovePair(revision, allRevisions) {
|
|
229
|
-
if (!isMove(revision) || revision.moveGroupId === void 0) {
|
|
230
|
-
return void 0;
|
|
231
|
-
}
|
|
232
|
-
return allRevisions.find(
|
|
233
|
-
(r) => r.moveGroupId === revision.moveGroupId && r.isMoveSource !== revision.isMoveSource
|
|
234
|
-
);
|
|
215
|
+
return revision.family === "properties_change";
|
|
235
216
|
}
|
|
236
217
|
var PlaceholderKinds = {
|
|
237
218
|
BlankFill: 1,
|
|
@@ -362,7 +343,46 @@ function targetSearchInElement(elementId, searchText, occurrence = 1) {
|
|
|
362
343
|
}
|
|
363
344
|
|
|
364
345
|
// src/session.ts
|
|
365
|
-
|
|
346
|
+
function mutationBatchChangeSet(before, after, key) {
|
|
347
|
+
const beforeGroups = /* @__PURE__ */ new Map();
|
|
348
|
+
const afterGroups = /* @__PURE__ */ new Map();
|
|
349
|
+
const group = (items, target) => {
|
|
350
|
+
items.forEach((item, index) => {
|
|
351
|
+
const identity = key(item);
|
|
352
|
+
const indices = target.get(identity) ?? [];
|
|
353
|
+
indices.push(index);
|
|
354
|
+
target.set(identity, indices);
|
|
355
|
+
});
|
|
356
|
+
};
|
|
357
|
+
group(before, beforeGroups);
|
|
358
|
+
group(after, afterGroups);
|
|
359
|
+
const beforeMatched = before.map(() => false);
|
|
360
|
+
const afterMatched = after.map(() => false);
|
|
361
|
+
const modified = after.map(() => false);
|
|
362
|
+
for (const [identity, afterIndices] of afterGroups) {
|
|
363
|
+
const beforeIndices = beforeGroups.get(identity) ?? [];
|
|
364
|
+
for (const afterIndex of afterIndices) {
|
|
365
|
+
const beforeIndex = beforeIndices.find((index) => !beforeMatched[index] && JSON.stringify(before[index]) === JSON.stringify(after[afterIndex]));
|
|
366
|
+
if (beforeIndex === void 0) continue;
|
|
367
|
+
beforeMatched[beforeIndex] = true;
|
|
368
|
+
afterMatched[afterIndex] = true;
|
|
369
|
+
}
|
|
370
|
+
const remainingBefore = beforeIndices.filter((index) => !beforeMatched[index]);
|
|
371
|
+
const remainingAfter = afterIndices.filter((index) => !afterMatched[index]);
|
|
372
|
+
const modifiedCount = Math.min(remainingBefore.length, remainingAfter.length);
|
|
373
|
+
for (let index = 0; index < modifiedCount; index++) {
|
|
374
|
+
beforeMatched[remainingBefore[index]] = true;
|
|
375
|
+
afterMatched[remainingAfter[index]] = true;
|
|
376
|
+
modified[remainingAfter[index]] = true;
|
|
377
|
+
}
|
|
378
|
+
}
|
|
379
|
+
return {
|
|
380
|
+
added: after.filter((_, index) => !afterMatched[index]),
|
|
381
|
+
removed: before.filter((_, index) => !beforeMatched[index]),
|
|
382
|
+
modified: after.filter((_, index) => modified[index])
|
|
383
|
+
};
|
|
384
|
+
}
|
|
385
|
+
var DocxSession = class _DocxSession {
|
|
366
386
|
/** @internal */
|
|
367
387
|
constructor(handle, wasm) {
|
|
368
388
|
// ─── Raw escape hatch ────────────────────────────────────────────────
|
|
@@ -378,6 +398,336 @@ var DocxSession = class {
|
|
|
378
398
|
project() {
|
|
379
399
|
return JSON.parse(this.wasm.Project(this.handle));
|
|
380
400
|
}
|
|
401
|
+
/** Monotonic document version (0 at open; +1 per committed mutation/undo/redo). */
|
|
402
|
+
getVersion() {
|
|
403
|
+
return JSON.parse(this.wasm.GetVersion(this.handle)).version;
|
|
404
|
+
}
|
|
405
|
+
/**
|
|
406
|
+
* Verification manifest for the current logical checkpoint. Unsaved edits are included and
|
|
407
|
+
* the read does not mutate package bytes, caches, history, or the document version.
|
|
408
|
+
*/
|
|
409
|
+
getPackageManifest() {
|
|
410
|
+
return JSON.parse(this.wasm.GetPackageManifest(this.handle));
|
|
411
|
+
}
|
|
412
|
+
/** Register a browser-materialized PageMap without changing the document version. */
|
|
413
|
+
registerPageMap(pageMap, expectedRendererFingerprint) {
|
|
414
|
+
return JSON.parse(this.wasm.RegisterPageMap(
|
|
415
|
+
this.handle,
|
|
416
|
+
JSON.stringify(pageMap),
|
|
417
|
+
expectedRendererFingerprint ?? ""
|
|
418
|
+
));
|
|
419
|
+
}
|
|
420
|
+
getPageMapStatus(request) {
|
|
421
|
+
return JSON.parse(this.wasm.GetPageMapStatus(
|
|
422
|
+
this.handle,
|
|
423
|
+
request ? JSON.stringify(request) : ""
|
|
424
|
+
));
|
|
425
|
+
}
|
|
426
|
+
getPageCitation(anchorId, request) {
|
|
427
|
+
return JSON.parse(this.wasm.GetPageCitation(
|
|
428
|
+
this.handle,
|
|
429
|
+
anchorId,
|
|
430
|
+
JSON.stringify(request)
|
|
431
|
+
));
|
|
432
|
+
}
|
|
433
|
+
/** Evaluate optimistic guards without mutating or advancing the version. */
|
|
434
|
+
checkPreconditions(preconditions) {
|
|
435
|
+
return JSON.parse(
|
|
436
|
+
this.wasm.CheckPreconditions(this.handle, JSON.stringify(preconditions))
|
|
437
|
+
);
|
|
438
|
+
}
|
|
439
|
+
/**
|
|
440
|
+
* Guard any synchronous mutation. WASM calls are synchronous and single-threaded, so the
|
|
441
|
+
* check and callback form one uninterrupted client-side operation. Prefer a method's native
|
|
442
|
+
* `preconditions` option where it has one (notably replaceTextRange's match-count guard).
|
|
443
|
+
*/
|
|
444
|
+
runWithPreconditions(preconditions, mutation) {
|
|
445
|
+
const checked = this.checkPreconditions(preconditions);
|
|
446
|
+
return checked.success ? mutation() : checked;
|
|
447
|
+
}
|
|
448
|
+
/**
|
|
449
|
+
* Execute synchronous mutations atomically by default. Atomic success is one undo/version
|
|
450
|
+
* unit; any failed or thrown step restores the exact package and history checkpoint.
|
|
451
|
+
*/
|
|
452
|
+
executeBatch(steps, mode = "atomic") {
|
|
453
|
+
if (mode !== "atomic" && mode !== "best_effort") {
|
|
454
|
+
throw new RangeError(`unknown mutation batch mode: ${String(mode)}`);
|
|
455
|
+
}
|
|
456
|
+
const baseVersion = this.getVersion();
|
|
457
|
+
const observationWarnings = [];
|
|
458
|
+
const inspect = (label, read, fallback) => {
|
|
459
|
+
try {
|
|
460
|
+
return read();
|
|
461
|
+
} catch (error) {
|
|
462
|
+
observationWarnings.push(`${label} unavailable: ${error instanceof Error ? error.message : String(error)}`);
|
|
463
|
+
return fallback;
|
|
464
|
+
}
|
|
465
|
+
};
|
|
466
|
+
const beforeRevisions = inspect("Revision delta inspection", () => this.listRevisions(), []);
|
|
467
|
+
const beforeComments = inspect("Comment delta inspection", () => this.listComments(), []);
|
|
468
|
+
const beforeAnnotations = inspect("Annotation delta inspection", () => this.listAnnotations(), []);
|
|
469
|
+
const complete = (result) => {
|
|
470
|
+
try {
|
|
471
|
+
const revisionChanges = mutationBatchChangeSet(
|
|
472
|
+
beforeRevisions,
|
|
473
|
+
inspect("Revision delta inspection", () => this.listRevisions(), beforeRevisions),
|
|
474
|
+
(revision) => revision.id
|
|
475
|
+
);
|
|
476
|
+
const commentChanges = mutationBatchChangeSet(
|
|
477
|
+
beforeComments,
|
|
478
|
+
inspect("Comment delta inspection", () => this.listComments(), beforeComments),
|
|
479
|
+
(comment) => comment.anchorId
|
|
480
|
+
);
|
|
481
|
+
const annotationChanges = mutationBatchChangeSet(
|
|
482
|
+
beforeAnnotations,
|
|
483
|
+
inspect("Annotation delta inspection", () => this.listAnnotations(), beforeAnnotations),
|
|
484
|
+
(annotation) => annotation.id ?? ""
|
|
485
|
+
);
|
|
486
|
+
const resultVersion = inspect(
|
|
487
|
+
"Result version inspection",
|
|
488
|
+
() => this.getVersion(),
|
|
489
|
+
baseVersion
|
|
490
|
+
);
|
|
491
|
+
const warnings = [...observationWarnings];
|
|
492
|
+
if ([...revisionChanges.added, ...revisionChanges.modified].some((revision) => revision.date !== void 0 && revision.date !== null)) {
|
|
493
|
+
warnings.push("Tracked-revision date attributes may use the execution clock; compare revision ids, authors, types, text, and anchors across separate executions.");
|
|
494
|
+
}
|
|
495
|
+
if ([...commentChanges.added, ...commentChanges.modified].some((comment) => comment.date !== void 0 && comment.date !== null)) {
|
|
496
|
+
warnings.push("Comment date attributes may be generated from the execution clock; supply dates explicitly when byte-identical replay is required.");
|
|
497
|
+
}
|
|
498
|
+
if (annotationChanges.added.some((annotation) => annotation.created !== void 0 && annotation.created !== null)) {
|
|
499
|
+
warnings.push("Auto-generated annotation ids or creation timestamps are execution metadata; supply id and created explicitly when byte-identical replay is required.");
|
|
500
|
+
}
|
|
501
|
+
if (result.steps.some((step) => step.results.some((edit) => edit.created.length > 0))) {
|
|
502
|
+
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.");
|
|
503
|
+
}
|
|
504
|
+
if (mode === "best_effort" && !result.success) {
|
|
505
|
+
warnings.push("Best-effort execution retains every successful step despite later failures.");
|
|
506
|
+
}
|
|
507
|
+
let packageHash = null;
|
|
508
|
+
if (!this.wasm.GetPackageContentHash) {
|
|
509
|
+
warnings.push("This WASM bundle predates package equivalence hashes; packageHash is unavailable.");
|
|
510
|
+
} else {
|
|
511
|
+
try {
|
|
512
|
+
packageHash = this.wasm.GetPackageContentHash(this.handle);
|
|
513
|
+
} catch (error) {
|
|
514
|
+
warnings.push(`Package equivalence hash unavailable: ${error instanceof Error ? error.message : String(error)}`);
|
|
515
|
+
}
|
|
516
|
+
}
|
|
517
|
+
return {
|
|
518
|
+
...result,
|
|
519
|
+
preview: false,
|
|
520
|
+
baseVersion,
|
|
521
|
+
resultVersion,
|
|
522
|
+
packageHash,
|
|
523
|
+
revisionChanges,
|
|
524
|
+
commentChanges,
|
|
525
|
+
annotationChanges,
|
|
526
|
+
warnings,
|
|
527
|
+
html: null
|
|
528
|
+
};
|
|
529
|
+
} catch (error) {
|
|
530
|
+
return {
|
|
531
|
+
...result,
|
|
532
|
+
preview: false,
|
|
533
|
+
baseVersion,
|
|
534
|
+
resultVersion: inspect("Result version inspection", () => this.getVersion(), baseVersion),
|
|
535
|
+
packageHash: null,
|
|
536
|
+
revisionChanges: { added: [], removed: [], modified: [] },
|
|
537
|
+
commentChanges: { added: [], removed: [], modified: [] },
|
|
538
|
+
annotationChanges: { added: [], removed: [], modified: [] },
|
|
539
|
+
warnings: [
|
|
540
|
+
...observationWarnings,
|
|
541
|
+
`Batch receipt enrichment unavailable: ${error instanceof Error ? error.message : String(error)}`
|
|
542
|
+
],
|
|
543
|
+
html: null
|
|
544
|
+
};
|
|
545
|
+
}
|
|
546
|
+
};
|
|
547
|
+
const internalFailure = (value) => ({
|
|
548
|
+
success: false,
|
|
549
|
+
error: { code: "internal_error", message: value instanceof Error ? value.message : String(value) },
|
|
550
|
+
created: [],
|
|
551
|
+
removed: [],
|
|
552
|
+
modified: []
|
|
553
|
+
});
|
|
554
|
+
const run = (step) => {
|
|
555
|
+
try {
|
|
556
|
+
const value = step.mutation();
|
|
557
|
+
const results = Array.isArray(value) ? value : [value];
|
|
558
|
+
if (results.some((result) => result === null || typeof result !== "object" || typeof result.success !== "boolean")) {
|
|
559
|
+
return [internalFailure("batch mutation returned invalid edit results")];
|
|
560
|
+
}
|
|
561
|
+
return results;
|
|
562
|
+
} catch (error) {
|
|
563
|
+
return [internalFailure(error)];
|
|
564
|
+
}
|
|
565
|
+
};
|
|
566
|
+
const failureOf = (step, rolledBack) => ({
|
|
567
|
+
index: step.index,
|
|
568
|
+
tool: step.tool,
|
|
569
|
+
action: step.action,
|
|
570
|
+
error: step.results.find((result) => !result.success)?.error ?? { code: "internal_error", message: "batch step failed without an error" },
|
|
571
|
+
rolledBack
|
|
572
|
+
});
|
|
573
|
+
const preflightOne = (step) => {
|
|
574
|
+
try {
|
|
575
|
+
return step.preflight?.();
|
|
576
|
+
} catch (error) {
|
|
577
|
+
return internalFailure(error).error;
|
|
578
|
+
}
|
|
579
|
+
};
|
|
580
|
+
if (mode === "atomic") {
|
|
581
|
+
const preflight = steps.map(preflightOne);
|
|
582
|
+
const failedPreflight = preflight.findIndex((error) => error !== void 0);
|
|
583
|
+
if (failedPreflight >= 0) {
|
|
584
|
+
const source = steps[failedPreflight];
|
|
585
|
+
const failed2 = {
|
|
586
|
+
index: failedPreflight,
|
|
587
|
+
tool: source.tool,
|
|
588
|
+
action: source.action,
|
|
589
|
+
success: false,
|
|
590
|
+
rolledBack: true,
|
|
591
|
+
results: [{ success: false, error: preflight[failedPreflight], created: [], removed: [], modified: [] }]
|
|
592
|
+
};
|
|
593
|
+
return complete({
|
|
594
|
+
mode,
|
|
595
|
+
status: "failed",
|
|
596
|
+
success: false,
|
|
597
|
+
rolledBack: true,
|
|
598
|
+
steps: [failed2],
|
|
599
|
+
failure: failureOf(failed2, true)
|
|
600
|
+
});
|
|
601
|
+
}
|
|
602
|
+
const transaction = this.wasm.BeginTransaction(this.handle);
|
|
603
|
+
const completed2 = [];
|
|
604
|
+
try {
|
|
605
|
+
for (let index = 0; index < steps.length; index++) {
|
|
606
|
+
const source = steps[index];
|
|
607
|
+
const results = run(source);
|
|
608
|
+
const step = {
|
|
609
|
+
index,
|
|
610
|
+
tool: source.tool,
|
|
611
|
+
action: source.action,
|
|
612
|
+
success: results.every((result) => result.success),
|
|
613
|
+
rolledBack: false,
|
|
614
|
+
results
|
|
615
|
+
};
|
|
616
|
+
completed2.push(step);
|
|
617
|
+
if (!step.success) {
|
|
618
|
+
this.wasm.RollbackTransaction(transaction);
|
|
619
|
+
const rolledBack = completed2.map((value) => ({ ...value, rolledBack: true }));
|
|
620
|
+
const failed2 = rolledBack[rolledBack.length - 1];
|
|
621
|
+
return complete({
|
|
622
|
+
mode,
|
|
623
|
+
status: "failed",
|
|
624
|
+
success: false,
|
|
625
|
+
rolledBack: true,
|
|
626
|
+
steps: rolledBack,
|
|
627
|
+
failure: failureOf(failed2, true)
|
|
628
|
+
});
|
|
629
|
+
}
|
|
630
|
+
}
|
|
631
|
+
this.wasm.CommitTransaction(transaction);
|
|
632
|
+
return complete({ mode, status: "ok", success: true, rolledBack: false, steps: completed2 });
|
|
633
|
+
} catch (error) {
|
|
634
|
+
try {
|
|
635
|
+
this.wasm.RollbackTransaction(transaction);
|
|
636
|
+
} catch {
|
|
637
|
+
}
|
|
638
|
+
throw error;
|
|
639
|
+
}
|
|
640
|
+
}
|
|
641
|
+
const completed = steps.map((source, index) => {
|
|
642
|
+
const preflight = preflightOne(source);
|
|
643
|
+
const results = preflight ? [{ success: false, error: preflight, created: [], removed: [], modified: [] }] : run(source);
|
|
644
|
+
return {
|
|
645
|
+
index,
|
|
646
|
+
tool: source.tool,
|
|
647
|
+
action: source.action,
|
|
648
|
+
success: results.every((result) => result.success),
|
|
649
|
+
rolledBack: false,
|
|
650
|
+
results
|
|
651
|
+
};
|
|
652
|
+
});
|
|
653
|
+
const failed = completed.find((step) => !step.success);
|
|
654
|
+
return complete({
|
|
655
|
+
mode,
|
|
656
|
+
status: failed ? completed.some((step) => step.success) ? "partial" : "failed" : "ok",
|
|
657
|
+
success: failed === void 0,
|
|
658
|
+
rolledBack: false,
|
|
659
|
+
steps: completed,
|
|
660
|
+
failure: failed ? failureOf(failed, false) : void 0
|
|
661
|
+
});
|
|
662
|
+
}
|
|
663
|
+
/**
|
|
664
|
+
* Execute the same callback batch algorithm against a complete isolated package clone.
|
|
665
|
+
* Callbacks receive the shadow session explicitly; mutate that argument. The live session's
|
|
666
|
+
* package, caches, version, configuration, and undo/redo history are never execution targets.
|
|
667
|
+
*/
|
|
668
|
+
previewBatch(steps, mode = "atomic", options) {
|
|
669
|
+
if (mode !== "atomic" && mode !== "best_effort") {
|
|
670
|
+
throw new RangeError(`unknown mutation batch mode: ${String(mode)}`);
|
|
671
|
+
}
|
|
672
|
+
const htmlMode = options?.html ?? "none";
|
|
673
|
+
if (htmlMode !== "none" && htmlMode !== "scoped" && htmlMode !== "full") {
|
|
674
|
+
throw new RangeError(`unknown preview HTML mode: ${String(htmlMode)}`);
|
|
675
|
+
}
|
|
676
|
+
if (!this.wasm.OpenPreviewSession) {
|
|
677
|
+
throw new Error("This WASM bundle does not support isolated mutation previews.");
|
|
678
|
+
}
|
|
679
|
+
const shadow = new _DocxSession(this.wasm.OpenPreviewSession(this.handle), this.wasm);
|
|
680
|
+
try {
|
|
681
|
+
const result = shadow.executeBatch(
|
|
682
|
+
steps.map((step) => ({
|
|
683
|
+
tool: step.tool,
|
|
684
|
+
action: step.action,
|
|
685
|
+
mutation: () => step.mutation(shadow),
|
|
686
|
+
preflight: step.preflight ? () => step.preflight(shadow) : void 0
|
|
687
|
+
})),
|
|
688
|
+
mode
|
|
689
|
+
);
|
|
690
|
+
const warnings = [...result.warnings];
|
|
691
|
+
let html = null;
|
|
692
|
+
const unwrapRendered = (rendered) => {
|
|
693
|
+
if (rendered.trimStart().startsWith("{")) {
|
|
694
|
+
const envelope = JSON.parse(rendered);
|
|
695
|
+
if (envelope.error) {
|
|
696
|
+
warnings.push(`Preview HTML could not be generated: ${envelope.error}`);
|
|
697
|
+
return null;
|
|
698
|
+
}
|
|
699
|
+
}
|
|
700
|
+
return rendered;
|
|
701
|
+
};
|
|
702
|
+
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.";
|
|
703
|
+
try {
|
|
704
|
+
if (htmlMode === "scoped") {
|
|
705
|
+
if (!options?.htmlAnchorId) {
|
|
706
|
+
warnings.push("Scoped HTML was requested without htmlAnchorId; no HTML was generated.");
|
|
707
|
+
} else if (this.wasm.RenderPreviewBlockHtml) {
|
|
708
|
+
html = unwrapRendered(
|
|
709
|
+
this.wasm.RenderPreviewBlockHtml(shadow.handle, options.htmlAnchorId)
|
|
710
|
+
);
|
|
711
|
+
} else {
|
|
712
|
+
warnings.push(legacyProfileWarning);
|
|
713
|
+
html = shadow.renderBlock(options.htmlAnchorId);
|
|
714
|
+
}
|
|
715
|
+
} else if (htmlMode === "full") {
|
|
716
|
+
if (this.wasm.RenderPreviewHtml) {
|
|
717
|
+
html = unwrapRendered(this.wasm.RenderPreviewHtml(shadow.handle));
|
|
718
|
+
} else {
|
|
719
|
+
warnings.push(legacyProfileWarning);
|
|
720
|
+
html = unwrapRendered(this.wasm.RenderHtmlForReview ? this.wasm.RenderHtmlForReview(shadow.handle, "docx-", false, false, 1, true) : this.wasm.RenderHtml(shadow.handle, "docx-", false, false, 1));
|
|
721
|
+
}
|
|
722
|
+
}
|
|
723
|
+
} catch (error) {
|
|
724
|
+
warnings.push(`Preview HTML could not be generated: ${error instanceof Error ? error.message : String(error)}`);
|
|
725
|
+
}
|
|
726
|
+
return { ...result, preview: true, warnings, html };
|
|
727
|
+
} finally {
|
|
728
|
+
shadow.close();
|
|
729
|
+
}
|
|
730
|
+
}
|
|
381
731
|
/**
|
|
382
732
|
* Project a slice of the document keyed off an anchor — useful for showing
|
|
383
733
|
* one section to an LLM at a time without paying the cost of projecting the
|
|
@@ -393,9 +743,9 @@ var DocxSession = class {
|
|
|
393
743
|
*
|
|
394
744
|
* @see docs/architecture/docx_mutation_api.md
|
|
395
745
|
*/
|
|
396
|
-
projectAnchor(anchorId, depth = 2 /* SubtreeAndFollowingSiblings
|
|
746
|
+
projectAnchor(anchorId, depth = 2 /* SubtreeAndFollowingSiblings */, citation) {
|
|
397
747
|
return JSON.parse(
|
|
398
|
-
this.wasm.ProjectAnchor(this.handle, anchorId, depth)
|
|
748
|
+
citation ? this.wasm.ProjectAnchorWithCitations(this.handle, anchorId, depth, JSON.stringify(citation)) : this.wasm.ProjectAnchor(this.handle, anchorId, depth)
|
|
399
749
|
);
|
|
400
750
|
}
|
|
401
751
|
/**
|
|
@@ -419,11 +769,21 @@ var DocxSession = class {
|
|
|
419
769
|
return html;
|
|
420
770
|
}
|
|
421
771
|
// ─── Tier A: text CRUD ───────────────────────────────────────────────
|
|
422
|
-
replaceText(anchorId, markdown) {
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
return
|
|
772
|
+
replaceText(anchorId, markdown, preconditions) {
|
|
773
|
+
const apply = () => JSON.parse(
|
|
774
|
+
this.wasm.ReplaceText(this.handle, anchorId, markdown)
|
|
775
|
+
);
|
|
776
|
+
return preconditions ? this.runWithPreconditions(
|
|
777
|
+
{ ...preconditions, anchorId: preconditions.anchorId ?? anchorId },
|
|
778
|
+
apply
|
|
779
|
+
) : apply();
|
|
780
|
+
}
|
|
781
|
+
deleteBlock(anchorId, preconditions) {
|
|
782
|
+
const apply = () => JSON.parse(this.wasm.DeleteBlock(this.handle, anchorId));
|
|
783
|
+
return preconditions ? this.runWithPreconditions(
|
|
784
|
+
{ ...preconditions, anchorId: preconditions.anchorId ?? anchorId },
|
|
785
|
+
apply
|
|
786
|
+
) : apply();
|
|
427
787
|
}
|
|
428
788
|
/** Reorder one top-level paragraph/heading/list/table block relative to another. */
|
|
429
789
|
moveBlock(sourceAnchorId, targetAnchorId, position) {
|
|
@@ -478,7 +838,7 @@ var DocxSession = class {
|
|
|
478
838
|
}
|
|
479
839
|
/**
|
|
480
840
|
* Insert a `rows`×`cols` table before/after the block. `options` controls borders, row-major
|
|
481
|
-
* cell markdown, and cell alignment. The returned `EditResult.created` lists
|
|
841
|
+
* cell markdown, and cell alignment. The returned `EditResult.created` lists canonical `tc`
|
|
482
842
|
* anchors (row-major), so each cell can then be addressed to fill/format.
|
|
483
843
|
*/
|
|
484
844
|
insertTable(anchorId, position, rows, cols, options) {
|
|
@@ -487,10 +847,26 @@ var DocxSession = class {
|
|
|
487
847
|
this.wasm.InsertTable(this.handle, anchorId, position, rows, cols, optionsJson)
|
|
488
848
|
);
|
|
489
849
|
}
|
|
850
|
+
/** Resolve a canonical `tbl` anchor to explicit table/row/column/cell identities. */
|
|
851
|
+
getTableMetadata(tableAnchorId) {
|
|
852
|
+
return JSON.parse(this.wasm.GetTableMetadata(this.handle, tableAnchorId));
|
|
853
|
+
}
|
|
854
|
+
/** Resolve a canonical `tc` anchor to its zero-based table-grid coordinate and spans. */
|
|
855
|
+
resolveTableCellAnchor(cellAnchorId) {
|
|
856
|
+
return JSON.parse(
|
|
857
|
+
this.wasm.ResolveTableCellAnchor(this.handle, cellAnchorId)
|
|
858
|
+
);
|
|
859
|
+
}
|
|
860
|
+
/** Resolve a zero-based table-grid coordinate to the physical `tc` covering it. */
|
|
861
|
+
resolveTableCellCoordinate(tableAnchorId, rowIndex, columnIndex) {
|
|
862
|
+
return JSON.parse(
|
|
863
|
+
this.wasm.ResolveTableCellCoordinate(this.handle, tableAnchorId, rowIndex, columnIndex)
|
|
864
|
+
);
|
|
865
|
+
}
|
|
490
866
|
/**
|
|
491
|
-
* Table row/column editing, addressed by
|
|
492
|
-
* {@link insertTable}'s `created`
|
|
493
|
-
* empty (`created` lists
|
|
867
|
+
* Table row/column editing, addressed by the canonical `tc` anchor returned from
|
|
868
|
+
* {@link insertTable}'s `created` or table metadata. Insert clones the reference row/column's
|
|
869
|
+
* widths and starts empty (`created` lists new `tc` anchors); delete of the last row/column removes
|
|
494
870
|
* the whole table. All four are grid-aware: inserting across a merge extends it, deleting
|
|
495
871
|
* through one narrows it, and deleting a vertical merge's lead row promotes the next row to
|
|
496
872
|
* carry it — the grid is never left ragged.
|
|
@@ -530,7 +906,7 @@ var DocxSession = class {
|
|
|
530
906
|
return JSON.parse(this.wasm.UnmergeCells(this.handle, cellAnchorId));
|
|
531
907
|
}
|
|
532
908
|
/**
|
|
533
|
-
* Table styling, addressed by a
|
|
909
|
+
* Table styling, addressed by a canonical `tc` anchor — the post-insert counterpart of
|
|
534
910
|
* {@link insertTable}'s options (issue #315 Stage A). `setColumnWidths` retunes `w:tblGrid` +
|
|
535
911
|
* every row's cell width (one positive twip value per column) and pins the table to fixed
|
|
536
912
|
* layout, exactly as inserting with explicit `columnWidths` would.
|
|
@@ -570,6 +946,19 @@ var DocxSession = class {
|
|
|
570
946
|
this.wasm.SetRepeatHeaderRow(this.handle, cellAnchorId, repeat)
|
|
571
947
|
);
|
|
572
948
|
}
|
|
949
|
+
/** Apply row layout options to the row containing the canonical cell anchor. */
|
|
950
|
+
setTableRowOptions(cellAnchorId, options) {
|
|
951
|
+
return JSON.parse(
|
|
952
|
+
this.wasm.SetTableRowOptions(
|
|
953
|
+
this.handle,
|
|
954
|
+
cellAnchorId,
|
|
955
|
+
options.repeatHeader ?? null,
|
|
956
|
+
options.allowBreakAcrossPages ?? null,
|
|
957
|
+
options.heightTwips ?? null,
|
|
958
|
+
options.heightRule ?? "atLeast"
|
|
959
|
+
)
|
|
960
|
+
);
|
|
961
|
+
}
|
|
573
962
|
// ─── Headers / footers / page numbers ────────────────────────────────
|
|
574
963
|
/**
|
|
575
964
|
* Set the running header story for the section that owns `anchorId` (any body block in that
|
|
@@ -620,6 +1009,58 @@ var DocxSession = class {
|
|
|
620
1009
|
this.wasm.SetPageNumbering(this.handle, anchorId, JSON.stringify(op))
|
|
621
1010
|
);
|
|
622
1011
|
}
|
|
1012
|
+
/**
|
|
1013
|
+
* Insert a **table of contents** before or after `anchorId` (issue #607). The field is written
|
|
1014
|
+
* dirty and the document asks for a field update on open, so Word paginates and fills the table
|
|
1015
|
+
* itself — nothing here ships a cached result that is stale the moment anything above it moves.
|
|
1016
|
+
*
|
|
1017
|
+
* The table is wrapped in the `w:sdt` content control Word puts around one, which is what gives
|
|
1018
|
+
* it the *Update Table* control in Word's UI. Word's `TOCHeading` and `TOC1` styles are
|
|
1019
|
+
* find-or-created; a document that already defines them keeps its own.
|
|
1020
|
+
*
|
|
1021
|
+
* Body anchors only, and refused under tracked-change recording: a generated table is regenerated
|
|
1022
|
+
* wholesale on every field update, so there is no reversible way to redline it.
|
|
1023
|
+
*/
|
|
1024
|
+
insertTableOfContents(anchorId, pos = "before", options) {
|
|
1025
|
+
return JSON.parse(
|
|
1026
|
+
this.wasm.InsertTableOfContents(
|
|
1027
|
+
this.handle,
|
|
1028
|
+
anchorId,
|
|
1029
|
+
pos,
|
|
1030
|
+
options ? JSON.stringify(options) : ""
|
|
1031
|
+
)
|
|
1032
|
+
);
|
|
1033
|
+
}
|
|
1034
|
+
/**
|
|
1035
|
+
* Insert a **table of figures** — the captions carrying `options.captionLabel` and their page
|
|
1036
|
+
* numbers. Same field mechanics as {@link insertTableOfContents}; Word writes this one as a bare
|
|
1037
|
+
* paragraph rather than inside a content control, so this does too.
|
|
1038
|
+
*/
|
|
1039
|
+
insertTableOfFigures(anchorId, pos = "before", options) {
|
|
1040
|
+
return JSON.parse(
|
|
1041
|
+
this.wasm.InsertTableOfFigures(
|
|
1042
|
+
this.handle,
|
|
1043
|
+
anchorId,
|
|
1044
|
+
pos,
|
|
1045
|
+
options ? JSON.stringify(options) : ""
|
|
1046
|
+
)
|
|
1047
|
+
);
|
|
1048
|
+
}
|
|
1049
|
+
/**
|
|
1050
|
+
* Insert a **table of authorities** — the cases, statutes or other authorities marked in the
|
|
1051
|
+
* document, grouped by `options.category`. The table lists entries the document has MARKED with
|
|
1052
|
+
* `TA` fields; a document with no marked citations produces a table that is correct and empty.
|
|
1053
|
+
*/
|
|
1054
|
+
insertTableOfAuthorities(anchorId, pos = "before", options) {
|
|
1055
|
+
return JSON.parse(
|
|
1056
|
+
this.wasm.InsertTableOfAuthorities(
|
|
1057
|
+
this.handle,
|
|
1058
|
+
anchorId,
|
|
1059
|
+
pos,
|
|
1060
|
+
options ? JSON.stringify(options) : ""
|
|
1061
|
+
)
|
|
1062
|
+
);
|
|
1063
|
+
}
|
|
623
1064
|
/**
|
|
624
1065
|
* Remove the section's page-numbering start/format: it reverts to continuing the previous
|
|
625
1066
|
* section's numbering in Word's default `1, 2, 3`. Chapter-numbering attributes
|
|
@@ -664,6 +1105,25 @@ var DocxSession = class {
|
|
|
664
1105
|
this.wasm.InsertFootnote(this.handle, anchorId, characterOffset, markdown)
|
|
665
1106
|
);
|
|
666
1107
|
}
|
|
1108
|
+
/**
|
|
1109
|
+
* Insert a Word-faithful internal cross-reference — a `REF` field targeting an existing
|
|
1110
|
+
* bookmark — at a character offset (issue #545). The field carries a cached result run
|
|
1111
|
+
* (the bookmarked text, or the target's auto-number under `referenceNumber`), so
|
|
1112
|
+
* renderers that do not recompute fields show a faithful snapshot and Word updates it
|
|
1113
|
+
* like a hand-authored cross-reference. A missing or incoherent bookmark fails with
|
|
1114
|
+
* `missing_bookmark_target`.
|
|
1115
|
+
*/
|
|
1116
|
+
insertCrossReference(anchorId, characterOffset, bookmarkName, options) {
|
|
1117
|
+
return JSON.parse(
|
|
1118
|
+
this.wasm.InsertCrossReference(
|
|
1119
|
+
this.handle,
|
|
1120
|
+
anchorId,
|
|
1121
|
+
characterOffset,
|
|
1122
|
+
bookmarkName,
|
|
1123
|
+
options ? JSON.stringify(options) : ""
|
|
1124
|
+
)
|
|
1125
|
+
);
|
|
1126
|
+
}
|
|
667
1127
|
/** Create an endnote — see {@link insertFootnote}; writes the endnotes part and a
|
|
668
1128
|
* `w:endnoteReference`, and the created definition anchor has kind `en`. */
|
|
669
1129
|
insertEndnote(anchorId, characterOffset, markdown) {
|
|
@@ -755,6 +1215,170 @@ var DocxSession = class {
|
|
|
755
1215
|
listComments() {
|
|
756
1216
|
return JSON.parse(this.wasm.ListComments(this.handle));
|
|
757
1217
|
}
|
|
1218
|
+
listHyperlinks(scopes = 63 /* All */) {
|
|
1219
|
+
return JSON.parse(this.wasm.ListHyperlinks(this.handle, scopes));
|
|
1220
|
+
}
|
|
1221
|
+
addHyperlink(anchorId, span, kind, target) {
|
|
1222
|
+
return JSON.parse(this.wasm.AddHyperlink(
|
|
1223
|
+
this.handle,
|
|
1224
|
+
anchorId,
|
|
1225
|
+
span.start,
|
|
1226
|
+
span.length,
|
|
1227
|
+
kind,
|
|
1228
|
+
target
|
|
1229
|
+
));
|
|
1230
|
+
}
|
|
1231
|
+
updateHyperlink(hyperlinkId, kind, target) {
|
|
1232
|
+
return JSON.parse(this.wasm.UpdateHyperlink(this.handle, hyperlinkId, kind, target));
|
|
1233
|
+
}
|
|
1234
|
+
removeHyperlink(hyperlinkId) {
|
|
1235
|
+
return JSON.parse(this.wasm.RemoveHyperlink(this.handle, hyperlinkId));
|
|
1236
|
+
}
|
|
1237
|
+
/** Versioned operational facts for native image inspection/mutation in this runtime. */
|
|
1238
|
+
getImageCapabilities() {
|
|
1239
|
+
return JSON.parse(this.wasm.GetImageCapabilities());
|
|
1240
|
+
}
|
|
1241
|
+
listImages(scopes = 63 /* All */) {
|
|
1242
|
+
return JSON.parse(this.wasm.ListImages(this.handle, scopes));
|
|
1243
|
+
}
|
|
1244
|
+
insertImage(anchorId, characterOffset, bytes, options = {}) {
|
|
1245
|
+
return JSON.parse(this.wasm.InsertImage(
|
|
1246
|
+
this.handle,
|
|
1247
|
+
anchorId,
|
|
1248
|
+
characterOffset,
|
|
1249
|
+
imageBytesToBase64(bytes),
|
|
1250
|
+
JSON.stringify(options)
|
|
1251
|
+
));
|
|
1252
|
+
}
|
|
1253
|
+
replaceImage(imageId, bytes) {
|
|
1254
|
+
return JSON.parse(this.wasm.ReplaceImage(
|
|
1255
|
+
this.handle,
|
|
1256
|
+
imageId,
|
|
1257
|
+
imageBytesToBase64(bytes)
|
|
1258
|
+
));
|
|
1259
|
+
}
|
|
1260
|
+
setImageDimensions(imageId, dimensions) {
|
|
1261
|
+
return JSON.parse(this.wasm.SetImageDimensions(
|
|
1262
|
+
this.handle,
|
|
1263
|
+
imageId,
|
|
1264
|
+
JSON.stringify(dimensions)
|
|
1265
|
+
));
|
|
1266
|
+
}
|
|
1267
|
+
setImageMetadata(imageId, altText, title) {
|
|
1268
|
+
return JSON.parse(this.wasm.SetImageMetadata(
|
|
1269
|
+
this.handle,
|
|
1270
|
+
imageId,
|
|
1271
|
+
altText,
|
|
1272
|
+
title
|
|
1273
|
+
));
|
|
1274
|
+
}
|
|
1275
|
+
setImageFloatingLayout(imageId, layout) {
|
|
1276
|
+
return JSON.parse(this.wasm.SetImageFloatingLayout(
|
|
1277
|
+
this.handle,
|
|
1278
|
+
imageId,
|
|
1279
|
+
JSON.stringify(layout)
|
|
1280
|
+
));
|
|
1281
|
+
}
|
|
1282
|
+
removeImage(imageId) {
|
|
1283
|
+
return JSON.parse(this.wasm.RemoveImage(this.handle, imageId));
|
|
1284
|
+
}
|
|
1285
|
+
/** Native Word structured-document tags, in outer-before-inner story order. */
|
|
1286
|
+
listContentControls(scopes = 63 /* All */) {
|
|
1287
|
+
return JSON.parse(this.wasm.ListContentControls(this.handle, scopes));
|
|
1288
|
+
}
|
|
1289
|
+
fillContentControlText(anchorId, text, options = {}) {
|
|
1290
|
+
return JSON.parse(this.wasm.FillContentControlText(
|
|
1291
|
+
this.handle,
|
|
1292
|
+
anchorId,
|
|
1293
|
+
text,
|
|
1294
|
+
JSON.stringify(options)
|
|
1295
|
+
));
|
|
1296
|
+
}
|
|
1297
|
+
fillContentControlRichText(anchorId, markdown, options = {}) {
|
|
1298
|
+
return JSON.parse(this.wasm.FillContentControlRichText(
|
|
1299
|
+
this.handle,
|
|
1300
|
+
anchorId,
|
|
1301
|
+
markdown,
|
|
1302
|
+
JSON.stringify(options)
|
|
1303
|
+
));
|
|
1304
|
+
}
|
|
1305
|
+
setContentControlChecked(anchorId, isChecked, options = {}) {
|
|
1306
|
+
return JSON.parse(this.wasm.SetContentControlChecked(
|
|
1307
|
+
this.handle,
|
|
1308
|
+
anchorId,
|
|
1309
|
+
isChecked,
|
|
1310
|
+
JSON.stringify(options)
|
|
1311
|
+
));
|
|
1312
|
+
}
|
|
1313
|
+
setContentControlDate(anchorId, value, displayText, options = {}) {
|
|
1314
|
+
const timestamp = value instanceof Date ? value.toISOString() : value;
|
|
1315
|
+
return JSON.parse(this.wasm.SetContentControlDate(
|
|
1316
|
+
this.handle,
|
|
1317
|
+
anchorId,
|
|
1318
|
+
timestamp,
|
|
1319
|
+
displayText ?? null,
|
|
1320
|
+
JSON.stringify(options)
|
|
1321
|
+
));
|
|
1322
|
+
}
|
|
1323
|
+
selectContentControlItem(anchorId, value, options = {}) {
|
|
1324
|
+
return JSON.parse(this.wasm.SelectContentControlItem(
|
|
1325
|
+
this.handle,
|
|
1326
|
+
anchorId,
|
|
1327
|
+
value,
|
|
1328
|
+
JSON.stringify(options)
|
|
1329
|
+
));
|
|
1330
|
+
}
|
|
1331
|
+
fillContentControlPicture(anchorId, bytes, options = {}) {
|
|
1332
|
+
return JSON.parse(this.wasm.FillContentControlPicture(
|
|
1333
|
+
this.handle,
|
|
1334
|
+
anchorId,
|
|
1335
|
+
imageBytesToBase64(bytes),
|
|
1336
|
+
JSON.stringify(options)
|
|
1337
|
+
));
|
|
1338
|
+
}
|
|
1339
|
+
addRepeatingSectionItem(sectionAnchorId, afterItemAnchorId, options = {}) {
|
|
1340
|
+
return JSON.parse(this.wasm.AddRepeatingSectionItem(
|
|
1341
|
+
this.handle,
|
|
1342
|
+
sectionAnchorId,
|
|
1343
|
+
afterItemAnchorId ?? "",
|
|
1344
|
+
JSON.stringify(options)
|
|
1345
|
+
));
|
|
1346
|
+
}
|
|
1347
|
+
removeRepeatingSectionItem(itemAnchorId) {
|
|
1348
|
+
return JSON.parse(this.wasm.RemoveRepeatingSectionItem(
|
|
1349
|
+
this.handle,
|
|
1350
|
+
itemAnchorId
|
|
1351
|
+
));
|
|
1352
|
+
}
|
|
1353
|
+
listBookmarks(scopes = 63 /* All */) {
|
|
1354
|
+
return JSON.parse(this.wasm.ListBookmarks(this.handle, scopes));
|
|
1355
|
+
}
|
|
1356
|
+
addBookmark(name, range) {
|
|
1357
|
+
return JSON.parse(this.wasm.AddBookmark(
|
|
1358
|
+
this.handle,
|
|
1359
|
+
name,
|
|
1360
|
+
range.startAnchorId,
|
|
1361
|
+
range.startOffset,
|
|
1362
|
+
range.endAnchorId,
|
|
1363
|
+
range.endOffset
|
|
1364
|
+
));
|
|
1365
|
+
}
|
|
1366
|
+
renameBookmark(name, newName) {
|
|
1367
|
+
return JSON.parse(this.wasm.RenameBookmark(this.handle, name, newName));
|
|
1368
|
+
}
|
|
1369
|
+
moveBookmark(name, range) {
|
|
1370
|
+
return JSON.parse(this.wasm.MoveBookmark(
|
|
1371
|
+
this.handle,
|
|
1372
|
+
name,
|
|
1373
|
+
range.startAnchorId,
|
|
1374
|
+
range.startOffset,
|
|
1375
|
+
range.endAnchorId,
|
|
1376
|
+
range.endOffset
|
|
1377
|
+
));
|
|
1378
|
+
}
|
|
1379
|
+
removeBookmark(name) {
|
|
1380
|
+
return JSON.parse(this.wasm.RemoveBookmark(this.handle, name));
|
|
1381
|
+
}
|
|
758
1382
|
// ─── Tracked revisions (issue #318) ──────────────────────────────────
|
|
759
1383
|
/** Markup-native tracked-revision listing, in document order across body, headers,
|
|
760
1384
|
* footers, footnotes, and endnotes. Ids are stable while the underlying markup
|
|
@@ -775,6 +1399,22 @@ var DocxSession = class {
|
|
|
775
1399
|
rejectRevision(revisionId) {
|
|
776
1400
|
return JSON.parse(this.wasm.RejectRevision(this.handle, revisionId));
|
|
777
1401
|
}
|
|
1402
|
+
/**
|
|
1403
|
+
* Accept every live revision as one undoable session mutation.
|
|
1404
|
+
*
|
|
1405
|
+
* Fails closed: an unsupported, malformed, or ambiguous registry entry aborts the whole
|
|
1406
|
+
* operation (`revisionUnsupported`/`revisionMalformed`/`revisionAmbiguous`) and nothing is
|
|
1407
|
+
* mutated. There is no force mode — call {@link listRevisions} and read each entry's
|
|
1408
|
+
* `diagnostic` to see what blocks it.
|
|
1409
|
+
*/
|
|
1410
|
+
acceptAllRevisions() {
|
|
1411
|
+
return JSON.parse(this.wasm.AcceptAllRevisions(this.handle));
|
|
1412
|
+
}
|
|
1413
|
+
/** Reject every live revision as one undoable session mutation. Fails closed exactly like
|
|
1414
|
+
* {@link acceptAllRevisions}. */
|
|
1415
|
+
rejectAllRevisions() {
|
|
1416
|
+
return JSON.parse(this.wasm.RejectAllRevisions(this.handle));
|
|
1417
|
+
}
|
|
778
1418
|
// ─── Tier C: formatting ──────────────────────────────────────────────
|
|
779
1419
|
applyFormat(anchorId, span, op) {
|
|
780
1420
|
const spanJson = span ? JSON.stringify(span) : "";
|
|
@@ -880,6 +1520,10 @@ var DocxSession = class {
|
|
|
880
1520
|
* replaces it with `replace`, preserving the surrounding run formatting that
|
|
881
1521
|
* the match didn't touch. Returns one `EditResult` per attempted match.
|
|
882
1522
|
*
|
|
1523
|
+
* A `find` that matches nothing fails with a single `text_not_found` result
|
|
1524
|
+
* naming the anchor and the needle — never an empty array. Pass
|
|
1525
|
+
* `expectedMatchCount: 0` to assert absence as a successful no-op instead.
|
|
1526
|
+
*
|
|
883
1527
|
* Run-formatting contract: the replacement text inherits the formatting of
|
|
884
1528
|
* the FIRST run the match spanned. Middle/trailing runs keep their `w:rPr`
|
|
885
1529
|
* but lose the slice of text the match consumed.
|
|
@@ -1056,10 +1700,16 @@ var DocxSession = class {
|
|
|
1056
1700
|
*
|
|
1057
1701
|
* @see docs/architecture/docx_mutation_api.md#findplaceholders
|
|
1058
1702
|
*/
|
|
1059
|
-
findPlaceholders(kinds = PlaceholderKinds.All, scope = 1, contextChars = 80, boundary = ContextBoundary.Char) {
|
|
1060
|
-
|
|
1061
|
-
this.
|
|
1062
|
-
|
|
1703
|
+
findPlaceholders(kinds = PlaceholderKinds.All, scope = 1, contextChars = 80, boundary = ContextBoundary.Char, citation) {
|
|
1704
|
+
const json = citation ? this.wasm.FindPlaceholdersWithCitations(
|
|
1705
|
+
this.handle,
|
|
1706
|
+
kinds,
|
|
1707
|
+
scope,
|
|
1708
|
+
contextChars,
|
|
1709
|
+
boundary,
|
|
1710
|
+
JSON.stringify(citation)
|
|
1711
|
+
) : this.wasm.FindPlaceholders(this.handle, kinds, scope, contextChars, boundary);
|
|
1712
|
+
return JSON.parse(json);
|
|
1063
1713
|
}
|
|
1064
1714
|
/**
|
|
1065
1715
|
* Returns a snapshot of edit-state introspection signals — placeholder counts,
|
|
@@ -1082,6 +1732,23 @@ var DocxSession = class {
|
|
|
1082
1732
|
}
|
|
1083
1733
|
return raw;
|
|
1084
1734
|
}
|
|
1735
|
+
/**
|
|
1736
|
+
* Return stable semantic changes from the package opened for this session to
|
|
1737
|
+
* its current state. Requires `captureInitialProjection` (enabled by default).
|
|
1738
|
+
*/
|
|
1739
|
+
getSemanticChanges() {
|
|
1740
|
+
return JSON.parse(this.wasm.GetSemanticChanges(this.handle));
|
|
1741
|
+
}
|
|
1742
|
+
/**
|
|
1743
|
+
* Run the default deliverable gate on this session's normal clean-save checkpoint.
|
|
1744
|
+
* With initial projection capture enabled (the default), exact opening bytes are the
|
|
1745
|
+
* baseline used for dispositions and semantic/package deltas.
|
|
1746
|
+
*/
|
|
1747
|
+
verifyDeliverable() {
|
|
1748
|
+
return JSON.parse(
|
|
1749
|
+
this.wasm.VerifyDeliverable(this.handle)
|
|
1750
|
+
);
|
|
1751
|
+
}
|
|
1085
1752
|
// ─── Annotation-based anchor discovery (#132) ────────────────────────
|
|
1086
1753
|
/**
|
|
1087
1754
|
* Resolves an annotation's range to the block-level markdown anchors covering
|
|
@@ -1097,8 +1764,9 @@ var DocxSession = class {
|
|
|
1097
1764
|
*
|
|
1098
1765
|
* @see docs/architecture/docx_mutation_api.md#findbyannotation
|
|
1099
1766
|
*/
|
|
1100
|
-
findByAnnotation(annotationId) {
|
|
1101
|
-
|
|
1767
|
+
findByAnnotation(annotationId, citation) {
|
|
1768
|
+
const json = citation ? this.wasm.FindByAnnotationWithCitations(this.handle, annotationId, JSON.stringify(citation)) : this.wasm.FindByAnnotation(this.handle, annotationId);
|
|
1769
|
+
return JSON.parse(json);
|
|
1102
1770
|
}
|
|
1103
1771
|
/**
|
|
1104
1772
|
* Finds every annotation whose `labelId` matches and resolves each of their
|
|
@@ -1107,8 +1775,9 @@ var DocxSession = class {
|
|
|
1107
1775
|
* annotations on different paragraphs become three entries). Annotations
|
|
1108
1776
|
* whose bookmark resolves to no anchors are omitted from the result.
|
|
1109
1777
|
*/
|
|
1110
|
-
findByLabel(labelId) {
|
|
1111
|
-
|
|
1778
|
+
findByLabel(labelId, citation) {
|
|
1779
|
+
const json = citation ? this.wasm.FindByLabelWithCitations(this.handle, labelId, JSON.stringify(citation)) : this.wasm.FindByLabel(this.handle, labelId);
|
|
1780
|
+
return JSON.parse(json);
|
|
1112
1781
|
}
|
|
1113
1782
|
/**
|
|
1114
1783
|
* Resolves any bookmark in the main document part (Docxodus-managed or
|
|
@@ -1116,8 +1785,9 @@ var DocxSession = class {
|
|
|
1116
1785
|
* order. Empty when the bookmark name is unknown. Use this for raw bookmark
|
|
1117
1786
|
* names that didn't come from the annotation system.
|
|
1118
1787
|
*/
|
|
1119
|
-
findByBookmark(bookmarkName) {
|
|
1120
|
-
|
|
1788
|
+
findByBookmark(bookmarkName, citation) {
|
|
1789
|
+
const json = citation ? this.wasm.FindByBookmarkWithCitations(this.handle, bookmarkName, JSON.stringify(citation)) : this.wasm.FindByBookmark(this.handle, bookmarkName);
|
|
1790
|
+
return JSON.parse(json);
|
|
1121
1791
|
}
|
|
1122
1792
|
// ─── Text/kind-based anchor discovery (#171) ─────────────────────────
|
|
1123
1793
|
/**
|
|
@@ -1162,15 +1832,25 @@ var DocxSession = class {
|
|
|
1162
1832
|
);
|
|
1163
1833
|
}
|
|
1164
1834
|
/**
|
|
1165
|
-
* Return every anchor of the given `kind`
|
|
1166
|
-
* `"
|
|
1835
|
+
* Return every anchor of the given `kind` — one of `"p"`, `"h"`, `"li"`,
|
|
1836
|
+
* `"tbl"`, `"tr"`, `"tc"`, `"col"`, `"sdt"`, `"sec"`, `"fn"`, `"en"`,
|
|
1837
|
+
* `"cmt"`, `"unk"` — in document order. Matching is an exact string comparison;
|
|
1838
|
+
* in particular the row and cell tokens are `"tr"` and `"tc"`, never
|
|
1839
|
+
* `"row"`/`"cell"`. Unknown strings are not parsed or rejected: they simply
|
|
1840
|
+
* return an empty array. `"img"` and `"drw"` likewise return empty because the
|
|
1841
|
+
* projection never assigns those reserved kinds; address images through the
|
|
1842
|
+
* image surface instead. Reads the projection's anchor
|
|
1167
1843
|
* index directly — no text scan. Pass `scope` (e.g. `"body"`) to restrict to
|
|
1168
1844
|
* a single part; omit it to span all scopes.
|
|
1169
1845
|
*/
|
|
1170
|
-
findByKind(kind, scope) {
|
|
1171
|
-
|
|
1172
|
-
this.
|
|
1173
|
-
|
|
1846
|
+
findByKind(kind, scope, citation) {
|
|
1847
|
+
const json = citation ? this.wasm.FindByKindWithCitations(
|
|
1848
|
+
this.handle,
|
|
1849
|
+
kind,
|
|
1850
|
+
scope ?? "",
|
|
1851
|
+
JSON.stringify(citation)
|
|
1852
|
+
) : this.wasm.FindByKind(this.handle, kind, scope ?? "");
|
|
1853
|
+
return JSON.parse(json);
|
|
1174
1854
|
}
|
|
1175
1855
|
/**
|
|
1176
1856
|
* Look up a single anchor's preview info — `{ id, kind, scope, textPreview }`.
|
|
@@ -1224,6 +1904,18 @@ var DocxSession = class {
|
|
|
1224
1904
|
const raw = this.wasm.GetSectionInfo(this.handle, anchorId);
|
|
1225
1905
|
return JSON.parse(raw);
|
|
1226
1906
|
}
|
|
1907
|
+
/** Enumerate the document's explicit style catalog with resolved high-signal properties. */
|
|
1908
|
+
listStyles() {
|
|
1909
|
+
return JSON.parse(this.wasm.ListStyles(this.handle));
|
|
1910
|
+
}
|
|
1911
|
+
/** Inspect direct and effective paragraph/run formatting for one paragraph anchor. */
|
|
1912
|
+
getFormatting(anchorId) {
|
|
1913
|
+
return JSON.parse(this.wasm.GetFormatting(this.handle, anchorId));
|
|
1914
|
+
}
|
|
1915
|
+
/** Enumerate text-bearing runs as mutation-compatible anchor/span pairs. */
|
|
1916
|
+
listInlineSpans(anchorId) {
|
|
1917
|
+
return JSON.parse(this.wasm.ListInlineSpans(this.handle, anchorId));
|
|
1918
|
+
}
|
|
1227
1919
|
/**
|
|
1228
1920
|
* Enumerates every annotation persisted in the document. Lets an agent prime
|
|
1229
1921
|
* itself with "here are the labeled regions you can target" before committing
|
|
@@ -1288,6 +1980,13 @@ var DocxSession = class {
|
|
|
1288
1980
|
this.close();
|
|
1289
1981
|
}
|
|
1290
1982
|
};
|
|
1983
|
+
function imageBytesToBase64(bytes) {
|
|
1984
|
+
let binary = "";
|
|
1985
|
+
for (let offset = 0; offset < bytes.length; offset += 32768) {
|
|
1986
|
+
binary += String.fromCharCode(...bytes.subarray(offset, offset + 32768));
|
|
1987
|
+
}
|
|
1988
|
+
return globalThis.btoa(binary);
|
|
1989
|
+
}
|
|
1291
1990
|
function openDocxSession(bytes, wasmExports2, settings) {
|
|
1292
1991
|
const bridge = wasmExports2.DocxSessionBridge;
|
|
1293
1992
|
const handle = bridge.OpenSession(bytes, settings ? JSON.stringify(settings) : "");
|
|
@@ -1339,19 +2038,43 @@ function pxToPt(px) {
|
|
|
1339
2038
|
function ptToPx(pt) {
|
|
1340
2039
|
return pt / 0.75;
|
|
1341
2040
|
}
|
|
1342
|
-
function
|
|
1343
|
-
|
|
1344
|
-
const
|
|
1345
|
-
|
|
1346
|
-
|
|
1347
|
-
|
|
1348
|
-
const
|
|
1349
|
-
|
|
1350
|
-
|
|
1351
|
-
|
|
1352
|
-
const
|
|
1353
|
-
return
|
|
1354
|
-
|
|
2041
|
+
function finiteDataNumber(value, fallback) {
|
|
2042
|
+
if (value === void 0 || value.trim() === "") return fallback;
|
|
2043
|
+
const parsed = Number(value);
|
|
2044
|
+
return Number.isFinite(parsed) ? parsed : fallback;
|
|
2045
|
+
}
|
|
2046
|
+
function positiveDataNumber(value, fallback) {
|
|
2047
|
+
const parsed = finiteDataNumber(value, fallback);
|
|
2048
|
+
return parsed > 0 ? parsed : fallback;
|
|
2049
|
+
}
|
|
2050
|
+
function nonNegativeDataNumber(value, fallback) {
|
|
2051
|
+
const parsed = finiteDataNumber(value, fallback);
|
|
2052
|
+
return parsed >= 0 ? parsed : fallback;
|
|
2053
|
+
}
|
|
2054
|
+
function positiveFinite(value, fallback) {
|
|
2055
|
+
return Number.isFinite(value) && value > 0 ? value : fallback;
|
|
2056
|
+
}
|
|
2057
|
+
function parseSectionDimensions(section) {
|
|
2058
|
+
const pageWidth = positiveDataNumber(section.dataset.pageWidth, DEFAULT_PAGE_WIDTH);
|
|
2059
|
+
const pageHeight = positiveDataNumber(section.dataset.pageHeight, DEFAULT_PAGE_HEIGHT);
|
|
2060
|
+
const marginTop = finiteDataNumber(section.dataset.marginTop, DEFAULT_MARGIN);
|
|
2061
|
+
const marginRight = finiteDataNumber(section.dataset.marginRight, DEFAULT_MARGIN);
|
|
2062
|
+
const marginBottom = finiteDataNumber(section.dataset.marginBottom, DEFAULT_MARGIN);
|
|
2063
|
+
const marginLeft = finiteDataNumber(section.dataset.marginLeft, DEFAULT_MARGIN);
|
|
2064
|
+
const derivedContentWidth = positiveFinite(pageWidth - marginLeft - marginRight, pageWidth);
|
|
2065
|
+
const derivedContentHeight = positiveFinite(pageHeight - marginTop - marginBottom, pageHeight);
|
|
2066
|
+
const contentWidth = positiveDataNumber(section.dataset.contentWidth, derivedContentWidth);
|
|
2067
|
+
const contentHeight = positiveDataNumber(section.dataset.contentHeight, derivedContentHeight);
|
|
2068
|
+
const headerDistance = nonNegativeDataNumber(
|
|
2069
|
+
section.dataset.headerHeight,
|
|
2070
|
+
DEFAULT_HEADER_FOOTER_DISTANCE
|
|
2071
|
+
);
|
|
2072
|
+
const footerDistance = nonNegativeDataNumber(
|
|
2073
|
+
section.dataset.footerHeight,
|
|
2074
|
+
DEFAULT_HEADER_FOOTER_DISTANCE
|
|
2075
|
+
);
|
|
2076
|
+
return {
|
|
2077
|
+
pageWidth,
|
|
1355
2078
|
pageHeight,
|
|
1356
2079
|
contentWidth,
|
|
1357
2080
|
contentHeight,
|
|
@@ -1429,8 +2152,93 @@ function applyZoom(el, scale, naturalPt) {
|
|
|
1429
2152
|
function samePageBox(a, b) {
|
|
1430
2153
|
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;
|
|
1431
2154
|
}
|
|
2155
|
+
function createUnavailablePageMap(documentVersion, rendererFingerprint, mode = "continuous") {
|
|
2156
|
+
if (!rendererFingerprint) throw new Error("rendererFingerprint must be non-empty");
|
|
2157
|
+
return {
|
|
2158
|
+
schemaVersion: 1,
|
|
2159
|
+
mode,
|
|
2160
|
+
availability: "unavailable",
|
|
2161
|
+
documentVersion,
|
|
2162
|
+
rendererFingerprint,
|
|
2163
|
+
pages: [],
|
|
2164
|
+
fragments: []
|
|
2165
|
+
};
|
|
2166
|
+
}
|
|
2167
|
+
var activePageCitationHighlights = /* @__PURE__ */ new WeakMap();
|
|
2168
|
+
function clearPageCitationHighlight(root) {
|
|
2169
|
+
const active = activePageCitationHighlights.get(root);
|
|
2170
|
+
if (!active) return;
|
|
2171
|
+
if (active.highlightClass && active.addedClass) {
|
|
2172
|
+
active.target.classList.remove(active.highlightClass);
|
|
2173
|
+
}
|
|
2174
|
+
for (const style of active.inlineStyles ?? []) {
|
|
2175
|
+
if (style.value) {
|
|
2176
|
+
active.target.style.setProperty(style.name, style.value, style.priority);
|
|
2177
|
+
} else {
|
|
2178
|
+
active.target.style.removeProperty(style.name);
|
|
2179
|
+
}
|
|
2180
|
+
}
|
|
2181
|
+
activePageCitationHighlights.delete(root);
|
|
2182
|
+
}
|
|
2183
|
+
function navigateToPageCitation(root, citation, options = {}) {
|
|
2184
|
+
clearPageCitationHighlight(root);
|
|
2185
|
+
if (citation.availability !== "available" || citation.fragments.length === 0) {
|
|
2186
|
+
return { navigated: false, unavailableReason: "citation_unavailable" };
|
|
2187
|
+
}
|
|
2188
|
+
const byAttribute = (name, value, within = root) => {
|
|
2189
|
+
for (const node of Array.from(within.querySelectorAll(`[${name}]`))) {
|
|
2190
|
+
if (node.getAttribute(name) === value) return node;
|
|
2191
|
+
}
|
|
2192
|
+
return null;
|
|
2193
|
+
};
|
|
2194
|
+
const fragment = citation.fragments[0];
|
|
2195
|
+
let target = byAttribute("data-page-fragment-id", fragment.fragmentId);
|
|
2196
|
+
if (!target) {
|
|
2197
|
+
const page = byAttribute("data-page-number", String(fragment.pageNumber));
|
|
2198
|
+
if (page) target = byAttribute("data-source-anchor-id", citation.anchorId, page) ?? page;
|
|
2199
|
+
}
|
|
2200
|
+
if (!target) {
|
|
2201
|
+
return {
|
|
2202
|
+
navigated: false,
|
|
2203
|
+
pageNumber: fragment.pageNumber,
|
|
2204
|
+
fragmentId: fragment.fragmentId,
|
|
2205
|
+
unavailableReason: "fragment_not_found"
|
|
2206
|
+
};
|
|
2207
|
+
}
|
|
2208
|
+
if (options.highlightClass) {
|
|
2209
|
+
const addedClass = !target.classList.contains(options.highlightClass);
|
|
2210
|
+
target.classList.add(options.highlightClass);
|
|
2211
|
+
activePageCitationHighlights.set(root, {
|
|
2212
|
+
target,
|
|
2213
|
+
highlightClass: options.highlightClass,
|
|
2214
|
+
addedClass
|
|
2215
|
+
});
|
|
2216
|
+
} else if (options.highlight !== false) {
|
|
2217
|
+
const names = ["outline", "outline-offset", "background-color"];
|
|
2218
|
+
const inlineStyles = names.map((name) => ({
|
|
2219
|
+
name,
|
|
2220
|
+
value: target.style.getPropertyValue(name),
|
|
2221
|
+
priority: target.style.getPropertyPriority(name)
|
|
2222
|
+
}));
|
|
2223
|
+
target.style.setProperty("outline", "3px solid #f4b400", "important");
|
|
2224
|
+
target.style.setProperty("outline-offset", "2px", "important");
|
|
2225
|
+
target.style.setProperty("background-color", "rgba(255, 235, 59, .18)", "important");
|
|
2226
|
+
activePageCitationHighlights.set(root, { target, inlineStyles });
|
|
2227
|
+
}
|
|
2228
|
+
target.scrollIntoView({
|
|
2229
|
+
behavior: options.behavior ?? "smooth",
|
|
2230
|
+
block: options.block ?? "center"
|
|
2231
|
+
});
|
|
2232
|
+
return {
|
|
2233
|
+
navigated: true,
|
|
2234
|
+
target,
|
|
2235
|
+
pageNumber: fragment.pageNumber,
|
|
2236
|
+
fragmentId: fragment.fragmentId
|
|
2237
|
+
};
|
|
2238
|
+
}
|
|
1432
2239
|
var MAX_FOOTNOTE_AREA_RATIO = 0.6;
|
|
1433
|
-
var
|
|
2240
|
+
var FOOTNOTE_MEASUREMENT_GUARD_PT = 2;
|
|
2241
|
+
var FOOTNOTE_SOURCE_POSITION_ATTR = "data-pagination-footnote-source-position";
|
|
1434
2242
|
var PaginationEngine = class {
|
|
1435
2243
|
/**
|
|
1436
2244
|
* Creates a new pagination engine.
|
|
@@ -1440,24 +2248,46 @@ var PaginationEngine = class {
|
|
|
1440
2248
|
* @param options - Pagination options
|
|
1441
2249
|
*/
|
|
1442
2250
|
constructor(staging, container, options = {}) {
|
|
2251
|
+
this.createdPageCount = 0;
|
|
2252
|
+
this.footnoteSeparator = null;
|
|
2253
|
+
this.footnoteContinuationSeparator = null;
|
|
2254
|
+
this.footnoteLayoutLikeWord8 = false;
|
|
1443
2255
|
this.pendingFootnoteContinuation = null;
|
|
1444
2256
|
/** Per-section `w:pgNumType` (start / format), read off the section wrappers. */
|
|
1445
2257
|
this.pageNumbering = /* @__PURE__ */ new Map();
|
|
1446
|
-
this.
|
|
1447
|
-
this.
|
|
2258
|
+
this.lastPages = [];
|
|
2259
|
+
this.expectedPageMapAnchorIds = /* @__PURE__ */ new Set();
|
|
2260
|
+
this.state = "ready";
|
|
2261
|
+
const ownerDocument = typeof staging !== "string" ? staging.ownerDocument : typeof container !== "string" ? container.ownerDocument : globalThis.document;
|
|
2262
|
+
this.stagingElement = typeof staging === "string" ? ownerDocument.getElementById(staging) : staging;
|
|
2263
|
+
this.containerElement = typeof container === "string" ? ownerDocument.getElementById(container) : container;
|
|
1448
2264
|
if (!this.stagingElement) {
|
|
1449
2265
|
throw new Error("Staging element not found");
|
|
1450
2266
|
}
|
|
1451
2267
|
if (!this.containerElement) {
|
|
1452
2268
|
throw new Error("Container element not found");
|
|
1453
2269
|
}
|
|
2270
|
+
if (this.stagingElement.ownerDocument !== this.containerElement.ownerDocument) {
|
|
2271
|
+
throw new Error("Staging and container elements must belong to the same document");
|
|
2272
|
+
}
|
|
2273
|
+
const view = ownerDocument.defaultView;
|
|
2274
|
+
if (!view) {
|
|
2275
|
+
throw new Error("Pagination requires an attached document with a defaultView");
|
|
2276
|
+
}
|
|
2277
|
+
this.document = ownerDocument;
|
|
2278
|
+
this.view = view;
|
|
1454
2279
|
this.scale = options.scale ?? 1;
|
|
1455
2280
|
this.cssPrefix = options.cssPrefix ?? "page-";
|
|
1456
2281
|
this.showPageNumbers = options.showPageNumbers ?? true;
|
|
1457
2282
|
this.pageGap = options.pageGap ?? 20;
|
|
1458
2283
|
this.fragmentParagraphs = options.fragmentParagraphs ?? false;
|
|
2284
|
+
this.deferFragmentIdentities = options.deferFragmentIdentities ?? false;
|
|
2285
|
+
this.cancellationCheckpoint = options.checkCancellation;
|
|
2286
|
+
this.pageCountCheckpoint = options.checkPageCount;
|
|
2287
|
+
this.layoutToken = options.layoutToken;
|
|
1459
2288
|
this.hfRegistry = /* @__PURE__ */ new Map();
|
|
1460
2289
|
this.footnoteRegistry = /* @__PURE__ */ new Map();
|
|
2290
|
+
this.commentMarginRegistry = /* @__PURE__ */ new Map();
|
|
1461
2291
|
}
|
|
1462
2292
|
/**
|
|
1463
2293
|
* Runs the pagination process.
|
|
@@ -1465,58 +2295,469 @@ var PaginationEngine = class {
|
|
|
1465
2295
|
* @returns PaginationResult with page information
|
|
1466
2296
|
*/
|
|
1467
2297
|
paginate() {
|
|
1468
|
-
|
|
1469
|
-
|
|
1470
|
-
|
|
1471
|
-
|
|
1472
|
-
|
|
1473
|
-
"
|
|
1474
|
-
|
|
1475
|
-
|
|
1476
|
-
|
|
1477
|
-
|
|
1478
|
-
|
|
1479
|
-
|
|
1480
|
-
const
|
|
1481
|
-
|
|
1482
|
-
|
|
1483
|
-
|
|
1484
|
-
|
|
1485
|
-
|
|
1486
|
-
|
|
1487
|
-
|
|
2298
|
+
this.checkpoint();
|
|
2299
|
+
if (this.state !== "ready") {
|
|
2300
|
+
throw new Error(`PaginationEngine is one-shot and is already ${this.state}`);
|
|
2301
|
+
}
|
|
2302
|
+
if (!this.stagingElement.isConnected || !this.containerElement.isConnected) {
|
|
2303
|
+
throw new Error("Pagination requires staging and container elements attached to their document");
|
|
2304
|
+
}
|
|
2305
|
+
if (this.document.documentElement.getBoundingClientRect().width <= 0) {
|
|
2306
|
+
throw new Error("Pagination requires a browsing context with non-zero layout");
|
|
2307
|
+
}
|
|
2308
|
+
this.state = "running";
|
|
2309
|
+
try {
|
|
2310
|
+
const pages = [];
|
|
2311
|
+
let pageNumber = 1;
|
|
2312
|
+
this.hfRegistry = this.parseHeaderFooterRegistry();
|
|
2313
|
+
this.footnoteRegistry = this.parseFootnoteRegistry();
|
|
2314
|
+
({
|
|
2315
|
+
normal: this.footnoteSeparator,
|
|
2316
|
+
continuation: this.footnoteContinuationSeparator
|
|
2317
|
+
} = this.parseFootnoteSeparators());
|
|
2318
|
+
this.commentMarginRegistry = this.parseCommentMarginRegistry();
|
|
2319
|
+
this.footnoteLayoutLikeWord8 = this.stagingElement.dataset.footnoteLayoutLikeWord8 === "true";
|
|
2320
|
+
const sections = this.stagingElement.querySelectorAll(
|
|
2321
|
+
"[data-section-index]"
|
|
2322
|
+
);
|
|
2323
|
+
this.pageNumbering = this.parsePageNumbering(sections);
|
|
2324
|
+
const sectionsToProcess = sections.length > 0 ? Array.from(sections) : [this.stagingElement];
|
|
2325
|
+
this.expectedPageMapAnchorIds = /* @__PURE__ */ new Set();
|
|
2326
|
+
const referencedFootnoteIds = /* @__PURE__ */ new Set();
|
|
2327
|
+
const referencedCommentIds = /* @__PURE__ */ new Set();
|
|
2328
|
+
for (const section of sectionsToProcess) {
|
|
2329
|
+
this.checkpoint();
|
|
2330
|
+
this.collectExpectedSourceAnchors(section, this.expectedPageMapAnchorIds, true);
|
|
2331
|
+
for (const reference of Array.from(section.querySelectorAll("[data-footnote-id]"))) {
|
|
2332
|
+
this.checkpoint();
|
|
2333
|
+
if (reference.closest("#pagination-footnote-registry, #pagination-hf-registry")) continue;
|
|
2334
|
+
const id = reference.dataset.footnoteId;
|
|
2335
|
+
if (id) referencedFootnoteIds.add(id);
|
|
2336
|
+
}
|
|
2337
|
+
for (const reference of Array.from(section.querySelectorAll("[data-comment-id]"))) {
|
|
2338
|
+
this.checkpoint();
|
|
2339
|
+
if (reference.closest(
|
|
2340
|
+
"#pagination-comment-margin-registry, #pagination-footnote-registry, #pagination-hf-registry"
|
|
2341
|
+
)) continue;
|
|
2342
|
+
const id = reference.dataset.commentId;
|
|
2343
|
+
if (id) referencedCommentIds.add(id);
|
|
2344
|
+
}
|
|
2345
|
+
}
|
|
2346
|
+
for (const id of referencedFootnoteIds) {
|
|
2347
|
+
this.checkpoint();
|
|
2348
|
+
const source = this.footnoteRegistry.get(id);
|
|
2349
|
+
if (!source) continue;
|
|
2350
|
+
this.collectExpectedSourceAnchors(source, this.expectedPageMapAnchorIds);
|
|
2351
|
+
for (const reference of Array.from(
|
|
2352
|
+
source.querySelectorAll("[data-comment-id]")
|
|
2353
|
+
)) {
|
|
2354
|
+
this.checkpoint();
|
|
2355
|
+
const commentId = reference.dataset.commentId;
|
|
2356
|
+
if (commentId) referencedCommentIds.add(commentId);
|
|
2357
|
+
}
|
|
2358
|
+
}
|
|
2359
|
+
for (const id of referencedCommentIds) {
|
|
2360
|
+
this.checkpoint();
|
|
2361
|
+
const source = this.commentMarginRegistry.get(id);
|
|
2362
|
+
if (source) this.collectExpectedSourceAnchors(source, this.expectedPageMapAnchorIds);
|
|
2363
|
+
}
|
|
2364
|
+
const runs = [];
|
|
2365
|
+
for (const section of sectionsToProcess) {
|
|
2366
|
+
this.checkpoint();
|
|
2367
|
+
const dims = parseSectionDimensions(section);
|
|
2368
|
+
const previous = runs[runs.length - 1];
|
|
2369
|
+
if (previous && section.dataset.sectionType === "continuous" && samePageBox(previous.dims, dims)) {
|
|
2370
|
+
previous.sections.push(section);
|
|
2371
|
+
previous.sectionDimensions.set(
|
|
2372
|
+
parseInt(section.dataset.sectionIndex || "0", 10),
|
|
2373
|
+
dims
|
|
2374
|
+
);
|
|
2375
|
+
} else {
|
|
2376
|
+
const sectionIndex = parseInt(section.dataset.sectionIndex || "0", 10);
|
|
2377
|
+
runs.push({
|
|
2378
|
+
sections: [section],
|
|
2379
|
+
sectionIndex,
|
|
2380
|
+
sectionType: section.dataset.sectionType ?? "nextPage",
|
|
2381
|
+
dims,
|
|
2382
|
+
sectionDimensions: /* @__PURE__ */ new Map([[sectionIndex, dims]])
|
|
2383
|
+
});
|
|
2384
|
+
}
|
|
2385
|
+
}
|
|
2386
|
+
for (const run of runs) {
|
|
2387
|
+
this.checkpoint();
|
|
2388
|
+
const needsParityFiller = pages.length > 0 && (run.sectionType === "oddPage" && pageNumber % 2 === 0 || run.sectionType === "evenPage" && pageNumber % 2 === 1);
|
|
2389
|
+
if (needsParityFiller) {
|
|
2390
|
+
const precedingPage = pages[pages.length - 1];
|
|
2391
|
+
const precedingPageInSection = parseInt(
|
|
2392
|
+
precedingPage.element.dataset.pageInSection ?? "1",
|
|
2393
|
+
10
|
|
2394
|
+
);
|
|
2395
|
+
const precedingDisplayedPageNumber = parseInt(
|
|
2396
|
+
precedingPage.element.dataset.displayedPageNumber ?? String(precedingPage.pageNumber),
|
|
2397
|
+
10
|
|
2398
|
+
);
|
|
2399
|
+
const filler = this.createPage(
|
|
2400
|
+
precedingPage.dimensions,
|
|
2401
|
+
pageNumber,
|
|
2402
|
+
precedingPage.sectionIndex,
|
|
2403
|
+
precedingDisplayedPageNumber + 1,
|
|
2404
|
+
[],
|
|
2405
|
+
precedingPageInSection + 1,
|
|
2406
|
+
[],
|
|
2407
|
+
0,
|
|
2408
|
+
null,
|
|
2409
|
+
void 0,
|
|
2410
|
+
true
|
|
2411
|
+
);
|
|
2412
|
+
pages.push(filler);
|
|
2413
|
+
pageNumber++;
|
|
2414
|
+
}
|
|
2415
|
+
this.stagingElement.style.visibility = "hidden";
|
|
2416
|
+
this.stagingElement.style.position = "absolute";
|
|
2417
|
+
this.stagingElement.style.left = "-9999px";
|
|
2418
|
+
this.stagingElement.style.display = "block";
|
|
2419
|
+
const blocks = [];
|
|
2420
|
+
for (const section of run.sections) {
|
|
2421
|
+
this.checkpoint();
|
|
2422
|
+
const sectionIndex = parseInt(section.dataset.sectionIndex || "0", 10);
|
|
2423
|
+
const sectionDims = run.sectionDimensions.get(sectionIndex) ?? run.dims;
|
|
2424
|
+
section.style.width = `${sectionDims.contentWidth}pt`;
|
|
2425
|
+
const columnCount = parseInt(section.dataset.cols || "1", 10);
|
|
2426
|
+
if (columnCount > 1) {
|
|
2427
|
+
const gap = parseFloat(section.dataset.colGap || "");
|
|
2428
|
+
blocks.push(...this.buildColumnBlocks(
|
|
2429
|
+
section,
|
|
2430
|
+
sectionDims,
|
|
2431
|
+
sectionIndex,
|
|
2432
|
+
columnCount,
|
|
2433
|
+
Number.isFinite(gap) ? gap : 36
|
|
2434
|
+
));
|
|
2435
|
+
} else {
|
|
2436
|
+
blocks.push(...this.measureBlocks(section, sectionDims, sectionIndex));
|
|
2437
|
+
}
|
|
2438
|
+
}
|
|
2439
|
+
const sectionPages = this.flowToPages(
|
|
2440
|
+
blocks,
|
|
2441
|
+
run.dims,
|
|
2442
|
+
pageNumber,
|
|
2443
|
+
run.sectionIndex,
|
|
2444
|
+
run.sectionDimensions
|
|
2445
|
+
);
|
|
2446
|
+
this.checkpoint();
|
|
2447
|
+
pages.push(...sectionPages);
|
|
2448
|
+
pageNumber += sectionPages.length;
|
|
2449
|
+
}
|
|
2450
|
+
this.stagingElement.style.display = "none";
|
|
2451
|
+
this.substitutePageNumberFields(pages.length);
|
|
2452
|
+
for (const page of pages) {
|
|
2453
|
+
this.checkpoint();
|
|
2454
|
+
if (page.element.dataset.sectionFiller === "true") continue;
|
|
2455
|
+
const pageInSection = parseInt(page.element.dataset.pageInSection || "1", 10);
|
|
2456
|
+
const displayedPageNumber = parseInt(
|
|
2457
|
+
page.element.dataset.displayedPageNumber || String(page.pageNumber),
|
|
2458
|
+
10
|
|
2459
|
+
);
|
|
2460
|
+
const header = this.selectHeader(page.sectionIndex, pageInSection, displayedPageNumber);
|
|
2461
|
+
const footer = this.selectFooter(page.sectionIndex, pageInSection, displayedPageNumber);
|
|
2462
|
+
if (header) this.collectExpectedSourceAnchors(header, this.expectedPageMapAnchorIds);
|
|
2463
|
+
if (footer) this.collectExpectedSourceAnchors(footer, this.expectedPageMapAnchorIds);
|
|
2464
|
+
}
|
|
2465
|
+
this.qualifyPageFragments(pages);
|
|
2466
|
+
this.transferVisibleFragmentTargets();
|
|
2467
|
+
if (!this.deferFragmentIdentities) this.normalizeVisiblePageFragments(pages);
|
|
2468
|
+
this.lastPages = pages;
|
|
2469
|
+
const result = {
|
|
2470
|
+
totalPages: pages.length,
|
|
2471
|
+
pages,
|
|
2472
|
+
pageMap: this.layoutToken ? this.materializePageMap(
|
|
2473
|
+
this.layoutToken.documentVersion,
|
|
2474
|
+
this.layoutToken.rendererFingerprint
|
|
2475
|
+
) : void 0
|
|
2476
|
+
};
|
|
2477
|
+
this.state = "complete";
|
|
2478
|
+
return result;
|
|
2479
|
+
} catch (error) {
|
|
2480
|
+
this.state = "failed";
|
|
2481
|
+
throw error;
|
|
2482
|
+
}
|
|
2483
|
+
}
|
|
2484
|
+
checkpoint() {
|
|
2485
|
+
this.cancellationCheckpoint?.();
|
|
2486
|
+
}
|
|
2487
|
+
/**
|
|
2488
|
+
* Normalize visible fragment identities after a caller applies final standalone styles. This
|
|
2489
|
+
* deliberately runs before the stability barrier; materializePageMap is read-only so PageMap
|
|
2490
|
+
* measurement cannot mutate a tree after it was declared stable.
|
|
2491
|
+
*/
|
|
2492
|
+
normalizePageMapFragmentIdentities() {
|
|
2493
|
+
if (this.lastPages.length === 0) {
|
|
2494
|
+
throw new Error("paginate() must complete before fragment identities can be normalized");
|
|
2495
|
+
}
|
|
2496
|
+
this.normalizeVisiblePageFragments(this.lastPages);
|
|
2497
|
+
}
|
|
2498
|
+
/**
|
|
2499
|
+
* Materialize the last completed browser layout as portable page-relative point geometry.
|
|
2500
|
+
* The caller supplies both invalidation tokens; this engine never guesses a document version
|
|
2501
|
+
* or renderer fingerprint.
|
|
2502
|
+
*/
|
|
2503
|
+
materializePageMap(documentVersion, rendererFingerprint) {
|
|
2504
|
+
if (!Number.isSafeInteger(documentVersion) || documentVersion < 0) {
|
|
2505
|
+
throw new Error("documentVersion must be a non-negative safe integer");
|
|
2506
|
+
}
|
|
2507
|
+
if (!rendererFingerprint) throw new Error("rendererFingerprint must be non-empty");
|
|
2508
|
+
if (this.lastPages.length === 0) throw new Error("paginate() must complete before materializePageMap()");
|
|
2509
|
+
const pages = this.lastPages.map((page) => ({
|
|
2510
|
+
pageNumber: page.pageNumber,
|
|
2511
|
+
pageInSection: parseInt(page.element.dataset.pageInSection || "1", 10),
|
|
2512
|
+
width: page.dimensions.pageWidth,
|
|
2513
|
+
height: page.dimensions.pageHeight,
|
|
2514
|
+
sectionIndex: page.sectionIndex,
|
|
2515
|
+
pageName: `docxodus-section-${page.sectionIndex}`
|
|
2516
|
+
}));
|
|
2517
|
+
const fragments = [];
|
|
2518
|
+
const requiredAnchorIds = new Set(this.expectedPageMapAnchorIds);
|
|
2519
|
+
if (requiredAnchorIds.size === 0) {
|
|
2520
|
+
throw new Error("cannot publish an available PageMap without canonical source inventory");
|
|
2521
|
+
}
|
|
2522
|
+
const measuredAnchorIds = /* @__PURE__ */ new Set();
|
|
2523
|
+
const emittedFragmentCounts = /* @__PURE__ */ new Map();
|
|
2524
|
+
for (const page of this.lastPages) {
|
|
2525
|
+
this.checkpoint();
|
|
2526
|
+
const pageRect = page.element.getBoundingClientRect();
|
|
2527
|
+
if (pageRect.width <= 0 || pageRect.height <= 0) {
|
|
2528
|
+
throw new Error(`page ${page.pageNumber} has no measurable geometry`);
|
|
2529
|
+
}
|
|
2530
|
+
const pointPerRenderedX = page.dimensions.pageWidth / pageRect.width;
|
|
2531
|
+
const pointPerRenderedY = page.dimensions.pageHeight / pageRect.height;
|
|
2532
|
+
const nodes = page.element.querySelectorAll("[data-source-anchor-id]");
|
|
2533
|
+
for (const element of Array.from(nodes)) {
|
|
2534
|
+
this.checkpoint();
|
|
2535
|
+
if (this.isDeliberatelyUnrenderedSource(element, page.element)) continue;
|
|
2536
|
+
const anchorId = element.dataset.sourceAnchorId;
|
|
2537
|
+
if (!anchorId || !element.dataset.pageFragmentId || !Number.isInteger(parseInt(element.dataset.fragmentIndex || "", 10))) {
|
|
2538
|
+
throw new Error(`page ${page.pageNumber} contains an unqualified source anchor`);
|
|
2539
|
+
}
|
|
2540
|
+
const rect = element.getBoundingClientRect();
|
|
2541
|
+
const style = this.view.getComputedStyle(element);
|
|
2542
|
+
const deliberatelyHidden = style.display === "none" || style.visibility === "hidden";
|
|
2543
|
+
if (deliberatelyHidden) continue;
|
|
2544
|
+
requiredAnchorIds.add(anchorId);
|
|
2545
|
+
const visibleRect = this.intersectWithClippingAncestors(element, page.element, pageRect, rect);
|
|
2546
|
+
const left2 = visibleRect.left;
|
|
2547
|
+
const top2 = visibleRect.top;
|
|
2548
|
+
const right2 = visibleRect.right;
|
|
2549
|
+
const bottom2 = visibleRect.bottom;
|
|
2550
|
+
if (rect.width <= 0 || rect.height <= 0 || right2 <= left2 || bottom2 <= top2) {
|
|
2551
|
+
continue;
|
|
2552
|
+
}
|
|
2553
|
+
measuredAnchorIds.add(anchorId);
|
|
2554
|
+
const fragmentIndex = emittedFragmentCounts.get(anchorId) ?? 0;
|
|
2555
|
+
emittedFragmentCounts.set(anchorId, fragmentIndex + 1);
|
|
2556
|
+
const fragmentId = `p${page.pageNumber}-f${fragmentIndex}-${anchorId}`;
|
|
2557
|
+
if (element.dataset.fragmentIndex !== String(fragmentIndex) || element.dataset.pageFragmentId !== fragmentId || element.dataset.pageNumber !== String(page.pageNumber)) {
|
|
2558
|
+
throw new Error(
|
|
2559
|
+
`page ${page.pageNumber} fragment identity changed after final-tree normalization`
|
|
2560
|
+
);
|
|
2561
|
+
}
|
|
2562
|
+
fragments.push({
|
|
2563
|
+
fragmentId,
|
|
2564
|
+
anchorId,
|
|
2565
|
+
fragmentIndex,
|
|
2566
|
+
pageNumber: page.pageNumber,
|
|
2567
|
+
geometry: {
|
|
2568
|
+
x: (left2 - pageRect.left) * pointPerRenderedX,
|
|
2569
|
+
y: (top2 - pageRect.top) * pointPerRenderedY,
|
|
2570
|
+
width: (right2 - left2) * pointPerRenderedX,
|
|
2571
|
+
height: (bottom2 - top2) * pointPerRenderedY
|
|
2572
|
+
},
|
|
2573
|
+
story: this.storyForCanonicalAnchor(anchorId),
|
|
2574
|
+
inTableCell: element.matches("td,th") || element.closest("td,th") !== null
|
|
1488
2575
|
});
|
|
1489
2576
|
}
|
|
1490
2577
|
}
|
|
1491
|
-
|
|
1492
|
-
|
|
1493
|
-
|
|
1494
|
-
|
|
1495
|
-
|
|
1496
|
-
|
|
1497
|
-
|
|
1498
|
-
|
|
1499
|
-
|
|
1500
|
-
|
|
1501
|
-
|
|
1502
|
-
|
|
1503
|
-
|
|
1504
|
-
|
|
1505
|
-
|
|
1506
|
-
|
|
1507
|
-
|
|
1508
|
-
|
|
1509
|
-
|
|
1510
|
-
|
|
2578
|
+
const missingAnchor = Array.from(requiredAnchorIds).find((id) => !measuredAnchorIds.has(id));
|
|
2579
|
+
if (missingAnchor) {
|
|
2580
|
+
throw new Error(`source anchor ${missingAnchor} has no measurable fragment in the paginated layout`);
|
|
2581
|
+
}
|
|
2582
|
+
return {
|
|
2583
|
+
schemaVersion: 1,
|
|
2584
|
+
mode: "paginated",
|
|
2585
|
+
availability: "available",
|
|
2586
|
+
documentVersion,
|
|
2587
|
+
rendererFingerprint,
|
|
2588
|
+
pages,
|
|
2589
|
+
fragments
|
|
2590
|
+
};
|
|
2591
|
+
}
|
|
2592
|
+
normalizeVisiblePageFragments(pages) {
|
|
2593
|
+
const emittedFragmentCounts = /* @__PURE__ */ new Map();
|
|
2594
|
+
for (const page of pages) {
|
|
2595
|
+
this.checkpoint();
|
|
2596
|
+
const pageRect = page.element.getBoundingClientRect();
|
|
2597
|
+
for (const element of Array.from(
|
|
2598
|
+
page.element.querySelectorAll("[data-source-anchor-id]")
|
|
2599
|
+
)) {
|
|
2600
|
+
this.checkpoint();
|
|
2601
|
+
if (this.isDeliberatelyUnrenderedSource(element, page.element)) continue;
|
|
2602
|
+
const anchorId = element.dataset.sourceAnchorId;
|
|
2603
|
+
if (!anchorId) continue;
|
|
2604
|
+
const style = this.view.getComputedStyle(element);
|
|
2605
|
+
if (style.display === "none" || style.visibility === "hidden") continue;
|
|
2606
|
+
const rect = element.getBoundingClientRect();
|
|
2607
|
+
const visible = this.intersectWithClippingAncestors(element, page.element, pageRect, rect);
|
|
2608
|
+
if (rect.width <= 0 || rect.height <= 0 || visible.right <= visible.left || visible.bottom <= visible.top) continue;
|
|
2609
|
+
const fragmentIndex = emittedFragmentCounts.get(anchorId) ?? 0;
|
|
2610
|
+
emittedFragmentCounts.set(anchorId, fragmentIndex + 1);
|
|
2611
|
+
element.dataset.pageNumber = String(page.pageNumber);
|
|
2612
|
+
element.dataset.fragmentIndex = String(fragmentIndex);
|
|
2613
|
+
element.dataset.pageFragmentId = `p${page.pageNumber}-f${fragmentIndex}-${anchorId}`;
|
|
2614
|
+
}
|
|
2615
|
+
}
|
|
2616
|
+
}
|
|
2617
|
+
/**
|
|
2618
|
+
* Intersect an element with every ancestor that establishes an overflow clip before the page
|
|
2619
|
+
* root. getBoundingClientRect() reports layout outside those clips, which is not rendered and
|
|
2620
|
+
* therefore must not satisfy PageMap completeness or inflate portable geometry.
|
|
2621
|
+
*/
|
|
2622
|
+
intersectWithClippingAncestors(element, page, pageRect, rect) {
|
|
2623
|
+
let left2 = Math.max(rect.left, pageRect.left);
|
|
2624
|
+
let top2 = Math.max(rect.top, pageRect.top);
|
|
2625
|
+
let right2 = Math.min(rect.right, pageRect.right);
|
|
2626
|
+
let bottom2 = Math.min(rect.bottom, pageRect.bottom);
|
|
2627
|
+
const clips = (value) => value === "hidden" || value === "clip" || value === "scroll" || value === "auto";
|
|
2628
|
+
for (let ancestor = element.parentElement; ancestor && ancestor !== page; ancestor = ancestor.parentElement) {
|
|
2629
|
+
const style = this.view.getComputedStyle(ancestor);
|
|
2630
|
+
const clipsX = clips(style.overflowX);
|
|
2631
|
+
const clipsY = clips(style.overflowY);
|
|
2632
|
+
if (!clipsX && !clipsY) continue;
|
|
2633
|
+
const ancestorRect = ancestor.getBoundingClientRect();
|
|
2634
|
+
if (clipsX) {
|
|
2635
|
+
left2 = Math.max(left2, ancestorRect.left);
|
|
2636
|
+
right2 = Math.min(right2, ancestorRect.right);
|
|
2637
|
+
}
|
|
2638
|
+
if (clipsY) {
|
|
2639
|
+
top2 = Math.max(top2, ancestorRect.top);
|
|
2640
|
+
bottom2 = Math.min(bottom2, ancestorRect.bottom);
|
|
2641
|
+
}
|
|
2642
|
+
}
|
|
2643
|
+
return { left: left2, top: top2, right: right2, bottom: bottom2 };
|
|
2644
|
+
}
|
|
2645
|
+
storyForCanonicalAnchor(anchorId) {
|
|
2646
|
+
const first = anchorId.indexOf(":");
|
|
2647
|
+
const second = first < 0 ? -1 : anchorId.indexOf(":", first + 1);
|
|
2648
|
+
const scope = first >= 0 && second > first ? anchorId.slice(first + 1, second) : "body";
|
|
2649
|
+
if (scope.startsWith("hdr")) return "header";
|
|
2650
|
+
if (scope.startsWith("ftr")) return "footer";
|
|
2651
|
+
if (scope === "fn") return "footnote";
|
|
2652
|
+
if (scope === "en") return "endnote";
|
|
2653
|
+
if (scope === "cmt") return "comment";
|
|
2654
|
+
return "body";
|
|
2655
|
+
}
|
|
2656
|
+
/**
|
|
2657
|
+
* Add canonical IDs from an addressable source subtree to the pre-pagination inventory.
|
|
2658
|
+
* Registry wrappers are excluded when scanning staging because selectable registry contents are
|
|
2659
|
+
* inventoried separately. Producers may explicitly mark content that has no visual substrate
|
|
2660
|
+
* with `data-page-map-exclude="true"`; native hidden semantics carry the same signal.
|
|
2661
|
+
*/
|
|
2662
|
+
collectExpectedSourceAnchors(source, destination, excludeRegistries = false) {
|
|
2663
|
+
const candidates = source.matches("[data-source-anchor-id]") ? [source] : [];
|
|
2664
|
+
candidates.push(...Array.from(source.querySelectorAll("[data-source-anchor-id]")));
|
|
2665
|
+
for (const element of candidates) {
|
|
2666
|
+
if (excludeRegistries && element.closest(
|
|
2667
|
+
"#pagination-hf-registry, #pagination-footnote-registry, #pagination-comment-margin-registry"
|
|
2668
|
+
)) {
|
|
2669
|
+
continue;
|
|
2670
|
+
}
|
|
2671
|
+
if (this.isZeroHeightExplicitBreakCarrier(element)) {
|
|
2672
|
+
element.dataset.pageMapExclude = "true";
|
|
2673
|
+
}
|
|
2674
|
+
if (this.isDeliberatelyUnrenderedSource(element, source)) continue;
|
|
2675
|
+
const anchorId = element.dataset.sourceAnchorId;
|
|
2676
|
+
if (anchorId) destination.add(anchorId);
|
|
2677
|
+
}
|
|
2678
|
+
}
|
|
2679
|
+
isZeroHeightExplicitBreakCarrier(element) {
|
|
2680
|
+
const next = element.nextElementSibling;
|
|
2681
|
+
const bounds = element.getBoundingClientRect();
|
|
2682
|
+
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);
|
|
2683
|
+
}
|
|
2684
|
+
isDeliberatelyUnrenderedSource(element, sourceRoot) {
|
|
2685
|
+
for (let current = element; current; current = current.parentElement) {
|
|
2686
|
+
const zeroHeightExplicitBreakCarrier = current === element && this.isZeroHeightExplicitBreakCarrier(current);
|
|
2687
|
+
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") {
|
|
2688
|
+
return true;
|
|
2689
|
+
}
|
|
2690
|
+
if (current === sourceRoot) break;
|
|
2691
|
+
}
|
|
2692
|
+
return false;
|
|
2693
|
+
}
|
|
2694
|
+
/**
|
|
2695
|
+
* Keep exactly one active bare-Unid editor anchor per source block. Presentation clones use
|
|
2696
|
+
* canonical source identity plus page/fragment qualification instead.
|
|
2697
|
+
*/
|
|
2698
|
+
qualifyPageFragments(pages) {
|
|
2699
|
+
const fragmentCounts = /* @__PURE__ */ new Map();
|
|
2700
|
+
const activeCanonicalIds = /* @__PURE__ */ new Set();
|
|
2701
|
+
const activeBareAnchorIds = /* @__PURE__ */ new Set();
|
|
2702
|
+
const makeInactive = (element) => {
|
|
2703
|
+
element.removeAttribute("data-anchor");
|
|
2704
|
+
element.removeAttribute("data-committed-text");
|
|
2705
|
+
if (element.hasAttribute("contenteditable")) element.setAttribute("contenteditable", "false");
|
|
2706
|
+
};
|
|
2707
|
+
for (const page of pages) {
|
|
2708
|
+
const nodes = page.element.querySelectorAll("[data-source-anchor-id]");
|
|
2709
|
+
for (const element of Array.from(nodes)) {
|
|
2710
|
+
const anchorId = element.dataset.sourceAnchorId;
|
|
2711
|
+
if (!anchorId) continue;
|
|
2712
|
+
const fragmentIndex = fragmentCounts.get(anchorId) ?? 0;
|
|
2713
|
+
fragmentCounts.set(anchorId, fragmentIndex + 1);
|
|
2714
|
+
element.dataset.pageNumber = String(page.pageNumber);
|
|
2715
|
+
element.dataset.fragmentIndex = String(fragmentIndex);
|
|
2716
|
+
element.dataset.pageFragmentId = `p${page.pageNumber}-f${fragmentIndex}-${anchorId}`;
|
|
2717
|
+
const story = this.storyForCanonicalAnchor(anchorId);
|
|
2718
|
+
const mayOwnActiveEditorAnchor = story === "body" || story === "comment" || story === "footnote" || story === "endnote";
|
|
2719
|
+
if (element.hasAttribute("data-anchor")) {
|
|
2720
|
+
const bareAnchorId = element.dataset.anchor;
|
|
2721
|
+
if (mayOwnActiveEditorAnchor && !activeCanonicalIds.has(anchorId) && !activeBareAnchorIds.has(bareAnchorId)) {
|
|
2722
|
+
activeCanonicalIds.add(anchorId);
|
|
2723
|
+
activeBareAnchorIds.add(bareAnchorId);
|
|
2724
|
+
} else {
|
|
2725
|
+
makeInactive(element);
|
|
2726
|
+
}
|
|
1511
2727
|
}
|
|
1512
2728
|
}
|
|
1513
|
-
const sectionPages = this.flowToPages(blocks, run.dims, pageNumber, run.sectionIndex);
|
|
1514
|
-
pages.push(...sectionPages);
|
|
1515
|
-
pageNumber += sectionPages.length;
|
|
1516
2729
|
}
|
|
1517
|
-
|
|
1518
|
-
|
|
1519
|
-
|
|
2730
|
+
const activeStagingCanonicalIds = /* @__PURE__ */ new Set();
|
|
2731
|
+
for (const element of Array.from(
|
|
2732
|
+
this.stagingElement.querySelectorAll("[data-source-anchor-id][data-anchor]")
|
|
2733
|
+
)) {
|
|
2734
|
+
const anchorId = element.dataset.sourceAnchorId;
|
|
2735
|
+
if (!anchorId) continue;
|
|
2736
|
+
const bareAnchorId = element.dataset.anchor;
|
|
2737
|
+
if (activeCanonicalIds.has(anchorId) || activeStagingCanonicalIds.has(anchorId) || activeBareAnchorIds.has(bareAnchorId)) {
|
|
2738
|
+
makeInactive(element);
|
|
2739
|
+
} else {
|
|
2740
|
+
activeStagingCanonicalIds.add(anchorId);
|
|
2741
|
+
activeBareAnchorIds.add(bareAnchorId);
|
|
2742
|
+
}
|
|
2743
|
+
}
|
|
2744
|
+
}
|
|
2745
|
+
/**
|
|
2746
|
+
* Page flow clones source blocks while the hidden staging tree stays in the document. Any HTML
|
|
2747
|
+
* fragment target copied into a visible page would therefore resolve to its earlier hidden
|
|
2748
|
+
* source. Transfer target ownership to the page presentation after flow is complete; registry
|
|
2749
|
+
* and wrapper IDs that have no visible counterpart remain available to pagination internals.
|
|
2750
|
+
*/
|
|
2751
|
+
transferVisibleFragmentTargets() {
|
|
2752
|
+
const visibleIds = new Set(Array.from(
|
|
2753
|
+
this.containerElement.querySelectorAll("[id]")
|
|
2754
|
+
).map((element) => element.id).filter(Boolean));
|
|
2755
|
+
if (visibleIds.size === 0) return;
|
|
2756
|
+
for (const source of Array.from(
|
|
2757
|
+
this.stagingElement.querySelectorAll("[id]")
|
|
2758
|
+
)) {
|
|
2759
|
+
if (visibleIds.has(source.id)) source.removeAttribute("id");
|
|
2760
|
+
}
|
|
1520
2761
|
}
|
|
1521
2762
|
/** Read each section's `w:pgNumType` off its wrapper (see {@link SectionPageNumbering}). */
|
|
1522
2763
|
parsePageNumbering(sections) {
|
|
@@ -1560,9 +2801,8 @@ var PaginationEngine = class {
|
|
|
1560
2801
|
if (markers.length === 0) continue;
|
|
1561
2802
|
const sectionIndex = parseInt(box.dataset.sectionIndex || "0", 10);
|
|
1562
2803
|
const pageNumber = parseInt(box.dataset.pageNumber || "1", 10);
|
|
1563
|
-
const pageInSection = parseInt(box.dataset.pageInSection || "1", 10);
|
|
1564
2804
|
const numbering = this.pageNumbering.get(sectionIndex) ?? {};
|
|
1565
|
-
const displayed =
|
|
2805
|
+
const displayed = parseInt(box.dataset.displayedPageNumber || String(pageNumber), 10);
|
|
1566
2806
|
for (const marker of Array.from(markers)) {
|
|
1567
2807
|
const kind = marker.dataset.field;
|
|
1568
2808
|
if (kind !== "PAGE" && kind !== "NUMPAGES") continue;
|
|
@@ -1574,17 +2814,26 @@ var PaginationEngine = class {
|
|
|
1574
2814
|
/**
|
|
1575
2815
|
* Measures all content blocks in a section.
|
|
1576
2816
|
*/
|
|
1577
|
-
measureBlocks(section, dims) {
|
|
2817
|
+
measureBlocks(section, dims, sectionIndex) {
|
|
1578
2818
|
const blocks = [];
|
|
1579
2819
|
const children = Array.from(section.children);
|
|
1580
2820
|
for (const child of children) {
|
|
2821
|
+
this.checkpoint();
|
|
1581
2822
|
if (child.dataset.sectionIndex !== void 0) {
|
|
1582
|
-
const
|
|
2823
|
+
const nestedSectionIndex = parseInt(child.dataset.sectionIndex || String(sectionIndex), 10);
|
|
2824
|
+
const nestedBlocks = this.measureBlocks(child, dims, nestedSectionIndex);
|
|
1583
2825
|
blocks.push(...nestedBlocks);
|
|
1584
2826
|
continue;
|
|
1585
2827
|
}
|
|
2828
|
+
if (child.matches("section.endnotes")) {
|
|
2829
|
+
const endnoteBlocks = this.measureSafeEndnoteBlocks(child, dims, sectionIndex);
|
|
2830
|
+
if (endnoteBlocks) {
|
|
2831
|
+
blocks.push(...endnoteBlocks);
|
|
2832
|
+
continue;
|
|
2833
|
+
}
|
|
2834
|
+
}
|
|
1586
2835
|
const rect = child.getBoundingClientRect();
|
|
1587
|
-
const style =
|
|
2836
|
+
const style = this.view.getComputedStyle(child);
|
|
1588
2837
|
const marginTopPx = parseFloat(style.marginTop) || 0;
|
|
1589
2838
|
const marginBottomPx = parseFloat(style.marginBottom) || 0;
|
|
1590
2839
|
const heightPt = pxToPt(rect.height);
|
|
@@ -1593,6 +2842,7 @@ var PaginationEngine = class {
|
|
|
1593
2842
|
const isPageBreak = child.dataset.pageBreak === "true" || child.classList.contains(`${this.cssPrefix}break`);
|
|
1594
2843
|
blocks.push({
|
|
1595
2844
|
element: child,
|
|
2845
|
+
sectionIndex,
|
|
1596
2846
|
heightPt,
|
|
1597
2847
|
marginTopPt,
|
|
1598
2848
|
marginBottomPt,
|
|
@@ -1605,6 +2855,95 @@ var PaginationEngine = class {
|
|
|
1605
2855
|
}
|
|
1606
2856
|
return blocks;
|
|
1607
2857
|
}
|
|
2858
|
+
/**
|
|
2859
|
+
* Flatten the converter's `section.endnotes > ol > li > p` presentation into
|
|
2860
|
+
* ordinary paragraph blocks. This preserves paragraph formatting and canonical
|
|
2861
|
+
* p:en/en:en identities while allowing the existing paragraph fragmenter to
|
|
2862
|
+
* split a long endnote across page boundaries.
|
|
2863
|
+
*/
|
|
2864
|
+
measureSafeEndnoteBlocks(section, dims, sectionIndex) {
|
|
2865
|
+
const sectionChildren = Array.from(section.children);
|
|
2866
|
+
const list = sectionChildren.find((child) => child.tagName === "OL");
|
|
2867
|
+
if (!list || sectionChildren.some((child) => child.tagName !== "HR" && child !== list) || Array.from(list.children).some((child) => child.tagName !== "LI")) {
|
|
2868
|
+
return null;
|
|
2869
|
+
}
|
|
2870
|
+
const items = Array.from(list.children);
|
|
2871
|
+
if (items.length === 0 || items.some(
|
|
2872
|
+
(item) => item.children.length === 0 || Array.from(item.children).some((child) => child.tagName !== "P")
|
|
2873
|
+
)) {
|
|
2874
|
+
return null;
|
|
2875
|
+
}
|
|
2876
|
+
const blocks = [];
|
|
2877
|
+
const sectionStyle = this.view.getComputedStyle(section);
|
|
2878
|
+
for (const rule of sectionChildren.filter((child) => child.tagName === "HR")) {
|
|
2879
|
+
this.checkpoint();
|
|
2880
|
+
const clonedRule = rule.cloneNode(true);
|
|
2881
|
+
if (blocks.length === 0) clonedRule.style.marginTop = sectionStyle.marginTop;
|
|
2882
|
+
blocks.push(this.measureElement(clonedRule, dims, sectionIndex));
|
|
2883
|
+
}
|
|
2884
|
+
const listStyle = this.view.getComputedStyle(list);
|
|
2885
|
+
for (let itemIndex = 0; itemIndex < items.length; itemIndex++) {
|
|
2886
|
+
this.checkpoint();
|
|
2887
|
+
const item = items[itemIndex];
|
|
2888
|
+
const ownerAnchorId = item.dataset.sourceAnchorId;
|
|
2889
|
+
const paragraphs = Array.from(item.children);
|
|
2890
|
+
for (let paragraphIndex = 0; paragraphIndex < paragraphs.length; paragraphIndex++) {
|
|
2891
|
+
this.checkpoint();
|
|
2892
|
+
const paragraph = paragraphs[paragraphIndex];
|
|
2893
|
+
if (!this.hasRangeFragmentSafeLayout(paragraph)) {
|
|
2894
|
+
return null;
|
|
2895
|
+
}
|
|
2896
|
+
const clone = paragraph.cloneNode(true);
|
|
2897
|
+
clone.dataset.paginationSafeEndnote = "true";
|
|
2898
|
+
clone.style.fontSize ||= sectionStyle.fontSize;
|
|
2899
|
+
clone.style.lineHeight ||= sectionStyle.lineHeight;
|
|
2900
|
+
clone.style.paddingLeft ||= listStyle.paddingLeft;
|
|
2901
|
+
if (ownerAnchorId && !Array.from(
|
|
2902
|
+
clone.querySelectorAll("[data-source-anchor-id]")
|
|
2903
|
+
).some((node) => node.dataset.sourceAnchorId === ownerAnchorId)) {
|
|
2904
|
+
const owner = this.document.createElement("span");
|
|
2905
|
+
owner.dataset.sourceAnchorId = ownerAnchorId;
|
|
2906
|
+
while (clone.firstChild) owner.appendChild(clone.firstChild);
|
|
2907
|
+
clone.appendChild(owner);
|
|
2908
|
+
}
|
|
2909
|
+
if (paragraphIndex === 0) {
|
|
2910
|
+
const itemId = item.id;
|
|
2911
|
+
if (itemId) {
|
|
2912
|
+
if (clone.id && clone.id !== itemId) return null;
|
|
2913
|
+
clone.id = itemId;
|
|
2914
|
+
}
|
|
2915
|
+
const value = parseInt(item.getAttribute("value") || String(itemIndex + 1), 10);
|
|
2916
|
+
const marker = this.formatOrderedListMarker(
|
|
2917
|
+
Number.isFinite(value) ? value : itemIndex + 1,
|
|
2918
|
+
listStyle.listStyleType
|
|
2919
|
+
);
|
|
2920
|
+
clone.insertBefore(this.document.createTextNode(`${marker}. `), clone.firstChild);
|
|
2921
|
+
}
|
|
2922
|
+
blocks.push(this.measureElement(clone, dims, sectionIndex));
|
|
2923
|
+
}
|
|
2924
|
+
}
|
|
2925
|
+
return blocks;
|
|
2926
|
+
}
|
|
2927
|
+
/** Render the CSS ordered-list formats emitted by the converter after an endnote is flattened. */
|
|
2928
|
+
formatOrderedListMarker(value, listStyleType) {
|
|
2929
|
+
const format = (() => {
|
|
2930
|
+
switch (listStyleType) {
|
|
2931
|
+
case "lower-roman":
|
|
2932
|
+
return "lowerRoman";
|
|
2933
|
+
case "upper-roman":
|
|
2934
|
+
return "upperRoman";
|
|
2935
|
+
case "lower-alpha":
|
|
2936
|
+
case "lower-latin":
|
|
2937
|
+
return "lowerLetter";
|
|
2938
|
+
case "upper-alpha":
|
|
2939
|
+
case "upper-latin":
|
|
2940
|
+
return "upperLetter";
|
|
2941
|
+
default:
|
|
2942
|
+
return "decimal";
|
|
2943
|
+
}
|
|
2944
|
+
})();
|
|
2945
|
+
return formatPageNumber(value, format);
|
|
2946
|
+
}
|
|
1608
2947
|
/**
|
|
1609
2948
|
* Flows a multi-column (`w:cols`) section's children into CSS-multicol container
|
|
1610
2949
|
* blocks. Word lays such a section out as N columns inside the same body extent;
|
|
@@ -1622,7 +2961,7 @@ var PaginationEngine = class {
|
|
|
1622
2961
|
const maxFragmentHeight = this.smallestEffectiveContentHeight(dims, sectionIndex);
|
|
1623
2962
|
const isBreak = (child) => child.dataset.pageBreak === "true" || child.classList.contains(`${this.cssPrefix}break`);
|
|
1624
2963
|
const makeContainer = (slice) => {
|
|
1625
|
-
const container = document.createElement("div");
|
|
2964
|
+
const container = this.document.createElement("div");
|
|
1626
2965
|
container.style.columnCount = String(columnCount);
|
|
1627
2966
|
container.style.columnGap = `${columnGapPt}pt`;
|
|
1628
2967
|
for (const child of slice) {
|
|
@@ -1632,17 +2971,19 @@ var PaginationEngine = class {
|
|
|
1632
2971
|
};
|
|
1633
2972
|
let start2 = 0;
|
|
1634
2973
|
while (start2 < children.length) {
|
|
2974
|
+
this.checkpoint();
|
|
1635
2975
|
if (isBreak(children[start2])) {
|
|
1636
|
-
blocks.push(this.measureElement(children[start2], dims));
|
|
2976
|
+
blocks.push(this.measureElement(children[start2], dims, sectionIndex));
|
|
1637
2977
|
start2++;
|
|
1638
2978
|
continue;
|
|
1639
2979
|
}
|
|
1640
2980
|
let end = start2 + 1;
|
|
1641
2981
|
let container = makeContainer(children.slice(start2, end));
|
|
1642
|
-
let measured = this.measureElement(container, dims);
|
|
2982
|
+
let measured = this.measureElement(container, dims, sectionIndex);
|
|
1643
2983
|
while (end < children.length && !isBreak(children[end])) {
|
|
2984
|
+
this.checkpoint();
|
|
1644
2985
|
const candidate = makeContainer(children.slice(start2, end + 1));
|
|
1645
|
-
const candidateMeasured = this.measureElement(candidate, dims);
|
|
2986
|
+
const candidateMeasured = this.measureElement(candidate, dims, sectionIndex);
|
|
1646
2987
|
if (candidateMeasured.heightPt > maxFragmentHeight) break;
|
|
1647
2988
|
container = candidate;
|
|
1648
2989
|
measured = candidateMeasured;
|
|
@@ -1650,6 +2991,7 @@ var PaginationEngine = class {
|
|
|
1650
2991
|
}
|
|
1651
2992
|
blocks.push({
|
|
1652
2993
|
element: container,
|
|
2994
|
+
sectionIndex,
|
|
1653
2995
|
heightPt: measured.heightPt,
|
|
1654
2996
|
marginTopPt: measured.marginTopPt,
|
|
1655
2997
|
marginBottomPt: measured.marginBottomPt,
|
|
@@ -1668,8 +3010,8 @@ var PaginationEngine = class {
|
|
|
1668
3010
|
* This is intentionally DOM-based: table row heights cannot be inferred from individual
|
|
1669
3011
|
* rows because wrapping and collapsed borders change the height of a fragment.
|
|
1670
3012
|
*/
|
|
1671
|
-
measureElement(element, dims) {
|
|
1672
|
-
const measurementHost = document.createElement("div");
|
|
3013
|
+
measureElement(element, dims, sectionIndex) {
|
|
3014
|
+
const measurementHost = this.document.createElement("div");
|
|
1673
3015
|
measurementHost.style.position = "absolute";
|
|
1674
3016
|
measurementHost.style.visibility = "hidden";
|
|
1675
3017
|
measurementHost.style.left = "-9999px";
|
|
@@ -1678,9 +3020,10 @@ var PaginationEngine = class {
|
|
|
1678
3020
|
measurementHost.appendChild(measuredElement);
|
|
1679
3021
|
this.stagingElement.appendChild(measurementHost);
|
|
1680
3022
|
const rect = measuredElement.getBoundingClientRect();
|
|
1681
|
-
const style =
|
|
3023
|
+
const style = this.view.getComputedStyle(measuredElement);
|
|
1682
3024
|
const measured = {
|
|
1683
3025
|
element,
|
|
3026
|
+
sectionIndex,
|
|
1684
3027
|
heightPt: pxToPt(rect.height),
|
|
1685
3028
|
marginTopPt: pxToPt(parseFloat(style.marginTop) || 0),
|
|
1686
3029
|
marginBottomPt: pxToPt(parseFloat(style.marginBottom) || 0),
|
|
@@ -1774,6 +3117,7 @@ var PaginationEngine = class {
|
|
|
1774
3117
|
const knownIds = new Set(existingFootnoteIds);
|
|
1775
3118
|
const newIds = [];
|
|
1776
3119
|
for (const block of blocks) {
|
|
3120
|
+
this.checkpoint();
|
|
1777
3121
|
for (const id of this.extractFootnoteRefs(block.element)) {
|
|
1778
3122
|
if (!knownIds.has(id)) {
|
|
1779
3123
|
knownIds.add(id);
|
|
@@ -1791,8 +3135,8 @@ var PaginationEngine = class {
|
|
|
1791
3135
|
smallestEffectiveContentHeight(dims, sectionIndex) {
|
|
1792
3136
|
return Math.min(
|
|
1793
3137
|
this.getPageBands(dims, sectionIndex, 1, 1).bodyHeight,
|
|
1794
|
-
this.getPageBands(dims, sectionIndex, 2,
|
|
1795
|
-
this.getPageBands(dims, sectionIndex,
|
|
3138
|
+
this.getPageBands(dims, sectionIndex, 2, 2).bodyHeight,
|
|
3139
|
+
this.getPageBands(dims, sectionIndex, 3, 3).bodyHeight
|
|
1796
3140
|
);
|
|
1797
3141
|
}
|
|
1798
3142
|
/**
|
|
@@ -1830,10 +3174,11 @@ var PaginationEngine = class {
|
|
|
1830
3174
|
if (wrapper.tagName !== "DIV" || wrapper.children.length !== 1 || block.keepWithNext || block.keepLines || block.pageBreakBefore || block.isPageBreak) {
|
|
1831
3175
|
return null;
|
|
1832
3176
|
}
|
|
1833
|
-
const
|
|
1834
|
-
if (!
|
|
3177
|
+
const tableElement = wrapper.firstElementChild;
|
|
3178
|
+
if (!tableElement || tableElement.localName !== "table") {
|
|
1835
3179
|
return null;
|
|
1836
3180
|
}
|
|
3181
|
+
const table = tableElement;
|
|
1837
3182
|
const body = table.tBodies.length === 1 ? table.tBodies[0] : null;
|
|
1838
3183
|
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]")) {
|
|
1839
3184
|
return null;
|
|
@@ -1847,8 +3192,10 @@ var PaginationEngine = class {
|
|
|
1847
3192
|
const groups = [];
|
|
1848
3193
|
let start2 = 0;
|
|
1849
3194
|
while (start2 < rows.length) {
|
|
3195
|
+
this.checkpoint();
|
|
1850
3196
|
let end = start2;
|
|
1851
3197
|
while (end < rows.length) {
|
|
3198
|
+
this.checkpoint();
|
|
1852
3199
|
const candidate = this.createSimpleTableFragment(
|
|
1853
3200
|
wrapper,
|
|
1854
3201
|
table,
|
|
@@ -1856,7 +3203,7 @@ var PaginationEngine = class {
|
|
|
1856
3203
|
rows.slice(start2, end + 1),
|
|
1857
3204
|
start2 === 0
|
|
1858
3205
|
);
|
|
1859
|
-
const measured = this.measureElement(candidate, dims);
|
|
3206
|
+
const measured = this.measureElement(candidate, dims, block.sectionIndex);
|
|
1860
3207
|
if (measured.heightPt > maximumFragmentHeight) {
|
|
1861
3208
|
break;
|
|
1862
3209
|
}
|
|
@@ -1888,7 +3235,7 @@ var PaginationEngine = class {
|
|
|
1888
3235
|
if (!isLast) {
|
|
1889
3236
|
fragment.style.setProperty("margin-bottom", "0", "important");
|
|
1890
3237
|
}
|
|
1891
|
-
const measured = this.measureElement(fragment, dims);
|
|
3238
|
+
const measured = this.measureElement(fragment, dims, block.sectionIndex);
|
|
1892
3239
|
if (measured.heightPt + measured.marginTopPt + measured.marginBottomPt > minimumContentHeight) {
|
|
1893
3240
|
return null;
|
|
1894
3241
|
}
|
|
@@ -1903,35 +3250,198 @@ var PaginationEngine = class {
|
|
|
1903
3250
|
return fragments;
|
|
1904
3251
|
}
|
|
1905
3252
|
/**
|
|
1906
|
-
*
|
|
1907
|
-
*
|
|
1908
|
-
*
|
|
1909
|
-
|
|
1910
|
-
|
|
1911
|
-
|
|
1912
|
-
|
|
3253
|
+
* DOM endpoints that can finish a paragraph fragment. The flattened UTF-16
|
|
3254
|
+
* offsets are checked against the browser's Unicode grapheme segmenter, so a
|
|
3255
|
+
* formatting-run boundary can never bisect a surrogate pair, combining
|
|
3256
|
+
* sequence, or joined emoji. NBSP/word-joiner boundaries remain indivisible.
|
|
3257
|
+
* PAGE/NUMPAGES field results are atomic because splitting their marker would
|
|
3258
|
+
* make later substitution duplicate or replace only half of the field.
|
|
3259
|
+
*/
|
|
3260
|
+
paragraphFragmentEndpoints(paragraph, emergencyGraphemeBreaks = false) {
|
|
3261
|
+
const textNodes = [];
|
|
3262
|
+
const textStarts = /* @__PURE__ */ new Map();
|
|
3263
|
+
const lastTextByField = /* @__PURE__ */ new Map();
|
|
3264
|
+
const walker = this.document.createTreeWalker(paragraph, this.view.NodeFilter.SHOW_TEXT);
|
|
3265
|
+
const flattenedChunks = [];
|
|
3266
|
+
let flattenedLength = 0;
|
|
1913
3267
|
let textNode;
|
|
1914
3268
|
while (textNode = walker.nextNode()) {
|
|
1915
|
-
|
|
1916
|
-
|
|
1917
|
-
|
|
3269
|
+
this.checkpoint();
|
|
3270
|
+
textStarts.set(textNode, flattenedLength);
|
|
3271
|
+
textNodes.push(textNode);
|
|
3272
|
+
flattenedChunks.push(textNode.data);
|
|
3273
|
+
flattenedLength += textNode.data.length;
|
|
3274
|
+
let field = textNode.parentElement?.closest("[data-field]") ?? null;
|
|
3275
|
+
while (field?.parentElement?.closest("[data-field]")) {
|
|
3276
|
+
field = field.parentElement.closest("[data-field]");
|
|
3277
|
+
}
|
|
3278
|
+
if (field && paragraph.contains(field)) lastTextByField.set(field, textNode);
|
|
3279
|
+
}
|
|
3280
|
+
const flattenedText = flattenedChunks.join("");
|
|
3281
|
+
if (!this.hasVisibleText(flattenedText)) return [];
|
|
3282
|
+
const invisible = /[\s\u200B-\u200F\uFEFF]/;
|
|
3283
|
+
let firstVisibleOffset = -1;
|
|
3284
|
+
let lastVisibleOffset = -1;
|
|
3285
|
+
for (let index = 0; index < flattenedText.length; index++) {
|
|
3286
|
+
if (index % 4096 === 0) this.checkpoint();
|
|
3287
|
+
if (invisible.test(flattenedText[index])) continue;
|
|
3288
|
+
if (firstVisibleOffset < 0) firstVisibleOffset = index;
|
|
3289
|
+
lastVisibleOffset = index;
|
|
3290
|
+
}
|
|
3291
|
+
const graphemeBoundaries = this.graphemeBoundaryOffsets(flattenedText);
|
|
3292
|
+
const candidates = [];
|
|
3293
|
+
const addCandidate = (candidate) => {
|
|
3294
|
+
const { textOffset } = candidate;
|
|
3295
|
+
if (textOffset <= 0 || textOffset >= flattenedText.length) return;
|
|
3296
|
+
if (textOffset <= firstVisibleOffset || textOffset > lastVisibleOffset) return;
|
|
3297
|
+
if (this.isNonBreakingTextBoundary(flattenedText, textOffset)) return;
|
|
3298
|
+
if (!this.isLegalParagraphLineBoundary(paragraph, flattenedText, textOffset)) return;
|
|
3299
|
+
if (graphemeBoundaries) {
|
|
3300
|
+
if (!graphemeBoundaries.has(textOffset)) return;
|
|
3301
|
+
} else if (!this.isConservativeFallbackTextBoundary(flattenedText, textOffset)) {
|
|
3302
|
+
return;
|
|
3303
|
+
}
|
|
3304
|
+
candidates.push(candidate);
|
|
3305
|
+
};
|
|
3306
|
+
for (const node of textNodes) {
|
|
3307
|
+
this.checkpoint();
|
|
3308
|
+
const start2 = textStarts.get(node);
|
|
3309
|
+
const atomic = node.parentElement?.closest(
|
|
3310
|
+
"[data-field], [data-list-marker], a[data-comment-id]"
|
|
3311
|
+
);
|
|
3312
|
+
if (atomic && paragraph.contains(atomic)) continue;
|
|
3313
|
+
const whitespace = /[\u0009-\u000D\u0020]+/g;
|
|
1918
3314
|
let match;
|
|
1919
|
-
|
|
1920
|
-
|
|
3315
|
+
let matchesSinceCheckpoint = 0;
|
|
3316
|
+
while ((match = whitespace.exec(node.data)) !== null) {
|
|
3317
|
+
if (++matchesSinceCheckpoint % 256 === 0) this.checkpoint();
|
|
3318
|
+
const offset = match.index + match[0].length;
|
|
3319
|
+
addCandidate({ node, offset, textOffset: start2 + offset, priority: 0 });
|
|
3320
|
+
}
|
|
3321
|
+
addCandidate({
|
|
3322
|
+
node,
|
|
3323
|
+
offset: node.data.length,
|
|
3324
|
+
textOffset: start2 + node.data.length,
|
|
3325
|
+
priority: 0
|
|
3326
|
+
});
|
|
3327
|
+
if (emergencyGraphemeBreaks) {
|
|
3328
|
+
for (let offset = 1; offset < node.data.length; offset++) {
|
|
3329
|
+
if (offset % 256 === 0) this.checkpoint();
|
|
3330
|
+
addCandidate({
|
|
3331
|
+
node,
|
|
3332
|
+
offset,
|
|
3333
|
+
textOffset: start2 + offset,
|
|
3334
|
+
priority: -1
|
|
3335
|
+
});
|
|
3336
|
+
}
|
|
1921
3337
|
}
|
|
1922
|
-
|
|
1923
|
-
|
|
3338
|
+
}
|
|
3339
|
+
for (const field of Array.from(paragraph.querySelectorAll("[data-field]"))) {
|
|
3340
|
+
this.checkpoint();
|
|
3341
|
+
if (field.parentElement?.closest("[data-field]")) continue;
|
|
3342
|
+
const last = lastTextByField.get(field);
|
|
3343
|
+
const parent = field.parentNode;
|
|
3344
|
+
if (!last || !parent) continue;
|
|
3345
|
+
const childIndex = Array.prototype.indexOf.call(parent.childNodes, field);
|
|
3346
|
+
if (childIndex < 0) continue;
|
|
3347
|
+
addCandidate({
|
|
3348
|
+
node: parent,
|
|
3349
|
+
offset: childIndex + 1,
|
|
3350
|
+
textOffset: textStarts.get(last) + last.data.length,
|
|
3351
|
+
priority: 1
|
|
3352
|
+
});
|
|
3353
|
+
}
|
|
3354
|
+
candidates.sort((left2, right2) => left2.textOffset - right2.textOffset || right2.priority - left2.priority);
|
|
3355
|
+
const endpoints = [];
|
|
3356
|
+
for (const candidate of candidates) {
|
|
3357
|
+
if (endpoints[endpoints.length - 1]?.textOffset !== candidate.textOffset) {
|
|
3358
|
+
endpoints.push(candidate);
|
|
1924
3359
|
}
|
|
1925
3360
|
}
|
|
1926
3361
|
return endpoints;
|
|
1927
3362
|
}
|
|
3363
|
+
/** Browser-native UAX #29 boundaries; null keeps older runtimes conservative. */
|
|
3364
|
+
graphemeBoundaryOffsets(text) {
|
|
3365
|
+
const SegmenterConstructor = this.view.Intl.Segmenter;
|
|
3366
|
+
if (!SegmenterConstructor) return null;
|
|
3367
|
+
const boundaries = /* @__PURE__ */ new Set([0, text.length]);
|
|
3368
|
+
let segmentCount = 0;
|
|
3369
|
+
for (const part of new SegmenterConstructor("en", { granularity: "grapheme" }).segment(text)) {
|
|
3370
|
+
if (++segmentCount % 256 === 0) this.checkpoint();
|
|
3371
|
+
boundaries.add(part.index);
|
|
3372
|
+
boundaries.add(part.index + part.segment.length);
|
|
3373
|
+
}
|
|
3374
|
+
return boundaries;
|
|
3375
|
+
}
|
|
3376
|
+
/**
|
|
3377
|
+
* Conservative UAX #14/CSS wrapping opportunities used for synthetic page
|
|
3378
|
+
* boundaries. In particular, a formatting-run boundary is not itself a word
|
|
3379
|
+
* boundary, and Japanese opening/closing punctuation stays with its pair.
|
|
3380
|
+
* Arbitrary grapheme breaks are admitted only when the paragraph's CSS asks
|
|
3381
|
+
* the browser to wrap anywhere.
|
|
3382
|
+
*/
|
|
3383
|
+
isLegalParagraphLineBoundary(paragraph, text, offset) {
|
|
3384
|
+
const lastCodeUnit = text.charCodeAt(offset - 1);
|
|
3385
|
+
const beforeOffset = lastCodeUnit >= 56320 && lastCodeUnit <= 57343 ? Math.max(0, offset - 2) : offset - 1;
|
|
3386
|
+
const beforeCodePoint = text.codePointAt(beforeOffset);
|
|
3387
|
+
const afterCodePoint = text.codePointAt(offset);
|
|
3388
|
+
const before = beforeCodePoint === void 0 ? "" : String.fromCodePoint(beforeCodePoint);
|
|
3389
|
+
const after = afterCodePoint === void 0 ? "" : String.fromCodePoint(afterCodePoint);
|
|
3390
|
+
if (/^[\u0009-\u000D\u0020]$/.test(before) || /^[\u0009-\u000D\u0020]$/.test(after)) return true;
|
|
3391
|
+
if (/[-\u00AD\u2010]$/u.test(before)) return true;
|
|
3392
|
+
const style = this.view.getComputedStyle(paragraph);
|
|
3393
|
+
if (style.wordBreak === "break-all" || style.overflowWrap === "anywhere" || style.overflowWrap === "break-word") return true;
|
|
3394
|
+
const eastAsian = /[\p{Script=Han}\p{Script=Hiragana}\p{Script=Katakana}\p{Script=Hangul}]/u;
|
|
3395
|
+
if (!eastAsian.test(before) && !eastAsian.test(after)) return false;
|
|
3396
|
+
const opening = "([{<\u2018\u201C\u3008\u300A\u300C\u300E\u3010\u3014\u3016\u3018\u301A\uFF08\uFF3B\uFF5B";
|
|
3397
|
+
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";
|
|
3398
|
+
return !opening.includes(before) && !closing.includes(after);
|
|
3399
|
+
}
|
|
3400
|
+
/** Never fragment across characters whose line-break meaning is explicitly non-breaking. */
|
|
3401
|
+
isNonBreakingTextBoundary(text, offset) {
|
|
3402
|
+
const codePointBefore = (index) => {
|
|
3403
|
+
if (index <= 0) return void 0;
|
|
3404
|
+
const last = text.charCodeAt(index - 1);
|
|
3405
|
+
const start2 = last >= 56320 && last <= 57343 ? index - 2 : index - 1;
|
|
3406
|
+
return text.codePointAt(Math.max(0, start2));
|
|
3407
|
+
};
|
|
3408
|
+
const nonBreaking = /* @__PURE__ */ new Set([
|
|
3409
|
+
160,
|
|
3410
|
+
8206,
|
|
3411
|
+
8207,
|
|
3412
|
+
8209,
|
|
3413
|
+
8234,
|
|
3414
|
+
8235,
|
|
3415
|
+
8236,
|
|
3416
|
+
8237,
|
|
3417
|
+
8238,
|
|
3418
|
+
8239,
|
|
3419
|
+
8288,
|
|
3420
|
+
8294,
|
|
3421
|
+
8295,
|
|
3422
|
+
8296,
|
|
3423
|
+
8297,
|
|
3424
|
+
65279
|
|
3425
|
+
]);
|
|
3426
|
+
return nonBreaking.has(codePointBefore(offset) ?? -1) || nonBreaking.has(text.codePointAt(offset) ?? -1);
|
|
3427
|
+
}
|
|
3428
|
+
/**
|
|
3429
|
+
* Without Intl.Segmenter, admit only an all-ASCII boundary. This still
|
|
3430
|
+
* fragments ordinary prose while refusing to guess about Unicode clusters.
|
|
3431
|
+
*/
|
|
3432
|
+
isConservativeFallbackTextBoundary(text, offset) {
|
|
3433
|
+
return text.charCodeAt(offset - 1) <= 127 && text.charCodeAt(offset) <= 127;
|
|
3434
|
+
}
|
|
1928
3435
|
/**
|
|
1929
3436
|
* Whether a range contains visible text after ignoring bidi/zero-width marks.
|
|
1930
3437
|
* Paragraph fragmentation deliberately excludes non-textual descendants, so
|
|
1931
3438
|
* this is enough to reject empty head or tail fragments.
|
|
1932
3439
|
*/
|
|
1933
3440
|
hasVisibleFragmentText(fragment) {
|
|
1934
|
-
return (fragment.textContent || "")
|
|
3441
|
+
return this.hasVisibleText(fragment.textContent || "");
|
|
3442
|
+
}
|
|
3443
|
+
hasVisibleText(text) {
|
|
3444
|
+
return text.replace(/[\u200B-\u200F\uFEFF]/g, "").trim().length > 0;
|
|
1935
3445
|
}
|
|
1936
3446
|
/**
|
|
1937
3447
|
* Phase one only handles text-like paragraph descendants. Objects, explicit
|
|
@@ -1940,13 +3450,25 @@ var PaginationEngine = class {
|
|
|
1940
3450
|
* fallback instead of risking broken content or duplicate anchors.
|
|
1941
3451
|
*/
|
|
1942
3452
|
canFragmentParagraph(block) {
|
|
1943
|
-
if (!this.fragmentParagraphs
|
|
3453
|
+
if (!this.fragmentParagraphs) {
|
|
1944
3454
|
return false;
|
|
1945
3455
|
}
|
|
1946
3456
|
const paragraph = block.element;
|
|
1947
3457
|
if (block.keepWithNext || block.keepLines || block.pageBreakBefore || block.isPageBreak || paragraph.dataset.widowControl === "true" || paragraph.hasAttribute("contenteditable")) {
|
|
1948
3458
|
return false;
|
|
1949
3459
|
}
|
|
3460
|
+
return this.canRangeFragmentParagraph(paragraph);
|
|
3461
|
+
}
|
|
3462
|
+
/**
|
|
3463
|
+
* Shared structural gate for body and note paragraph fragmentation. Footnote
|
|
3464
|
+
* first paragraphs are inline beside their marker, so that one known layout
|
|
3465
|
+
* context may opt into an inline root while retaining every descendant and
|
|
3466
|
+
* break-safety restriction used for body text.
|
|
3467
|
+
*/
|
|
3468
|
+
canRangeFragmentParagraph(paragraph, allowInlineRoot = false) {
|
|
3469
|
+
if (paragraph.tagName !== "P" || paragraph.dataset.keepWithNext === "true" || paragraph.dataset.keepLines === "true" || paragraph.dataset.pageBreakBefore === "true" || paragraph.dataset.widowControl === "true" || paragraph.hasAttribute("contenteditable")) {
|
|
3470
|
+
return false;
|
|
3471
|
+
}
|
|
1950
3472
|
const unsupportedDescendants = [
|
|
1951
3473
|
"br",
|
|
1952
3474
|
"img",
|
|
@@ -1977,21 +3499,27 @@ var PaginationEngine = class {
|
|
|
1977
3499
|
"fieldset",
|
|
1978
3500
|
"details",
|
|
1979
3501
|
"[data-footnote-id]",
|
|
1980
|
-
"[data-list-marker]",
|
|
1981
|
-
"[data-anchor]",
|
|
1982
|
-
"[id]",
|
|
1983
3502
|
"[contenteditable]"
|
|
1984
3503
|
].join(", ");
|
|
1985
3504
|
if (paragraph.querySelector(unsupportedDescendants)) {
|
|
1986
3505
|
return false;
|
|
1987
3506
|
}
|
|
1988
|
-
const
|
|
1989
|
-
|
|
3507
|
+
const isValidatedEndnote = paragraph.dataset.paginationSafeEndnote === "true";
|
|
3508
|
+
return isValidatedEndnote || this.hasRangeFragmentSafeLayout(paragraph, allowInlineRoot);
|
|
3509
|
+
}
|
|
3510
|
+
/**
|
|
3511
|
+
* A range clone preserves nested inline formatting exactly. Anything that establishes its own
|
|
3512
|
+
* box/layout context is deferred until a future fragmenter can model it accurately. Callers
|
|
3513
|
+
* must invoke this while the paragraph is attached to the styled document.
|
|
3514
|
+
*/
|
|
3515
|
+
hasRangeFragmentSafeLayout(paragraph, allowInlineRoot = false) {
|
|
3516
|
+
const paragraphStyle = this.view.getComputedStyle(paragraph);
|
|
3517
|
+
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") {
|
|
1990
3518
|
return false;
|
|
1991
3519
|
}
|
|
1992
3520
|
for (const descendant of Array.from(paragraph.querySelectorAll("*"))) {
|
|
1993
|
-
const style =
|
|
1994
|
-
if (style.display !== "inline" || style.position !== "static" || style.float !== "none" || style.whiteSpace !== "normal") {
|
|
3521
|
+
const style = this.view.getComputedStyle(descendant);
|
|
3522
|
+
if (style.display !== "inline" || style.position !== "static" || style.float !== "none" || style.whiteSpace !== "normal" && style.whiteSpace !== "pre-wrap") {
|
|
1995
3523
|
return false;
|
|
1996
3524
|
}
|
|
1997
3525
|
}
|
|
@@ -2018,58 +3546,122 @@ var PaginationEngine = class {
|
|
|
2018
3546
|
return fragment;
|
|
2019
3547
|
}
|
|
2020
3548
|
/**
|
|
2021
|
-
*
|
|
2022
|
-
*
|
|
2023
|
-
*
|
|
3549
|
+
* Range clones repeat an inline ancestor when the split lands inside it. Keep
|
|
3550
|
+
* semantic wrappers (links, comments, formatting) on both sides, but retain a
|
|
3551
|
+
* duplicated HTML/editor identity only on the leading fragment. Targets that
|
|
3552
|
+
* occur wholly after the split are absent from `head` and remain on `tail`.
|
|
2024
3553
|
*/
|
|
2025
|
-
|
|
2026
|
-
|
|
2027
|
-
|
|
2028
|
-
|
|
2029
|
-
|
|
2030
|
-
const
|
|
2031
|
-
|
|
2032
|
-
|
|
2033
|
-
}
|
|
2034
|
-
|
|
2035
|
-
|
|
2036
|
-
|
|
2037
|
-
|
|
2038
|
-
|
|
2039
|
-
|
|
2040
|
-
|
|
2041
|
-
|
|
2042
|
-
|
|
2043
|
-
|
|
2044
|
-
|
|
2045
|
-
|
|
2046
|
-
|
|
3554
|
+
reconcileParagraphFragmentIdentities(head, tail) {
|
|
3555
|
+
const elements = (root, selector2) => [
|
|
3556
|
+
...root.matches(selector2) ? [root] : [],
|
|
3557
|
+
...Array.from(root.querySelectorAll(selector2))
|
|
3558
|
+
];
|
|
3559
|
+
const headIds = new Set(elements(head, "[id]").map((element) => element.id));
|
|
3560
|
+
for (const element of elements(tail, "[id]")) {
|
|
3561
|
+
if (headIds.has(element.id)) element.removeAttribute("id");
|
|
3562
|
+
}
|
|
3563
|
+
const headAnchors = new Set(elements(head, "[data-anchor]").map((element) => element.dataset.anchor).filter((anchor) => Boolean(anchor)));
|
|
3564
|
+
for (const element of elements(tail, "[data-anchor]")) {
|
|
3565
|
+
if (!element.dataset.anchor || !headAnchors.has(element.dataset.anchor)) continue;
|
|
3566
|
+
element.removeAttribute("data-anchor");
|
|
3567
|
+
element.removeAttribute("data-committed-text");
|
|
3568
|
+
}
|
|
3569
|
+
}
|
|
3570
|
+
/**
|
|
3571
|
+
* Range-clone the largest safe prefix accepted by `fits`. The measurement
|
|
3572
|
+
* policy stays with the caller, allowing body blocks and note bands to share
|
|
3573
|
+
* one DOM fragmenter while measuring in their respective layout contexts.
|
|
3574
|
+
*/
|
|
3575
|
+
splitParagraphAtLargestFit(paragraph, fits, options = {}) {
|
|
3576
|
+
const largestFit = (endpoints2) => {
|
|
3577
|
+
const lastEndpoint = endpoints2[endpoints2.length - 1];
|
|
3578
|
+
if (lastEndpoint) {
|
|
3579
|
+
const lastRange = this.document.createRange();
|
|
3580
|
+
lastRange.setStart(paragraph, 0);
|
|
3581
|
+
lastRange.setEnd(lastEndpoint.node, lastEndpoint.offset);
|
|
3582
|
+
const lastContents = lastRange.cloneContents();
|
|
3583
|
+
if (this.hasVisibleFragmentText(lastContents)) {
|
|
3584
|
+
const lastHead = this.createParagraphFragment(paragraph, lastRange, true, false);
|
|
3585
|
+
if (fits(lastHead)) return { endpoint: lastEndpoint, head: lastHead };
|
|
3586
|
+
}
|
|
2047
3587
|
}
|
|
2048
|
-
|
|
2049
|
-
|
|
2050
|
-
|
|
2051
|
-
|
|
2052
|
-
|
|
2053
|
-
|
|
2054
|
-
|
|
3588
|
+
let low = 0;
|
|
3589
|
+
let high = endpoints2.length - 2;
|
|
3590
|
+
let best2 = null;
|
|
3591
|
+
while (low <= high) {
|
|
3592
|
+
this.checkpoint();
|
|
3593
|
+
const middle = Math.floor((low + high) / 2);
|
|
3594
|
+
const endpoint = endpoints2[middle];
|
|
3595
|
+
const headRange = this.document.createRange();
|
|
3596
|
+
headRange.setStart(paragraph, 0);
|
|
3597
|
+
headRange.setEnd(endpoint.node, endpoint.offset);
|
|
3598
|
+
const headContents = headRange.cloneContents();
|
|
3599
|
+
if (!this.hasVisibleFragmentText(headContents)) {
|
|
3600
|
+
low = middle + 1;
|
|
3601
|
+
continue;
|
|
3602
|
+
}
|
|
3603
|
+
const head = this.createParagraphFragment(paragraph, headRange, true, false);
|
|
3604
|
+
if (fits(head)) {
|
|
3605
|
+
best2 = { endpoint, head };
|
|
3606
|
+
low = middle + 1;
|
|
3607
|
+
} else {
|
|
3608
|
+
high = middle - 1;
|
|
3609
|
+
}
|
|
2055
3610
|
}
|
|
3611
|
+
return best2;
|
|
3612
|
+
};
|
|
3613
|
+
let endpoints = this.paragraphFragmentEndpoints(paragraph);
|
|
3614
|
+
let best = largestFit(endpoints);
|
|
3615
|
+
if (!best && options.emergencyGraphemeBreaks) {
|
|
3616
|
+
endpoints = this.paragraphFragmentEndpoints(paragraph, true);
|
|
3617
|
+
best = largestFit(endpoints);
|
|
3618
|
+
}
|
|
3619
|
+
if (!best && options.forceFirstOnNoFit && endpoints.length > 0) {
|
|
3620
|
+
const endpoint = endpoints[0];
|
|
3621
|
+
const headRange = this.document.createRange();
|
|
3622
|
+
headRange.setStart(paragraph, 0);
|
|
3623
|
+
headRange.setEnd(endpoint.node, endpoint.offset);
|
|
3624
|
+
best = {
|
|
3625
|
+
endpoint,
|
|
3626
|
+
head: this.createParagraphFragment(paragraph, headRange, true, false)
|
|
3627
|
+
};
|
|
2056
3628
|
}
|
|
2057
3629
|
if (!best) {
|
|
2058
|
-
return
|
|
3630
|
+
return endpoints.length > 0 ? { kind: "no-fit" } : { kind: "indivisible" };
|
|
2059
3631
|
}
|
|
2060
|
-
const tailRange = document.createRange();
|
|
3632
|
+
const tailRange = this.document.createRange();
|
|
2061
3633
|
tailRange.setStart(best.endpoint.node, best.endpoint.offset);
|
|
2062
3634
|
tailRange.setEnd(paragraph, paragraph.childNodes.length);
|
|
2063
3635
|
const tailContents = tailRange.cloneContents();
|
|
2064
|
-
if (!this.hasVisibleFragmentText(tailContents)) {
|
|
3636
|
+
if (!this.hasVisibleFragmentText(tailContents)) return { kind: "indivisible" };
|
|
3637
|
+
const tail = this.createParagraphFragment(paragraph, tailRange, false, true);
|
|
3638
|
+
this.reconcileParagraphFragmentIdentities(best.head, tail);
|
|
3639
|
+
return {
|
|
3640
|
+
kind: "split",
|
|
3641
|
+
head: best.head,
|
|
3642
|
+
tail
|
|
3643
|
+
};
|
|
3644
|
+
}
|
|
3645
|
+
/**
|
|
3646
|
+
* Splits a simple paragraph at the largest DOM Range endpoint that fits the
|
|
3647
|
+
* currently available body space. The caller then processes the tail normally,
|
|
3648
|
+
* allowing it to fragment again on later pages when necessary.
|
|
3649
|
+
*/
|
|
3650
|
+
tryFragmentParagraph(block, dims, availableHeightPt, effectiveMarginTopPt) {
|
|
3651
|
+
if (!this.canFragmentParagraph(block) || availableHeightPt <= effectiveMarginTopPt) {
|
|
2065
3652
|
return null;
|
|
2066
3653
|
}
|
|
2067
|
-
const
|
|
2068
|
-
|
|
3654
|
+
const split = this.splitParagraphAtLargestFit(block.element, (head) => {
|
|
3655
|
+
const measured = this.measureElement(head, dims, block.sectionIndex);
|
|
3656
|
+
return effectiveMarginTopPt + measured.heightPt <= availableHeightPt;
|
|
3657
|
+
});
|
|
3658
|
+
if (split.kind !== "split") return null;
|
|
3659
|
+
const headMeasured = this.measureElement(split.head, dims, block.sectionIndex);
|
|
3660
|
+
const tailMeasured = this.measureElement(split.tail, dims, block.sectionIndex);
|
|
2069
3661
|
return [
|
|
2070
3662
|
{
|
|
2071
|
-
...
|
|
2072
|
-
element:
|
|
3663
|
+
...headMeasured,
|
|
3664
|
+
element: split.head,
|
|
2073
3665
|
keepWithNext: false,
|
|
2074
3666
|
keepLines: false,
|
|
2075
3667
|
pageBreakBefore: false,
|
|
@@ -2077,7 +3669,7 @@ var PaginationEngine = class {
|
|
|
2077
3669
|
},
|
|
2078
3670
|
{
|
|
2079
3671
|
...tailMeasured,
|
|
2080
|
-
element: tail,
|
|
3672
|
+
element: split.tail,
|
|
2081
3673
|
keepWithNext: false,
|
|
2082
3674
|
keepLines: false,
|
|
2083
3675
|
pageBreakBefore: false,
|
|
@@ -2148,7 +3740,7 @@ var PaginationEngine = class {
|
|
|
2148
3740
|
const registry = /* @__PURE__ */ new Map();
|
|
2149
3741
|
const registryEl = this.stagingElement.querySelector("#pagination-footnote-registry");
|
|
2150
3742
|
if (!registryEl) return registry;
|
|
2151
|
-
const entries = Array.from(registryEl.
|
|
3743
|
+
const entries = Array.from(registryEl.children).filter((entry) => entry instanceof this.view.HTMLElement && entry.hasAttribute("data-footnote-id"));
|
|
2152
3744
|
for (const entry of entries) {
|
|
2153
3745
|
const footnoteId = entry.dataset.footnoteId;
|
|
2154
3746
|
if (footnoteId) {
|
|
@@ -2157,6 +3749,68 @@ var PaginationEngine = class {
|
|
|
2157
3749
|
}
|
|
2158
3750
|
return registry;
|
|
2159
3751
|
}
|
|
3752
|
+
/** Read Word's optional normal and continuation separator stories. */
|
|
3753
|
+
parseFootnoteSeparators() {
|
|
3754
|
+
const registry = this.stagingElement.querySelector("#pagination-footnote-registry");
|
|
3755
|
+
const clone = (kind) => {
|
|
3756
|
+
const source = Array.from(registry?.children ?? []).find(
|
|
3757
|
+
(entry) => entry instanceof this.view.HTMLElement && entry.getAttribute("data-footnote-separator") === kind
|
|
3758
|
+
);
|
|
3759
|
+
return source?.cloneNode(true);
|
|
3760
|
+
};
|
|
3761
|
+
return {
|
|
3762
|
+
normal: clone("normal") ?? null,
|
|
3763
|
+
continuation: clone("continuation") ?? null
|
|
3764
|
+
};
|
|
3765
|
+
}
|
|
3766
|
+
/** Append the exact separator story selected for this initial/continued note band. */
|
|
3767
|
+
appendFootnoteSeparator(container, continuation) {
|
|
3768
|
+
const kind = continuation ? "continuation" : "normal";
|
|
3769
|
+
const source = continuation ? this.footnoteContinuationSeparator : this.footnoteSeparator;
|
|
3770
|
+
if (!source) {
|
|
3771
|
+
const fallback = this.document.createElement("hr");
|
|
3772
|
+
fallback.dataset.footnoteSeparator = kind;
|
|
3773
|
+
container.appendChild(fallback);
|
|
3774
|
+
return;
|
|
3775
|
+
}
|
|
3776
|
+
const clone = source.cloneNode(true);
|
|
3777
|
+
for (const element of [clone, ...Array.from(clone.querySelectorAll("*"))]) {
|
|
3778
|
+
element.removeAttribute("id");
|
|
3779
|
+
element.removeAttribute("data-anchor");
|
|
3780
|
+
element.removeAttribute("data-committed-text");
|
|
3781
|
+
element.removeAttribute("data-source-anchor-id");
|
|
3782
|
+
element.removeAttribute("data-page-fragment-id");
|
|
3783
|
+
element.removeAttribute("data-fragment-index");
|
|
3784
|
+
element.removeAttribute("data-footnote-id");
|
|
3785
|
+
element.removeAttribute("data-comment-id");
|
|
3786
|
+
element.removeAttribute("name");
|
|
3787
|
+
if (element instanceof this.view.HTMLAnchorElement && element.getAttribute("href")?.startsWith("#")) {
|
|
3788
|
+
element.removeAttribute("href");
|
|
3789
|
+
}
|
|
3790
|
+
element.setAttribute("contenteditable", "false");
|
|
3791
|
+
}
|
|
3792
|
+
container.appendChild(clone);
|
|
3793
|
+
}
|
|
3794
|
+
/** Parses the hidden source notes used to render paginated margin comments. */
|
|
3795
|
+
parseCommentMarginRegistry() {
|
|
3796
|
+
const registry = /* @__PURE__ */ new Map();
|
|
3797
|
+
const registryEl = this.stagingElement.querySelector(
|
|
3798
|
+
"#pagination-comment-margin-registry"
|
|
3799
|
+
);
|
|
3800
|
+
if (!registryEl) return registry;
|
|
3801
|
+
for (const entry of Array.from(
|
|
3802
|
+
registryEl.querySelectorAll("[data-comment-id]")
|
|
3803
|
+
)) {
|
|
3804
|
+
const commentId = entry.dataset.commentId;
|
|
3805
|
+
if (!commentId) continue;
|
|
3806
|
+
let root = entry;
|
|
3807
|
+
for (let parent = root.parentElement?.closest("[data-comment-id]"); parent && registryEl.contains(parent); parent = root.parentElement?.closest("[data-comment-id]")) {
|
|
3808
|
+
root = parent;
|
|
3809
|
+
}
|
|
3810
|
+
registry.set(commentId, root.cloneNode(true));
|
|
3811
|
+
}
|
|
3812
|
+
return registry;
|
|
3813
|
+
}
|
|
2160
3814
|
/**
|
|
2161
3815
|
* Extracts footnote reference IDs from an element.
|
|
2162
3816
|
*/
|
|
@@ -2171,6 +3825,92 @@ var PaginationEngine = class {
|
|
|
2171
3825
|
}
|
|
2172
3826
|
return ids;
|
|
2173
3827
|
}
|
|
3828
|
+
/** Clone a note child for the continuation queue with its original selector position. */
|
|
3829
|
+
cloneFootnoteElementForContinuation(element, sourceIndex) {
|
|
3830
|
+
const clone = element.cloneNode(true);
|
|
3831
|
+
clone.setAttribute(
|
|
3832
|
+
FOOTNOTE_SOURCE_POSITION_ATTR,
|
|
3833
|
+
sourceIndex === 0 ? "first" : "later"
|
|
3834
|
+
);
|
|
3835
|
+
return clone;
|
|
3836
|
+
}
|
|
3837
|
+
/** Remove identities that belong only to the initial registry presentation shell. */
|
|
3838
|
+
makeContinuationShellInert(element) {
|
|
3839
|
+
element.removeAttribute("id");
|
|
3840
|
+
element.removeAttribute("data-anchor");
|
|
3841
|
+
element.removeAttribute("data-committed-text");
|
|
3842
|
+
}
|
|
3843
|
+
/**
|
|
3844
|
+
* Build the exact continuation shape shared by measurement and paint.
|
|
3845
|
+
*
|
|
3846
|
+
* Keep the source `.footnote-item > .footnote-content` ancestry: document
|
|
3847
|
+
* CSS, inherited direction/language, and custom classes frequently target
|
|
3848
|
+
* those shells. Only the number and initial HTML/editor identities are
|
|
3849
|
+
* omitted. A hidden sentinel preserves `p:not(:first-of-type)` for a page
|
|
3850
|
+
* whose first carried element was a later source paragraph.
|
|
3851
|
+
*/
|
|
3852
|
+
createFootnoteContinuationWrapper(continuation, elements = continuation.remainingElements) {
|
|
3853
|
+
const source = this.footnoteRegistry.get(continuation.footnoteId);
|
|
3854
|
+
const wrapper = source ? source.cloneNode(false) : this.document.createElement("div");
|
|
3855
|
+
this.makeContinuationShellInert(wrapper);
|
|
3856
|
+
wrapper.classList.add("footnote-continuation");
|
|
3857
|
+
wrapper.dataset.footnoteId = continuation.footnoteId;
|
|
3858
|
+
if (continuation.sourceAnchorId) {
|
|
3859
|
+
wrapper.dataset.sourceAnchorId = continuation.sourceAnchorId;
|
|
3860
|
+
}
|
|
3861
|
+
const sourceContent = source?.querySelector(".footnote-content");
|
|
3862
|
+
const content = sourceContent ? sourceContent.cloneNode(false) : this.document.createElement("span");
|
|
3863
|
+
this.makeContinuationShellInert(content);
|
|
3864
|
+
if (!sourceContent) content.className = "footnote-content";
|
|
3865
|
+
const first = elements[0];
|
|
3866
|
+
if (first?.tagName === "P" && first.getAttribute(FOOTNOTE_SOURCE_POSITION_ATTR) === "later") {
|
|
3867
|
+
const sentinel = this.document.createElement("p");
|
|
3868
|
+
sentinel.className = "footnote-continuation-position-sentinel";
|
|
3869
|
+
sentinel.setAttribute("aria-hidden", "true");
|
|
3870
|
+
sentinel.style.setProperty("display", "none", "important");
|
|
3871
|
+
content.appendChild(sentinel);
|
|
3872
|
+
}
|
|
3873
|
+
for (let index = 0; index < elements.length; index++) {
|
|
3874
|
+
this.checkpoint();
|
|
3875
|
+
const element = elements[index];
|
|
3876
|
+
const clone = element.cloneNode(true);
|
|
3877
|
+
clone.removeAttribute(FOOTNOTE_SOURCE_POSITION_ATTR);
|
|
3878
|
+
if (index === 0 && clone.tagName === "P") {
|
|
3879
|
+
clone.style.setProperty("display", "block", "important");
|
|
3880
|
+
}
|
|
3881
|
+
content.appendChild(clone);
|
|
3882
|
+
}
|
|
3883
|
+
wrapper.appendChild(content);
|
|
3884
|
+
return wrapper;
|
|
3885
|
+
}
|
|
3886
|
+
/** Build the exact partial-note item shape shared by measurement and paint. */
|
|
3887
|
+
createPartialFootnoteItem(footnote, fittingElements) {
|
|
3888
|
+
const item = footnote.cloneNode(false);
|
|
3889
|
+
const number = footnote.querySelector(".footnote-number");
|
|
3890
|
+
if (number) item.appendChild(number.cloneNode(true));
|
|
3891
|
+
const sourceContent = footnote.querySelector(".footnote-content");
|
|
3892
|
+
const content = sourceContent ? sourceContent.cloneNode(false) : this.document.createElement("span");
|
|
3893
|
+
if (!sourceContent) content.className = "footnote-content";
|
|
3894
|
+
for (const element of fittingElements) {
|
|
3895
|
+
this.checkpoint();
|
|
3896
|
+
content.appendChild(element.cloneNode(true));
|
|
3897
|
+
}
|
|
3898
|
+
item.appendChild(content);
|
|
3899
|
+
return item;
|
|
3900
|
+
}
|
|
3901
|
+
/**
|
|
3902
|
+
* Where a note measurement tree is attached.
|
|
3903
|
+
*
|
|
3904
|
+
* A note band is reserved from a measurement and then painted; if the two
|
|
3905
|
+
* happen under different inherited typography the painted notes overflow the
|
|
3906
|
+
* reserve, which is the invisible clipping this engine exists to avoid. The
|
|
3907
|
+
* registry lives in the staging tree but a band paints inside the output
|
|
3908
|
+
* container, so measure there. The host is never a page box, so a page's
|
|
3909
|
+
* `zoom` cannot scale a reserve that is accounted for in unscaled points.
|
|
3910
|
+
*/
|
|
3911
|
+
noteMeasurementHost() {
|
|
3912
|
+
return this.containerElement.isConnected ? this.containerElement : this.stagingElement;
|
|
3913
|
+
}
|
|
2174
3914
|
/**
|
|
2175
3915
|
* Measures the height of footnotes for given IDs (in points).
|
|
2176
3916
|
* Creates a temporary container to measure the footnotes.
|
|
@@ -2178,140 +3918,246 @@ var PaginationEngine = class {
|
|
|
2178
3918
|
* @param contentWidth - Width for measurement
|
|
2179
3919
|
* @param continuation - Optional continuation content to include first
|
|
2180
3920
|
*/
|
|
2181
|
-
measureFootnotesHeight(footnoteIds, contentWidth, continuation) {
|
|
3921
|
+
measureFootnotesHeight(footnoteIds, contentWidth, continuation, partialFootnotes) {
|
|
2182
3922
|
const hasContinuation = continuation && continuation.remainingElements.length > 0;
|
|
2183
|
-
if (footnoteIds.length === 0 && !hasContinuation
|
|
3923
|
+
if (footnoteIds.length === 0 && !hasContinuation) {
|
|
2184
3924
|
return 0;
|
|
2185
3925
|
}
|
|
2186
|
-
|
|
3926
|
+
if (this.footnoteRegistry.size === 0 && !hasContinuation) {
|
|
3927
|
+
return 0;
|
|
3928
|
+
}
|
|
3929
|
+
const measureContainer = this.document.createElement("div");
|
|
2187
3930
|
measureContainer.style.position = "absolute";
|
|
2188
3931
|
measureContainer.style.visibility = "hidden";
|
|
2189
3932
|
measureContainer.style.width = `${contentWidth}pt`;
|
|
2190
3933
|
measureContainer.style.left = "-9999px";
|
|
2191
3934
|
measureContainer.className = this.cssPrefix + "footnotes";
|
|
2192
|
-
|
|
2193
|
-
measureContainer.appendChild(hr);
|
|
3935
|
+
this.appendFootnoteSeparator(measureContainer, Boolean(hasContinuation));
|
|
2194
3936
|
if (hasContinuation) {
|
|
2195
|
-
|
|
2196
|
-
contWrapper.className = "footnote-continuation";
|
|
2197
|
-
for (const el of continuation.remainingElements) {
|
|
2198
|
-
contWrapper.appendChild(el.cloneNode(true));
|
|
2199
|
-
}
|
|
2200
|
-
measureContainer.appendChild(contWrapper);
|
|
3937
|
+
measureContainer.appendChild(this.createFootnoteContinuationWrapper(continuation));
|
|
2201
3938
|
}
|
|
3939
|
+
const partialById = new Map(
|
|
3940
|
+
partialFootnotes?.map((partial) => [partial.footnoteId, partial]) ?? []
|
|
3941
|
+
);
|
|
2202
3942
|
for (const id of footnoteIds) {
|
|
3943
|
+
this.checkpoint();
|
|
2203
3944
|
const footnote = this.footnoteRegistry.get(id);
|
|
2204
3945
|
if (footnote) {
|
|
2205
|
-
|
|
3946
|
+
const partial = partialById.get(id);
|
|
3947
|
+
measureContainer.appendChild(partial ? this.createPartialFootnoteItem(footnote, partial.fittingElements) : footnote.cloneNode(true));
|
|
2206
3948
|
}
|
|
2207
3949
|
}
|
|
2208
|
-
this.
|
|
2209
|
-
|
|
2210
|
-
|
|
2211
|
-
|
|
2212
|
-
|
|
3950
|
+
this.noteMeasurementHost().appendChild(measureContainer);
|
|
3951
|
+
try {
|
|
3952
|
+
return pxToPt(measureContainer.getBoundingClientRect().height);
|
|
3953
|
+
} finally {
|
|
3954
|
+
measureContainer.remove();
|
|
3955
|
+
}
|
|
2213
3956
|
}
|
|
2214
3957
|
/**
|
|
2215
|
-
*
|
|
3958
|
+
* Partition a continuation for one page's note band, preferring complete children
|
|
3959
|
+
* and range-fragmenting an eligible paragraph when necessary. Always advances by
|
|
3960
|
+
* at least one element so an indivisible oversized paragraph follows the established
|
|
3961
|
+
* clipped fallback without trapping pagination in a loop.
|
|
2216
3962
|
*/
|
|
2217
|
-
|
|
2218
|
-
|
|
2219
|
-
|
|
2220
|
-
|
|
2221
|
-
const measureContainer = document.createElement("div");
|
|
3963
|
+
splitContinuationForPage(continuation, availableHeightPt, contentWidth) {
|
|
3964
|
+
const fitting = [];
|
|
3965
|
+
let remaining = [];
|
|
3966
|
+
const measureContainer = this.document.createElement("div");
|
|
2222
3967
|
measureContainer.style.position = "absolute";
|
|
2223
3968
|
measureContainer.style.visibility = "hidden";
|
|
2224
3969
|
measureContainer.style.width = `${contentWidth}pt`;
|
|
2225
3970
|
measureContainer.style.left = "-9999px";
|
|
2226
3971
|
measureContainer.className = this.cssPrefix + "footnotes";
|
|
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
|
-
|
|
2285
|
-
|
|
2286
|
-
|
|
2287
|
-
|
|
2288
|
-
|
|
2289
|
-
|
|
2290
|
-
|
|
3972
|
+
this.appendFootnoteSeparator(measureContainer, true);
|
|
3973
|
+
const wrapper = this.createFootnoteContinuationWrapper(continuation, []);
|
|
3974
|
+
const content = wrapper.querySelector(":scope > .footnote-content");
|
|
3975
|
+
if (!content) {
|
|
3976
|
+
throw new Error("Footnote continuation is missing its content shell");
|
|
3977
|
+
}
|
|
3978
|
+
measureContainer.appendChild(wrapper);
|
|
3979
|
+
this.noteMeasurementHost().appendChild(measureContainer);
|
|
3980
|
+
try {
|
|
3981
|
+
for (let index = 0; index < continuation.remainingElements.length; index++) {
|
|
3982
|
+
this.checkpoint();
|
|
3983
|
+
const element = continuation.remainingElements[index];
|
|
3984
|
+
const sourcePosition = element.getAttribute(FOOTNOTE_SOURCE_POSITION_ATTR);
|
|
3985
|
+
if (fitting.length === 0 && element.tagName === "P" && element.getAttribute(FOOTNOTE_SOURCE_POSITION_ATTR) === "later") {
|
|
3986
|
+
const sentinel = this.document.createElement("p");
|
|
3987
|
+
sentinel.className = "footnote-continuation-position-sentinel";
|
|
3988
|
+
sentinel.setAttribute("aria-hidden", "true");
|
|
3989
|
+
sentinel.style.setProperty("display", "none", "important");
|
|
3990
|
+
content.appendChild(sentinel);
|
|
3991
|
+
}
|
|
3992
|
+
const candidate = element.cloneNode(true);
|
|
3993
|
+
candidate.removeAttribute(FOOTNOTE_SOURCE_POSITION_ATTR);
|
|
3994
|
+
if (fitting.length === 0 && candidate.tagName === "P") {
|
|
3995
|
+
candidate.style.setProperty("display", "block", "important");
|
|
3996
|
+
}
|
|
3997
|
+
content.appendChild(candidate);
|
|
3998
|
+
const candidateHeight = pxToPt(measureContainer.getBoundingClientRect().height);
|
|
3999
|
+
if (candidateHeight <= availableHeightPt) {
|
|
4000
|
+
fitting.push(element);
|
|
4001
|
+
continue;
|
|
4002
|
+
}
|
|
4003
|
+
const canSplitCandidate = candidate.tagName === "P" && this.canRangeFragmentParagraph(candidate);
|
|
4004
|
+
candidate.remove();
|
|
4005
|
+
let split = null;
|
|
4006
|
+
if (canSplitCandidate) {
|
|
4007
|
+
split = this.splitParagraphAtLargestFit(candidate, (head) => {
|
|
4008
|
+
content.appendChild(head);
|
|
4009
|
+
try {
|
|
4010
|
+
return pxToPt(measureContainer.getBoundingClientRect().height) <= availableHeightPt;
|
|
4011
|
+
} finally {
|
|
4012
|
+
head.remove();
|
|
4013
|
+
}
|
|
4014
|
+
}, {
|
|
4015
|
+
// Both fallbacks below cut where ordinary line breaking would not,
|
|
4016
|
+
// so they are only ever right for an element that owns the whole
|
|
4017
|
+
// band: if this page already carries earlier content, a paragraph
|
|
4018
|
+
// that will not start here belongs intact in the next note band.
|
|
4019
|
+
emergencyGraphemeBreaks: fitting.length === 0,
|
|
4020
|
+
// A continuation page owns the full note band. If even one legal
|
|
4021
|
+
// grapheme is taller than it, clip only that unit and keep draining.
|
|
4022
|
+
forceFirstOnNoFit: fitting.length === 0
|
|
4023
|
+
});
|
|
4024
|
+
}
|
|
4025
|
+
if (split?.kind === "split") {
|
|
4026
|
+
if (sourcePosition) {
|
|
4027
|
+
split.head.setAttribute(FOOTNOTE_SOURCE_POSITION_ATTR, sourcePosition);
|
|
4028
|
+
split.tail.setAttribute(FOOTNOTE_SOURCE_POSITION_ATTR, sourcePosition);
|
|
4029
|
+
}
|
|
4030
|
+
fitting.push(split.head);
|
|
4031
|
+
remaining = [split.tail, ...continuation.remainingElements.slice(index + 1)];
|
|
4032
|
+
} else if (fitting.length === 0) {
|
|
4033
|
+
fitting.push(element);
|
|
4034
|
+
remaining = continuation.remainingElements.slice(index + 1);
|
|
4035
|
+
} else {
|
|
4036
|
+
remaining = continuation.remainingElements.slice(index);
|
|
2291
4037
|
}
|
|
2292
4038
|
break;
|
|
2293
4039
|
}
|
|
4040
|
+
} finally {
|
|
4041
|
+
measureContainer.remove();
|
|
2294
4042
|
}
|
|
2295
|
-
return {
|
|
4043
|
+
return {
|
|
4044
|
+
current: {
|
|
4045
|
+
footnoteId: continuation.footnoteId,
|
|
4046
|
+
sourceAnchorId: continuation.sourceAnchorId,
|
|
4047
|
+
remainingElements: fitting
|
|
4048
|
+
},
|
|
4049
|
+
overflow: remaining.length > 0 ? {
|
|
4050
|
+
footnoteId: continuation.footnoteId,
|
|
4051
|
+
sourceAnchorId: continuation.sourceAnchorId,
|
|
4052
|
+
remainingElements: remaining
|
|
4053
|
+
} : null
|
|
4054
|
+
};
|
|
2296
4055
|
}
|
|
2297
4056
|
/**
|
|
2298
|
-
*
|
|
4057
|
+
* Splits a footnote element into parts that fit within the available height.
|
|
4058
|
+
* Returns the elements that fit and the elements that need to continue.
|
|
2299
4059
|
*/
|
|
2300
|
-
|
|
2301
|
-
const
|
|
2302
|
-
|
|
2303
|
-
|
|
4060
|
+
splitFootnoteToFit(footnoteElement, availableHeightPt, contentWidth, forceProgress = false, existingPayload) {
|
|
4061
|
+
const layoutContext = this.document.createElement("div");
|
|
4062
|
+
layoutContext.style.position = "absolute";
|
|
4063
|
+
layoutContext.style.visibility = "hidden";
|
|
4064
|
+
layoutContext.style.width = `${contentWidth}pt`;
|
|
4065
|
+
layoutContext.style.left = "-9999px";
|
|
4066
|
+
layoutContext.className = this.cssPrefix + "footnotes";
|
|
4067
|
+
this.appendFootnoteSeparator(layoutContext, false);
|
|
4068
|
+
const attachedFootnote = footnoteElement.cloneNode(true);
|
|
4069
|
+
layoutContext.appendChild(attachedFootnote);
|
|
4070
|
+
this.noteMeasurementHost().appendChild(layoutContext);
|
|
4071
|
+
const measureContainer = this.document.createElement("div");
|
|
2304
4072
|
measureContainer.style.position = "absolute";
|
|
2305
4073
|
measureContainer.style.visibility = "hidden";
|
|
2306
4074
|
measureContainer.style.width = `${contentWidth}pt`;
|
|
2307
4075
|
measureContainer.style.left = "-9999px";
|
|
2308
4076
|
measureContainer.className = this.cssPrefix + "footnotes";
|
|
2309
|
-
|
|
2310
|
-
this.
|
|
2311
|
-
|
|
2312
|
-
|
|
2313
|
-
|
|
2314
|
-
|
|
4077
|
+
const hasContinuation = Boolean(existingPayload?.continuation?.remainingElements.length);
|
|
4078
|
+
this.appendFootnoteSeparator(measureContainer, hasContinuation);
|
|
4079
|
+
if (hasContinuation) {
|
|
4080
|
+
measureContainer.appendChild(this.createFootnoteContinuationWrapper(
|
|
4081
|
+
existingPayload.continuation
|
|
4082
|
+
));
|
|
4083
|
+
}
|
|
4084
|
+
const partialById = new Map(
|
|
4085
|
+
existingPayload?.partialFootnotes?.map((partial) => [partial.footnoteId, partial]) ?? []
|
|
4086
|
+
);
|
|
4087
|
+
for (const id of existingPayload?.footnoteIds ?? []) {
|
|
4088
|
+
this.checkpoint();
|
|
4089
|
+
const source = this.footnoteRegistry.get(id);
|
|
4090
|
+
if (!source) continue;
|
|
4091
|
+
const partial = partialById.get(id);
|
|
4092
|
+
measureContainer.appendChild(partial ? this.createPartialFootnoteItem(source, partial.fittingElements) : source.cloneNode(true));
|
|
4093
|
+
}
|
|
4094
|
+
const measuredPartial = this.createPartialFootnoteItem(attachedFootnote, []);
|
|
4095
|
+
const measuredContent = measuredPartial.querySelector(":scope > .footnote-content");
|
|
4096
|
+
if (!measuredContent) {
|
|
4097
|
+
layoutContext.remove();
|
|
4098
|
+
throw new Error("Footnote is missing its content shell");
|
|
4099
|
+
}
|
|
4100
|
+
measureContainer.appendChild(measuredPartial);
|
|
4101
|
+
this.noteMeasurementHost().appendChild(measureContainer);
|
|
4102
|
+
try {
|
|
4103
|
+
const footnoteContent = attachedFootnote.querySelector(".footnote-content");
|
|
4104
|
+
if (!footnoteContent) {
|
|
4105
|
+
return {
|
|
4106
|
+
fits: Array.from(attachedFootnote.children).map((el) => el.cloneNode(true)),
|
|
4107
|
+
overflow: []
|
|
4108
|
+
};
|
|
4109
|
+
}
|
|
4110
|
+
const children = Array.from(footnoteContent.children);
|
|
4111
|
+
const fits = [];
|
|
4112
|
+
let overflow = [];
|
|
4113
|
+
for (let i = 0; i < children.length; i++) {
|
|
4114
|
+
this.checkpoint();
|
|
4115
|
+
const child = children[i];
|
|
4116
|
+
const measuredCandidate = child.cloneNode(true);
|
|
4117
|
+
measuredContent.appendChild(measuredCandidate);
|
|
4118
|
+
const candidateFits = pxToPt(measureContainer.getBoundingClientRect().height) <= availableHeightPt;
|
|
4119
|
+
if (candidateFits) {
|
|
4120
|
+
fits.push(child.cloneNode(true));
|
|
4121
|
+
continue;
|
|
4122
|
+
}
|
|
4123
|
+
measuredCandidate.remove();
|
|
4124
|
+
const split = this.canRangeFragmentParagraph(child, true) ? this.splitParagraphAtLargestFit(child, (head) => {
|
|
4125
|
+
measuredContent.appendChild(head);
|
|
4126
|
+
try {
|
|
4127
|
+
return pxToPt(measureContainer.getBoundingClientRect().height) <= availableHeightPt;
|
|
4128
|
+
} finally {
|
|
4129
|
+
head.remove();
|
|
4130
|
+
}
|
|
4131
|
+
}, {
|
|
4132
|
+
// Both fallbacks cut where ordinary line breaking would not, so they
|
|
4133
|
+
// are only ever right for an element that owns the whole band: with
|
|
4134
|
+
// earlier siblings already packed here, a paragraph that will not
|
|
4135
|
+
// start belongs intact in the next note band.
|
|
4136
|
+
emergencyGraphemeBreaks: fits.length === 0 && forceProgress,
|
|
4137
|
+
forceFirstOnNoFit: fits.length === 0 && forceProgress
|
|
4138
|
+
}) : null;
|
|
4139
|
+
if (split?.kind === "split") {
|
|
4140
|
+
fits.push(split.head);
|
|
4141
|
+
split.tail.setAttribute(
|
|
4142
|
+
FOOTNOTE_SOURCE_POSITION_ATTR,
|
|
4143
|
+
i === 0 ? "first" : "later"
|
|
4144
|
+
);
|
|
4145
|
+
overflow = [split.tail, ...children.slice(i + 1).map((element, offset) => this.cloneFootnoteElementForContinuation(element, i + 1 + offset))];
|
|
4146
|
+
} else if (split?.kind === "no-fit") {
|
|
4147
|
+
overflow = children.slice(i).map((element, offset) => this.cloneFootnoteElementForContinuation(element, i + offset));
|
|
4148
|
+
} else if (fits.length === 0 && forceProgress) {
|
|
4149
|
+
fits.push(child.cloneNode(true));
|
|
4150
|
+
overflow = children.slice(i + 1).map((element, offset) => this.cloneFootnoteElementForContinuation(element, i + 1 + offset));
|
|
4151
|
+
} else {
|
|
4152
|
+
overflow = children.slice(i).map((element, offset) => this.cloneFootnoteElementForContinuation(element, i + offset));
|
|
4153
|
+
}
|
|
4154
|
+
break;
|
|
4155
|
+
}
|
|
4156
|
+
return { fits, overflow };
|
|
4157
|
+
} finally {
|
|
4158
|
+
measureContainer.remove();
|
|
4159
|
+
layoutContext.remove();
|
|
4160
|
+
}
|
|
2315
4161
|
}
|
|
2316
4162
|
/**
|
|
2317
4163
|
* Adds footnotes to a page container, including continuation content.
|
|
@@ -2324,12 +4170,11 @@ var PaginationEngine = class {
|
|
|
2324
4170
|
if (this.footnoteRegistry.size === 0 && !hasContinuation) {
|
|
2325
4171
|
return;
|
|
2326
4172
|
}
|
|
2327
|
-
const partialFootnoteIds = new Set(partialFootnotes?.map((p) => p.footnoteId) || []);
|
|
2328
4173
|
const maxFootnoteHeight = Math.min(
|
|
2329
4174
|
footnoteHeight,
|
|
2330
4175
|
bands.bodyHeight * MAX_FOOTNOTE_AREA_RATIO
|
|
2331
4176
|
);
|
|
2332
|
-
const footnotesDiv = document.createElement("div");
|
|
4177
|
+
const footnotesDiv = this.document.createElement("div");
|
|
2333
4178
|
footnotesDiv.className = `${this.cssPrefix}footnotes`;
|
|
2334
4179
|
footnotesDiv.style.position = "absolute";
|
|
2335
4180
|
footnotesDiv.style.bottom = `${dims.pageHeight - (bands.bodyTop + bands.bodyHeight)}pt`;
|
|
@@ -2338,35 +4183,18 @@ var PaginationEngine = class {
|
|
|
2338
4183
|
footnotesDiv.style.boxSizing = "border-box";
|
|
2339
4184
|
footnotesDiv.style.maxHeight = `${maxFootnoteHeight}pt`;
|
|
2340
4185
|
footnotesDiv.style.overflow = "hidden";
|
|
2341
|
-
|
|
2342
|
-
footnotesDiv.appendChild(hr);
|
|
4186
|
+
this.appendFootnoteSeparator(footnotesDiv, Boolean(hasContinuation));
|
|
2343
4187
|
if (hasContinuation) {
|
|
2344
|
-
|
|
2345
|
-
contWrapper.className = "footnote-continuation";
|
|
2346
|
-
for (const el of continuation.remainingElements) {
|
|
2347
|
-
contWrapper.appendChild(el.cloneNode(true));
|
|
2348
|
-
}
|
|
2349
|
-
footnotesDiv.appendChild(contWrapper);
|
|
4188
|
+
footnotesDiv.appendChild(this.createFootnoteContinuationWrapper(continuation));
|
|
2350
4189
|
}
|
|
2351
4190
|
for (const id of footnoteIds) {
|
|
2352
4191
|
const partial = partialFootnotes?.find((p) => p.footnoteId === id);
|
|
2353
4192
|
if (partial) {
|
|
2354
4193
|
const footnote = this.footnoteRegistry.get(id);
|
|
2355
4194
|
if (footnote) {
|
|
2356
|
-
|
|
2357
|
-
|
|
2358
|
-
|
|
2359
|
-
const numberSpan = footnote.querySelector(".footnote-number");
|
|
2360
|
-
if (numberSpan) {
|
|
2361
|
-
partialDiv.appendChild(numberSpan.cloneNode(true));
|
|
2362
|
-
}
|
|
2363
|
-
const contentSpan = document.createElement("span");
|
|
2364
|
-
contentSpan.className = "footnote-content";
|
|
2365
|
-
for (const el of partial.fittingElements) {
|
|
2366
|
-
contentSpan.appendChild(el.cloneNode(true));
|
|
2367
|
-
}
|
|
2368
|
-
partialDiv.appendChild(contentSpan);
|
|
2369
|
-
footnotesDiv.appendChild(partialDiv);
|
|
4195
|
+
footnotesDiv.appendChild(
|
|
4196
|
+
this.createPartialFootnoteItem(footnote, partial.fittingElements)
|
|
4197
|
+
);
|
|
2370
4198
|
}
|
|
2371
4199
|
} else {
|
|
2372
4200
|
const footnote = this.footnoteRegistry.get(id);
|
|
@@ -2377,30 +4205,26 @@ var PaginationEngine = class {
|
|
|
2377
4205
|
}
|
|
2378
4206
|
pageBox.appendChild(footnotesDiv);
|
|
2379
4207
|
}
|
|
2380
|
-
/**
|
|
2381
|
-
|
|
2382
|
-
*/
|
|
2383
|
-
selectHeader(sectionIndex, pageInSection, globalPageNumber) {
|
|
4208
|
+
/** Select the section's first/odd/even header from its one-based page position. */
|
|
4209
|
+
selectHeader(sectionIndex, pageInSection, displayedPageNumber) {
|
|
2384
4210
|
const sectionHf = this.hfRegistry.get(sectionIndex);
|
|
2385
4211
|
if (!sectionHf) return void 0;
|
|
2386
4212
|
if (pageInSection === 1 && sectionHf.headerFirst) {
|
|
2387
4213
|
return sectionHf.headerFirst;
|
|
2388
4214
|
}
|
|
2389
|
-
if (
|
|
4215
|
+
if (displayedPageNumber % 2 === 0 && sectionHf.headerEven) {
|
|
2390
4216
|
return sectionHf.headerEven;
|
|
2391
4217
|
}
|
|
2392
4218
|
return sectionHf.headerDefault;
|
|
2393
4219
|
}
|
|
2394
|
-
/**
|
|
2395
|
-
|
|
2396
|
-
*/
|
|
2397
|
-
selectFooter(sectionIndex, pageInSection, globalPageNumber) {
|
|
4220
|
+
/** Select the section's first/odd/even footer from its one-based page position. */
|
|
4221
|
+
selectFooter(sectionIndex, pageInSection, displayedPageNumber) {
|
|
2398
4222
|
const sectionHf = this.hfRegistry.get(sectionIndex);
|
|
2399
4223
|
if (!sectionHf) return void 0;
|
|
2400
4224
|
if (pageInSection === 1 && sectionHf.footerFirst) {
|
|
2401
4225
|
return sectionHf.footerFirst;
|
|
2402
4226
|
}
|
|
2403
|
-
if (
|
|
4227
|
+
if (displayedPageNumber % 2 === 0 && sectionHf.footerEven) {
|
|
2404
4228
|
return sectionHf.footerEven;
|
|
2405
4229
|
}
|
|
2406
4230
|
return sectionHf.footerDefault;
|
|
@@ -2415,7 +4239,7 @@ var PaginationEngine = class {
|
|
|
2415
4239
|
* Deterministic: it depends only on the section's page setup and the registry's pre-measured
|
|
2416
4240
|
* story heights, never on the page's content, which is what keeps it lazy-loading compatible.
|
|
2417
4241
|
*/
|
|
2418
|
-
getPageBands(dims, sectionIndex, pageInSection,
|
|
4242
|
+
getPageBands(dims, sectionIndex, pageInSection, displayedPageNumber) {
|
|
2419
4243
|
const sectionHf = this.hfRegistry.get(sectionIndex);
|
|
2420
4244
|
return resolvePageBands(
|
|
2421
4245
|
dims,
|
|
@@ -2424,14 +4248,14 @@ var PaginationEngine = class {
|
|
|
2424
4248
|
sectionHf?.headerEvenHeight,
|
|
2425
4249
|
sectionHf?.headerDefaultHeight,
|
|
2426
4250
|
pageInSection,
|
|
2427
|
-
|
|
4251
|
+
displayedPageNumber
|
|
2428
4252
|
),
|
|
2429
4253
|
this.selectStoryHeight(
|
|
2430
4254
|
sectionHf?.footerFirstHeight,
|
|
2431
4255
|
sectionHf?.footerEvenHeight,
|
|
2432
4256
|
sectionHf?.footerDefaultHeight,
|
|
2433
4257
|
pageInSection,
|
|
2434
|
-
|
|
4258
|
+
displayedPageNumber
|
|
2435
4259
|
)
|
|
2436
4260
|
);
|
|
2437
4261
|
}
|
|
@@ -2439,9 +4263,9 @@ var PaginationEngine = class {
|
|
|
2439
4263
|
* The measured height of the running story this page position selects, mirroring
|
|
2440
4264
|
* {@link selectHeader}/{@link selectFooter}. Zero when the page has no such story.
|
|
2441
4265
|
*/
|
|
2442
|
-
selectStoryHeight(first, even, fallback, pageInSection,
|
|
4266
|
+
selectStoryHeight(first, even, fallback, pageInSection, displayedPageNumber) {
|
|
2443
4267
|
if (pageInSection === 1 && first != null) return first;
|
|
2444
|
-
if (
|
|
4268
|
+
if (displayedPageNumber % 2 === 0 && even != null) return even;
|
|
2445
4269
|
return fallback ?? 0;
|
|
2446
4270
|
}
|
|
2447
4271
|
/**
|
|
@@ -2453,7 +4277,7 @@ var PaginationEngine = class {
|
|
|
2453
4277
|
* silently starts overlapping body text.
|
|
2454
4278
|
*/
|
|
2455
4279
|
measureHeaderFooterHeight(source, contentWidth) {
|
|
2456
|
-
const measureContainer = document.createElement("div");
|
|
4280
|
+
const measureContainer = this.document.createElement("div");
|
|
2457
4281
|
measureContainer.style.position = "absolute";
|
|
2458
4282
|
measureContainer.style.visibility = "hidden";
|
|
2459
4283
|
measureContainer.style.width = `${contentWidth}pt`;
|
|
@@ -2472,79 +4296,275 @@ var PaginationEngine = class {
|
|
|
2472
4296
|
* Implements a single-pass, forward-only algorithm that is compatible with future lazy loading.
|
|
2473
4297
|
* Supports footnote continuation - long footnotes can split across pages.
|
|
2474
4298
|
*/
|
|
2475
|
-
flowToPages(blocks, dims, startPageNumber, sectionIndex) {
|
|
4299
|
+
flowToPages(blocks, dims, startPageNumber, sectionIndex, sectionDimensions) {
|
|
2476
4300
|
const pages = [];
|
|
2477
4301
|
let currentContent = [];
|
|
2478
4302
|
let pageNumber = startPageNumber;
|
|
2479
|
-
let
|
|
4303
|
+
let pageSectionIndex = sectionIndex;
|
|
4304
|
+
const sectionStartPages = /* @__PURE__ */ new Map([[sectionIndex, startPageNumber]]);
|
|
4305
|
+
const pageInSection = (owner = pageSectionIndex, physicalPage = pageNumber) => physicalPage - (sectionStartPages.get(owner) ?? physicalPage) + 1;
|
|
4306
|
+
const dimensionsFor = (owner = pageSectionIndex) => sectionDimensions.get(owner) ?? dims;
|
|
4307
|
+
const markSectionPlaced = (owner) => {
|
|
4308
|
+
if (!sectionStartPages.has(owner)) sectionStartPages.set(owner, pageNumber);
|
|
4309
|
+
};
|
|
4310
|
+
const previousBox = this.containerElement.querySelector(
|
|
4311
|
+
`.${this.cssPrefix}box:last-of-type`
|
|
4312
|
+
);
|
|
4313
|
+
let precedingDisplayedPageNumber = parseInt(
|
|
4314
|
+
previousBox?.dataset.displayedPageNumber ?? "0",
|
|
4315
|
+
10
|
|
4316
|
+
);
|
|
4317
|
+
const displayedPageNumber = (owner = pageSectionIndex, ownerPageInSection = pageInSection(owner), physicalPage = pageNumber) => {
|
|
4318
|
+
const numbering = this.pageNumbering.get(owner) ?? {};
|
|
4319
|
+
if (numbering.start !== void 0) {
|
|
4320
|
+
return numbering.start + ownerPageInSection - 1;
|
|
4321
|
+
}
|
|
4322
|
+
const ownerNumbering = this.pageNumbering.get(pageSectionIndex) ?? {};
|
|
4323
|
+
const currentPhysicalNumber = ownerNumbering.start !== void 0 ? ownerNumbering.start + pageInSection(pageSectionIndex) - 1 : precedingDisplayedPageNumber + 1;
|
|
4324
|
+
return currentPhysicalNumber + physicalPage - pageNumber;
|
|
4325
|
+
};
|
|
2480
4326
|
let { bodyHeight: effectiveContentHeight } = this.getPageBands(
|
|
2481
|
-
|
|
2482
|
-
|
|
2483
|
-
pageInSection,
|
|
2484
|
-
|
|
4327
|
+
dimensionsFor(),
|
|
4328
|
+
pageSectionIndex,
|
|
4329
|
+
pageInSection(),
|
|
4330
|
+
displayedPageNumber()
|
|
2485
4331
|
);
|
|
2486
4332
|
let remainingHeight = effectiveContentHeight;
|
|
2487
4333
|
let prevMarginBottomPt = 0;
|
|
2488
4334
|
let currentFootnoteIds = [];
|
|
4335
|
+
let currentPageHasFootnoteReference = false;
|
|
2489
4336
|
let currentFootnoteHeight = 0;
|
|
2490
4337
|
let currentContinuation = this.pendingFootnoteContinuation;
|
|
2491
4338
|
let nextPageContinuation = null;
|
|
4339
|
+
let currentContinuationPartitioned = false;
|
|
4340
|
+
let currentPageAdmitted = false;
|
|
2492
4341
|
let deferredFootnoteIds = [];
|
|
2493
4342
|
let currentPartialFootnotes = [];
|
|
2494
|
-
|
|
2495
|
-
|
|
2496
|
-
|
|
2497
|
-
|
|
4343
|
+
const adoptEmptyPageOwner = (owner) => {
|
|
4344
|
+
pageSectionIndex = owner;
|
|
4345
|
+
const bands = this.getPageBands(
|
|
4346
|
+
dimensionsFor(owner),
|
|
4347
|
+
owner,
|
|
4348
|
+
pageInSection(owner),
|
|
4349
|
+
displayedPageNumber(owner)
|
|
4350
|
+
);
|
|
4351
|
+
effectiveContentHeight = bands.bodyHeight;
|
|
4352
|
+
remainingHeight = effectiveContentHeight;
|
|
4353
|
+
};
|
|
4354
|
+
const admitCurrentPage = () => {
|
|
4355
|
+
if (currentPageAdmitted) return;
|
|
4356
|
+
this.admitPageAllocation();
|
|
4357
|
+
currentPageAdmitted = true;
|
|
4358
|
+
};
|
|
4359
|
+
const prepareCurrentContinuation = () => {
|
|
4360
|
+
if (currentContinuationPartitioned || (currentContinuation?.remainingElements.length ?? 0) === 0) return;
|
|
4361
|
+
admitCurrentPage();
|
|
4362
|
+
const ownedDimensions = dimensionsFor();
|
|
4363
|
+
const pageBands = this.getPageBands(
|
|
4364
|
+
ownedDimensions,
|
|
4365
|
+
pageSectionIndex,
|
|
4366
|
+
pageInSection(),
|
|
4367
|
+
displayedPageNumber()
|
|
4368
|
+
);
|
|
4369
|
+
const partition = this.splitContinuationForPage(
|
|
4370
|
+
currentContinuation,
|
|
4371
|
+
pageBands.bodyHeight * MAX_FOOTNOTE_AREA_RATIO,
|
|
4372
|
+
ownedDimensions.contentWidth
|
|
4373
|
+
);
|
|
4374
|
+
currentContinuation = partition.current;
|
|
4375
|
+
if (partition.overflow) nextPageContinuation = partition.overflow;
|
|
4376
|
+
currentContinuationPartitioned = true;
|
|
4377
|
+
currentFootnoteHeight = this.measureFootnotesHeight(
|
|
4378
|
+
currentFootnoteIds,
|
|
4379
|
+
ownedDimensions.contentWidth,
|
|
4380
|
+
currentContinuation,
|
|
4381
|
+
currentPartialFootnotes
|
|
4382
|
+
);
|
|
4383
|
+
};
|
|
4384
|
+
const finishPage = (nextPageSectionIndex = pageSectionIndex, forceEmptyPage = false) => {
|
|
2498
4385
|
const hasCurrentContinuation = (currentContinuation?.remainingElements.length ?? 0) > 0;
|
|
2499
|
-
if (currentContent.length === 0 && !hasCurrentContinuation)
|
|
4386
|
+
if (!forceEmptyPage && currentContent.length === 0 && currentFootnoteIds.length === 0 && !hasCurrentContinuation) {
|
|
4387
|
+
adoptEmptyPageOwner(nextPageSectionIndex);
|
|
4388
|
+
return;
|
|
4389
|
+
}
|
|
4390
|
+
prepareCurrentContinuation();
|
|
4391
|
+
admitCurrentPage();
|
|
4392
|
+
let pageContinuation = currentContinuation;
|
|
4393
|
+
const ownedPageInSection = pageInSection();
|
|
4394
|
+
const ownedDimensions = dimensionsFor();
|
|
4395
|
+
const ownedDisplayedPageNumber = displayedPageNumber(
|
|
4396
|
+
pageSectionIndex,
|
|
4397
|
+
ownedPageInSection
|
|
4398
|
+
);
|
|
4399
|
+
const pageBands = this.getPageBands(
|
|
4400
|
+
ownedDimensions,
|
|
4401
|
+
pageSectionIndex,
|
|
4402
|
+
ownedPageInSection,
|
|
4403
|
+
ownedDisplayedPageNumber
|
|
4404
|
+
);
|
|
4405
|
+
const maxFootnoteHeight = pageBands.bodyHeight * MAX_FOOTNOTE_AREA_RATIO;
|
|
4406
|
+
pageContinuation = currentContinuation;
|
|
4407
|
+
currentFootnoteHeight = this.measureFootnotesHeight(
|
|
4408
|
+
currentFootnoteIds,
|
|
4409
|
+
ownedDimensions.contentWidth,
|
|
4410
|
+
pageContinuation,
|
|
4411
|
+
currentPartialFootnotes
|
|
4412
|
+
);
|
|
4413
|
+
if (currentContent.length === 0 && currentFootnoteIds.length > 0 && currentPartialFootnotes.length === 0) {
|
|
4414
|
+
const fittingIds = [];
|
|
4415
|
+
for (let index = 0; index < currentFootnoteIds.length; index++) {
|
|
4416
|
+
const footnoteId = currentFootnoteIds[index];
|
|
4417
|
+
const candidateIds = [...fittingIds, footnoteId];
|
|
4418
|
+
const candidateHeight = this.measureFootnotesHeight(
|
|
4419
|
+
candidateIds,
|
|
4420
|
+
ownedDimensions.contentWidth,
|
|
4421
|
+
pageContinuation
|
|
4422
|
+
);
|
|
4423
|
+
const guardedCandidateHeight = candidateHeight + FOOTNOTE_MEASUREMENT_GUARD_PT;
|
|
4424
|
+
if (guardedCandidateHeight <= maxFootnoteHeight) {
|
|
4425
|
+
fittingIds.push(footnoteId);
|
|
4426
|
+
currentFootnoteHeight = guardedCandidateHeight;
|
|
4427
|
+
continue;
|
|
4428
|
+
}
|
|
4429
|
+
const hasPageContinuation = (pageContinuation?.remainingElements.length ?? 0) > 0;
|
|
4430
|
+
if (fittingIds.length === 0 && !hasPageContinuation) {
|
|
4431
|
+
const source = this.footnoteRegistry.get(footnoteId);
|
|
4432
|
+
const split = source ? this.splitFootnoteToFit(
|
|
4433
|
+
source,
|
|
4434
|
+
maxFootnoteHeight - FOOTNOTE_MEASUREMENT_GUARD_PT,
|
|
4435
|
+
ownedDimensions.contentWidth,
|
|
4436
|
+
true
|
|
4437
|
+
) : null;
|
|
4438
|
+
fittingIds.push(footnoteId);
|
|
4439
|
+
if (source && split && split.fits.length > 0 && split.overflow.length > 0) {
|
|
4440
|
+
currentPartialFootnotes.push({ footnoteId, fittingElements: split.fits });
|
|
4441
|
+
nextPageContinuation = {
|
|
4442
|
+
footnoteId,
|
|
4443
|
+
sourceAnchorId: source.dataset.sourceAnchorId,
|
|
4444
|
+
remainingElements: split.overflow
|
|
4445
|
+
};
|
|
4446
|
+
currentFootnoteHeight = maxFootnoteHeight;
|
|
4447
|
+
} else {
|
|
4448
|
+
currentFootnoteHeight = guardedCandidateHeight;
|
|
4449
|
+
}
|
|
4450
|
+
deferredFootnoteIds.push(...currentFootnoteIds.slice(index + 1));
|
|
4451
|
+
} else {
|
|
4452
|
+
deferredFootnoteIds.push(...currentFootnoteIds.slice(index));
|
|
4453
|
+
}
|
|
4454
|
+
break;
|
|
4455
|
+
}
|
|
4456
|
+
currentFootnoteIds = fittingIds;
|
|
4457
|
+
}
|
|
2500
4458
|
const page = this.createPage(
|
|
2501
|
-
|
|
4459
|
+
ownedDimensions,
|
|
2502
4460
|
pageNumber,
|
|
2503
|
-
|
|
4461
|
+
pageSectionIndex,
|
|
4462
|
+
ownedDisplayedPageNumber,
|
|
2504
4463
|
currentContent,
|
|
2505
|
-
|
|
4464
|
+
ownedPageInSection,
|
|
2506
4465
|
currentFootnoteIds,
|
|
2507
4466
|
currentFootnoteHeight,
|
|
2508
|
-
|
|
2509
|
-
currentPartialFootnotes.length > 0 ? currentPartialFootnotes : void 0
|
|
4467
|
+
pageContinuation,
|
|
4468
|
+
currentPartialFootnotes.length > 0 ? currentPartialFootnotes : void 0,
|
|
4469
|
+
false,
|
|
4470
|
+
currentPageAdmitted
|
|
2510
4471
|
);
|
|
2511
4472
|
pages.push(page);
|
|
4473
|
+
precedingDisplayedPageNumber = ownedDisplayedPageNumber;
|
|
2512
4474
|
pageNumber++;
|
|
2513
|
-
pageInSection++;
|
|
2514
4475
|
currentContent = [];
|
|
2515
|
-
|
|
4476
|
+
pageSectionIndex = nextPageSectionIndex;
|
|
4477
|
+
if (!sectionStartPages.has(pageSectionIndex)) {
|
|
4478
|
+
sectionStartPages.set(pageSectionIndex, pageNumber);
|
|
4479
|
+
}
|
|
4480
|
+
const newBands = this.getPageBands(
|
|
4481
|
+
dimensionsFor(),
|
|
4482
|
+
pageSectionIndex,
|
|
4483
|
+
pageInSection(),
|
|
4484
|
+
displayedPageNumber()
|
|
4485
|
+
);
|
|
2516
4486
|
effectiveContentHeight = newBands.bodyHeight;
|
|
2517
4487
|
remainingHeight = effectiveContentHeight;
|
|
2518
4488
|
prevMarginBottomPt = 0;
|
|
2519
4489
|
currentFootnoteIds = [];
|
|
4490
|
+
currentPageHasFootnoteReference = false;
|
|
2520
4491
|
currentPartialFootnotes = [];
|
|
2521
4492
|
currentContinuation = nextPageContinuation;
|
|
2522
4493
|
nextPageContinuation = null;
|
|
4494
|
+
currentContinuationPartitioned = false;
|
|
4495
|
+
currentPageAdmitted = false;
|
|
2523
4496
|
if (deferredFootnoteIds.length > 0) {
|
|
2524
4497
|
currentFootnoteIds = [...deferredFootnoteIds];
|
|
2525
4498
|
deferredFootnoteIds = [];
|
|
2526
4499
|
}
|
|
2527
|
-
|
|
2528
|
-
|
|
2529
|
-
} else {
|
|
2530
|
-
currentFootnoteHeight = 0;
|
|
2531
|
-
}
|
|
2532
|
-
if (currentFootnoteIds.length > 0) {
|
|
2533
|
-
currentFootnoteHeight += this.measureFootnotesHeight(
|
|
2534
|
-
currentFootnoteIds,
|
|
2535
|
-
dims.contentWidth,
|
|
2536
|
-
null
|
|
2537
|
-
);
|
|
2538
|
-
}
|
|
4500
|
+
const nextDimensions = dimensionsFor();
|
|
4501
|
+
currentFootnoteHeight = currentContinuation ? 0 : this.measureFootnotesHeight(currentFootnoteIds, nextDimensions.contentWidth);
|
|
2539
4502
|
};
|
|
4503
|
+
const pageHasPayload = () => currentContent.length > 0 || currentFootnoteIds.length > 0 || (currentContinuation?.remainingElements.length ?? 0) > 0;
|
|
2540
4504
|
for (let i = 0; i < blocks.length; i++) {
|
|
4505
|
+
this.checkpoint();
|
|
2541
4506
|
const block = blocks[i];
|
|
4507
|
+
const allBlockFootnoteIds = this.extractFootnoteRefs(block.element);
|
|
4508
|
+
if (block.sectionIndex !== pageSectionIndex && currentPageHasFootnoteReference && (!this.footnoteLayoutLikeWord8 || allBlockFootnoteIds.length > 0)) {
|
|
4509
|
+
finishPage(block.sectionIndex);
|
|
4510
|
+
}
|
|
4511
|
+
const pageIsEmpty = currentContent.length === 0 && currentFootnoteIds.length === 0 && (currentContinuation?.remainingElements.length ?? 0) === 0;
|
|
4512
|
+
if (pageIsEmpty && block.sectionIndex !== pageSectionIndex) {
|
|
4513
|
+
adoptEmptyPageOwner(block.sectionIndex);
|
|
4514
|
+
}
|
|
4515
|
+
prepareCurrentContinuation();
|
|
4516
|
+
if (currentFootnoteHeight > 0) {
|
|
4517
|
+
const ownerDimensions = dimensionsFor();
|
|
4518
|
+
const ownerBands = this.getPageBands(
|
|
4519
|
+
ownerDimensions,
|
|
4520
|
+
pageSectionIndex,
|
|
4521
|
+
pageInSection(),
|
|
4522
|
+
displayedPageNumber()
|
|
4523
|
+
);
|
|
4524
|
+
const ownerMaxFootnoteArea = ownerBands.bodyHeight * MAX_FOOTNOTE_AREA_RATIO;
|
|
4525
|
+
if (currentFootnoteHeight > ownerMaxFootnoteArea) {
|
|
4526
|
+
const fittingIds = [];
|
|
4527
|
+
let fittingHeight = 0;
|
|
4528
|
+
if ((currentContinuation?.remainingElements.length ?? 0) === 0 && currentPartialFootnotes.length === 0) {
|
|
4529
|
+
for (const footnoteId of currentFootnoteIds) {
|
|
4530
|
+
this.checkpoint();
|
|
4531
|
+
const candidateHeight = this.measureFootnotesHeight(
|
|
4532
|
+
[...fittingIds, footnoteId],
|
|
4533
|
+
ownerDimensions.contentWidth
|
|
4534
|
+
);
|
|
4535
|
+
if (candidateHeight + FOOTNOTE_MEASUREMENT_GUARD_PT > ownerMaxFootnoteArea) break;
|
|
4536
|
+
fittingIds.push(footnoteId);
|
|
4537
|
+
fittingHeight = candidateHeight;
|
|
4538
|
+
}
|
|
4539
|
+
}
|
|
4540
|
+
if (fittingIds.length > 0) {
|
|
4541
|
+
deferredFootnoteIds = [
|
|
4542
|
+
...currentFootnoteIds.slice(fittingIds.length),
|
|
4543
|
+
...deferredFootnoteIds
|
|
4544
|
+
];
|
|
4545
|
+
currentFootnoteIds = fittingIds;
|
|
4546
|
+
currentFootnoteHeight = fittingHeight;
|
|
4547
|
+
} else {
|
|
4548
|
+
finishPage(block.sectionIndex);
|
|
4549
|
+
i--;
|
|
4550
|
+
continue;
|
|
4551
|
+
}
|
|
4552
|
+
}
|
|
4553
|
+
}
|
|
4554
|
+
const blockDimensions = dimensionsFor(block.sectionIndex);
|
|
4555
|
+
const nextBlockPageInSection = pageInSection(block.sectionIndex, pageNumber + 1);
|
|
4556
|
+
const freshBlockPageBodyHeight = this.getPageBands(
|
|
4557
|
+
blockDimensions,
|
|
4558
|
+
block.sectionIndex,
|
|
4559
|
+
nextBlockPageInSection,
|
|
4560
|
+
displayedPageNumber(block.sectionIndex, nextBlockPageInSection, pageNumber + 1)
|
|
4561
|
+
).bodyHeight;
|
|
2542
4562
|
if (block.isPageBreak) {
|
|
2543
|
-
finishPage();
|
|
4563
|
+
finishPage(block.sectionIndex);
|
|
2544
4564
|
continue;
|
|
2545
4565
|
}
|
|
2546
4566
|
if (block.pageBreakBefore && currentContent.length > 0) {
|
|
2547
|
-
finishPage();
|
|
4567
|
+
finishPage(block.sectionIndex);
|
|
2548
4568
|
}
|
|
2549
4569
|
const previousBlock = blocks[i - 1];
|
|
2550
4570
|
const startsKeepChain = !previousBlock || !previousBlock.keepWithNext || previousBlock.isPageBreak || block.pageBreakBefore;
|
|
@@ -2560,7 +4580,7 @@ var PaginationEngine = class {
|
|
|
2560
4580
|
if (newChainFootnoteIds.length > 0 && this.footnoteRegistry.size > 0) {
|
|
2561
4581
|
const totalChainFootnoteHeight = this.measureFootnotesHeight(
|
|
2562
4582
|
[...currentFootnoteIds, ...newChainFootnoteIds],
|
|
2563
|
-
|
|
4583
|
+
blockDimensions.contentWidth,
|
|
2564
4584
|
currentContinuation
|
|
2565
4585
|
);
|
|
2566
4586
|
additionalChainFootnoteHeight = Math.max(
|
|
@@ -2572,61 +4592,68 @@ var PaginationEngine = class {
|
|
|
2572
4592
|
keepChain,
|
|
2573
4593
|
prevMarginBottomPt,
|
|
2574
4594
|
currentContent.length === 0,
|
|
2575
|
-
pageInSection
|
|
4595
|
+
pageInSection(block.sectionIndex)
|
|
2576
4596
|
) + additionalChainFootnoteHeight;
|
|
2577
4597
|
const currentAvailableHeight = remainingHeight - currentFootnoteHeight;
|
|
2578
4598
|
if (currentChainHeight > currentAvailableHeight) {
|
|
2579
4599
|
const nextPageBands = this.getPageBands(
|
|
2580
|
-
|
|
2581
|
-
sectionIndex,
|
|
2582
|
-
pageInSection + 1,
|
|
2583
|
-
|
|
4600
|
+
dimensionsFor(block.sectionIndex),
|
|
4601
|
+
block.sectionIndex,
|
|
4602
|
+
pageInSection(block.sectionIndex, pageNumber + 1),
|
|
4603
|
+
displayedPageNumber(
|
|
4604
|
+
block.sectionIndex,
|
|
4605
|
+
pageInSection(block.sectionIndex, pageNumber + 1),
|
|
4606
|
+
pageNumber + 1
|
|
4607
|
+
)
|
|
2584
4608
|
);
|
|
2585
4609
|
const freshChainBodyHeight = this.measureKeepWithNextChainBodyHeight(
|
|
2586
4610
|
keepChain,
|
|
2587
4611
|
0,
|
|
2588
4612
|
true,
|
|
2589
|
-
pageInSection + 1
|
|
4613
|
+
pageInSection(block.sectionIndex, pageNumber + 1)
|
|
2590
4614
|
);
|
|
2591
4615
|
const freshChainFootnoteHeight = this.measureFootnotesHeight(
|
|
2592
4616
|
newChainFootnoteIds,
|
|
2593
|
-
|
|
4617
|
+
blockDimensions.contentWidth,
|
|
2594
4618
|
nextPageContinuation
|
|
2595
4619
|
);
|
|
2596
4620
|
if (freshChainBodyHeight + freshChainFootnoteHeight <= nextPageBands.bodyHeight) {
|
|
2597
|
-
finishPage();
|
|
4621
|
+
finishPage(block.sectionIndex);
|
|
2598
4622
|
}
|
|
2599
4623
|
}
|
|
2600
4624
|
}
|
|
2601
4625
|
}
|
|
2602
|
-
const allBlockFootnoteIds = this.extractFootnoteRefs(block.element);
|
|
2603
4626
|
const newFootnoteIds = this.collectNewFootnoteIds([block], currentFootnoteIds);
|
|
4627
|
+
let combinedFootnoteHeight = currentFootnoteHeight;
|
|
2604
4628
|
let additionalFootnoteHeight = 0;
|
|
2605
4629
|
if (newFootnoteIds.length > 0 && this.footnoteRegistry.size > 0) {
|
|
2606
4630
|
const combinedFootnoteIds = [...currentFootnoteIds, ...newFootnoteIds];
|
|
2607
|
-
|
|
4631
|
+
combinedFootnoteHeight = this.measureFootnotesHeight(
|
|
2608
4632
|
combinedFootnoteIds,
|
|
2609
|
-
|
|
2610
|
-
currentContinuation
|
|
4633
|
+
blockDimensions.contentWidth,
|
|
4634
|
+
currentContinuation,
|
|
4635
|
+
currentPartialFootnotes
|
|
4636
|
+
);
|
|
4637
|
+
additionalFootnoteHeight = Math.max(
|
|
4638
|
+
0,
|
|
4639
|
+
combinedFootnoteHeight - currentFootnoteHeight
|
|
2611
4640
|
);
|
|
2612
|
-
additionalFootnoteHeight = totalFootnoteHeight - currentFootnoteHeight;
|
|
2613
4641
|
}
|
|
2614
4642
|
const isFirstOnPage = currentContent.length === 0;
|
|
2615
4643
|
const effectiveMarginTop = this.effectiveBlockMarginTop(
|
|
2616
4644
|
block,
|
|
2617
4645
|
prevMarginBottomPt,
|
|
2618
4646
|
isFirstOnPage,
|
|
2619
|
-
pageInSection
|
|
4647
|
+
pageInSection(block.sectionIndex)
|
|
2620
4648
|
);
|
|
2621
4649
|
const blockSpace = effectiveMarginTop + block.heightPt + additionalFootnoteHeight;
|
|
2622
4650
|
const effectiveRemainingHeight = remainingHeight - currentFootnoteHeight;
|
|
2623
4651
|
const bodyContentUsed = effectiveContentHeight - remainingHeight;
|
|
2624
4652
|
const maxFootnoteArea = effectiveContentHeight * MAX_FOOTNOTE_AREA_RATIO;
|
|
2625
|
-
const maxFootnoteExpansion = Math.max(0, maxFootnoteArea - currentFootnoteHeight);
|
|
2626
4653
|
if (blockSpace > effectiveRemainingHeight) {
|
|
2627
4654
|
const paragraphFragments = this.tryFragmentParagraph(
|
|
2628
4655
|
block,
|
|
2629
|
-
|
|
4656
|
+
blockDimensions,
|
|
2630
4657
|
effectiveRemainingHeight,
|
|
2631
4658
|
effectiveMarginTop
|
|
2632
4659
|
);
|
|
@@ -2636,40 +4663,74 @@ var PaginationEngine = class {
|
|
|
2636
4663
|
continue;
|
|
2637
4664
|
}
|
|
2638
4665
|
}
|
|
2639
|
-
if (blockSpace <= effectiveRemainingHeight) {
|
|
2640
|
-
|
|
4666
|
+
if (blockSpace <= effectiveRemainingHeight && (combinedFootnoteHeight === 0 || combinedFootnoteHeight + FOOTNOTE_MEASUREMENT_GUARD_PT <= maxFootnoteArea)) {
|
|
4667
|
+
markSectionPlaced(block.sectionIndex);
|
|
4668
|
+
currentContent.push(this.cloneBlockForPage(
|
|
4669
|
+
block,
|
|
4670
|
+
isFirstOnPage,
|
|
4671
|
+
pageInSection(block.sectionIndex)
|
|
4672
|
+
));
|
|
2641
4673
|
remainingHeight -= effectiveMarginTop + block.heightPt + block.marginBottomPt;
|
|
2642
4674
|
prevMarginBottomPt = block.marginBottomPt;
|
|
2643
4675
|
if (newFootnoteIds.length > 0) {
|
|
2644
4676
|
currentFootnoteIds.push(...newFootnoteIds);
|
|
2645
|
-
currentFootnoteHeight
|
|
4677
|
+
currentFootnoteHeight = combinedFootnoteHeight;
|
|
2646
4678
|
}
|
|
2647
|
-
|
|
4679
|
+
currentPageHasFootnoteReference ||= allBlockFootnoteIds.length > 0;
|
|
4680
|
+
} else if (block.heightPt + this.effectiveBlockMarginTop(
|
|
4681
|
+
block,
|
|
4682
|
+
0,
|
|
4683
|
+
true,
|
|
4684
|
+
pageInSection(block.sectionIndex, pageNumber + 1)
|
|
4685
|
+
) <= freshBlockPageBodyHeight) {
|
|
2648
4686
|
const blockSpaceWithoutFootnotes = effectiveMarginTop + block.heightPt;
|
|
2649
|
-
const minBodySpaceNeeded = bodyContentUsed + blockSpaceWithoutFootnotes + MIN_BODY_CONTENT_HEIGHT;
|
|
2650
|
-
const canExpandFootnotes = minBodySpaceNeeded <= effectiveContentHeight;
|
|
2651
4687
|
if (newFootnoteIds.length > 0 && blockSpaceWithoutFootnotes <= effectiveRemainingHeight) {
|
|
2652
|
-
|
|
4688
|
+
markSectionPlaced(block.sectionIndex);
|
|
4689
|
+
currentContent.push(this.cloneBlockForPage(
|
|
4690
|
+
block,
|
|
4691
|
+
isFirstOnPage,
|
|
4692
|
+
pageInSection(block.sectionIndex)
|
|
4693
|
+
));
|
|
2653
4694
|
remainingHeight -= effectiveMarginTop + block.heightPt + block.marginBottomPt;
|
|
2654
4695
|
prevMarginBottomPt = block.marginBottomPt;
|
|
2655
4696
|
const availableForFootnotes = Math.min(
|
|
2656
4697
|
maxFootnoteArea,
|
|
2657
4698
|
effectiveContentHeight - bodyContentUsed - blockSpaceWithoutFootnotes
|
|
2658
4699
|
);
|
|
2659
|
-
|
|
4700
|
+
const nextPageMaxFootnoteArea = freshBlockPageBodyHeight * MAX_FOOTNOTE_AREA_RATIO;
|
|
4701
|
+
const deferralCannotHelp = availableForFootnotes >= nextPageMaxFootnoteArea;
|
|
4702
|
+
for (let noteIndex = 0; noteIndex < newFootnoteIds.length; noteIndex++) {
|
|
4703
|
+
this.checkpoint();
|
|
4704
|
+
const footnoteId = newFootnoteIds[noteIndex];
|
|
2660
4705
|
const footnote = this.footnoteRegistry.get(footnoteId);
|
|
2661
4706
|
if (!footnote) continue;
|
|
2662
|
-
|
|
4707
|
+
if (nextPageContinuation) {
|
|
4708
|
+
deferredFootnoteIds.push(...newFootnoteIds.slice(noteIndex));
|
|
4709
|
+
break;
|
|
4710
|
+
}
|
|
4711
|
+
const candidateIds = [...currentFootnoteIds, footnoteId];
|
|
4712
|
+
const candidateHeight = this.measureFootnotesHeight(
|
|
4713
|
+
candidateIds,
|
|
4714
|
+
blockDimensions.contentWidth,
|
|
4715
|
+
currentContinuation,
|
|
4716
|
+
currentPartialFootnotes
|
|
4717
|
+
);
|
|
2663
4718
|
const spaceLeftForFootnotes = availableForFootnotes - currentFootnoteHeight;
|
|
2664
|
-
if (
|
|
4719
|
+
if (candidateHeight + FOOTNOTE_MEASUREMENT_GUARD_PT <= availableForFootnotes) {
|
|
2665
4720
|
currentFootnoteIds.push(footnoteId);
|
|
2666
|
-
currentFootnoteHeight
|
|
4721
|
+
currentFootnoteHeight = candidateHeight;
|
|
2667
4722
|
} else {
|
|
2668
4723
|
if (spaceLeftForFootnotes > 20) {
|
|
2669
4724
|
const { fits, overflow } = this.splitFootnoteToFit(
|
|
2670
4725
|
footnote,
|
|
2671
|
-
|
|
2672
|
-
|
|
4726
|
+
availableForFootnotes,
|
|
4727
|
+
blockDimensions.contentWidth,
|
|
4728
|
+
deferralCannotHelp,
|
|
4729
|
+
{
|
|
4730
|
+
footnoteIds: currentFootnoteIds,
|
|
4731
|
+
continuation: currentContinuation,
|
|
4732
|
+
partialFootnotes: currentPartialFootnotes
|
|
4733
|
+
}
|
|
2673
4734
|
);
|
|
2674
4735
|
if (fits.length > 0) {
|
|
2675
4736
|
currentFootnoteIds.push(footnoteId);
|
|
@@ -2680,78 +4741,70 @@ var PaginationEngine = class {
|
|
|
2680
4741
|
if (overflow.length > 0) {
|
|
2681
4742
|
nextPageContinuation = {
|
|
2682
4743
|
footnoteId,
|
|
4744
|
+
sourceAnchorId: footnote.dataset.sourceAnchorId,
|
|
2683
4745
|
remainingElements: overflow
|
|
2684
4746
|
};
|
|
2685
4747
|
}
|
|
2686
|
-
currentFootnoteHeight =
|
|
4748
|
+
currentFootnoteHeight = this.measureFootnotesHeight(
|
|
4749
|
+
currentFootnoteIds,
|
|
4750
|
+
blockDimensions.contentWidth,
|
|
4751
|
+
currentContinuation,
|
|
4752
|
+
currentPartialFootnotes
|
|
4753
|
+
);
|
|
4754
|
+
if (overflow.length > 0) {
|
|
4755
|
+
deferredFootnoteIds.push(...newFootnoteIds.slice(noteIndex + 1));
|
|
4756
|
+
break;
|
|
4757
|
+
}
|
|
2687
4758
|
} else {
|
|
2688
|
-
deferredFootnoteIds.push(
|
|
4759
|
+
deferredFootnoteIds.push(...newFootnoteIds.slice(noteIndex));
|
|
4760
|
+
break;
|
|
2689
4761
|
}
|
|
2690
4762
|
} else {
|
|
2691
|
-
deferredFootnoteIds.push(
|
|
4763
|
+
deferredFootnoteIds.push(...newFootnoteIds.slice(noteIndex));
|
|
4764
|
+
break;
|
|
2692
4765
|
}
|
|
2693
4766
|
}
|
|
2694
4767
|
}
|
|
2695
|
-
|
|
2696
|
-
const expandedFootnoteSpace = Math.min(maxFootnoteArea, additionalFootnoteHeight + currentFootnoteHeight);
|
|
2697
|
-
const bodySpaceAfterExpansion = effectiveContentHeight - expandedFootnoteSpace;
|
|
2698
|
-
if (blockSpaceWithoutFootnotes <= bodySpaceAfterExpansion - bodyContentUsed) {
|
|
2699
|
-
currentContent.push(this.cloneBlockForPage(block, isFirstOnPage, pageInSection));
|
|
2700
|
-
remainingHeight = bodySpaceAfterExpansion - bodyContentUsed - blockSpaceWithoutFootnotes;
|
|
2701
|
-
prevMarginBottomPt = block.marginBottomPt;
|
|
2702
|
-
currentFootnoteIds.push(...newFootnoteIds);
|
|
2703
|
-
currentFootnoteHeight = expandedFootnoteSpace;
|
|
2704
|
-
} else {
|
|
2705
|
-
finishPage();
|
|
2706
|
-
const newPageFootnoteHeight = allBlockFootnoteIds.length > 0 ? this.measureFootnotesHeight(allBlockFootnoteIds, dims.contentWidth, currentContinuation) : currentContinuation ? this.measureContinuationHeight(currentContinuation, dims.contentWidth) : 0;
|
|
2707
|
-
const newPageMarginTop = this.effectiveBlockMarginTop(
|
|
2708
|
-
block,
|
|
2709
|
-
0,
|
|
2710
|
-
true,
|
|
2711
|
-
pageInSection
|
|
2712
|
-
);
|
|
2713
|
-
const newPageSpace = newPageMarginTop + block.heightPt + block.marginBottomPt;
|
|
2714
|
-
currentContent.push(this.cloneBlockForPage(block, true, pageInSection));
|
|
2715
|
-
remainingHeight = effectiveContentHeight - newPageSpace;
|
|
2716
|
-
prevMarginBottomPt = block.marginBottomPt;
|
|
2717
|
-
currentFootnoteIds = [...currentFootnoteIds, ...allBlockFootnoteIds];
|
|
2718
|
-
currentFootnoteHeight = newPageFootnoteHeight;
|
|
2719
|
-
}
|
|
4768
|
+
currentPageHasFootnoteReference ||= allBlockFootnoteIds.length > 0;
|
|
2720
4769
|
} else {
|
|
2721
|
-
finishPage();
|
|
2722
|
-
|
|
2723
|
-
|
|
2724
|
-
block,
|
|
2725
|
-
0,
|
|
2726
|
-
true,
|
|
2727
|
-
pageInSection
|
|
2728
|
-
);
|
|
2729
|
-
const newPageSpace = newPageMarginTop + block.heightPt + block.marginBottomPt;
|
|
2730
|
-
currentContent.push(this.cloneBlockForPage(block, true, pageInSection));
|
|
2731
|
-
remainingHeight = effectiveContentHeight - newPageSpace;
|
|
2732
|
-
prevMarginBottomPt = block.marginBottomPt;
|
|
2733
|
-
currentFootnoteIds = [...currentFootnoteIds, ...allBlockFootnoteIds];
|
|
2734
|
-
currentFootnoteHeight = newPageFootnoteHeight;
|
|
4770
|
+
finishPage(block.sectionIndex, !pageHasPayload());
|
|
4771
|
+
i--;
|
|
4772
|
+
continue;
|
|
2735
4773
|
}
|
|
2736
4774
|
} else {
|
|
2737
|
-
const tableFragments = this.trySplitSimpleOversizedTable(
|
|
4775
|
+
const tableFragments = this.trySplitSimpleOversizedTable(
|
|
4776
|
+
block,
|
|
4777
|
+
dimensionsFor(block.sectionIndex),
|
|
4778
|
+
block.sectionIndex
|
|
4779
|
+
);
|
|
2738
4780
|
if (tableFragments) {
|
|
2739
4781
|
if (currentContent.length > 0 || currentContinuation) {
|
|
2740
|
-
finishPage();
|
|
4782
|
+
finishPage(block.sectionIndex);
|
|
2741
4783
|
}
|
|
2742
4784
|
blocks.splice(i, 1, ...tableFragments);
|
|
2743
4785
|
i--;
|
|
2744
4786
|
continue;
|
|
2745
4787
|
}
|
|
2746
|
-
if (
|
|
2747
|
-
finishPage();
|
|
4788
|
+
if (pageHasPayload()) {
|
|
4789
|
+
finishPage(block.sectionIndex);
|
|
4790
|
+
i--;
|
|
4791
|
+
continue;
|
|
2748
4792
|
}
|
|
2749
|
-
|
|
2750
|
-
|
|
2751
|
-
|
|
4793
|
+
markSectionPlaced(block.sectionIndex);
|
|
4794
|
+
currentContent.push(this.cloneBlockForPage(
|
|
4795
|
+
block,
|
|
4796
|
+
true,
|
|
4797
|
+
pageInSection(block.sectionIndex)
|
|
4798
|
+
));
|
|
4799
|
+
deferredFootnoteIds.push(...newFootnoteIds);
|
|
4800
|
+
currentPageHasFootnoteReference ||= allBlockFootnoteIds.length > 0;
|
|
4801
|
+
finishPage(block.sectionIndex);
|
|
2752
4802
|
}
|
|
2753
4803
|
}
|
|
2754
4804
|
finishPage();
|
|
4805
|
+
while (currentFootnoteIds.length > 0 || deferredFootnoteIds.length > 0 || (currentContinuation?.remainingElements.length ?? 0) > 0) {
|
|
4806
|
+
finishPage();
|
|
4807
|
+
}
|
|
2755
4808
|
this.pendingFootnoteContinuation = nextPageContinuation;
|
|
2756
4809
|
return pages;
|
|
2757
4810
|
}
|
|
@@ -2776,6 +4829,19 @@ var PaginationEngine = class {
|
|
|
2776
4829
|
if (el.getAttribute("contenteditable") !== null) el.setAttribute("contenteditable", "false");
|
|
2777
4830
|
}
|
|
2778
4831
|
}
|
|
4832
|
+
/** A repeated margin note is presentation, not a second bookmark/link target. */
|
|
4833
|
+
makeClonedMarginCommentInert(root) {
|
|
4834
|
+
this.makeClonedStoryInert(root);
|
|
4835
|
+
const nodes = [root, ...Array.from(root.querySelectorAll("*"))];
|
|
4836
|
+
for (const element of nodes) {
|
|
4837
|
+
element.removeAttribute("id");
|
|
4838
|
+
if (element.localName === "a" && element.getAttribute("href")?.startsWith("#")) {
|
|
4839
|
+
element.removeAttribute("href");
|
|
4840
|
+
element.setAttribute("aria-disabled", "true");
|
|
4841
|
+
element.tabIndex = -1;
|
|
4842
|
+
}
|
|
4843
|
+
}
|
|
4844
|
+
}
|
|
2779
4845
|
/**
|
|
2780
4846
|
* Resolves floating DrawingML objects after their anchor paragraphs have landed on a page.
|
|
2781
4847
|
*
|
|
@@ -2801,7 +4867,7 @@ var PaginationEngine = class {
|
|
|
2801
4867
|
const context = {
|
|
2802
4868
|
staticLeft: toPageX(staticRect.left),
|
|
2803
4869
|
staticTop: toPageY(staticRect.top),
|
|
2804
|
-
lineHeight: paragraph ? (parseFloat(getComputedStyle(paragraph).lineHeight) || staticRect.height) / pixelsPerPoint : staticRect.height / pixelsPerPoint,
|
|
4870
|
+
lineHeight: paragraph ? (parseFloat(this.view.getComputedStyle(paragraph).lineHeight) || staticRect.height) / pixelsPerPoint : staticRect.height / pixelsPerPoint,
|
|
2805
4871
|
paragraphLeft: toPageX(paragraphRect.left),
|
|
2806
4872
|
paragraphTop: toPageY(paragraphRect.top),
|
|
2807
4873
|
paragraphWidth: paragraphRect.width / pixelsPerPoint,
|
|
@@ -2931,36 +4997,48 @@ var PaginationEngine = class {
|
|
|
2931
4997
|
else if (alignment === "outside") fraction = pageNumber % 2 === 1 ? 1 : 0;
|
|
2932
4998
|
return reference.start + (reference.size - objectSize) * fraction;
|
|
2933
4999
|
}
|
|
5000
|
+
/** Charge a physical page before any page-owned partitioning or DOM allocation. */
|
|
5001
|
+
admitPageAllocation() {
|
|
5002
|
+
const prospectivePageCount = this.createdPageCount + 1;
|
|
5003
|
+
this.pageCountCheckpoint?.(prospectivePageCount);
|
|
5004
|
+
this.createdPageCount = prospectivePageCount;
|
|
5005
|
+
}
|
|
2934
5006
|
/**
|
|
2935
5007
|
* Creates a page container element.
|
|
2936
5008
|
*/
|
|
2937
|
-
createPage(dims, pageNumber, sectionIndex, content, pageInSection, footnoteIds = [], footnoteHeight = 0, continuation, partialFootnotes) {
|
|
2938
|
-
|
|
5009
|
+
createPage(dims, pageNumber, sectionIndex, displayedPageNumber, content, pageInSection, footnoteIds = [], footnoteHeight = 0, continuation, partialFootnotes, isSectionFiller = false, pageAlreadyAdmitted = false) {
|
|
5010
|
+
if (!pageAlreadyAdmitted) this.admitPageAllocation();
|
|
5011
|
+
const pageBox = this.document.createElement("div");
|
|
2939
5012
|
pageBox.className = `${this.cssPrefix}box`;
|
|
2940
5013
|
pageBox.style.width = `${dims.pageWidth}pt`;
|
|
2941
5014
|
pageBox.style.height = `${dims.pageHeight}pt`;
|
|
2942
5015
|
pageBox.style.overflow = "hidden";
|
|
2943
5016
|
pageBox.style.position = "relative";
|
|
2944
5017
|
if (this.scale !== 1) {
|
|
2945
|
-
|
|
2946
|
-
|
|
2947
|
-
|
|
2948
|
-
|
|
2949
|
-
|
|
2950
|
-
|
|
2951
|
-
|
|
2952
|
-
|
|
2953
|
-
|
|
5018
|
+
if (this.view.CSS?.supports("zoom", "1")) {
|
|
5019
|
+
pageBox.style.zoom = String(this.scale);
|
|
5020
|
+
} else {
|
|
5021
|
+
pageBox.style.transform = `scale(${this.scale})`;
|
|
5022
|
+
pageBox.style.transformOrigin = "top left";
|
|
5023
|
+
const heightReductionPt = dims.pageHeight * (1 - this.scale);
|
|
5024
|
+
const widthReductionPt = dims.pageWidth * (1 - this.scale);
|
|
5025
|
+
const heightReductionPx = ptToPx(heightReductionPt);
|
|
5026
|
+
const widthReductionPx = ptToPx(widthReductionPt);
|
|
5027
|
+
pageBox.style.marginRight = `-${widthReductionPx}px`;
|
|
5028
|
+
pageBox.style.marginBottom = `${this.pageGap - heightReductionPx}px`;
|
|
5029
|
+
}
|
|
2954
5030
|
}
|
|
2955
5031
|
pageBox.style.willChange = "transform";
|
|
2956
5032
|
pageBox.style.contain = "layout paint";
|
|
2957
5033
|
pageBox.dataset.pageNumber = String(pageNumber);
|
|
2958
5034
|
pageBox.dataset.sectionIndex = String(sectionIndex);
|
|
5035
|
+
pageBox.dataset.displayedPageNumber = String(displayedPageNumber);
|
|
5036
|
+
if (isSectionFiller) pageBox.dataset.sectionFiller = "true";
|
|
2959
5037
|
pageBox.dataset.pageInSection = String(pageInSection);
|
|
2960
|
-
const bands = this.getPageBands(dims, sectionIndex, pageInSection,
|
|
2961
|
-
const headerSource = this.selectHeader(sectionIndex, pageInSection,
|
|
5038
|
+
const bands = this.getPageBands(dims, sectionIndex, pageInSection, displayedPageNumber);
|
|
5039
|
+
const headerSource = isSectionFiller ? void 0 : this.selectHeader(sectionIndex, pageInSection, displayedPageNumber);
|
|
2962
5040
|
if (headerSource) {
|
|
2963
|
-
const headerDiv = document.createElement("div");
|
|
5041
|
+
const headerDiv = this.document.createElement("div");
|
|
2964
5042
|
headerDiv.className = `${this.cssPrefix}header`;
|
|
2965
5043
|
headerDiv.style.position = "absolute";
|
|
2966
5044
|
headerDiv.style.top = `${bands.headerTop}pt`;
|
|
@@ -2982,7 +5060,7 @@ var PaginationEngine = class {
|
|
|
2982
5060
|
}
|
|
2983
5061
|
const contentAreaTop = bands.bodyTop;
|
|
2984
5062
|
const contentAreaHeight = bands.bodyHeight;
|
|
2985
|
-
const contentArea = document.createElement("div");
|
|
5063
|
+
const contentArea = this.document.createElement("div");
|
|
2986
5064
|
contentArea.className = `${this.cssPrefix}content`;
|
|
2987
5065
|
contentArea.style.position = "absolute";
|
|
2988
5066
|
contentArea.style.top = `${contentAreaTop}pt`;
|
|
@@ -2995,12 +5073,46 @@ var PaginationEngine = class {
|
|
|
2995
5073
|
}
|
|
2996
5074
|
pageBox.appendChild(contentArea);
|
|
2997
5075
|
const hasContinuation = continuation && continuation.remainingElements.length > 0;
|
|
2998
|
-
if (footnoteIds.length > 0 || hasContinuation) {
|
|
5076
|
+
if (!isSectionFiller && (footnoteIds.length > 0 || hasContinuation)) {
|
|
2999
5077
|
this.addPageFootnotes(pageBox, footnoteIds, dims, bands, footnoteHeight, continuation, partialFootnotes);
|
|
3000
5078
|
}
|
|
3001
|
-
const
|
|
5079
|
+
const pageCommentIds = [];
|
|
5080
|
+
for (const marker of Array.from(
|
|
5081
|
+
pageBox.querySelectorAll("[data-comment-id]")
|
|
5082
|
+
)) {
|
|
5083
|
+
if (marker.closest(`.${this.cssPrefix}comment-margin`)) continue;
|
|
5084
|
+
const id = marker.dataset.commentId;
|
|
5085
|
+
if (id && this.commentMarginRegistry.has(id) && !pageCommentIds.includes(id)) {
|
|
5086
|
+
pageCommentIds.push(id);
|
|
5087
|
+
}
|
|
5088
|
+
}
|
|
5089
|
+
if (pageCommentIds.length > 0) {
|
|
5090
|
+
const marginColumn = this.document.createElement("aside");
|
|
5091
|
+
marginColumn.className = `${this.cssPrefix}comment-margin`;
|
|
5092
|
+
marginColumn.style.position = "absolute";
|
|
5093
|
+
marginColumn.style.top = `${contentAreaTop}pt`;
|
|
5094
|
+
marginColumn.style.left = `${dims.marginLeft + dims.contentWidth + 3}pt`;
|
|
5095
|
+
marginColumn.style.width = `${Math.max(12, dims.marginRight - 6)}pt`;
|
|
5096
|
+
marginColumn.style.maxHeight = `${contentAreaHeight}pt`;
|
|
5097
|
+
marginColumn.style.overflow = "hidden";
|
|
5098
|
+
marginColumn.style.boxSizing = "border-box";
|
|
5099
|
+
const placedThreadRoots = /* @__PURE__ */ new Set();
|
|
5100
|
+
for (const id of pageCommentIds) {
|
|
5101
|
+
const source = this.commentMarginRegistry.get(id);
|
|
5102
|
+
if (source) {
|
|
5103
|
+
const rootId = source.dataset.commentId ?? id;
|
|
5104
|
+
if (placedThreadRoots.has(rootId)) continue;
|
|
5105
|
+
placedThreadRoots.add(rootId);
|
|
5106
|
+
const clone = source.cloneNode(true);
|
|
5107
|
+
this.makeClonedMarginCommentInert(clone);
|
|
5108
|
+
marginColumn.appendChild(clone);
|
|
5109
|
+
}
|
|
5110
|
+
}
|
|
5111
|
+
pageBox.appendChild(marginColumn);
|
|
5112
|
+
}
|
|
5113
|
+
const footerSource = isSectionFiller ? void 0 : this.selectFooter(sectionIndex, pageInSection, displayedPageNumber);
|
|
3002
5114
|
if (footerSource) {
|
|
3003
|
-
const footerDiv = document.createElement("div");
|
|
5115
|
+
const footerDiv = this.document.createElement("div");
|
|
3004
5116
|
footerDiv.className = `${this.cssPrefix}footer`;
|
|
3005
5117
|
footerDiv.style.position = "absolute";
|
|
3006
5118
|
footerDiv.style.top = "auto";
|
|
@@ -3020,8 +5132,8 @@ var PaginationEngine = class {
|
|
|
3020
5132
|
}
|
|
3021
5133
|
pageBox.appendChild(footerDiv);
|
|
3022
5134
|
}
|
|
3023
|
-
if (this.showPageNumbers) {
|
|
3024
|
-
const pageNum = document.createElement("div");
|
|
5135
|
+
if (this.showPageNumbers && !isSectionFiller) {
|
|
5136
|
+
const pageNum = this.document.createElement("div");
|
|
3025
5137
|
pageNum.className = `${this.cssPrefix}number`;
|
|
3026
5138
|
pageNum.textContent = String(pageNumber);
|
|
3027
5139
|
pageBox.appendChild(pageNum);
|
|
@@ -3044,7 +5156,8 @@ var PaginationEngine = class {
|
|
|
3044
5156
|
}
|
|
3045
5157
|
};
|
|
3046
5158
|
function paginateHtml(html, container, options = {}) {
|
|
3047
|
-
const
|
|
5159
|
+
const ownerDocument = typeof container === "string" ? globalThis.document : container.ownerDocument;
|
|
5160
|
+
const containerEl = typeof container === "string" ? ownerDocument.getElementById(container) : container;
|
|
3048
5161
|
if (!containerEl) {
|
|
3049
5162
|
throw new Error("Container element not found");
|
|
3050
5163
|
}
|
|
@@ -6346,6 +8459,13 @@ function fontWeightIsBold(w) {
|
|
|
6346
8459
|
function escapeInlineMarkdown(text) {
|
|
6347
8460
|
return text.replace(/([\\`*_[\]])/g, "\\$1");
|
|
6348
8461
|
}
|
|
8462
|
+
function escapeLeadingBlockMarkers(md) {
|
|
8463
|
+
return md.split("\n").map((line) => {
|
|
8464
|
+
if (/^[#>|]/.test(line) || /^[-+] /.test(line)) return "\\" + line;
|
|
8465
|
+
if (/^\d+\. /.test(line)) return line.replace(".", "\\.");
|
|
8466
|
+
return line;
|
|
8467
|
+
}).join("\n");
|
|
8468
|
+
}
|
|
6349
8469
|
function collectInlineSegments(node, out) {
|
|
6350
8470
|
node.childNodes.forEach((child) => {
|
|
6351
8471
|
if (isGeneratedChrome(child)) return;
|
|
@@ -6397,7 +8517,7 @@ function serializeInlineMarkdown(block) {
|
|
|
6397
8517
|
merged.push({ ...s });
|
|
6398
8518
|
}
|
|
6399
8519
|
}
|
|
6400
|
-
return merged.map(segToMarkdown).join("").trim();
|
|
8520
|
+
return escapeLeadingBlockMarkers(merged.map(segToMarkdown).join("").trim());
|
|
6401
8521
|
}
|
|
6402
8522
|
function isListBlock(block) {
|
|
6403
8523
|
return !!block.querySelector(":scope > [data-list-marker]");
|
|
@@ -8432,17 +10552,19 @@ var DocxEditor = class _DocxEditor {
|
|
|
8432
10552
|
this.refreshAfter(block, idx, false);
|
|
8433
10553
|
}
|
|
8434
10554
|
// ─── Table row / column editing (active block must be inside a table cell) ──────────
|
|
8435
|
-
/** Run a table-structure op on the active cell
|
|
10555
|
+
/** Run a table-structure op on the active cell's canonical tc anchor and re-render. */
|
|
8436
10556
|
tableEdit(run) {
|
|
8437
10557
|
const block = this.activeBlock;
|
|
8438
|
-
|
|
10558
|
+
const cell = block?.closest("td, th");
|
|
10559
|
+
if (this.closed || !block || !cell || !block.closest("table")) return;
|
|
8439
10560
|
const unid = block.getAttribute("data-anchor");
|
|
8440
10561
|
if (!unid) return;
|
|
8441
|
-
|
|
8442
|
-
|
|
10562
|
+
const paragraphId = this.anchorIdOf(block);
|
|
10563
|
+
const cellId = this.anchorIdOf(cell);
|
|
10564
|
+
if (!paragraphId || !cellId) return;
|
|
8443
10565
|
const idx = this.blockIndex(block);
|
|
8444
|
-
|
|
8445
|
-
const res = this.parseEdit(run(
|
|
10566
|
+
this.syncBlock(block, paragraphId);
|
|
10567
|
+
const res = this.parseEdit(run(cellId));
|
|
8446
10568
|
if (!res.success) return;
|
|
8447
10569
|
this.refreshAfter(block, idx, false);
|
|
8448
10570
|
}
|
|
@@ -8595,6 +10717,59 @@ var DocxEditor = class _DocxEditor {
|
|
|
8595
10717
|
if (!res.success) return;
|
|
8596
10718
|
this.reconcile(idx, false);
|
|
8597
10719
|
}
|
|
10720
|
+
/**
|
|
10721
|
+
* Add a native Word comment on the current selection in the active block — or on the whole
|
|
10722
|
+
* block when the selection is collapsed (a null span comments the paragraph). The definition,
|
|
10723
|
+
* threading parts, and body-side range markers are written by the session's `AddComment`
|
|
10724
|
+
* (issue #300); the re-render shows the marker chrome the converter already draws. The
|
|
10725
|
+
* annotation type legal review runs on, finally authorable from the shipped surface
|
|
10726
|
+
* (issue #580).
|
|
10727
|
+
*/
|
|
10728
|
+
addComment(markdown = "New comment.", author = "Reviewer") {
|
|
10729
|
+
const block = this.activeBlock;
|
|
10730
|
+
if (this.closed || !block) return;
|
|
10731
|
+
const bridge = this.exports.DocxSessionBridge;
|
|
10732
|
+
if (!bridge.AddComment) return;
|
|
10733
|
+
let fullId = this.anchorIdOf(block);
|
|
10734
|
+
if (!fullId) return;
|
|
10735
|
+
const idx = this.blockIndex(block);
|
|
10736
|
+
const span = selectionSpanIn(block);
|
|
10737
|
+
fullId = this.syncBlock(block, fullId);
|
|
10738
|
+
const res = this.parseEdit(
|
|
10739
|
+
bridge.AddComment(
|
|
10740
|
+
this.handle,
|
|
10741
|
+
fullId,
|
|
10742
|
+
span ? JSON.stringify(span) : "",
|
|
10743
|
+
author,
|
|
10744
|
+
"",
|
|
10745
|
+
"",
|
|
10746
|
+
markdown
|
|
10747
|
+
)
|
|
10748
|
+
);
|
|
10749
|
+
if (!res.success) return;
|
|
10750
|
+
this.reconcile(idx, false);
|
|
10751
|
+
}
|
|
10752
|
+
/** The document's native comment threads (session truth), for review UIs — flat entries
|
|
10753
|
+
* with `parentAnchorId` linking replies and `resolved` carrying thread state. */
|
|
10754
|
+
listComments() {
|
|
10755
|
+
if (this.closed) return [];
|
|
10756
|
+
const bridge = this.exports.DocxSessionBridge;
|
|
10757
|
+
if (!bridge.ListComments) return [];
|
|
10758
|
+
try {
|
|
10759
|
+
return JSON.parse(bridge.ListComments(this.handle));
|
|
10760
|
+
} catch {
|
|
10761
|
+
return [];
|
|
10762
|
+
}
|
|
10763
|
+
}
|
|
10764
|
+
/** Resolve or reopen a comment thread by its `cmt` anchor id (from {@link listComments}).
|
|
10765
|
+
* Resolution is thread metadata (`commentsExtended`), not body markup, so no re-render is
|
|
10766
|
+
* needed — a review UI re-reads {@link listComments} for the new state. */
|
|
10767
|
+
setCommentResolved(commentAnchorId, resolved) {
|
|
10768
|
+
if (this.closed) return;
|
|
10769
|
+
const bridge = this.exports.DocxSessionBridge;
|
|
10770
|
+
if (!bridge.SetCommentResolved) return;
|
|
10771
|
+
this.parseEdit(bridge.SetCommentResolved(this.handle, commentAnchorId, resolved));
|
|
10772
|
+
}
|
|
8598
10773
|
applyParagraphFormat(op) {
|
|
8599
10774
|
const block = this.activeBlock;
|
|
8600
10775
|
if (this.closed || !block) return;
|
|
@@ -9122,8 +11297,8 @@ var DocxEditor = class _DocxEditor {
|
|
|
9122
11297
|
const backref = document.createElement("a");
|
|
9123
11298
|
backref.setAttribute("class", `${prefix}-backref`);
|
|
9124
11299
|
backref.setAttribute("contenteditable", "false");
|
|
9125
|
-
backref.textContent = "\u21A9";
|
|
9126
|
-
last.append(
|
|
11300
|
+
backref.textContent = " \u21A9";
|
|
11301
|
+
last.append(backref);
|
|
9127
11302
|
}
|
|
9128
11303
|
li.querySelectorAll("[data-anchor]").forEach((b) => this.wireBlock(b));
|
|
9129
11304
|
return li;
|
|
@@ -9232,7 +11407,7 @@ var DocxEditor = class _DocxEditor {
|
|
|
9232
11407
|
};
|
|
9233
11408
|
|
|
9234
11409
|
// src/ribbon-chrome.ts
|
|
9235
|
-
var RIBBON_STYLE_VERSION = "
|
|
11410
|
+
var RIBBON_STYLE_VERSION = "9";
|
|
9236
11411
|
var RIBBON_STYLE_ATTR = "data-docxodus-ribbon-styles";
|
|
9237
11412
|
var RIBBON_CSS = `
|
|
9238
11413
|
.dxr {
|
|
@@ -9295,6 +11470,27 @@ var RIBBON_CSS = `
|
|
|
9295
11470
|
/* Touch devices get bigger hit targets everywhere the token is used. */
|
|
9296
11471
|
@media (pointer: coarse) { .dxr { --dxr-tap: 40px; } }
|
|
9297
11472
|
|
|
11473
|
+
/* \u2500\u2500 Scroll-edge affordances \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500
|
|
11474
|
+
The chrome's strips (title bar, tabs, panels, rail) scroll horizontally
|
|
11475
|
+
rather than squashing or wrapping \u2014 the compact rule is "no command is
|
|
11476
|
+
removed, the strip scrolls". But a hard clip mid-control reads as a broken
|
|
11477
|
+
layout, especially on a phone, so ribbon.ts measures every strip and stamps
|
|
11478
|
+
data-fade with the edges that hide more content ("l", "r", or both). The
|
|
11479
|
+
mask dissolves content at exactly those edges \u2014 the horizontal twin of
|
|
11480
|
+
.dxr-scroll's gradient veils \u2014 and lifts entirely once the strip fits, so
|
|
11481
|
+
nothing is ever faded unless there is more behind it. */
|
|
11482
|
+
.dxr-titlebar, .dxr-tabs, .dxr-panel, .dxr-rail { --dxr-fade-l: 0px; --dxr-fade-r: 0px; }
|
|
11483
|
+
.dxr [data-fade] {
|
|
11484
|
+
-webkit-mask-image: linear-gradient(to right,
|
|
11485
|
+
transparent, #000 var(--dxr-fade-l),
|
|
11486
|
+
#000 calc(100% - var(--dxr-fade-r)), transparent);
|
|
11487
|
+
mask-image: linear-gradient(to right,
|
|
11488
|
+
transparent, #000 var(--dxr-fade-l),
|
|
11489
|
+
#000 calc(100% - var(--dxr-fade-r)), transparent);
|
|
11490
|
+
}
|
|
11491
|
+
.dxr [data-fade~="l"] { --dxr-fade-l: 28px; }
|
|
11492
|
+
.dxr [data-fade~="r"] { --dxr-fade-r: 28px; }
|
|
11493
|
+
|
|
9298
11494
|
/* \u2500\u2500 Chrome shell \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 */
|
|
9299
11495
|
.dxr-chrome {
|
|
9300
11496
|
position: sticky;
|
|
@@ -9305,12 +11501,17 @@ var RIBBON_CSS = `
|
|
|
9305
11501
|
border-bottom: 1px solid var(--dxr-rule);
|
|
9306
11502
|
}
|
|
9307
11503
|
|
|
11504
|
+
/* Scrolls in EVERY density (compact just tightens it): the fade affordance
|
|
11505
|
+
marks any overflowing strip as scrollable, so it must actually be. */
|
|
9308
11506
|
.dxr-titlebar {
|
|
9309
11507
|
display: flex;
|
|
9310
11508
|
align-items: center;
|
|
9311
11509
|
gap: 10px;
|
|
9312
11510
|
padding: 6px 10px 0;
|
|
11511
|
+
overflow-x: auto;
|
|
11512
|
+
scrollbar-width: none;
|
|
9313
11513
|
}
|
|
11514
|
+
.dxr-titlebar::-webkit-scrollbar { display: none; }
|
|
9314
11515
|
.dxr-brand {
|
|
9315
11516
|
display: flex;
|
|
9316
11517
|
align-items: baseline;
|
|
@@ -9680,6 +11881,25 @@ var RIBBON_CSS = `
|
|
|
9680
11881
|
box-shadow: 0 1px 3px rgba(15, 23, 42, .08), 0 12px 24px rgba(15, 23, 42, .06);
|
|
9681
11882
|
}
|
|
9682
11883
|
|
|
11884
|
+
/* \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 */
|
|
11885
|
+
.dxr-threads {
|
|
11886
|
+
display: flex; flex-direction: column; gap: 4px;
|
|
11887
|
+
max-height: 92px; overflow-y: auto; min-width: 260px; padding-right: 2px;
|
|
11888
|
+
}
|
|
11889
|
+
.dxr-thread {
|
|
11890
|
+
display: flex; align-items: center; gap: 6px;
|
|
11891
|
+
font-size: 11px; line-height: 1.3; padding: 3px 6px;
|
|
11892
|
+
border: 1px solid var(--dxr-rule); border-radius: 4px; background: var(--dxr-chrome-sunk);
|
|
11893
|
+
}
|
|
11894
|
+
.dxr-thread[data-resolved] { opacity: 0.55; }
|
|
11895
|
+
.dxr-thread[data-reply] { margin-left: 14px; }
|
|
11896
|
+
.dxr-thread .dxr-tauthor { font-weight: 600; white-space: nowrap; }
|
|
11897
|
+
.dxr-thread .dxr-ttext {
|
|
11898
|
+
flex: 1; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; max-width: 220px;
|
|
11899
|
+
}
|
|
11900
|
+
.dxr-thread button { flex: none; }
|
|
11901
|
+
.dxr-group.dxr-grow { flex: 1; min-width: 0; }
|
|
11902
|
+
|
|
9683
11903
|
/* \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 */
|
|
9684
11904
|
.dxr-pop {
|
|
9685
11905
|
display: none;
|
|
@@ -9721,10 +11941,7 @@ var RIBBON_CSS = `
|
|
|
9721
11941
|
.dxr[data-chrome="compact"] .dxr-titlebar {
|
|
9722
11942
|
gap: 6px;
|
|
9723
11943
|
padding: 5px 8px 0;
|
|
9724
|
-
overflow-x: auto;
|
|
9725
|
-
scrollbar-width: none;
|
|
9726
11944
|
}
|
|
9727
|
-
.dxr[data-chrome="compact"] .dxr-titlebar::-webkit-scrollbar { display: none; }
|
|
9728
11945
|
.dxr[data-chrome="compact"] .dxr-brandname { display: none; }
|
|
9729
11946
|
.dxr[data-chrome="compact"] .dxr-brand { flex: 0 1 auto; }
|
|
9730
11947
|
/* A filename clipped to "do..." tells you less than no filename at all, so it keeps a
|
|
@@ -9998,6 +12215,7 @@ var RIBBON_HTML = `
|
|
|
9998
12215
|
<button type="button" class="dxr-tab" role="tab" data-tab="home" aria-selected="true">Home</button>
|
|
9999
12216
|
<button type="button" class="dxr-tab" role="tab" data-tab="insert" aria-selected="false">Insert</button>
|
|
10000
12217
|
<button type="button" class="dxr-tab" role="tab" data-tab="layout" aria-selected="false">Layout</button>
|
|
12218
|
+
<button type="button" class="dxr-tab" role="tab" data-tab="review" aria-selected="false">Review</button>
|
|
10001
12219
|
<button type="button" class="dxr-tab" role="tab" data-tab="table" data-contextual aria-selected="false" hidden>Table</button>
|
|
10002
12220
|
</div>
|
|
10003
12221
|
|
|
@@ -10163,6 +12381,31 @@ var RIBBON_HTML = `
|
|
|
10163
12381
|
</div>
|
|
10164
12382
|
</div>
|
|
10165
12383
|
|
|
12384
|
+
<!-- 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
|
|
12385
|
+
Comment authoring over the session's native comment family (issue #580):
|
|
12386
|
+
the button comments the selection (the whole block when collapsed), and
|
|
12387
|
+
the thread list is live session truth with resolve/reopen per thread. -->
|
|
12388
|
+
<div class="dxr-panel" data-panel="review">
|
|
12389
|
+
<div class="dxr-group">
|
|
12390
|
+
<span class="dxr-glabel">Comments</span>
|
|
12391
|
+
<div class="dxr-row">
|
|
12392
|
+
<input data-dxr="commenttext" type="text" placeholder="Comment text…"
|
|
12393
|
+
title="The comment body; markdown is honored" />
|
|
12394
|
+
<button type="button" data-dxr="comment"
|
|
12395
|
+
title="Comment on the selection, or on the whole block when nothing is selected">💬 Comment</button>
|
|
12396
|
+
</div>
|
|
12397
|
+
<div class="dxr-row">
|
|
12398
|
+
<span class="dxr-note">Comments the selected text; the whole block when the caret is collapsed.</span>
|
|
12399
|
+
</div>
|
|
12400
|
+
</div>
|
|
12401
|
+
<div class="dxr-group dxr-grow">
|
|
12402
|
+
<span class="dxr-glabel">Threads</span>
|
|
12403
|
+
<div class="dxr-threads" data-dxr="threads" aria-live="polite">
|
|
12404
|
+
<span class="dxr-note">No comments yet.</span>
|
|
12405
|
+
</div>
|
|
12406
|
+
</div>
|
|
12407
|
+
</div>
|
|
12408
|
+
|
|
10166
12409
|
<!-- 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
|
|
10167
12410
|
Row/column editing lives here rather than in a floating toolbar: a docked
|
|
10168
12411
|
contextual tab cannot overlap the cell you are editing. -->
|
|
@@ -10364,6 +12607,8 @@ var RibbonSurface = class {
|
|
|
10364
12607
|
this.featureTimer = null;
|
|
10365
12608
|
this.featureIndex = 0;
|
|
10366
12609
|
this.resizeObserver = null;
|
|
12610
|
+
this.strips = [];
|
|
12611
|
+
this.stripObserver = null;
|
|
10367
12612
|
this.lastAnchorText = "";
|
|
10368
12613
|
this.selectionFrame = null;
|
|
10369
12614
|
this.options = options;
|
|
@@ -10394,6 +12639,7 @@ var RibbonSurface = class {
|
|
|
10394
12639
|
this.applyStaticOptions();
|
|
10395
12640
|
this.buildGrid();
|
|
10396
12641
|
this.wire();
|
|
12642
|
+
this.wireScrollFades();
|
|
10397
12643
|
this.applyChrome();
|
|
10398
12644
|
this.loader = this.createLoaderController();
|
|
10399
12645
|
if (this.loaderOptions) this.loader.show();
|
|
@@ -10473,6 +12719,38 @@ var RibbonSurface = class {
|
|
|
10473
12719
|
this.density = next;
|
|
10474
12720
|
this.element.dataset.chrome = next;
|
|
10475
12721
|
this.closePicker();
|
|
12722
|
+
this.syncAllStripFades();
|
|
12723
|
+
}
|
|
12724
|
+
// ── scroll-edge affordances ─────────────────────────────────────────────────
|
|
12725
|
+
/**
|
|
12726
|
+
* The chrome's strips scroll horizontally instead of squashing ("no command
|
|
12727
|
+
* is removed in compact"), so each one reports which edges hide more content
|
|
12728
|
+
* via `data-fade` and the stylesheet dissolves content at exactly those
|
|
12729
|
+
* edges. Without this a strip hard-clips mid-control and reads as broken.
|
|
12730
|
+
*/
|
|
12731
|
+
syncStripFade(strip) {
|
|
12732
|
+
const max = strip.scrollWidth - strip.clientWidth;
|
|
12733
|
+
const tokens = max <= 1 ? "" : `${strip.scrollLeft > 1 ? "l " : ""}${strip.scrollLeft < max - 1 ? "r" : ""}`.trim();
|
|
12734
|
+
if (tokens) strip.setAttribute("data-fade", tokens);
|
|
12735
|
+
else strip.removeAttribute("data-fade");
|
|
12736
|
+
}
|
|
12737
|
+
syncAllStripFades() {
|
|
12738
|
+
for (const strip of this.strips) this.syncStripFade(strip);
|
|
12739
|
+
}
|
|
12740
|
+
wireScrollFades() {
|
|
12741
|
+
this.strips = Array.from(
|
|
12742
|
+
this.element.querySelectorAll(".dxr-titlebar, .dxr-tabs, .dxr-panel, .dxr-rail")
|
|
12743
|
+
);
|
|
12744
|
+
if (typeof ResizeObserver !== "undefined") {
|
|
12745
|
+
this.stripObserver = new ResizeObserver((entries) => {
|
|
12746
|
+
for (const entry of entries) this.syncStripFade(entry.target);
|
|
12747
|
+
});
|
|
12748
|
+
}
|
|
12749
|
+
for (const strip of this.strips) {
|
|
12750
|
+
strip.addEventListener("scroll", () => this.syncStripFade(strip), { passive: true });
|
|
12751
|
+
this.stripObserver?.observe(strip);
|
|
12752
|
+
this.syncStripFade(strip);
|
|
12753
|
+
}
|
|
10476
12754
|
}
|
|
10477
12755
|
// ── status ──────────────────────────────────────────────────────────────────
|
|
10478
12756
|
/** Publish the lifecycle on the root, where CSS and host pages can key off it. */
|
|
@@ -10566,6 +12844,8 @@ var RibbonSurface = class {
|
|
|
10566
12844
|
doc.removeEventListener("mousedown", this.onDocumentMouseDown);
|
|
10567
12845
|
this.resizeObserver?.disconnect();
|
|
10568
12846
|
this.resizeObserver = null;
|
|
12847
|
+
this.stripObserver?.disconnect();
|
|
12848
|
+
this.stripObserver = null;
|
|
10569
12849
|
if (this.selectionFrame != null) cancelAnimationFrame(this.selectionFrame);
|
|
10570
12850
|
this.selectionFrame = null;
|
|
10571
12851
|
this.stopRotation();
|
|
@@ -10670,6 +12950,18 @@ var RibbonSurface = class {
|
|
|
10670
12950
|
if (this.live && style.value) this.run("style", () => this.live.setParagraphStyle(style.value));
|
|
10671
12951
|
style.value = "";
|
|
10672
12952
|
});
|
|
12953
|
+
const commentButton = this.control("comment");
|
|
12954
|
+
if (commentButton) {
|
|
12955
|
+
this.keepSelection(commentButton);
|
|
12956
|
+
commentButton.addEventListener("click", () => {
|
|
12957
|
+
if (!this.live) return;
|
|
12958
|
+
const input = this.control("commenttext");
|
|
12959
|
+
const text = input?.value.trim() || "New comment.";
|
|
12960
|
+
this.run("comment", () => this.live.addComment(text, this.options.revisionAuthor ?? "Reviewer"));
|
|
12961
|
+
if (input) input.value = "";
|
|
12962
|
+
this.renderThreads();
|
|
12963
|
+
});
|
|
12964
|
+
}
|
|
10673
12965
|
this.wireFileActions();
|
|
10674
12966
|
this.wireLayout();
|
|
10675
12967
|
this.wirePicker();
|
|
@@ -10792,6 +13084,54 @@ var RibbonSurface = class {
|
|
|
10792
13084
|
else panel.removeAttribute("data-active");
|
|
10793
13085
|
}
|
|
10794
13086
|
if (name === "layout") this.syncPageNumbering();
|
|
13087
|
+
if (name === "review") this.renderThreads();
|
|
13088
|
+
}
|
|
13089
|
+
/** Re-render the Review tab's thread list from session truth ({@link DocxEditor.listComments}):
|
|
13090
|
+
* one row per comment, replies indented under their thread root, resolve/reopen per root. */
|
|
13091
|
+
renderThreads() {
|
|
13092
|
+
const host = this.control("threads");
|
|
13093
|
+
if (!host || !this.live) return;
|
|
13094
|
+
const doc = this.element.ownerDocument ?? document;
|
|
13095
|
+
host.textContent = "";
|
|
13096
|
+
const comments = this.live.listComments();
|
|
13097
|
+
if (comments.length === 0) {
|
|
13098
|
+
const empty = doc.createElement("span");
|
|
13099
|
+
empty.className = "dxr-note";
|
|
13100
|
+
empty.textContent = "No comments yet.";
|
|
13101
|
+
host.appendChild(empty);
|
|
13102
|
+
return;
|
|
13103
|
+
}
|
|
13104
|
+
for (const entry of comments) {
|
|
13105
|
+
const row = doc.createElement("div");
|
|
13106
|
+
row.className = "dxr-thread";
|
|
13107
|
+
row.setAttribute("data-thread", entry.anchorId);
|
|
13108
|
+
if (entry.resolved) row.setAttribute("data-resolved", "");
|
|
13109
|
+
if (entry.parentAnchorId) row.setAttribute("data-reply", "");
|
|
13110
|
+
const author = doc.createElement("span");
|
|
13111
|
+
author.className = "dxr-tauthor";
|
|
13112
|
+
author.textContent = entry.author || "\u2014";
|
|
13113
|
+
row.appendChild(author);
|
|
13114
|
+
const text = doc.createElement("span");
|
|
13115
|
+
text.className = "dxr-ttext";
|
|
13116
|
+
text.textContent = entry.text;
|
|
13117
|
+
text.title = entry.text;
|
|
13118
|
+
row.appendChild(text);
|
|
13119
|
+
if (!entry.parentAnchorId) {
|
|
13120
|
+
const toggle = doc.createElement("button");
|
|
13121
|
+
toggle.type = "button";
|
|
13122
|
+
toggle.textContent = entry.resolved ? "Reopen" : "Resolve";
|
|
13123
|
+
toggle.title = entry.resolved ? "Reopen this comment thread" : "Mark this comment thread resolved";
|
|
13124
|
+
toggle.addEventListener("click", () => {
|
|
13125
|
+
if (!this.live) return;
|
|
13126
|
+
this.run(entry.resolved ? "reopen comment" : "resolve comment", () => this.live.setCommentResolved(entry.anchorId, !entry.resolved));
|
|
13127
|
+
this.renderThreads();
|
|
13128
|
+
});
|
|
13129
|
+
row.appendChild(toggle);
|
|
13130
|
+
}
|
|
13131
|
+
host.appendChild(row);
|
|
13132
|
+
}
|
|
13133
|
+
const panel = this.element.querySelector('.dxr-panel[data-panel="review"]');
|
|
13134
|
+
if (panel) this.syncStripFade(panel);
|
|
10795
13135
|
}
|
|
10796
13136
|
// ── selection-driven state ──────────────────────────────────────────────────
|
|
10797
13137
|
selectionElement() {
|
|
@@ -11093,6 +13433,55 @@ async function toBytes(input) {
|
|
|
11093
13433
|
const buffer = await input.arrayBuffer();
|
|
11094
13434
|
return new Uint8Array(buffer);
|
|
11095
13435
|
}
|
|
13436
|
+
async function generatePackageManifest(document2) {
|
|
13437
|
+
const exports = ensureInitialized();
|
|
13438
|
+
const bytes = await toBytes(document2);
|
|
13439
|
+
await yieldToMain();
|
|
13440
|
+
return JSON.parse(
|
|
13441
|
+
exports.DocumentConverter.GeneratePackageManifest(bytes)
|
|
13442
|
+
);
|
|
13443
|
+
}
|
|
13444
|
+
async function verifyDeliverable(document2, baseline) {
|
|
13445
|
+
const exports = ensureInitialized();
|
|
13446
|
+
const bytes = await toBytes(document2);
|
|
13447
|
+
const baselineBytes = baseline === void 0 ? void 0 : await toBytes(baseline);
|
|
13448
|
+
await yieldToMain();
|
|
13449
|
+
return JSON.parse(
|
|
13450
|
+
baselineBytes === void 0 ? exports.DocumentConverter.VerifyDeliverable(bytes) : exports.DocumentConverter.VerifyDeliverableWithBaseline(
|
|
13451
|
+
baselineBytes,
|
|
13452
|
+
bytes
|
|
13453
|
+
)
|
|
13454
|
+
);
|
|
13455
|
+
}
|
|
13456
|
+
async function verifyDeliveryReceipt(receiptJson, artifacts) {
|
|
13457
|
+
const exports = ensureInitialized();
|
|
13458
|
+
const artifactsJson = artifacts === void 0 ? "" : JSON.stringify(
|
|
13459
|
+
Object.fromEntries(
|
|
13460
|
+
Object.entries(artifacts).map(([artifactId, bytes]) => [
|
|
13461
|
+
artifactId,
|
|
13462
|
+
bytesToBase64(bytes)
|
|
13463
|
+
])
|
|
13464
|
+
)
|
|
13465
|
+
);
|
|
13466
|
+
await yieldToMain();
|
|
13467
|
+
return JSON.parse(
|
|
13468
|
+
exports.DocumentConverter.VerifyDeliveryReceipt(receiptJson, artifactsJson)
|
|
13469
|
+
);
|
|
13470
|
+
}
|
|
13471
|
+
async function proveRedlineReversibility(baseline, intendedFinal, redline) {
|
|
13472
|
+
const exports = ensureInitialized();
|
|
13473
|
+
const baselineBytes = await toBytes(baseline);
|
|
13474
|
+
const intendedFinalBytes = await toBytes(intendedFinal);
|
|
13475
|
+
const redlineBytes = await toBytes(redline);
|
|
13476
|
+
await yieldToMain();
|
|
13477
|
+
return JSON.parse(
|
|
13478
|
+
exports.DocumentConverter.ProveRedlineReversibility(
|
|
13479
|
+
baselineBytes,
|
|
13480
|
+
intendedFinalBytes,
|
|
13481
|
+
redlineBytes
|
|
13482
|
+
)
|
|
13483
|
+
);
|
|
13484
|
+
}
|
|
11096
13485
|
async function renderBlockHtml(document2, anchorId, options) {
|
|
11097
13486
|
const exports = ensureInitialized();
|
|
11098
13487
|
const bytes = await toBytes(document2);
|
|
@@ -11176,22 +13565,18 @@ async function compareDocuments(original, modified, options) {
|
|
|
11176
13565
|
const modifiedBytes = await toBytes(modified);
|
|
11177
13566
|
await yieldToMain();
|
|
11178
13567
|
let result;
|
|
11179
|
-
|
|
11180
|
-
if (options?.detailThreshold !== void 0 || options?.caseInsensitive) {
|
|
13568
|
+
if (options?.caseInsensitive) {
|
|
11181
13569
|
result = exports.DocumentComparer.CompareDocumentsWithOptions(
|
|
11182
13570
|
originalBytes,
|
|
11183
13571
|
modifiedBytes,
|
|
11184
13572
|
options?.authorName ?? "Docxodus",
|
|
11185
|
-
options
|
|
11186
|
-
options?.caseInsensitive ?? false,
|
|
11187
|
-
engine
|
|
13573
|
+
options.caseInsensitive
|
|
11188
13574
|
);
|
|
11189
13575
|
} else {
|
|
11190
13576
|
result = exports.DocumentComparer.CompareDocuments(
|
|
11191
13577
|
originalBytes,
|
|
11192
13578
|
modifiedBytes,
|
|
11193
|
-
options?.authorName ?? "Docxodus"
|
|
11194
|
-
engine
|
|
13579
|
+
options?.authorName ?? "Docxodus"
|
|
11195
13580
|
);
|
|
11196
13581
|
}
|
|
11197
13582
|
if (result.length === 0) {
|
|
@@ -11205,25 +13590,21 @@ async function compareDocumentsToHtml(original, modified, options) {
|
|
|
11205
13590
|
const modifiedBytes = await toBytes(modified);
|
|
11206
13591
|
await yieldToMain();
|
|
11207
13592
|
const renderTrackedChanges = options?.renderTrackedChanges ?? true;
|
|
11208
|
-
const engine = options?.engine ?? 1 /* DocxDiff */;
|
|
11209
13593
|
let result;
|
|
11210
|
-
if (options?.
|
|
13594
|
+
if (options?.caseInsensitive !== void 0) {
|
|
11211
13595
|
result = exports.DocumentComparer.CompareDocumentsToHtmlFull(
|
|
11212
13596
|
originalBytes,
|
|
11213
13597
|
modifiedBytes,
|
|
11214
13598
|
options?.authorName ?? "Docxodus",
|
|
11215
|
-
options
|
|
11216
|
-
|
|
11217
|
-
renderTrackedChanges,
|
|
11218
|
-
engine
|
|
13599
|
+
options.caseInsensitive,
|
|
13600
|
+
renderTrackedChanges
|
|
11219
13601
|
);
|
|
11220
13602
|
} else {
|
|
11221
13603
|
result = exports.DocumentComparer.CompareDocumentsToHtmlWithOptions(
|
|
11222
13604
|
originalBytes,
|
|
11223
13605
|
modifiedBytes,
|
|
11224
13606
|
options?.authorName ?? "Docxodus",
|
|
11225
|
-
renderTrackedChanges
|
|
11226
|
-
engine
|
|
13607
|
+
renderTrackedChanges
|
|
11227
13608
|
);
|
|
11228
13609
|
}
|
|
11229
13610
|
if (isErrorResponse(result)) {
|
|
@@ -11232,93 +13613,16 @@ async function compareDocumentsToHtml(original, modified, options) {
|
|
|
11232
13613
|
}
|
|
11233
13614
|
return result;
|
|
11234
13615
|
}
|
|
11235
|
-
async function
|
|
11236
|
-
const exports = ensureInitialized();
|
|
11237
|
-
const originalBytes = await toBytes(original);
|
|
11238
|
-
const modifiedBytes = await toBytes(modified);
|
|
11239
|
-
await yieldToMain();
|
|
11240
|
-
const result = exports.DocumentComparer.CompareDocumentsWithLog(
|
|
11241
|
-
originalBytes,
|
|
11242
|
-
modifiedBytes,
|
|
11243
|
-
options?.authorName ?? "Docxodus",
|
|
11244
|
-
options?.detailThreshold ?? 0.15,
|
|
11245
|
-
options?.caseInsensitive ?? false
|
|
11246
|
-
);
|
|
11247
|
-
const parsed = JSON.parse(result);
|
|
11248
|
-
return {
|
|
11249
|
-
success: parsed.Success ?? parsed.success ?? false,
|
|
11250
|
-
document: parsed.DocumentBase64 || parsed.documentBase64 ? Uint8Array.from(atob(parsed.DocumentBase64 || parsed.documentBase64), (c) => c.charCodeAt(0)) : void 0,
|
|
11251
|
-
error: parsed.Error ?? parsed.error,
|
|
11252
|
-
log: convertLogEntries(parsed.Log || parsed.log || []),
|
|
11253
|
-
hasWarnings: parsed.HasWarnings ?? parsed.hasWarnings ?? false,
|
|
11254
|
-
hasErrors: parsed.HasErrors ?? parsed.hasErrors ?? false
|
|
11255
|
-
};
|
|
11256
|
-
}
|
|
11257
|
-
async function compareDocumentsToHtmlWithLog(original, modified, options) {
|
|
11258
|
-
const exports = ensureInitialized();
|
|
11259
|
-
const originalBytes = await toBytes(original);
|
|
11260
|
-
const modifiedBytes = await toBytes(modified);
|
|
11261
|
-
await yieldToMain();
|
|
11262
|
-
const result = exports.DocumentComparer.CompareDocumentsToHtmlWithLog(
|
|
11263
|
-
originalBytes,
|
|
11264
|
-
modifiedBytes,
|
|
11265
|
-
options?.authorName ?? "Docxodus",
|
|
11266
|
-
options?.detailThreshold ?? 0.15,
|
|
11267
|
-
options?.caseInsensitive ?? false,
|
|
11268
|
-
options?.renderTrackedChanges ?? true
|
|
11269
|
-
);
|
|
11270
|
-
const parsed = JSON.parse(result);
|
|
11271
|
-
return {
|
|
11272
|
-
success: parsed.Success ?? parsed.success ?? false,
|
|
11273
|
-
html: parsed.Html ?? parsed.html,
|
|
11274
|
-
error: parsed.Error ?? parsed.error,
|
|
11275
|
-
log: convertLogEntries(parsed.Log || parsed.log || []),
|
|
11276
|
-
hasWarnings: parsed.HasWarnings ?? parsed.hasWarnings ?? false,
|
|
11277
|
-
hasErrors: parsed.HasErrors ?? parsed.hasErrors ?? false
|
|
11278
|
-
};
|
|
11279
|
-
}
|
|
11280
|
-
function convertLogEntries(entries) {
|
|
11281
|
-
return entries.map((e) => ({
|
|
11282
|
-
level: e.Level ?? e.level ?? "Info",
|
|
11283
|
-
code: e.Code ?? e.code ?? "",
|
|
11284
|
-
message: e.Message ?? e.message ?? "",
|
|
11285
|
-
details: e.Details ?? e.details,
|
|
11286
|
-
location: e.Location ?? e.location
|
|
11287
|
-
}));
|
|
11288
|
-
}
|
|
11289
|
-
async function getRevisions(document2, options) {
|
|
13616
|
+
async function getRevisions(document2) {
|
|
11290
13617
|
const exports = ensureInitialized();
|
|
11291
13618
|
const bytes = await toBytes(document2);
|
|
11292
13619
|
await yieldToMain();
|
|
11293
|
-
const
|
|
11294
|
-
const moveSimilarityThreshold = options?.moveSimilarityThreshold ?? 0.8;
|
|
11295
|
-
const moveMinimumWordCount = options?.moveMinimumWordCount ?? 3;
|
|
11296
|
-
const caseInsensitive = options?.caseInsensitive ?? false;
|
|
11297
|
-
const result = exports.DocumentComparer.GetRevisionsJsonWithOptions(
|
|
11298
|
-
bytes,
|
|
11299
|
-
detectMoves,
|
|
11300
|
-
moveSimilarityThreshold,
|
|
11301
|
-
moveMinimumWordCount,
|
|
11302
|
-
caseInsensitive
|
|
11303
|
-
);
|
|
13620
|
+
const result = exports.DocumentComparer.GetRevisionsJson(bytes);
|
|
11304
13621
|
if (isErrorResponse(result)) {
|
|
11305
13622
|
const error = parseError(result);
|
|
11306
13623
|
throw new Error(`Failed to get revisions: ${error.error}`);
|
|
11307
13624
|
}
|
|
11308
|
-
|
|
11309
|
-
return (parsed.Revisions || parsed.revisions || []).map((r) => ({
|
|
11310
|
-
author: r.Author || r.author,
|
|
11311
|
-
date: r.Date || r.date,
|
|
11312
|
-
revisionType: r.RevisionType || r.revisionType,
|
|
11313
|
-
text: r.Text || r.text,
|
|
11314
|
-
moveGroupId: r.MoveGroupId ?? r.moveGroupId,
|
|
11315
|
-
isMoveSource: r.IsMoveSource ?? r.isMoveSource,
|
|
11316
|
-
formatChange: r.FormatChange || r.formatChange ? {
|
|
11317
|
-
oldProperties: r.FormatChange?.OldProperties || r.formatChange?.oldProperties,
|
|
11318
|
-
newProperties: r.FormatChange?.NewProperties || r.formatChange?.newProperties,
|
|
11319
|
-
changedPropertyNames: r.FormatChange?.ChangedPropertyNames || r.formatChange?.changedPropertyNames
|
|
11320
|
-
} : void 0
|
|
11321
|
-
}));
|
|
13625
|
+
return JSON.parse(result);
|
|
11322
13626
|
}
|
|
11323
13627
|
function docxDiffSettingsJson(settings) {
|
|
11324
13628
|
return settings ? JSON.stringify(settings) : "";
|
|
@@ -11353,20 +13657,61 @@ async function docxDiffGetRevisions(left2, right2, settings) {
|
|
|
11353
13657
|
throw new Error(`Failed to get DocxDiff revisions: ${error.error}`);
|
|
11354
13658
|
}
|
|
11355
13659
|
const parsed = JSON.parse(result);
|
|
11356
|
-
return (parsed.revisions || parsed.Revisions || []).map(
|
|
11357
|
-
|
|
11358
|
-
|
|
11359
|
-
|
|
11360
|
-
|
|
11361
|
-
|
|
11362
|
-
|
|
11363
|
-
|
|
11364
|
-
|
|
11365
|
-
|
|
11366
|
-
|
|
11367
|
-
|
|
11368
|
-
|
|
11369
|
-
|
|
13660
|
+
return (parsed.revisions || parsed.Revisions || []).map(mapDocxDiffRevision);
|
|
13661
|
+
}
|
|
13662
|
+
async function docxDiffCompareProducts(left2, right2, settings, products) {
|
|
13663
|
+
const exports = ensureInitialized();
|
|
13664
|
+
const leftBytes = await toBytes(left2);
|
|
13665
|
+
const rightBytes = await toBytes(right2);
|
|
13666
|
+
await yieldToMain();
|
|
13667
|
+
const result = exports.DocxDiffBridge.CompareProductsJson(
|
|
13668
|
+
leftBytes,
|
|
13669
|
+
rightBytes,
|
|
13670
|
+
docxDiffSettingsJson(settings),
|
|
13671
|
+
products ? JSON.stringify(products) : ""
|
|
13672
|
+
);
|
|
13673
|
+
if (isErrorResponse(result)) {
|
|
13674
|
+
const error = parseError(result);
|
|
13675
|
+
throw new Error(`Failed to compare products: ${error.error}`);
|
|
13676
|
+
}
|
|
13677
|
+
const parsed = JSON.parse(result);
|
|
13678
|
+
return {
|
|
13679
|
+
redline: typeof parsed.redlineB64 === "string" ? Uint8Array.from(atob(parsed.redlineB64), (c) => c.charCodeAt(0)) : void 0,
|
|
13680
|
+
revisions: Array.isArray(parsed.revisions) ? parsed.revisions.map(mapDocxDiffRevision) : void 0,
|
|
13681
|
+
editScript: parsed.editScript !== void 0 ? parsed.editScript : void 0,
|
|
13682
|
+
semanticChanges: parsed.semanticChanges !== void 0 ? parsed.semanticChanges : void 0
|
|
13683
|
+
};
|
|
13684
|
+
}
|
|
13685
|
+
async function docxDiffCompareBatch(baseline, candidates, settings, products) {
|
|
13686
|
+
const exports = ensureInitialized();
|
|
13687
|
+
const baselineBytes = await toBytes(baseline);
|
|
13688
|
+
const payload = [];
|
|
13689
|
+
for (let i = 0; i < candidates.length; i++) {
|
|
13690
|
+
const bytes = await toBytes(candidates[i].document);
|
|
13691
|
+
payload.push({
|
|
13692
|
+
name: candidates[i].name ?? String(i),
|
|
13693
|
+
docB64: bytesToBase64(bytes)
|
|
13694
|
+
});
|
|
13695
|
+
}
|
|
13696
|
+
await yieldToMain();
|
|
13697
|
+
const result = exports.DocxDiffBridge.CompareBatchJson(
|
|
13698
|
+
baselineBytes,
|
|
13699
|
+
JSON.stringify(payload),
|
|
13700
|
+
docxDiffSettingsJson(settings),
|
|
13701
|
+
products ? JSON.stringify(products) : ""
|
|
13702
|
+
);
|
|
13703
|
+
if (isErrorResponse(result)) {
|
|
13704
|
+
const error = parseError(result);
|
|
13705
|
+
throw new Error(`Failed to compare batch: ${error.error}`);
|
|
13706
|
+
}
|
|
13707
|
+
const parsed = JSON.parse(result);
|
|
13708
|
+
return (parsed.results ?? []).map((entry) => ({
|
|
13709
|
+
name: String(entry.name ?? ""),
|
|
13710
|
+
error: typeof entry.error === "string" ? entry.error : void 0,
|
|
13711
|
+
redline: typeof entry.redlineB64 === "string" ? Uint8Array.from(atob(entry.redlineB64), (c) => c.charCodeAt(0)) : void 0,
|
|
13712
|
+
revisions: Array.isArray(entry.revisions) ? entry.revisions.map(mapDocxDiffRevision) : void 0,
|
|
13713
|
+
editScript: entry.editScript !== void 0 ? entry.editScript : void 0,
|
|
13714
|
+
semanticChanges: entry.semanticChanges !== void 0 ? entry.semanticChanges : void 0
|
|
11370
13715
|
}));
|
|
11371
13716
|
}
|
|
11372
13717
|
async function docxDiffGetEditScript(left2, right2, settings) {
|
|
@@ -11385,6 +13730,22 @@ async function docxDiffGetEditScript(left2, right2, settings) {
|
|
|
11385
13730
|
}
|
|
11386
13731
|
return result;
|
|
11387
13732
|
}
|
|
13733
|
+
async function docxDiffGetSemanticChanges(left2, right2, settings) {
|
|
13734
|
+
const exports = ensureInitialized();
|
|
13735
|
+
const leftBytes = await toBytes(left2);
|
|
13736
|
+
const rightBytes = await toBytes(right2);
|
|
13737
|
+
await yieldToMain();
|
|
13738
|
+
const result = exports.DocxDiffBridge.GetSemanticChangesJson(
|
|
13739
|
+
leftBytes,
|
|
13740
|
+
rightBytes,
|
|
13741
|
+
docxDiffSettingsJson(settings)
|
|
13742
|
+
);
|
|
13743
|
+
if (isErrorResponse(result)) {
|
|
13744
|
+
const error = parseError(result);
|
|
13745
|
+
throw new Error(`Failed to get semantic changes: ${error.error}`);
|
|
13746
|
+
}
|
|
13747
|
+
return JSON.parse(result);
|
|
13748
|
+
}
|
|
11388
13749
|
async function docxDiffAcceptRevisions(redline) {
|
|
11389
13750
|
const exports = ensureInitialized();
|
|
11390
13751
|
const bytes = await toBytes(redline);
|
|
@@ -11638,6 +13999,7 @@ async function getDocumentStructure(document2) {
|
|
|
11638
13999
|
const parsed = JSON.parse(result);
|
|
11639
14000
|
const convertElement = (el) => ({
|
|
11640
14001
|
id: el.Id || el.id,
|
|
14002
|
+
anchorId: el.AnchorId || el.anchorId,
|
|
11641
14003
|
type: el.Type || el.type,
|
|
11642
14004
|
textPreview: el.TextPreview || el.textPreview,
|
|
11643
14005
|
index: el.Index ?? el.index,
|
|
@@ -11649,8 +14011,12 @@ async function getDocumentStructure(document2) {
|
|
|
11649
14011
|
});
|
|
11650
14012
|
const convertTableColumn = (col) => ({
|
|
11651
14013
|
tableId: col.TableId || col.tableId,
|
|
14014
|
+
anchorId: col.AnchorId || col.anchorId,
|
|
14015
|
+
tableAnchorId: col.TableAnchorId || col.tableAnchorId,
|
|
14016
|
+
isVirtual: col.IsVirtual ?? col.isVirtual ?? false,
|
|
11652
14017
|
columnIndex: col.ColumnIndex ?? col.columnIndex,
|
|
11653
14018
|
cellIds: col.CellIds || col.cellIds || [],
|
|
14019
|
+
cellAnchorIds: col.CellAnchorIds || col.cellAnchorIds || [],
|
|
11654
14020
|
rowCount: col.RowCount ?? col.rowCount
|
|
11655
14021
|
});
|
|
11656
14022
|
const root = convertElement(parsed.Root || parsed.root);
|
|
@@ -11713,7 +14079,8 @@ async function getDocumentMetadata(document2) {
|
|
|
11713
14079
|
hasEndnotes: parsed.HasEndnotes ?? parsed.hasEndnotes,
|
|
11714
14080
|
hasTrackedChanges: parsed.HasTrackedChanges ?? parsed.hasTrackedChanges,
|
|
11715
14081
|
hasComments: parsed.HasComments ?? parsed.hasComments,
|
|
11716
|
-
estimatedPageCount: parsed.EstimatedPageCount ?? parsed.estimatedPageCount
|
|
14082
|
+
estimatedPageCount: parsed.EstimatedPageCount ?? parsed.estimatedPageCount,
|
|
14083
|
+
estimatedPageCountSource: parsed.EstimatedPageCountSource ?? parsed.estimatedPageCountSource ?? "heuristic"
|
|
11717
14084
|
};
|
|
11718
14085
|
}
|
|
11719
14086
|
async function exportToOpenContract(document2) {
|
|
@@ -12482,7 +14849,6 @@ export {
|
|
|
12482
14849
|
AnchorRenderMode,
|
|
12483
14850
|
AnnotationLabelMode,
|
|
12484
14851
|
CommentRenderMode,
|
|
12485
|
-
ComparisonEngine,
|
|
12486
14852
|
ComparisonLogCodes,
|
|
12487
14853
|
ComparisonLogLevel,
|
|
12488
14854
|
ConflictResolution,
|
|
@@ -12509,10 +14875,9 @@ export {
|
|
|
12509
14875
|
addAnnotation,
|
|
12510
14876
|
addAnnotationToHtml,
|
|
12511
14877
|
addAnnotationWithTarget,
|
|
14878
|
+
clearPageCitationHighlight,
|
|
12512
14879
|
compareDocuments,
|
|
12513
14880
|
compareDocumentsToHtml,
|
|
12514
|
-
compareDocumentsToHtmlWithLog,
|
|
12515
|
-
compareDocumentsWithLog,
|
|
12516
14881
|
computeDocumentHash,
|
|
12517
14882
|
convertDocxToHtml,
|
|
12518
14883
|
convertDocxToHtmlWithExternalAnnotations,
|
|
@@ -12523,24 +14888,28 @@ export {
|
|
|
12523
14888
|
createEditor,
|
|
12524
14889
|
createExternalAnnotationSet,
|
|
12525
14890
|
createRibbonEditor,
|
|
14891
|
+
createUnavailablePageMap,
|
|
12526
14892
|
createViewer,
|
|
12527
14893
|
docxDiffAcceptRevisions,
|
|
12528
14894
|
docxDiffCompare,
|
|
14895
|
+
docxDiffCompareBatch,
|
|
14896
|
+
docxDiffCompareProducts,
|
|
12529
14897
|
docxDiffConsolidate,
|
|
12530
14898
|
docxDiffGetConflicts,
|
|
12531
14899
|
docxDiffGetConsolidatedEditScript,
|
|
12532
14900
|
docxDiffGetConsolidatedRevisions,
|
|
12533
14901
|
docxDiffGetEditScript,
|
|
12534
14902
|
docxDiffGetRevisions,
|
|
14903
|
+
docxDiffGetSemanticChanges,
|
|
12535
14904
|
docxDiffRejectRevisions,
|
|
12536
14905
|
exportToOpenContract,
|
|
12537
14906
|
findElementById,
|
|
12538
14907
|
findElementsByType,
|
|
12539
|
-
findMovePair,
|
|
12540
14908
|
findTextOccurrences,
|
|
12541
14909
|
fitScale,
|
|
12542
14910
|
generateAnnotationCss,
|
|
12543
14911
|
generateAnnotationVisibilityCss,
|
|
14912
|
+
generatePackageManifest,
|
|
12544
14913
|
getAnnotations,
|
|
12545
14914
|
getDocumentMetadata,
|
|
12546
14915
|
getDocumentStructure,
|
|
@@ -12557,13 +14926,13 @@ export {
|
|
|
12557
14926
|
isInitialized,
|
|
12558
14927
|
isInsertion,
|
|
12559
14928
|
isMove,
|
|
12560
|
-
isMoveDestination,
|
|
12561
|
-
isMoveSource,
|
|
12562
14929
|
mountRibbon,
|
|
14930
|
+
navigateToPageCitation,
|
|
12563
14931
|
openDocxSession2 as openDocxSession,
|
|
12564
14932
|
paginateHtml,
|
|
12565
14933
|
parseSectionDimensions,
|
|
12566
14934
|
projectAnnotationsOntoHtml,
|
|
14935
|
+
proveRedlineReversibility,
|
|
12567
14936
|
ptToPx,
|
|
12568
14937
|
pxToPt,
|
|
12569
14938
|
removeAnnotation,
|
|
@@ -12583,5 +14952,7 @@ export {
|
|
|
12583
14952
|
targetTableRow,
|
|
12584
14953
|
toDocumentBytes,
|
|
12585
14954
|
validateExternalAnnotations,
|
|
14955
|
+
verifyDeliverable,
|
|
14956
|
+
verifyDeliveryReceipt,
|
|
12586
14957
|
wasmBasePath
|
|
12587
14958
|
};
|