docxodus 9.8.0 → 10.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +178 -0
- package/dist/canonical.d.ts +7 -0
- package/dist/canonical.d.ts.map +1 -0
- package/dist/canonical.js +56 -0
- package/dist/canonical.js.map +1 -0
- package/dist/docxodus.worker.d.ts +2 -1
- package/dist/docxodus.worker.d.ts.map +1 -1
- package/dist/docxodus.worker.js +244 -3
- package/dist/docxodus.worker.js.map +1 -1
- package/dist/editor.bundle.js +2024 -388
- package/dist/editor.d.ts +22 -2
- package/dist/editor.d.ts.map +1 -1
- package/dist/editor.js +94 -9
- package/dist/editor.js.map +1 -1
- package/dist/embed.bundle.js +2912 -437
- package/dist/embed.iife.js +2911 -436
- package/dist/export-assets.json +283 -0
- package/dist/export-browser.bundle.js +7990 -0
- package/dist/export-browser.d.ts +266 -0
- package/dist/export-browser.d.ts.map +1 -0
- package/dist/export-browser.js +2915 -0
- package/dist/export-browser.js.map +1 -0
- package/dist/export-resource-limits-v1.json +57 -0
- package/dist/font-contract.d.ts +150 -0
- package/dist/font-contract.d.ts.map +1 -0
- package/dist/font-contract.js +59 -0
- package/dist/font-contract.js.map +1 -0
- package/dist/font-runtime.d.ts +33 -0
- package/dist/font-runtime.d.ts.map +1 -0
- package/dist/font-runtime.js +1100 -0
- package/dist/font-runtime.js.map +1 -0
- package/dist/index.d.ts +62 -5
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +130 -16
- package/dist/index.js.map +1 -1
- package/dist/page-geometry.d.ts +8 -1
- package/dist/page-geometry.d.ts.map +1 -1
- package/dist/page-geometry.js +37 -11
- package/dist/page-geometry.js.map +1 -1
- package/dist/pagination.bundle.js +1937 -380
- package/dist/pagination.d.ts +277 -15
- package/dist/pagination.d.ts.map +1 -1
- package/dist/pagination.js +2055 -438
- package/dist/pagination.js.map +1 -1
- package/dist/react.d.ts +11 -4
- package/dist/react.d.ts.map +1 -1
- package/dist/react.js +38 -5
- package/dist/react.js.map +1 -1
- package/dist/render-report-v2.schema.json +1581 -0
- package/dist/ribbon-chrome.d.ts +1 -1
- package/dist/ribbon-chrome.d.ts.map +1 -1
- package/dist/ribbon-chrome.js +46 -0
- package/dist/ribbon-chrome.js.map +1 -1
- package/dist/ribbon.js +70 -0
- package/dist/ribbon.js.map +1 -1
- package/dist/session.bundle.js +695 -29
- package/dist/session.d.ts +134 -19
- package/dist/session.d.ts.map +1 -1
- package/dist/session.js +562 -25
- package/dist/session.js.map +1 -1
- package/dist/types.d.ts +1207 -16
- package/dist/types.d.ts.map +1 -1
- package/dist/types.js.map +1 -1
- package/dist/wasm/_framework/DocumentFormat.OpenXml.Framework.wasm +0 -0
- package/dist/wasm/_framework/DocumentFormat.OpenXml.Framework.wasm.br +0 -0
- package/dist/wasm/_framework/DocumentFormat.OpenXml.wasm +0 -0
- package/dist/wasm/_framework/DocumentFormat.OpenXml.wasm.br +0 -0
- package/dist/wasm/_framework/Docxodus.wasm +0 -0
- package/dist/wasm/_framework/Docxodus.wasm.br +0 -0
- package/dist/wasm/_framework/DocxodusWasm.wasm +0 -0
- package/dist/wasm/_framework/DocxodusWasm.wasm.br +0 -0
- package/dist/wasm/_framework/System.Collections.Concurrent.wasm +0 -0
- package/dist/wasm/_framework/System.Collections.Concurrent.wasm.br +0 -0
- package/dist/wasm/_framework/System.Collections.Immutable.wasm +0 -0
- package/dist/wasm/_framework/System.Collections.Immutable.wasm.br +0 -0
- package/dist/wasm/_framework/System.Collections.NonGeneric.wasm +0 -0
- package/dist/wasm/_framework/System.Collections.NonGeneric.wasm.br +0 -0
- package/dist/wasm/_framework/System.Collections.Specialized.wasm +0 -0
- package/dist/wasm/_framework/System.Collections.Specialized.wasm.br +0 -0
- package/dist/wasm/_framework/System.Collections.wasm +0 -0
- package/dist/wasm/_framework/System.Collections.wasm.br +0 -0
- package/dist/wasm/_framework/System.ComponentModel.Primitives.wasm +0 -0
- package/dist/wasm/_framework/System.ComponentModel.Primitives.wasm.br +0 -0
- package/dist/wasm/_framework/System.ComponentModel.TypeConverter.wasm +0 -0
- package/dist/wasm/_framework/System.ComponentModel.TypeConverter.wasm.br +0 -0
- package/dist/wasm/_framework/System.ComponentModel.wasm +0 -0
- package/dist/wasm/_framework/System.ComponentModel.wasm.br +0 -0
- package/dist/wasm/_framework/System.Console.wasm +0 -0
- package/dist/wasm/_framework/System.Console.wasm.br +0 -0
- package/dist/wasm/_framework/System.Diagnostics.Process.wasm +0 -0
- package/dist/wasm/_framework/System.Diagnostics.Process.wasm.br +0 -0
- package/dist/wasm/_framework/System.IO.Compression.wasm +0 -0
- package/dist/wasm/_framework/System.IO.Compression.wasm.br +0 -0
- package/dist/wasm/_framework/System.IO.Packaging.wasm +0 -0
- package/dist/wasm/_framework/System.IO.Packaging.wasm.br +0 -0
- package/dist/wasm/_framework/System.IO.Pipelines.wasm +0 -0
- package/dist/wasm/_framework/System.IO.Pipelines.wasm.br +0 -0
- package/dist/wasm/_framework/System.Linq.Expressions.wasm +0 -0
- package/dist/wasm/_framework/System.Linq.Expressions.wasm.br +0 -0
- package/dist/wasm/_framework/System.Linq.wasm +0 -0
- package/dist/wasm/_framework/System.Linq.wasm.br +0 -0
- package/dist/wasm/_framework/System.Memory.wasm +0 -0
- package/dist/wasm/_framework/System.Memory.wasm.br +0 -0
- package/dist/wasm/_framework/System.Net.Http.wasm +0 -0
- package/dist/wasm/_framework/System.Net.Http.wasm.br +0 -0
- package/dist/wasm/_framework/System.Net.Primitives.wasm +0 -0
- package/dist/wasm/_framework/System.Net.Primitives.wasm.br +0 -0
- package/dist/wasm/_framework/System.ObjectModel.wasm +0 -0
- package/dist/wasm/_framework/System.ObjectModel.wasm.br +0 -0
- package/dist/wasm/_framework/System.Private.CoreLib.wasm +0 -0
- package/dist/wasm/_framework/System.Private.CoreLib.wasm.br +0 -0
- package/dist/wasm/_framework/System.Private.Uri.wasm +0 -0
- package/dist/wasm/_framework/System.Private.Uri.wasm.br +0 -0
- package/dist/wasm/_framework/System.Private.Xml.Linq.wasm +0 -0
- package/dist/wasm/_framework/System.Private.Xml.Linq.wasm.br +0 -0
- package/dist/wasm/_framework/System.Private.Xml.wasm +0 -0
- package/dist/wasm/_framework/System.Private.Xml.wasm.br +0 -0
- package/dist/wasm/_framework/System.Runtime.InteropServices.JavaScript.wasm +0 -0
- package/dist/wasm/_framework/System.Runtime.InteropServices.JavaScript.wasm.br +0 -0
- package/dist/wasm/_framework/System.Runtime.wasm +0 -0
- package/dist/wasm/_framework/System.Runtime.wasm.br +0 -0
- package/dist/wasm/_framework/System.Security.Cryptography.wasm +0 -0
- package/dist/wasm/_framework/System.Security.Cryptography.wasm.br +0 -0
- package/dist/wasm/_framework/System.Text.Encodings.Web.wasm +0 -0
- package/dist/wasm/_framework/System.Text.Encodings.Web.wasm.br +0 -0
- package/dist/wasm/_framework/System.Text.Json.wasm +0 -0
- package/dist/wasm/_framework/System.Text.Json.wasm.br +0 -0
- package/dist/wasm/_framework/System.Text.RegularExpressions.wasm +0 -0
- package/dist/wasm/_framework/System.Text.RegularExpressions.wasm.br +0 -0
- package/dist/wasm/_framework/System.Xml.Linq.wasm +0 -0
- package/dist/wasm/_framework/System.Xml.Linq.wasm.br +0 -0
- package/dist/wasm/_framework/System.Xml.XDocument.wasm +0 -0
- package/dist/wasm/_framework/System.Xml.XDocument.wasm.br +0 -0
- package/dist/wasm/_framework/System.wasm +0 -0
- package/dist/wasm/_framework/System.wasm.br +0 -0
- package/dist/wasm/_framework/dotnet.boot.js +38 -38
- package/dist/wasm/_framework/dotnet.boot.js.br +0 -0
- package/dist/wasm/_framework/dotnet.js +1 -1
- package/dist/wasm/_framework/dotnet.js.br +0 -0
- package/dist/wasm/_framework/dotnet.native.js +3 -3
- package/dist/wasm/_framework/dotnet.native.js.br +0 -0
- package/dist/wasm/_framework/dotnet.native.wasm +0 -0
- package/dist/wasm/_framework/dotnet.native.wasm.br +0 -0
- package/dist/wasm/_framework/dotnet.runtime.js +1 -1
- package/dist/wasm/_framework/dotnet.runtime.js.br +0 -0
- package/dist/worker-proxy.bundle.js +196 -29
- package/dist/worker-proxy.d.ts +36 -3
- package/dist/worker-proxy.d.ts.map +1 -1
- package/dist/worker-proxy.js +186 -33
- package/dist/worker-proxy.js.map +1 -1
- package/package.json +24 -7
package/dist/embed.bundle.js
CHANGED
|
@@ -362,7 +362,46 @@ function targetSearchInElement(elementId, searchText, occurrence = 1) {
|
|
|
362
362
|
}
|
|
363
363
|
|
|
364
364
|
// src/session.ts
|
|
365
|
-
|
|
365
|
+
function mutationBatchChangeSet(before, after, key) {
|
|
366
|
+
const beforeGroups = /* @__PURE__ */ new Map();
|
|
367
|
+
const afterGroups = /* @__PURE__ */ new Map();
|
|
368
|
+
const group = (items, target) => {
|
|
369
|
+
items.forEach((item, index) => {
|
|
370
|
+
const identity = key(item);
|
|
371
|
+
const indices = target.get(identity) ?? [];
|
|
372
|
+
indices.push(index);
|
|
373
|
+
target.set(identity, indices);
|
|
374
|
+
});
|
|
375
|
+
};
|
|
376
|
+
group(before, beforeGroups);
|
|
377
|
+
group(after, afterGroups);
|
|
378
|
+
const beforeMatched = before.map(() => false);
|
|
379
|
+
const afterMatched = after.map(() => false);
|
|
380
|
+
const modified = after.map(() => false);
|
|
381
|
+
for (const [identity, afterIndices] of afterGroups) {
|
|
382
|
+
const beforeIndices = beforeGroups.get(identity) ?? [];
|
|
383
|
+
for (const afterIndex of afterIndices) {
|
|
384
|
+
const beforeIndex = beforeIndices.find((index) => !beforeMatched[index] && JSON.stringify(before[index]) === JSON.stringify(after[afterIndex]));
|
|
385
|
+
if (beforeIndex === void 0) continue;
|
|
386
|
+
beforeMatched[beforeIndex] = true;
|
|
387
|
+
afterMatched[afterIndex] = true;
|
|
388
|
+
}
|
|
389
|
+
const remainingBefore = beforeIndices.filter((index) => !beforeMatched[index]);
|
|
390
|
+
const remainingAfter = afterIndices.filter((index) => !afterMatched[index]);
|
|
391
|
+
const modifiedCount = Math.min(remainingBefore.length, remainingAfter.length);
|
|
392
|
+
for (let index = 0; index < modifiedCount; index++) {
|
|
393
|
+
beforeMatched[remainingBefore[index]] = true;
|
|
394
|
+
afterMatched[remainingAfter[index]] = true;
|
|
395
|
+
modified[remainingAfter[index]] = true;
|
|
396
|
+
}
|
|
397
|
+
}
|
|
398
|
+
return {
|
|
399
|
+
added: after.filter((_, index) => !afterMatched[index]),
|
|
400
|
+
removed: before.filter((_, index) => !beforeMatched[index]),
|
|
401
|
+
modified: after.filter((_, index) => modified[index])
|
|
402
|
+
};
|
|
403
|
+
}
|
|
404
|
+
var DocxSession = class _DocxSession {
|
|
366
405
|
/** @internal */
|
|
367
406
|
constructor(handle, wasm) {
|
|
368
407
|
// ─── Raw escape hatch ────────────────────────────────────────────────
|
|
@@ -378,6 +417,336 @@ var DocxSession = class {
|
|
|
378
417
|
project() {
|
|
379
418
|
return JSON.parse(this.wasm.Project(this.handle));
|
|
380
419
|
}
|
|
420
|
+
/** Monotonic document version (0 at open; +1 per committed mutation/undo/redo). */
|
|
421
|
+
getVersion() {
|
|
422
|
+
return JSON.parse(this.wasm.GetVersion(this.handle)).version;
|
|
423
|
+
}
|
|
424
|
+
/**
|
|
425
|
+
* Verification manifest for the current logical checkpoint. Unsaved edits are included and
|
|
426
|
+
* the read does not mutate package bytes, caches, history, or the document version.
|
|
427
|
+
*/
|
|
428
|
+
getPackageManifest() {
|
|
429
|
+
return JSON.parse(this.wasm.GetPackageManifest(this.handle));
|
|
430
|
+
}
|
|
431
|
+
/** Register a browser-materialized PageMap without changing the document version. */
|
|
432
|
+
registerPageMap(pageMap, expectedRendererFingerprint) {
|
|
433
|
+
return JSON.parse(this.wasm.RegisterPageMap(
|
|
434
|
+
this.handle,
|
|
435
|
+
JSON.stringify(pageMap),
|
|
436
|
+
expectedRendererFingerprint ?? ""
|
|
437
|
+
));
|
|
438
|
+
}
|
|
439
|
+
getPageMapStatus(request) {
|
|
440
|
+
return JSON.parse(this.wasm.GetPageMapStatus(
|
|
441
|
+
this.handle,
|
|
442
|
+
request ? JSON.stringify(request) : ""
|
|
443
|
+
));
|
|
444
|
+
}
|
|
445
|
+
getPageCitation(anchorId, request) {
|
|
446
|
+
return JSON.parse(this.wasm.GetPageCitation(
|
|
447
|
+
this.handle,
|
|
448
|
+
anchorId,
|
|
449
|
+
JSON.stringify(request)
|
|
450
|
+
));
|
|
451
|
+
}
|
|
452
|
+
/** Evaluate optimistic guards without mutating or advancing the version. */
|
|
453
|
+
checkPreconditions(preconditions) {
|
|
454
|
+
return JSON.parse(
|
|
455
|
+
this.wasm.CheckPreconditions(this.handle, JSON.stringify(preconditions))
|
|
456
|
+
);
|
|
457
|
+
}
|
|
458
|
+
/**
|
|
459
|
+
* Guard any synchronous mutation. WASM calls are synchronous and single-threaded, so the
|
|
460
|
+
* check and callback form one uninterrupted client-side operation. Prefer a method's native
|
|
461
|
+
* `preconditions` option where it has one (notably replaceTextRange's match-count guard).
|
|
462
|
+
*/
|
|
463
|
+
runWithPreconditions(preconditions, mutation) {
|
|
464
|
+
const checked = this.checkPreconditions(preconditions);
|
|
465
|
+
return checked.success ? mutation() : checked;
|
|
466
|
+
}
|
|
467
|
+
/**
|
|
468
|
+
* Execute synchronous mutations atomically by default. Atomic success is one undo/version
|
|
469
|
+
* unit; any failed or thrown step restores the exact package and history checkpoint.
|
|
470
|
+
*/
|
|
471
|
+
executeBatch(steps, mode = "atomic") {
|
|
472
|
+
if (mode !== "atomic" && mode !== "best_effort") {
|
|
473
|
+
throw new RangeError(`unknown mutation batch mode: ${String(mode)}`);
|
|
474
|
+
}
|
|
475
|
+
const baseVersion = this.getVersion();
|
|
476
|
+
const observationWarnings = [];
|
|
477
|
+
const inspect = (label, read, fallback) => {
|
|
478
|
+
try {
|
|
479
|
+
return read();
|
|
480
|
+
} catch (error) {
|
|
481
|
+
observationWarnings.push(`${label} unavailable: ${error instanceof Error ? error.message : String(error)}`);
|
|
482
|
+
return fallback;
|
|
483
|
+
}
|
|
484
|
+
};
|
|
485
|
+
const beforeRevisions = inspect("Revision delta inspection", () => this.listRevisions(), []);
|
|
486
|
+
const beforeComments = inspect("Comment delta inspection", () => this.listComments(), []);
|
|
487
|
+
const beforeAnnotations = inspect("Annotation delta inspection", () => this.listAnnotations(), []);
|
|
488
|
+
const complete = (result) => {
|
|
489
|
+
try {
|
|
490
|
+
const revisionChanges = mutationBatchChangeSet(
|
|
491
|
+
beforeRevisions,
|
|
492
|
+
inspect("Revision delta inspection", () => this.listRevisions(), beforeRevisions),
|
|
493
|
+
(revision) => revision.id
|
|
494
|
+
);
|
|
495
|
+
const commentChanges = mutationBatchChangeSet(
|
|
496
|
+
beforeComments,
|
|
497
|
+
inspect("Comment delta inspection", () => this.listComments(), beforeComments),
|
|
498
|
+
(comment) => comment.anchorId
|
|
499
|
+
);
|
|
500
|
+
const annotationChanges = mutationBatchChangeSet(
|
|
501
|
+
beforeAnnotations,
|
|
502
|
+
inspect("Annotation delta inspection", () => this.listAnnotations(), beforeAnnotations),
|
|
503
|
+
(annotation) => annotation.id ?? ""
|
|
504
|
+
);
|
|
505
|
+
const resultVersion = inspect(
|
|
506
|
+
"Result version inspection",
|
|
507
|
+
() => this.getVersion(),
|
|
508
|
+
baseVersion
|
|
509
|
+
);
|
|
510
|
+
const warnings = [...observationWarnings];
|
|
511
|
+
if ([...revisionChanges.added, ...revisionChanges.modified].some((revision) => revision.date !== void 0 && revision.date !== null)) {
|
|
512
|
+
warnings.push("Tracked-revision date attributes may use the execution clock; compare revision ids, authors, types, text, and anchors across separate executions.");
|
|
513
|
+
}
|
|
514
|
+
if ([...commentChanges.added, ...commentChanges.modified].some((comment) => comment.date !== void 0 && comment.date !== null)) {
|
|
515
|
+
warnings.push("Comment date attributes may be generated from the execution clock; supply dates explicitly when byte-identical replay is required.");
|
|
516
|
+
}
|
|
517
|
+
if (annotationChanges.added.some((annotation) => annotation.created !== void 0 && annotation.created !== null)) {
|
|
518
|
+
warnings.push("Auto-generated annotation ids or creation timestamps are execution metadata; supply id and created explicitly when byte-identical replay is required.");
|
|
519
|
+
}
|
|
520
|
+
if (result.steps.some((step) => step.results.some((edit) => edit.created.length > 0))) {
|
|
521
|
+
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.");
|
|
522
|
+
}
|
|
523
|
+
if (mode === "best_effort" && !result.success) {
|
|
524
|
+
warnings.push("Best-effort execution retains every successful step despite later failures.");
|
|
525
|
+
}
|
|
526
|
+
let packageHash = null;
|
|
527
|
+
if (!this.wasm.GetPackageContentHash) {
|
|
528
|
+
warnings.push("This WASM bundle predates package equivalence hashes; packageHash is unavailable.");
|
|
529
|
+
} else {
|
|
530
|
+
try {
|
|
531
|
+
packageHash = this.wasm.GetPackageContentHash(this.handle);
|
|
532
|
+
} catch (error) {
|
|
533
|
+
warnings.push(`Package equivalence hash unavailable: ${error instanceof Error ? error.message : String(error)}`);
|
|
534
|
+
}
|
|
535
|
+
}
|
|
536
|
+
return {
|
|
537
|
+
...result,
|
|
538
|
+
preview: false,
|
|
539
|
+
baseVersion,
|
|
540
|
+
resultVersion,
|
|
541
|
+
packageHash,
|
|
542
|
+
revisionChanges,
|
|
543
|
+
commentChanges,
|
|
544
|
+
annotationChanges,
|
|
545
|
+
warnings,
|
|
546
|
+
html: null
|
|
547
|
+
};
|
|
548
|
+
} catch (error) {
|
|
549
|
+
return {
|
|
550
|
+
...result,
|
|
551
|
+
preview: false,
|
|
552
|
+
baseVersion,
|
|
553
|
+
resultVersion: inspect("Result version inspection", () => this.getVersion(), baseVersion),
|
|
554
|
+
packageHash: null,
|
|
555
|
+
revisionChanges: { added: [], removed: [], modified: [] },
|
|
556
|
+
commentChanges: { added: [], removed: [], modified: [] },
|
|
557
|
+
annotationChanges: { added: [], removed: [], modified: [] },
|
|
558
|
+
warnings: [
|
|
559
|
+
...observationWarnings,
|
|
560
|
+
`Batch receipt enrichment unavailable: ${error instanceof Error ? error.message : String(error)}`
|
|
561
|
+
],
|
|
562
|
+
html: null
|
|
563
|
+
};
|
|
564
|
+
}
|
|
565
|
+
};
|
|
566
|
+
const internalFailure = (value) => ({
|
|
567
|
+
success: false,
|
|
568
|
+
error: { code: "internal_error", message: value instanceof Error ? value.message : String(value) },
|
|
569
|
+
created: [],
|
|
570
|
+
removed: [],
|
|
571
|
+
modified: []
|
|
572
|
+
});
|
|
573
|
+
const run = (step) => {
|
|
574
|
+
try {
|
|
575
|
+
const value = step.mutation();
|
|
576
|
+
const results = Array.isArray(value) ? value : [value];
|
|
577
|
+
if (results.some((result) => result === null || typeof result !== "object" || typeof result.success !== "boolean")) {
|
|
578
|
+
return [internalFailure("batch mutation returned invalid edit results")];
|
|
579
|
+
}
|
|
580
|
+
return results;
|
|
581
|
+
} catch (error) {
|
|
582
|
+
return [internalFailure(error)];
|
|
583
|
+
}
|
|
584
|
+
};
|
|
585
|
+
const failureOf = (step, rolledBack) => ({
|
|
586
|
+
index: step.index,
|
|
587
|
+
tool: step.tool,
|
|
588
|
+
action: step.action,
|
|
589
|
+
error: step.results.find((result) => !result.success)?.error ?? { code: "internal_error", message: "batch step failed without an error" },
|
|
590
|
+
rolledBack
|
|
591
|
+
});
|
|
592
|
+
const preflightOne = (step) => {
|
|
593
|
+
try {
|
|
594
|
+
return step.preflight?.();
|
|
595
|
+
} catch (error) {
|
|
596
|
+
return internalFailure(error).error;
|
|
597
|
+
}
|
|
598
|
+
};
|
|
599
|
+
if (mode === "atomic") {
|
|
600
|
+
const preflight = steps.map(preflightOne);
|
|
601
|
+
const failedPreflight = preflight.findIndex((error) => error !== void 0);
|
|
602
|
+
if (failedPreflight >= 0) {
|
|
603
|
+
const source = steps[failedPreflight];
|
|
604
|
+
const failed2 = {
|
|
605
|
+
index: failedPreflight,
|
|
606
|
+
tool: source.tool,
|
|
607
|
+
action: source.action,
|
|
608
|
+
success: false,
|
|
609
|
+
rolledBack: true,
|
|
610
|
+
results: [{ success: false, error: preflight[failedPreflight], created: [], removed: [], modified: [] }]
|
|
611
|
+
};
|
|
612
|
+
return complete({
|
|
613
|
+
mode,
|
|
614
|
+
status: "failed",
|
|
615
|
+
success: false,
|
|
616
|
+
rolledBack: true,
|
|
617
|
+
steps: [failed2],
|
|
618
|
+
failure: failureOf(failed2, true)
|
|
619
|
+
});
|
|
620
|
+
}
|
|
621
|
+
const transaction = this.wasm.BeginTransaction(this.handle);
|
|
622
|
+
const completed2 = [];
|
|
623
|
+
try {
|
|
624
|
+
for (let index = 0; index < steps.length; index++) {
|
|
625
|
+
const source = steps[index];
|
|
626
|
+
const results = run(source);
|
|
627
|
+
const step = {
|
|
628
|
+
index,
|
|
629
|
+
tool: source.tool,
|
|
630
|
+
action: source.action,
|
|
631
|
+
success: results.every((result) => result.success),
|
|
632
|
+
rolledBack: false,
|
|
633
|
+
results
|
|
634
|
+
};
|
|
635
|
+
completed2.push(step);
|
|
636
|
+
if (!step.success) {
|
|
637
|
+
this.wasm.RollbackTransaction(transaction);
|
|
638
|
+
const rolledBack = completed2.map((value) => ({ ...value, rolledBack: true }));
|
|
639
|
+
const failed2 = rolledBack[rolledBack.length - 1];
|
|
640
|
+
return complete({
|
|
641
|
+
mode,
|
|
642
|
+
status: "failed",
|
|
643
|
+
success: false,
|
|
644
|
+
rolledBack: true,
|
|
645
|
+
steps: rolledBack,
|
|
646
|
+
failure: failureOf(failed2, true)
|
|
647
|
+
});
|
|
648
|
+
}
|
|
649
|
+
}
|
|
650
|
+
this.wasm.CommitTransaction(transaction);
|
|
651
|
+
return complete({ mode, status: "ok", success: true, rolledBack: false, steps: completed2 });
|
|
652
|
+
} catch (error) {
|
|
653
|
+
try {
|
|
654
|
+
this.wasm.RollbackTransaction(transaction);
|
|
655
|
+
} catch {
|
|
656
|
+
}
|
|
657
|
+
throw error;
|
|
658
|
+
}
|
|
659
|
+
}
|
|
660
|
+
const completed = steps.map((source, index) => {
|
|
661
|
+
const preflight = preflightOne(source);
|
|
662
|
+
const results = preflight ? [{ success: false, error: preflight, created: [], removed: [], modified: [] }] : run(source);
|
|
663
|
+
return {
|
|
664
|
+
index,
|
|
665
|
+
tool: source.tool,
|
|
666
|
+
action: source.action,
|
|
667
|
+
success: results.every((result) => result.success),
|
|
668
|
+
rolledBack: false,
|
|
669
|
+
results
|
|
670
|
+
};
|
|
671
|
+
});
|
|
672
|
+
const failed = completed.find((step) => !step.success);
|
|
673
|
+
return complete({
|
|
674
|
+
mode,
|
|
675
|
+
status: failed ? completed.some((step) => step.success) ? "partial" : "failed" : "ok",
|
|
676
|
+
success: failed === void 0,
|
|
677
|
+
rolledBack: false,
|
|
678
|
+
steps: completed,
|
|
679
|
+
failure: failed ? failureOf(failed, false) : void 0
|
|
680
|
+
});
|
|
681
|
+
}
|
|
682
|
+
/**
|
|
683
|
+
* Execute the same callback batch algorithm against a complete isolated package clone.
|
|
684
|
+
* Callbacks receive the shadow session explicitly; mutate that argument. The live session's
|
|
685
|
+
* package, caches, version, configuration, and undo/redo history are never execution targets.
|
|
686
|
+
*/
|
|
687
|
+
previewBatch(steps, mode = "atomic", options) {
|
|
688
|
+
if (mode !== "atomic" && mode !== "best_effort") {
|
|
689
|
+
throw new RangeError(`unknown mutation batch mode: ${String(mode)}`);
|
|
690
|
+
}
|
|
691
|
+
const htmlMode = options?.html ?? "none";
|
|
692
|
+
if (htmlMode !== "none" && htmlMode !== "scoped" && htmlMode !== "full") {
|
|
693
|
+
throw new RangeError(`unknown preview HTML mode: ${String(htmlMode)}`);
|
|
694
|
+
}
|
|
695
|
+
if (!this.wasm.OpenPreviewSession) {
|
|
696
|
+
throw new Error("This WASM bundle does not support isolated mutation previews.");
|
|
697
|
+
}
|
|
698
|
+
const shadow = new _DocxSession(this.wasm.OpenPreviewSession(this.handle), this.wasm);
|
|
699
|
+
try {
|
|
700
|
+
const result = shadow.executeBatch(
|
|
701
|
+
steps.map((step) => ({
|
|
702
|
+
tool: step.tool,
|
|
703
|
+
action: step.action,
|
|
704
|
+
mutation: () => step.mutation(shadow),
|
|
705
|
+
preflight: step.preflight ? () => step.preflight(shadow) : void 0
|
|
706
|
+
})),
|
|
707
|
+
mode
|
|
708
|
+
);
|
|
709
|
+
const warnings = [...result.warnings];
|
|
710
|
+
let html = null;
|
|
711
|
+
const unwrapRendered = (rendered) => {
|
|
712
|
+
if (rendered.trimStart().startsWith("{")) {
|
|
713
|
+
const envelope = JSON.parse(rendered);
|
|
714
|
+
if (envelope.error) {
|
|
715
|
+
warnings.push(`Preview HTML could not be generated: ${envelope.error}`);
|
|
716
|
+
return null;
|
|
717
|
+
}
|
|
718
|
+
}
|
|
719
|
+
return rendered;
|
|
720
|
+
};
|
|
721
|
+
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.";
|
|
722
|
+
try {
|
|
723
|
+
if (htmlMode === "scoped") {
|
|
724
|
+
if (!options?.htmlAnchorId) {
|
|
725
|
+
warnings.push("Scoped HTML was requested without htmlAnchorId; no HTML was generated.");
|
|
726
|
+
} else if (this.wasm.RenderPreviewBlockHtml) {
|
|
727
|
+
html = unwrapRendered(
|
|
728
|
+
this.wasm.RenderPreviewBlockHtml(shadow.handle, options.htmlAnchorId)
|
|
729
|
+
);
|
|
730
|
+
} else {
|
|
731
|
+
warnings.push(legacyProfileWarning);
|
|
732
|
+
html = shadow.renderBlock(options.htmlAnchorId);
|
|
733
|
+
}
|
|
734
|
+
} else if (htmlMode === "full") {
|
|
735
|
+
if (this.wasm.RenderPreviewHtml) {
|
|
736
|
+
html = unwrapRendered(this.wasm.RenderPreviewHtml(shadow.handle));
|
|
737
|
+
} else {
|
|
738
|
+
warnings.push(legacyProfileWarning);
|
|
739
|
+
html = unwrapRendered(this.wasm.RenderHtmlForReview ? this.wasm.RenderHtmlForReview(shadow.handle, "docx-", false, false, 1, true) : this.wasm.RenderHtml(shadow.handle, "docx-", false, false, 1));
|
|
740
|
+
}
|
|
741
|
+
}
|
|
742
|
+
} catch (error) {
|
|
743
|
+
warnings.push(`Preview HTML could not be generated: ${error instanceof Error ? error.message : String(error)}`);
|
|
744
|
+
}
|
|
745
|
+
return { ...result, preview: true, warnings, html };
|
|
746
|
+
} finally {
|
|
747
|
+
shadow.close();
|
|
748
|
+
}
|
|
749
|
+
}
|
|
381
750
|
/**
|
|
382
751
|
* Project a slice of the document keyed off an anchor — useful for showing
|
|
383
752
|
* one section to an LLM at a time without paying the cost of projecting the
|
|
@@ -393,9 +762,9 @@ var DocxSession = class {
|
|
|
393
762
|
*
|
|
394
763
|
* @see docs/architecture/docx_mutation_api.md
|
|
395
764
|
*/
|
|
396
|
-
projectAnchor(anchorId, depth = 2 /* SubtreeAndFollowingSiblings
|
|
765
|
+
projectAnchor(anchorId, depth = 2 /* SubtreeAndFollowingSiblings */, citation) {
|
|
397
766
|
return JSON.parse(
|
|
398
|
-
this.wasm.ProjectAnchor(this.handle, anchorId, depth)
|
|
767
|
+
citation ? this.wasm.ProjectAnchorWithCitations(this.handle, anchorId, depth, JSON.stringify(citation)) : this.wasm.ProjectAnchor(this.handle, anchorId, depth)
|
|
399
768
|
);
|
|
400
769
|
}
|
|
401
770
|
/**
|
|
@@ -419,11 +788,21 @@ var DocxSession = class {
|
|
|
419
788
|
return html;
|
|
420
789
|
}
|
|
421
790
|
// ─── Tier A: text CRUD ───────────────────────────────────────────────
|
|
422
|
-
replaceText(anchorId, markdown) {
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
return
|
|
791
|
+
replaceText(anchorId, markdown, preconditions) {
|
|
792
|
+
const apply = () => JSON.parse(
|
|
793
|
+
this.wasm.ReplaceText(this.handle, anchorId, markdown)
|
|
794
|
+
);
|
|
795
|
+
return preconditions ? this.runWithPreconditions(
|
|
796
|
+
{ ...preconditions, anchorId: preconditions.anchorId ?? anchorId },
|
|
797
|
+
apply
|
|
798
|
+
) : apply();
|
|
799
|
+
}
|
|
800
|
+
deleteBlock(anchorId, preconditions) {
|
|
801
|
+
const apply = () => JSON.parse(this.wasm.DeleteBlock(this.handle, anchorId));
|
|
802
|
+
return preconditions ? this.runWithPreconditions(
|
|
803
|
+
{ ...preconditions, anchorId: preconditions.anchorId ?? anchorId },
|
|
804
|
+
apply
|
|
805
|
+
) : apply();
|
|
427
806
|
}
|
|
428
807
|
/** Reorder one top-level paragraph/heading/list/table block relative to another. */
|
|
429
808
|
moveBlock(sourceAnchorId, targetAnchorId, position) {
|
|
@@ -478,7 +857,7 @@ var DocxSession = class {
|
|
|
478
857
|
}
|
|
479
858
|
/**
|
|
480
859
|
* 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
|
|
860
|
+
* cell markdown, and cell alignment. The returned `EditResult.created` lists canonical `tc`
|
|
482
861
|
* anchors (row-major), so each cell can then be addressed to fill/format.
|
|
483
862
|
*/
|
|
484
863
|
insertTable(anchorId, position, rows, cols, options) {
|
|
@@ -487,10 +866,26 @@ var DocxSession = class {
|
|
|
487
866
|
this.wasm.InsertTable(this.handle, anchorId, position, rows, cols, optionsJson)
|
|
488
867
|
);
|
|
489
868
|
}
|
|
869
|
+
/** Resolve a canonical `tbl` anchor to explicit table/row/column/cell identities. */
|
|
870
|
+
getTableMetadata(tableAnchorId) {
|
|
871
|
+
return JSON.parse(this.wasm.GetTableMetadata(this.handle, tableAnchorId));
|
|
872
|
+
}
|
|
873
|
+
/** Resolve a canonical `tc` anchor to its zero-based table-grid coordinate and spans. */
|
|
874
|
+
resolveTableCellAnchor(cellAnchorId) {
|
|
875
|
+
return JSON.parse(
|
|
876
|
+
this.wasm.ResolveTableCellAnchor(this.handle, cellAnchorId)
|
|
877
|
+
);
|
|
878
|
+
}
|
|
879
|
+
/** Resolve a zero-based table-grid coordinate to the physical `tc` covering it. */
|
|
880
|
+
resolveTableCellCoordinate(tableAnchorId, rowIndex, columnIndex) {
|
|
881
|
+
return JSON.parse(
|
|
882
|
+
this.wasm.ResolveTableCellCoordinate(this.handle, tableAnchorId, rowIndex, columnIndex)
|
|
883
|
+
);
|
|
884
|
+
}
|
|
490
885
|
/**
|
|
491
|
-
* Table row/column editing, addressed by
|
|
492
|
-
* {@link insertTable}'s `created`
|
|
493
|
-
* empty (`created` lists
|
|
886
|
+
* Table row/column editing, addressed by the canonical `tc` anchor returned from
|
|
887
|
+
* {@link insertTable}'s `created` or table metadata. Insert clones the reference row/column's
|
|
888
|
+
* widths and starts empty (`created` lists new `tc` anchors); delete of the last row/column removes
|
|
494
889
|
* the whole table. All four are grid-aware: inserting across a merge extends it, deleting
|
|
495
890
|
* through one narrows it, and deleting a vertical merge's lead row promotes the next row to
|
|
496
891
|
* carry it — the grid is never left ragged.
|
|
@@ -530,7 +925,7 @@ var DocxSession = class {
|
|
|
530
925
|
return JSON.parse(this.wasm.UnmergeCells(this.handle, cellAnchorId));
|
|
531
926
|
}
|
|
532
927
|
/**
|
|
533
|
-
* Table styling, addressed by a
|
|
928
|
+
* Table styling, addressed by a canonical `tc` anchor — the post-insert counterpart of
|
|
534
929
|
* {@link insertTable}'s options (issue #315 Stage A). `setColumnWidths` retunes `w:tblGrid` +
|
|
535
930
|
* every row's cell width (one positive twip value per column) and pins the table to fixed
|
|
536
931
|
* layout, exactly as inserting with explicit `columnWidths` would.
|
|
@@ -570,6 +965,19 @@ var DocxSession = class {
|
|
|
570
965
|
this.wasm.SetRepeatHeaderRow(this.handle, cellAnchorId, repeat)
|
|
571
966
|
);
|
|
572
967
|
}
|
|
968
|
+
/** Apply row layout options to the row containing the canonical cell anchor. */
|
|
969
|
+
setTableRowOptions(cellAnchorId, options) {
|
|
970
|
+
return JSON.parse(
|
|
971
|
+
this.wasm.SetTableRowOptions(
|
|
972
|
+
this.handle,
|
|
973
|
+
cellAnchorId,
|
|
974
|
+
options.repeatHeader ?? null,
|
|
975
|
+
options.allowBreakAcrossPages ?? null,
|
|
976
|
+
options.heightTwips ?? null,
|
|
977
|
+
options.heightRule ?? "atLeast"
|
|
978
|
+
)
|
|
979
|
+
);
|
|
980
|
+
}
|
|
573
981
|
// ─── Headers / footers / page numbers ────────────────────────────────
|
|
574
982
|
/**
|
|
575
983
|
* Set the running header story for the section that owns `anchorId` (any body block in that
|
|
@@ -664,6 +1072,25 @@ var DocxSession = class {
|
|
|
664
1072
|
this.wasm.InsertFootnote(this.handle, anchorId, characterOffset, markdown)
|
|
665
1073
|
);
|
|
666
1074
|
}
|
|
1075
|
+
/**
|
|
1076
|
+
* Insert a Word-faithful internal cross-reference — a `REF` field targeting an existing
|
|
1077
|
+
* bookmark — at a character offset (issue #545). The field carries a cached result run
|
|
1078
|
+
* (the bookmarked text, or the target's auto-number under `referenceNumber`), so
|
|
1079
|
+
* renderers that do not recompute fields show a faithful snapshot and Word updates it
|
|
1080
|
+
* like a hand-authored cross-reference. A missing or incoherent bookmark fails with
|
|
1081
|
+
* `missing_bookmark_target`.
|
|
1082
|
+
*/
|
|
1083
|
+
insertCrossReference(anchorId, characterOffset, bookmarkName, options) {
|
|
1084
|
+
return JSON.parse(
|
|
1085
|
+
this.wasm.InsertCrossReference(
|
|
1086
|
+
this.handle,
|
|
1087
|
+
anchorId,
|
|
1088
|
+
characterOffset,
|
|
1089
|
+
bookmarkName,
|
|
1090
|
+
options ? JSON.stringify(options) : ""
|
|
1091
|
+
)
|
|
1092
|
+
);
|
|
1093
|
+
}
|
|
667
1094
|
/** Create an endnote — see {@link insertFootnote}; writes the endnotes part and a
|
|
668
1095
|
* `w:endnoteReference`, and the created definition anchor has kind `en`. */
|
|
669
1096
|
insertEndnote(anchorId, characterOffset, markdown) {
|
|
@@ -755,6 +1182,170 @@ var DocxSession = class {
|
|
|
755
1182
|
listComments() {
|
|
756
1183
|
return JSON.parse(this.wasm.ListComments(this.handle));
|
|
757
1184
|
}
|
|
1185
|
+
listHyperlinks(scopes = 63 /* All */) {
|
|
1186
|
+
return JSON.parse(this.wasm.ListHyperlinks(this.handle, scopes));
|
|
1187
|
+
}
|
|
1188
|
+
addHyperlink(anchorId, span, kind, target) {
|
|
1189
|
+
return JSON.parse(this.wasm.AddHyperlink(
|
|
1190
|
+
this.handle,
|
|
1191
|
+
anchorId,
|
|
1192
|
+
span.start,
|
|
1193
|
+
span.length,
|
|
1194
|
+
kind,
|
|
1195
|
+
target
|
|
1196
|
+
));
|
|
1197
|
+
}
|
|
1198
|
+
updateHyperlink(hyperlinkId, kind, target) {
|
|
1199
|
+
return JSON.parse(this.wasm.UpdateHyperlink(this.handle, hyperlinkId, kind, target));
|
|
1200
|
+
}
|
|
1201
|
+
removeHyperlink(hyperlinkId) {
|
|
1202
|
+
return JSON.parse(this.wasm.RemoveHyperlink(this.handle, hyperlinkId));
|
|
1203
|
+
}
|
|
1204
|
+
/** Versioned operational facts for native image inspection/mutation in this runtime. */
|
|
1205
|
+
getImageCapabilities() {
|
|
1206
|
+
return JSON.parse(this.wasm.GetImageCapabilities());
|
|
1207
|
+
}
|
|
1208
|
+
listImages(scopes = 63 /* All */) {
|
|
1209
|
+
return JSON.parse(this.wasm.ListImages(this.handle, scopes));
|
|
1210
|
+
}
|
|
1211
|
+
insertImage(anchorId, characterOffset, bytes, options = {}) {
|
|
1212
|
+
return JSON.parse(this.wasm.InsertImage(
|
|
1213
|
+
this.handle,
|
|
1214
|
+
anchorId,
|
|
1215
|
+
characterOffset,
|
|
1216
|
+
imageBytesToBase64(bytes),
|
|
1217
|
+
JSON.stringify(options)
|
|
1218
|
+
));
|
|
1219
|
+
}
|
|
1220
|
+
replaceImage(imageId, bytes) {
|
|
1221
|
+
return JSON.parse(this.wasm.ReplaceImage(
|
|
1222
|
+
this.handle,
|
|
1223
|
+
imageId,
|
|
1224
|
+
imageBytesToBase64(bytes)
|
|
1225
|
+
));
|
|
1226
|
+
}
|
|
1227
|
+
setImageDimensions(imageId, dimensions) {
|
|
1228
|
+
return JSON.parse(this.wasm.SetImageDimensions(
|
|
1229
|
+
this.handle,
|
|
1230
|
+
imageId,
|
|
1231
|
+
JSON.stringify(dimensions)
|
|
1232
|
+
));
|
|
1233
|
+
}
|
|
1234
|
+
setImageMetadata(imageId, altText, title) {
|
|
1235
|
+
return JSON.parse(this.wasm.SetImageMetadata(
|
|
1236
|
+
this.handle,
|
|
1237
|
+
imageId,
|
|
1238
|
+
altText,
|
|
1239
|
+
title
|
|
1240
|
+
));
|
|
1241
|
+
}
|
|
1242
|
+
setImageFloatingLayout(imageId, layout) {
|
|
1243
|
+
return JSON.parse(this.wasm.SetImageFloatingLayout(
|
|
1244
|
+
this.handle,
|
|
1245
|
+
imageId,
|
|
1246
|
+
JSON.stringify(layout)
|
|
1247
|
+
));
|
|
1248
|
+
}
|
|
1249
|
+
removeImage(imageId) {
|
|
1250
|
+
return JSON.parse(this.wasm.RemoveImage(this.handle, imageId));
|
|
1251
|
+
}
|
|
1252
|
+
/** Native Word structured-document tags, in outer-before-inner story order. */
|
|
1253
|
+
listContentControls(scopes = 63 /* All */) {
|
|
1254
|
+
return JSON.parse(this.wasm.ListContentControls(this.handle, scopes));
|
|
1255
|
+
}
|
|
1256
|
+
fillContentControlText(anchorId, text, options = {}) {
|
|
1257
|
+
return JSON.parse(this.wasm.FillContentControlText(
|
|
1258
|
+
this.handle,
|
|
1259
|
+
anchorId,
|
|
1260
|
+
text,
|
|
1261
|
+
JSON.stringify(options)
|
|
1262
|
+
));
|
|
1263
|
+
}
|
|
1264
|
+
fillContentControlRichText(anchorId, markdown, options = {}) {
|
|
1265
|
+
return JSON.parse(this.wasm.FillContentControlRichText(
|
|
1266
|
+
this.handle,
|
|
1267
|
+
anchorId,
|
|
1268
|
+
markdown,
|
|
1269
|
+
JSON.stringify(options)
|
|
1270
|
+
));
|
|
1271
|
+
}
|
|
1272
|
+
setContentControlChecked(anchorId, isChecked, options = {}) {
|
|
1273
|
+
return JSON.parse(this.wasm.SetContentControlChecked(
|
|
1274
|
+
this.handle,
|
|
1275
|
+
anchorId,
|
|
1276
|
+
isChecked,
|
|
1277
|
+
JSON.stringify(options)
|
|
1278
|
+
));
|
|
1279
|
+
}
|
|
1280
|
+
setContentControlDate(anchorId, value, displayText, options = {}) {
|
|
1281
|
+
const timestamp = value instanceof Date ? value.toISOString() : value;
|
|
1282
|
+
return JSON.parse(this.wasm.SetContentControlDate(
|
|
1283
|
+
this.handle,
|
|
1284
|
+
anchorId,
|
|
1285
|
+
timestamp,
|
|
1286
|
+
displayText ?? null,
|
|
1287
|
+
JSON.stringify(options)
|
|
1288
|
+
));
|
|
1289
|
+
}
|
|
1290
|
+
selectContentControlItem(anchorId, value, options = {}) {
|
|
1291
|
+
return JSON.parse(this.wasm.SelectContentControlItem(
|
|
1292
|
+
this.handle,
|
|
1293
|
+
anchorId,
|
|
1294
|
+
value,
|
|
1295
|
+
JSON.stringify(options)
|
|
1296
|
+
));
|
|
1297
|
+
}
|
|
1298
|
+
fillContentControlPicture(anchorId, bytes, options = {}) {
|
|
1299
|
+
return JSON.parse(this.wasm.FillContentControlPicture(
|
|
1300
|
+
this.handle,
|
|
1301
|
+
anchorId,
|
|
1302
|
+
imageBytesToBase64(bytes),
|
|
1303
|
+
JSON.stringify(options)
|
|
1304
|
+
));
|
|
1305
|
+
}
|
|
1306
|
+
addRepeatingSectionItem(sectionAnchorId, afterItemAnchorId, options = {}) {
|
|
1307
|
+
return JSON.parse(this.wasm.AddRepeatingSectionItem(
|
|
1308
|
+
this.handle,
|
|
1309
|
+
sectionAnchorId,
|
|
1310
|
+
afterItemAnchorId ?? "",
|
|
1311
|
+
JSON.stringify(options)
|
|
1312
|
+
));
|
|
1313
|
+
}
|
|
1314
|
+
removeRepeatingSectionItem(itemAnchorId) {
|
|
1315
|
+
return JSON.parse(this.wasm.RemoveRepeatingSectionItem(
|
|
1316
|
+
this.handle,
|
|
1317
|
+
itemAnchorId
|
|
1318
|
+
));
|
|
1319
|
+
}
|
|
1320
|
+
listBookmarks(scopes = 63 /* All */) {
|
|
1321
|
+
return JSON.parse(this.wasm.ListBookmarks(this.handle, scopes));
|
|
1322
|
+
}
|
|
1323
|
+
addBookmark(name, range) {
|
|
1324
|
+
return JSON.parse(this.wasm.AddBookmark(
|
|
1325
|
+
this.handle,
|
|
1326
|
+
name,
|
|
1327
|
+
range.startAnchorId,
|
|
1328
|
+
range.startOffset,
|
|
1329
|
+
range.endAnchorId,
|
|
1330
|
+
range.endOffset
|
|
1331
|
+
));
|
|
1332
|
+
}
|
|
1333
|
+
renameBookmark(name, newName) {
|
|
1334
|
+
return JSON.parse(this.wasm.RenameBookmark(this.handle, name, newName));
|
|
1335
|
+
}
|
|
1336
|
+
moveBookmark(name, range) {
|
|
1337
|
+
return JSON.parse(this.wasm.MoveBookmark(
|
|
1338
|
+
this.handle,
|
|
1339
|
+
name,
|
|
1340
|
+
range.startAnchorId,
|
|
1341
|
+
range.startOffset,
|
|
1342
|
+
range.endAnchorId,
|
|
1343
|
+
range.endOffset
|
|
1344
|
+
));
|
|
1345
|
+
}
|
|
1346
|
+
removeBookmark(name) {
|
|
1347
|
+
return JSON.parse(this.wasm.RemoveBookmark(this.handle, name));
|
|
1348
|
+
}
|
|
758
1349
|
// ─── Tracked revisions (issue #318) ──────────────────────────────────
|
|
759
1350
|
/** Markup-native tracked-revision listing, in document order across body, headers,
|
|
760
1351
|
* footers, footnotes, and endnotes. Ids are stable while the underlying markup
|
|
@@ -775,6 +1366,22 @@ var DocxSession = class {
|
|
|
775
1366
|
rejectRevision(revisionId) {
|
|
776
1367
|
return JSON.parse(this.wasm.RejectRevision(this.handle, revisionId));
|
|
777
1368
|
}
|
|
1369
|
+
/**
|
|
1370
|
+
* Accept every live revision as one undoable session mutation.
|
|
1371
|
+
*
|
|
1372
|
+
* Fails closed: an unsupported, malformed, or ambiguous registry entry aborts the whole
|
|
1373
|
+
* operation (`revisionUnsupported`/`revisionMalformed`/`revisionAmbiguous`) and nothing is
|
|
1374
|
+
* mutated. There is no force mode — call {@link listRevisions} and read each entry's
|
|
1375
|
+
* `diagnostic` to see what blocks it.
|
|
1376
|
+
*/
|
|
1377
|
+
acceptAllRevisions() {
|
|
1378
|
+
return JSON.parse(this.wasm.AcceptAllRevisions(this.handle));
|
|
1379
|
+
}
|
|
1380
|
+
/** Reject every live revision as one undoable session mutation. Fails closed exactly like
|
|
1381
|
+
* {@link acceptAllRevisions}. */
|
|
1382
|
+
rejectAllRevisions() {
|
|
1383
|
+
return JSON.parse(this.wasm.RejectAllRevisions(this.handle));
|
|
1384
|
+
}
|
|
778
1385
|
// ─── Tier C: formatting ──────────────────────────────────────────────
|
|
779
1386
|
applyFormat(anchorId, span, op) {
|
|
780
1387
|
const spanJson = span ? JSON.stringify(span) : "";
|
|
@@ -880,6 +1487,10 @@ var DocxSession = class {
|
|
|
880
1487
|
* replaces it with `replace`, preserving the surrounding run formatting that
|
|
881
1488
|
* the match didn't touch. Returns one `EditResult` per attempted match.
|
|
882
1489
|
*
|
|
1490
|
+
* A `find` that matches nothing fails with a single `text_not_found` result
|
|
1491
|
+
* naming the anchor and the needle — never an empty array. Pass
|
|
1492
|
+
* `expectedMatchCount: 0` to assert absence as a successful no-op instead.
|
|
1493
|
+
*
|
|
883
1494
|
* Run-formatting contract: the replacement text inherits the formatting of
|
|
884
1495
|
* the FIRST run the match spanned. Middle/trailing runs keep their `w:rPr`
|
|
885
1496
|
* but lose the slice of text the match consumed.
|
|
@@ -1056,10 +1667,16 @@ var DocxSession = class {
|
|
|
1056
1667
|
*
|
|
1057
1668
|
* @see docs/architecture/docx_mutation_api.md#findplaceholders
|
|
1058
1669
|
*/
|
|
1059
|
-
findPlaceholders(kinds = PlaceholderKinds.All, scope = 1, contextChars = 80, boundary = ContextBoundary.Char) {
|
|
1060
|
-
|
|
1061
|
-
this.
|
|
1062
|
-
|
|
1670
|
+
findPlaceholders(kinds = PlaceholderKinds.All, scope = 1, contextChars = 80, boundary = ContextBoundary.Char, citation) {
|
|
1671
|
+
const json = citation ? this.wasm.FindPlaceholdersWithCitations(
|
|
1672
|
+
this.handle,
|
|
1673
|
+
kinds,
|
|
1674
|
+
scope,
|
|
1675
|
+
contextChars,
|
|
1676
|
+
boundary,
|
|
1677
|
+
JSON.stringify(citation)
|
|
1678
|
+
) : this.wasm.FindPlaceholders(this.handle, kinds, scope, contextChars, boundary);
|
|
1679
|
+
return JSON.parse(json);
|
|
1063
1680
|
}
|
|
1064
1681
|
/**
|
|
1065
1682
|
* Returns a snapshot of edit-state introspection signals — placeholder counts,
|
|
@@ -1082,6 +1699,23 @@ var DocxSession = class {
|
|
|
1082
1699
|
}
|
|
1083
1700
|
return raw;
|
|
1084
1701
|
}
|
|
1702
|
+
/**
|
|
1703
|
+
* Return stable semantic changes from the package opened for this session to
|
|
1704
|
+
* its current state. Requires `captureInitialProjection` (enabled by default).
|
|
1705
|
+
*/
|
|
1706
|
+
getSemanticChanges() {
|
|
1707
|
+
return JSON.parse(this.wasm.GetSemanticChanges(this.handle));
|
|
1708
|
+
}
|
|
1709
|
+
/**
|
|
1710
|
+
* Run the default deliverable gate on this session's normal clean-save checkpoint.
|
|
1711
|
+
* With initial projection capture enabled (the default), exact opening bytes are the
|
|
1712
|
+
* baseline used for dispositions and semantic/package deltas.
|
|
1713
|
+
*/
|
|
1714
|
+
verifyDeliverable() {
|
|
1715
|
+
return JSON.parse(
|
|
1716
|
+
this.wasm.VerifyDeliverable(this.handle)
|
|
1717
|
+
);
|
|
1718
|
+
}
|
|
1085
1719
|
// ─── Annotation-based anchor discovery (#132) ────────────────────────
|
|
1086
1720
|
/**
|
|
1087
1721
|
* Resolves an annotation's range to the block-level markdown anchors covering
|
|
@@ -1097,8 +1731,9 @@ var DocxSession = class {
|
|
|
1097
1731
|
*
|
|
1098
1732
|
* @see docs/architecture/docx_mutation_api.md#findbyannotation
|
|
1099
1733
|
*/
|
|
1100
|
-
findByAnnotation(annotationId) {
|
|
1101
|
-
|
|
1734
|
+
findByAnnotation(annotationId, citation) {
|
|
1735
|
+
const json = citation ? this.wasm.FindByAnnotationWithCitations(this.handle, annotationId, JSON.stringify(citation)) : this.wasm.FindByAnnotation(this.handle, annotationId);
|
|
1736
|
+
return JSON.parse(json);
|
|
1102
1737
|
}
|
|
1103
1738
|
/**
|
|
1104
1739
|
* Finds every annotation whose `labelId` matches and resolves each of their
|
|
@@ -1107,8 +1742,9 @@ var DocxSession = class {
|
|
|
1107
1742
|
* annotations on different paragraphs become three entries). Annotations
|
|
1108
1743
|
* whose bookmark resolves to no anchors are omitted from the result.
|
|
1109
1744
|
*/
|
|
1110
|
-
findByLabel(labelId) {
|
|
1111
|
-
|
|
1745
|
+
findByLabel(labelId, citation) {
|
|
1746
|
+
const json = citation ? this.wasm.FindByLabelWithCitations(this.handle, labelId, JSON.stringify(citation)) : this.wasm.FindByLabel(this.handle, labelId);
|
|
1747
|
+
return JSON.parse(json);
|
|
1112
1748
|
}
|
|
1113
1749
|
/**
|
|
1114
1750
|
* Resolves any bookmark in the main document part (Docxodus-managed or
|
|
@@ -1116,8 +1752,9 @@ var DocxSession = class {
|
|
|
1116
1752
|
* order. Empty when the bookmark name is unknown. Use this for raw bookmark
|
|
1117
1753
|
* names that didn't come from the annotation system.
|
|
1118
1754
|
*/
|
|
1119
|
-
findByBookmark(bookmarkName) {
|
|
1120
|
-
|
|
1755
|
+
findByBookmark(bookmarkName, citation) {
|
|
1756
|
+
const json = citation ? this.wasm.FindByBookmarkWithCitations(this.handle, bookmarkName, JSON.stringify(citation)) : this.wasm.FindByBookmark(this.handle, bookmarkName);
|
|
1757
|
+
return JSON.parse(json);
|
|
1121
1758
|
}
|
|
1122
1759
|
// ─── Text/kind-based anchor discovery (#171) ─────────────────────────
|
|
1123
1760
|
/**
|
|
@@ -1162,15 +1799,25 @@ var DocxSession = class {
|
|
|
1162
1799
|
);
|
|
1163
1800
|
}
|
|
1164
1801
|
/**
|
|
1165
|
-
* Return every anchor of the given `kind`
|
|
1166
|
-
* `"
|
|
1802
|
+
* Return every anchor of the given `kind` — one of `"p"`, `"h"`, `"li"`,
|
|
1803
|
+
* `"tbl"`, `"tr"`, `"tc"`, `"col"`, `"sdt"`, `"sec"`, `"fn"`, `"en"`,
|
|
1804
|
+
* `"cmt"`, `"unk"` — in document order. Matching is an exact string comparison;
|
|
1805
|
+
* in particular the row and cell tokens are `"tr"` and `"tc"`, never
|
|
1806
|
+
* `"row"`/`"cell"`. Unknown strings are not parsed or rejected: they simply
|
|
1807
|
+
* return an empty array. `"img"` and `"drw"` likewise return empty because the
|
|
1808
|
+
* projection never assigns those reserved kinds; address images through the
|
|
1809
|
+
* image surface instead. Reads the projection's anchor
|
|
1167
1810
|
* index directly — no text scan. Pass `scope` (e.g. `"body"`) to restrict to
|
|
1168
1811
|
* a single part; omit it to span all scopes.
|
|
1169
1812
|
*/
|
|
1170
|
-
findByKind(kind, scope) {
|
|
1171
|
-
|
|
1172
|
-
this.
|
|
1173
|
-
|
|
1813
|
+
findByKind(kind, scope, citation) {
|
|
1814
|
+
const json = citation ? this.wasm.FindByKindWithCitations(
|
|
1815
|
+
this.handle,
|
|
1816
|
+
kind,
|
|
1817
|
+
scope ?? "",
|
|
1818
|
+
JSON.stringify(citation)
|
|
1819
|
+
) : this.wasm.FindByKind(this.handle, kind, scope ?? "");
|
|
1820
|
+
return JSON.parse(json);
|
|
1174
1821
|
}
|
|
1175
1822
|
/**
|
|
1176
1823
|
* Look up a single anchor's preview info — `{ id, kind, scope, textPreview }`.
|
|
@@ -1224,6 +1871,18 @@ var DocxSession = class {
|
|
|
1224
1871
|
const raw = this.wasm.GetSectionInfo(this.handle, anchorId);
|
|
1225
1872
|
return JSON.parse(raw);
|
|
1226
1873
|
}
|
|
1874
|
+
/** Enumerate the document's explicit style catalog with resolved high-signal properties. */
|
|
1875
|
+
listStyles() {
|
|
1876
|
+
return JSON.parse(this.wasm.ListStyles(this.handle));
|
|
1877
|
+
}
|
|
1878
|
+
/** Inspect direct and effective paragraph/run formatting for one paragraph anchor. */
|
|
1879
|
+
getFormatting(anchorId) {
|
|
1880
|
+
return JSON.parse(this.wasm.GetFormatting(this.handle, anchorId));
|
|
1881
|
+
}
|
|
1882
|
+
/** Enumerate text-bearing runs as mutation-compatible anchor/span pairs. */
|
|
1883
|
+
listInlineSpans(anchorId) {
|
|
1884
|
+
return JSON.parse(this.wasm.ListInlineSpans(this.handle, anchorId));
|
|
1885
|
+
}
|
|
1227
1886
|
/**
|
|
1228
1887
|
* Enumerates every annotation persisted in the document. Lets an agent prime
|
|
1229
1888
|
* itself with "here are the labeled regions you can target" before committing
|
|
@@ -1288,6 +1947,13 @@ var DocxSession = class {
|
|
|
1288
1947
|
this.close();
|
|
1289
1948
|
}
|
|
1290
1949
|
};
|
|
1950
|
+
function imageBytesToBase64(bytes) {
|
|
1951
|
+
let binary = "";
|
|
1952
|
+
for (let offset = 0; offset < bytes.length; offset += 32768) {
|
|
1953
|
+
binary += String.fromCharCode(...bytes.subarray(offset, offset + 32768));
|
|
1954
|
+
}
|
|
1955
|
+
return globalThis.btoa(binary);
|
|
1956
|
+
}
|
|
1291
1957
|
function openDocxSession(bytes, wasmExports2, settings) {
|
|
1292
1958
|
const bridge = wasmExports2.DocxSessionBridge;
|
|
1293
1959
|
const handle = bridge.OpenSession(bytes, settings ? JSON.stringify(settings) : "");
|
|
@@ -1339,17 +2005,41 @@ function pxToPt(px) {
|
|
|
1339
2005
|
function ptToPx(pt) {
|
|
1340
2006
|
return pt / 0.75;
|
|
1341
2007
|
}
|
|
2008
|
+
function finiteDataNumber(value, fallback) {
|
|
2009
|
+
if (value === void 0 || value.trim() === "") return fallback;
|
|
2010
|
+
const parsed = Number(value);
|
|
2011
|
+
return Number.isFinite(parsed) ? parsed : fallback;
|
|
2012
|
+
}
|
|
2013
|
+
function positiveDataNumber(value, fallback) {
|
|
2014
|
+
const parsed = finiteDataNumber(value, fallback);
|
|
2015
|
+
return parsed > 0 ? parsed : fallback;
|
|
2016
|
+
}
|
|
2017
|
+
function nonNegativeDataNumber(value, fallback) {
|
|
2018
|
+
const parsed = finiteDataNumber(value, fallback);
|
|
2019
|
+
return parsed >= 0 ? parsed : fallback;
|
|
2020
|
+
}
|
|
2021
|
+
function positiveFinite(value, fallback) {
|
|
2022
|
+
return Number.isFinite(value) && value > 0 ? value : fallback;
|
|
2023
|
+
}
|
|
1342
2024
|
function parseSectionDimensions(section) {
|
|
1343
|
-
const pageWidth =
|
|
1344
|
-
const pageHeight =
|
|
1345
|
-
const
|
|
1346
|
-
const
|
|
1347
|
-
const
|
|
1348
|
-
const
|
|
1349
|
-
const
|
|
1350
|
-
const
|
|
1351
|
-
const
|
|
1352
|
-
const
|
|
2025
|
+
const pageWidth = positiveDataNumber(section.dataset.pageWidth, DEFAULT_PAGE_WIDTH);
|
|
2026
|
+
const pageHeight = positiveDataNumber(section.dataset.pageHeight, DEFAULT_PAGE_HEIGHT);
|
|
2027
|
+
const marginTop = finiteDataNumber(section.dataset.marginTop, DEFAULT_MARGIN);
|
|
2028
|
+
const marginRight = finiteDataNumber(section.dataset.marginRight, DEFAULT_MARGIN);
|
|
2029
|
+
const marginBottom = finiteDataNumber(section.dataset.marginBottom, DEFAULT_MARGIN);
|
|
2030
|
+
const marginLeft = finiteDataNumber(section.dataset.marginLeft, DEFAULT_MARGIN);
|
|
2031
|
+
const derivedContentWidth = positiveFinite(pageWidth - marginLeft - marginRight, pageWidth);
|
|
2032
|
+
const derivedContentHeight = positiveFinite(pageHeight - marginTop - marginBottom, pageHeight);
|
|
2033
|
+
const contentWidth = positiveDataNumber(section.dataset.contentWidth, derivedContentWidth);
|
|
2034
|
+
const contentHeight = positiveDataNumber(section.dataset.contentHeight, derivedContentHeight);
|
|
2035
|
+
const headerDistance = nonNegativeDataNumber(
|
|
2036
|
+
section.dataset.headerHeight,
|
|
2037
|
+
DEFAULT_HEADER_FOOTER_DISTANCE
|
|
2038
|
+
);
|
|
2039
|
+
const footerDistance = nonNegativeDataNumber(
|
|
2040
|
+
section.dataset.footerHeight,
|
|
2041
|
+
DEFAULT_HEADER_FOOTER_DISTANCE
|
|
2042
|
+
);
|
|
1353
2043
|
return {
|
|
1354
2044
|
pageWidth,
|
|
1355
2045
|
pageHeight,
|
|
@@ -1426,8 +2116,96 @@ function applyZoom(el, scale, naturalPt) {
|
|
|
1426
2116
|
}
|
|
1427
2117
|
|
|
1428
2118
|
// src/pagination.ts
|
|
2119
|
+
function samePageBox(a, b) {
|
|
2120
|
+
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;
|
|
2121
|
+
}
|
|
2122
|
+
function createUnavailablePageMap(documentVersion, rendererFingerprint, mode = "continuous") {
|
|
2123
|
+
if (!rendererFingerprint) throw new Error("rendererFingerprint must be non-empty");
|
|
2124
|
+
return {
|
|
2125
|
+
schemaVersion: 1,
|
|
2126
|
+
mode,
|
|
2127
|
+
availability: "unavailable",
|
|
2128
|
+
documentVersion,
|
|
2129
|
+
rendererFingerprint,
|
|
2130
|
+
pages: [],
|
|
2131
|
+
fragments: []
|
|
2132
|
+
};
|
|
2133
|
+
}
|
|
2134
|
+
var activePageCitationHighlights = /* @__PURE__ */ new WeakMap();
|
|
2135
|
+
function clearPageCitationHighlight(root) {
|
|
2136
|
+
const active = activePageCitationHighlights.get(root);
|
|
2137
|
+
if (!active) return;
|
|
2138
|
+
if (active.highlightClass && active.addedClass) {
|
|
2139
|
+
active.target.classList.remove(active.highlightClass);
|
|
2140
|
+
}
|
|
2141
|
+
for (const style of active.inlineStyles ?? []) {
|
|
2142
|
+
if (style.value) {
|
|
2143
|
+
active.target.style.setProperty(style.name, style.value, style.priority);
|
|
2144
|
+
} else {
|
|
2145
|
+
active.target.style.removeProperty(style.name);
|
|
2146
|
+
}
|
|
2147
|
+
}
|
|
2148
|
+
activePageCitationHighlights.delete(root);
|
|
2149
|
+
}
|
|
2150
|
+
function navigateToPageCitation(root, citation, options = {}) {
|
|
2151
|
+
clearPageCitationHighlight(root);
|
|
2152
|
+
if (citation.availability !== "available" || citation.fragments.length === 0) {
|
|
2153
|
+
return { navigated: false, unavailableReason: "citation_unavailable" };
|
|
2154
|
+
}
|
|
2155
|
+
const byAttribute = (name, value, within = root) => {
|
|
2156
|
+
for (const node of Array.from(within.querySelectorAll(`[${name}]`))) {
|
|
2157
|
+
if (node.getAttribute(name) === value) return node;
|
|
2158
|
+
}
|
|
2159
|
+
return null;
|
|
2160
|
+
};
|
|
2161
|
+
const fragment = citation.fragments[0];
|
|
2162
|
+
let target = byAttribute("data-page-fragment-id", fragment.fragmentId);
|
|
2163
|
+
if (!target) {
|
|
2164
|
+
const page = byAttribute("data-page-number", String(fragment.pageNumber));
|
|
2165
|
+
if (page) target = byAttribute("data-source-anchor-id", citation.anchorId, page) ?? page;
|
|
2166
|
+
}
|
|
2167
|
+
if (!target) {
|
|
2168
|
+
return {
|
|
2169
|
+
navigated: false,
|
|
2170
|
+
pageNumber: fragment.pageNumber,
|
|
2171
|
+
fragmentId: fragment.fragmentId,
|
|
2172
|
+
unavailableReason: "fragment_not_found"
|
|
2173
|
+
};
|
|
2174
|
+
}
|
|
2175
|
+
if (options.highlightClass) {
|
|
2176
|
+
const addedClass = !target.classList.contains(options.highlightClass);
|
|
2177
|
+
target.classList.add(options.highlightClass);
|
|
2178
|
+
activePageCitationHighlights.set(root, {
|
|
2179
|
+
target,
|
|
2180
|
+
highlightClass: options.highlightClass,
|
|
2181
|
+
addedClass
|
|
2182
|
+
});
|
|
2183
|
+
} else if (options.highlight !== false) {
|
|
2184
|
+
const names = ["outline", "outline-offset", "background-color"];
|
|
2185
|
+
const inlineStyles = names.map((name) => ({
|
|
2186
|
+
name,
|
|
2187
|
+
value: target.style.getPropertyValue(name),
|
|
2188
|
+
priority: target.style.getPropertyPriority(name)
|
|
2189
|
+
}));
|
|
2190
|
+
target.style.setProperty("outline", "3px solid #f4b400", "important");
|
|
2191
|
+
target.style.setProperty("outline-offset", "2px", "important");
|
|
2192
|
+
target.style.setProperty("background-color", "rgba(255, 235, 59, .18)", "important");
|
|
2193
|
+
activePageCitationHighlights.set(root, { target, inlineStyles });
|
|
2194
|
+
}
|
|
2195
|
+
target.scrollIntoView({
|
|
2196
|
+
behavior: options.behavior ?? "smooth",
|
|
2197
|
+
block: options.block ?? "center"
|
|
2198
|
+
});
|
|
2199
|
+
return {
|
|
2200
|
+
navigated: true,
|
|
2201
|
+
target,
|
|
2202
|
+
pageNumber: fragment.pageNumber,
|
|
2203
|
+
fragmentId: fragment.fragmentId
|
|
2204
|
+
};
|
|
2205
|
+
}
|
|
1429
2206
|
var MAX_FOOTNOTE_AREA_RATIO = 0.6;
|
|
1430
|
-
var
|
|
2207
|
+
var FOOTNOTE_MEASUREMENT_GUARD_PT = 2;
|
|
2208
|
+
var FOOTNOTE_SOURCE_POSITION_ATTR = "data-pagination-footnote-source-position";
|
|
1431
2209
|
var PaginationEngine = class {
|
|
1432
2210
|
/**
|
|
1433
2211
|
* Creates a new pagination engine.
|
|
@@ -1437,24 +2215,46 @@ var PaginationEngine = class {
|
|
|
1437
2215
|
* @param options - Pagination options
|
|
1438
2216
|
*/
|
|
1439
2217
|
constructor(staging, container, options = {}) {
|
|
2218
|
+
this.createdPageCount = 0;
|
|
2219
|
+
this.footnoteSeparator = null;
|
|
2220
|
+
this.footnoteContinuationSeparator = null;
|
|
2221
|
+
this.footnoteLayoutLikeWord8 = false;
|
|
1440
2222
|
this.pendingFootnoteContinuation = null;
|
|
1441
2223
|
/** Per-section `w:pgNumType` (start / format), read off the section wrappers. */
|
|
1442
2224
|
this.pageNumbering = /* @__PURE__ */ new Map();
|
|
1443
|
-
this.
|
|
1444
|
-
this.
|
|
2225
|
+
this.lastPages = [];
|
|
2226
|
+
this.expectedPageMapAnchorIds = /* @__PURE__ */ new Set();
|
|
2227
|
+
this.state = "ready";
|
|
2228
|
+
const ownerDocument = typeof staging !== "string" ? staging.ownerDocument : typeof container !== "string" ? container.ownerDocument : globalThis.document;
|
|
2229
|
+
this.stagingElement = typeof staging === "string" ? ownerDocument.getElementById(staging) : staging;
|
|
2230
|
+
this.containerElement = typeof container === "string" ? ownerDocument.getElementById(container) : container;
|
|
1445
2231
|
if (!this.stagingElement) {
|
|
1446
2232
|
throw new Error("Staging element not found");
|
|
1447
2233
|
}
|
|
1448
2234
|
if (!this.containerElement) {
|
|
1449
2235
|
throw new Error("Container element not found");
|
|
1450
2236
|
}
|
|
2237
|
+
if (this.stagingElement.ownerDocument !== this.containerElement.ownerDocument) {
|
|
2238
|
+
throw new Error("Staging and container elements must belong to the same document");
|
|
2239
|
+
}
|
|
2240
|
+
const view = ownerDocument.defaultView;
|
|
2241
|
+
if (!view) {
|
|
2242
|
+
throw new Error("Pagination requires an attached document with a defaultView");
|
|
2243
|
+
}
|
|
2244
|
+
this.document = ownerDocument;
|
|
2245
|
+
this.view = view;
|
|
1451
2246
|
this.scale = options.scale ?? 1;
|
|
1452
2247
|
this.cssPrefix = options.cssPrefix ?? "page-";
|
|
1453
2248
|
this.showPageNumbers = options.showPageNumbers ?? true;
|
|
1454
2249
|
this.pageGap = options.pageGap ?? 20;
|
|
1455
2250
|
this.fragmentParagraphs = options.fragmentParagraphs ?? false;
|
|
2251
|
+
this.deferFragmentIdentities = options.deferFragmentIdentities ?? false;
|
|
2252
|
+
this.cancellationCheckpoint = options.checkCancellation;
|
|
2253
|
+
this.pageCountCheckpoint = options.checkPageCount;
|
|
2254
|
+
this.layoutToken = options.layoutToken;
|
|
1456
2255
|
this.hfRegistry = /* @__PURE__ */ new Map();
|
|
1457
2256
|
this.footnoteRegistry = /* @__PURE__ */ new Map();
|
|
2257
|
+
this.commentMarginRegistry = /* @__PURE__ */ new Map();
|
|
1458
2258
|
}
|
|
1459
2259
|
/**
|
|
1460
2260
|
* Runs the pagination process.
|
|
@@ -1462,31 +2262,469 @@ var PaginationEngine = class {
|
|
|
1462
2262
|
* @returns PaginationResult with page information
|
|
1463
2263
|
*/
|
|
1464
2264
|
paginate() {
|
|
1465
|
-
|
|
1466
|
-
|
|
1467
|
-
|
|
1468
|
-
|
|
1469
|
-
|
|
1470
|
-
"
|
|
1471
|
-
|
|
1472
|
-
|
|
1473
|
-
|
|
1474
|
-
|
|
1475
|
-
|
|
1476
|
-
|
|
1477
|
-
|
|
1478
|
-
|
|
1479
|
-
this.
|
|
1480
|
-
this.
|
|
1481
|
-
|
|
1482
|
-
|
|
1483
|
-
|
|
1484
|
-
|
|
1485
|
-
|
|
2265
|
+
this.checkpoint();
|
|
2266
|
+
if (this.state !== "ready") {
|
|
2267
|
+
throw new Error(`PaginationEngine is one-shot and is already ${this.state}`);
|
|
2268
|
+
}
|
|
2269
|
+
if (!this.stagingElement.isConnected || !this.containerElement.isConnected) {
|
|
2270
|
+
throw new Error("Pagination requires staging and container elements attached to their document");
|
|
2271
|
+
}
|
|
2272
|
+
if (this.document.documentElement.getBoundingClientRect().width <= 0) {
|
|
2273
|
+
throw new Error("Pagination requires a browsing context with non-zero layout");
|
|
2274
|
+
}
|
|
2275
|
+
this.state = "running";
|
|
2276
|
+
try {
|
|
2277
|
+
const pages = [];
|
|
2278
|
+
let pageNumber = 1;
|
|
2279
|
+
this.hfRegistry = this.parseHeaderFooterRegistry();
|
|
2280
|
+
this.footnoteRegistry = this.parseFootnoteRegistry();
|
|
2281
|
+
({
|
|
2282
|
+
normal: this.footnoteSeparator,
|
|
2283
|
+
continuation: this.footnoteContinuationSeparator
|
|
2284
|
+
} = this.parseFootnoteSeparators());
|
|
2285
|
+
this.commentMarginRegistry = this.parseCommentMarginRegistry();
|
|
2286
|
+
this.footnoteLayoutLikeWord8 = this.stagingElement.dataset.footnoteLayoutLikeWord8 === "true";
|
|
2287
|
+
const sections = this.stagingElement.querySelectorAll(
|
|
2288
|
+
"[data-section-index]"
|
|
2289
|
+
);
|
|
2290
|
+
this.pageNumbering = this.parsePageNumbering(sections);
|
|
2291
|
+
const sectionsToProcess = sections.length > 0 ? Array.from(sections) : [this.stagingElement];
|
|
2292
|
+
this.expectedPageMapAnchorIds = /* @__PURE__ */ new Set();
|
|
2293
|
+
const referencedFootnoteIds = /* @__PURE__ */ new Set();
|
|
2294
|
+
const referencedCommentIds = /* @__PURE__ */ new Set();
|
|
2295
|
+
for (const section of sectionsToProcess) {
|
|
2296
|
+
this.checkpoint();
|
|
2297
|
+
this.collectExpectedSourceAnchors(section, this.expectedPageMapAnchorIds, true);
|
|
2298
|
+
for (const reference of Array.from(section.querySelectorAll("[data-footnote-id]"))) {
|
|
2299
|
+
this.checkpoint();
|
|
2300
|
+
if (reference.closest("#pagination-footnote-registry, #pagination-hf-registry")) continue;
|
|
2301
|
+
const id = reference.dataset.footnoteId;
|
|
2302
|
+
if (id) referencedFootnoteIds.add(id);
|
|
2303
|
+
}
|
|
2304
|
+
for (const reference of Array.from(section.querySelectorAll("[data-comment-id]"))) {
|
|
2305
|
+
this.checkpoint();
|
|
2306
|
+
if (reference.closest(
|
|
2307
|
+
"#pagination-comment-margin-registry, #pagination-footnote-registry, #pagination-hf-registry"
|
|
2308
|
+
)) continue;
|
|
2309
|
+
const id = reference.dataset.commentId;
|
|
2310
|
+
if (id) referencedCommentIds.add(id);
|
|
2311
|
+
}
|
|
2312
|
+
}
|
|
2313
|
+
for (const id of referencedFootnoteIds) {
|
|
2314
|
+
this.checkpoint();
|
|
2315
|
+
const source = this.footnoteRegistry.get(id);
|
|
2316
|
+
if (!source) continue;
|
|
2317
|
+
this.collectExpectedSourceAnchors(source, this.expectedPageMapAnchorIds);
|
|
2318
|
+
for (const reference of Array.from(
|
|
2319
|
+
source.querySelectorAll("[data-comment-id]")
|
|
2320
|
+
)) {
|
|
2321
|
+
this.checkpoint();
|
|
2322
|
+
const commentId = reference.dataset.commentId;
|
|
2323
|
+
if (commentId) referencedCommentIds.add(commentId);
|
|
2324
|
+
}
|
|
2325
|
+
}
|
|
2326
|
+
for (const id of referencedCommentIds) {
|
|
2327
|
+
this.checkpoint();
|
|
2328
|
+
const source = this.commentMarginRegistry.get(id);
|
|
2329
|
+
if (source) this.collectExpectedSourceAnchors(source, this.expectedPageMapAnchorIds);
|
|
2330
|
+
}
|
|
2331
|
+
const runs = [];
|
|
2332
|
+
for (const section of sectionsToProcess) {
|
|
2333
|
+
this.checkpoint();
|
|
2334
|
+
const dims = parseSectionDimensions(section);
|
|
2335
|
+
const previous = runs[runs.length - 1];
|
|
2336
|
+
if (previous && section.dataset.sectionType === "continuous" && samePageBox(previous.dims, dims)) {
|
|
2337
|
+
previous.sections.push(section);
|
|
2338
|
+
previous.sectionDimensions.set(
|
|
2339
|
+
parseInt(section.dataset.sectionIndex || "0", 10),
|
|
2340
|
+
dims
|
|
2341
|
+
);
|
|
2342
|
+
} else {
|
|
2343
|
+
const sectionIndex = parseInt(section.dataset.sectionIndex || "0", 10);
|
|
2344
|
+
runs.push({
|
|
2345
|
+
sections: [section],
|
|
2346
|
+
sectionIndex,
|
|
2347
|
+
sectionType: section.dataset.sectionType ?? "nextPage",
|
|
2348
|
+
dims,
|
|
2349
|
+
sectionDimensions: /* @__PURE__ */ new Map([[sectionIndex, dims]])
|
|
2350
|
+
});
|
|
2351
|
+
}
|
|
2352
|
+
}
|
|
2353
|
+
for (const run of runs) {
|
|
2354
|
+
this.checkpoint();
|
|
2355
|
+
const needsParityFiller = pages.length > 0 && (run.sectionType === "oddPage" && pageNumber % 2 === 0 || run.sectionType === "evenPage" && pageNumber % 2 === 1);
|
|
2356
|
+
if (needsParityFiller) {
|
|
2357
|
+
const precedingPage = pages[pages.length - 1];
|
|
2358
|
+
const precedingPageInSection = parseInt(
|
|
2359
|
+
precedingPage.element.dataset.pageInSection ?? "1",
|
|
2360
|
+
10
|
|
2361
|
+
);
|
|
2362
|
+
const precedingDisplayedPageNumber = parseInt(
|
|
2363
|
+
precedingPage.element.dataset.displayedPageNumber ?? String(precedingPage.pageNumber),
|
|
2364
|
+
10
|
|
2365
|
+
);
|
|
2366
|
+
const filler = this.createPage(
|
|
2367
|
+
precedingPage.dimensions,
|
|
2368
|
+
pageNumber,
|
|
2369
|
+
precedingPage.sectionIndex,
|
|
2370
|
+
precedingDisplayedPageNumber + 1,
|
|
2371
|
+
[],
|
|
2372
|
+
precedingPageInSection + 1,
|
|
2373
|
+
[],
|
|
2374
|
+
0,
|
|
2375
|
+
null,
|
|
2376
|
+
void 0,
|
|
2377
|
+
true
|
|
2378
|
+
);
|
|
2379
|
+
pages.push(filler);
|
|
2380
|
+
pageNumber++;
|
|
2381
|
+
}
|
|
2382
|
+
this.stagingElement.style.visibility = "hidden";
|
|
2383
|
+
this.stagingElement.style.position = "absolute";
|
|
2384
|
+
this.stagingElement.style.left = "-9999px";
|
|
2385
|
+
this.stagingElement.style.display = "block";
|
|
2386
|
+
const blocks = [];
|
|
2387
|
+
for (const section of run.sections) {
|
|
2388
|
+
this.checkpoint();
|
|
2389
|
+
const sectionIndex = parseInt(section.dataset.sectionIndex || "0", 10);
|
|
2390
|
+
const sectionDims = run.sectionDimensions.get(sectionIndex) ?? run.dims;
|
|
2391
|
+
section.style.width = `${sectionDims.contentWidth}pt`;
|
|
2392
|
+
const columnCount = parseInt(section.dataset.cols || "1", 10);
|
|
2393
|
+
if (columnCount > 1) {
|
|
2394
|
+
const gap = parseFloat(section.dataset.colGap || "");
|
|
2395
|
+
blocks.push(...this.buildColumnBlocks(
|
|
2396
|
+
section,
|
|
2397
|
+
sectionDims,
|
|
2398
|
+
sectionIndex,
|
|
2399
|
+
columnCount,
|
|
2400
|
+
Number.isFinite(gap) ? gap : 36
|
|
2401
|
+
));
|
|
2402
|
+
} else {
|
|
2403
|
+
blocks.push(...this.measureBlocks(section, sectionDims, sectionIndex));
|
|
2404
|
+
}
|
|
2405
|
+
}
|
|
2406
|
+
const sectionPages = this.flowToPages(
|
|
2407
|
+
blocks,
|
|
2408
|
+
run.dims,
|
|
2409
|
+
pageNumber,
|
|
2410
|
+
run.sectionIndex,
|
|
2411
|
+
run.sectionDimensions
|
|
2412
|
+
);
|
|
2413
|
+
this.checkpoint();
|
|
2414
|
+
pages.push(...sectionPages);
|
|
2415
|
+
pageNumber += sectionPages.length;
|
|
2416
|
+
}
|
|
2417
|
+
this.stagingElement.style.display = "none";
|
|
2418
|
+
this.substitutePageNumberFields(pages.length);
|
|
2419
|
+
for (const page of pages) {
|
|
2420
|
+
this.checkpoint();
|
|
2421
|
+
if (page.element.dataset.sectionFiller === "true") continue;
|
|
2422
|
+
const pageInSection = parseInt(page.element.dataset.pageInSection || "1", 10);
|
|
2423
|
+
const displayedPageNumber = parseInt(
|
|
2424
|
+
page.element.dataset.displayedPageNumber || String(page.pageNumber),
|
|
2425
|
+
10
|
|
2426
|
+
);
|
|
2427
|
+
const header = this.selectHeader(page.sectionIndex, pageInSection, displayedPageNumber);
|
|
2428
|
+
const footer = this.selectFooter(page.sectionIndex, pageInSection, displayedPageNumber);
|
|
2429
|
+
if (header) this.collectExpectedSourceAnchors(header, this.expectedPageMapAnchorIds);
|
|
2430
|
+
if (footer) this.collectExpectedSourceAnchors(footer, this.expectedPageMapAnchorIds);
|
|
2431
|
+
}
|
|
2432
|
+
this.qualifyPageFragments(pages);
|
|
2433
|
+
this.transferVisibleFragmentTargets();
|
|
2434
|
+
if (!this.deferFragmentIdentities) this.normalizeVisiblePageFragments(pages);
|
|
2435
|
+
this.lastPages = pages;
|
|
2436
|
+
const result = {
|
|
2437
|
+
totalPages: pages.length,
|
|
2438
|
+
pages,
|
|
2439
|
+
pageMap: this.layoutToken ? this.materializePageMap(
|
|
2440
|
+
this.layoutToken.documentVersion,
|
|
2441
|
+
this.layoutToken.rendererFingerprint
|
|
2442
|
+
) : void 0
|
|
2443
|
+
};
|
|
2444
|
+
this.state = "complete";
|
|
2445
|
+
return result;
|
|
2446
|
+
} catch (error) {
|
|
2447
|
+
this.state = "failed";
|
|
2448
|
+
throw error;
|
|
2449
|
+
}
|
|
2450
|
+
}
|
|
2451
|
+
checkpoint() {
|
|
2452
|
+
this.cancellationCheckpoint?.();
|
|
2453
|
+
}
|
|
2454
|
+
/**
|
|
2455
|
+
* Normalize visible fragment identities after a caller applies final standalone styles. This
|
|
2456
|
+
* deliberately runs before the stability barrier; materializePageMap is read-only so PageMap
|
|
2457
|
+
* measurement cannot mutate a tree after it was declared stable.
|
|
2458
|
+
*/
|
|
2459
|
+
normalizePageMapFragmentIdentities() {
|
|
2460
|
+
if (this.lastPages.length === 0) {
|
|
2461
|
+
throw new Error("paginate() must complete before fragment identities can be normalized");
|
|
2462
|
+
}
|
|
2463
|
+
this.normalizeVisiblePageFragments(this.lastPages);
|
|
2464
|
+
}
|
|
2465
|
+
/**
|
|
2466
|
+
* Materialize the last completed browser layout as portable page-relative point geometry.
|
|
2467
|
+
* The caller supplies both invalidation tokens; this engine never guesses a document version
|
|
2468
|
+
* or renderer fingerprint.
|
|
2469
|
+
*/
|
|
2470
|
+
materializePageMap(documentVersion, rendererFingerprint) {
|
|
2471
|
+
if (!Number.isSafeInteger(documentVersion) || documentVersion < 0) {
|
|
2472
|
+
throw new Error("documentVersion must be a non-negative safe integer");
|
|
2473
|
+
}
|
|
2474
|
+
if (!rendererFingerprint) throw new Error("rendererFingerprint must be non-empty");
|
|
2475
|
+
if (this.lastPages.length === 0) throw new Error("paginate() must complete before materializePageMap()");
|
|
2476
|
+
const pages = this.lastPages.map((page) => ({
|
|
2477
|
+
pageNumber: page.pageNumber,
|
|
2478
|
+
pageInSection: parseInt(page.element.dataset.pageInSection || "1", 10),
|
|
2479
|
+
width: page.dimensions.pageWidth,
|
|
2480
|
+
height: page.dimensions.pageHeight,
|
|
2481
|
+
sectionIndex: page.sectionIndex,
|
|
2482
|
+
pageName: `docxodus-section-${page.sectionIndex}`
|
|
2483
|
+
}));
|
|
2484
|
+
const fragments = [];
|
|
2485
|
+
const requiredAnchorIds = new Set(this.expectedPageMapAnchorIds);
|
|
2486
|
+
if (requiredAnchorIds.size === 0) {
|
|
2487
|
+
throw new Error("cannot publish an available PageMap without canonical source inventory");
|
|
2488
|
+
}
|
|
2489
|
+
const measuredAnchorIds = /* @__PURE__ */ new Set();
|
|
2490
|
+
const emittedFragmentCounts = /* @__PURE__ */ new Map();
|
|
2491
|
+
for (const page of this.lastPages) {
|
|
2492
|
+
this.checkpoint();
|
|
2493
|
+
const pageRect = page.element.getBoundingClientRect();
|
|
2494
|
+
if (pageRect.width <= 0 || pageRect.height <= 0) {
|
|
2495
|
+
throw new Error(`page ${page.pageNumber} has no measurable geometry`);
|
|
2496
|
+
}
|
|
2497
|
+
const pointPerRenderedX = page.dimensions.pageWidth / pageRect.width;
|
|
2498
|
+
const pointPerRenderedY = page.dimensions.pageHeight / pageRect.height;
|
|
2499
|
+
const nodes = page.element.querySelectorAll("[data-source-anchor-id]");
|
|
2500
|
+
for (const element of Array.from(nodes)) {
|
|
2501
|
+
this.checkpoint();
|
|
2502
|
+
if (this.isDeliberatelyUnrenderedSource(element, page.element)) continue;
|
|
2503
|
+
const anchorId = element.dataset.sourceAnchorId;
|
|
2504
|
+
if (!anchorId || !element.dataset.pageFragmentId || !Number.isInteger(parseInt(element.dataset.fragmentIndex || "", 10))) {
|
|
2505
|
+
throw new Error(`page ${page.pageNumber} contains an unqualified source anchor`);
|
|
2506
|
+
}
|
|
2507
|
+
const rect = element.getBoundingClientRect();
|
|
2508
|
+
const style = this.view.getComputedStyle(element);
|
|
2509
|
+
const deliberatelyHidden = style.display === "none" || style.visibility === "hidden";
|
|
2510
|
+
if (deliberatelyHidden) continue;
|
|
2511
|
+
requiredAnchorIds.add(anchorId);
|
|
2512
|
+
const visibleRect = this.intersectWithClippingAncestors(element, page.element, pageRect, rect);
|
|
2513
|
+
const left2 = visibleRect.left;
|
|
2514
|
+
const top2 = visibleRect.top;
|
|
2515
|
+
const right2 = visibleRect.right;
|
|
2516
|
+
const bottom2 = visibleRect.bottom;
|
|
2517
|
+
if (rect.width <= 0 || rect.height <= 0 || right2 <= left2 || bottom2 <= top2) {
|
|
2518
|
+
continue;
|
|
2519
|
+
}
|
|
2520
|
+
measuredAnchorIds.add(anchorId);
|
|
2521
|
+
const fragmentIndex = emittedFragmentCounts.get(anchorId) ?? 0;
|
|
2522
|
+
emittedFragmentCounts.set(anchorId, fragmentIndex + 1);
|
|
2523
|
+
const fragmentId = `p${page.pageNumber}-f${fragmentIndex}-${anchorId}`;
|
|
2524
|
+
if (element.dataset.fragmentIndex !== String(fragmentIndex) || element.dataset.pageFragmentId !== fragmentId || element.dataset.pageNumber !== String(page.pageNumber)) {
|
|
2525
|
+
throw new Error(
|
|
2526
|
+
`page ${page.pageNumber} fragment identity changed after final-tree normalization`
|
|
2527
|
+
);
|
|
2528
|
+
}
|
|
2529
|
+
fragments.push({
|
|
2530
|
+
fragmentId,
|
|
2531
|
+
anchorId,
|
|
2532
|
+
fragmentIndex,
|
|
2533
|
+
pageNumber: page.pageNumber,
|
|
2534
|
+
geometry: {
|
|
2535
|
+
x: (left2 - pageRect.left) * pointPerRenderedX,
|
|
2536
|
+
y: (top2 - pageRect.top) * pointPerRenderedY,
|
|
2537
|
+
width: (right2 - left2) * pointPerRenderedX,
|
|
2538
|
+
height: (bottom2 - top2) * pointPerRenderedY
|
|
2539
|
+
},
|
|
2540
|
+
story: this.storyForCanonicalAnchor(anchorId),
|
|
2541
|
+
inTableCell: element.matches("td,th") || element.closest("td,th") !== null
|
|
2542
|
+
});
|
|
2543
|
+
}
|
|
2544
|
+
}
|
|
2545
|
+
const missingAnchor = Array.from(requiredAnchorIds).find((id) => !measuredAnchorIds.has(id));
|
|
2546
|
+
if (missingAnchor) {
|
|
2547
|
+
throw new Error(`source anchor ${missingAnchor} has no measurable fragment in the paginated layout`);
|
|
2548
|
+
}
|
|
2549
|
+
return {
|
|
2550
|
+
schemaVersion: 1,
|
|
2551
|
+
mode: "paginated",
|
|
2552
|
+
availability: "available",
|
|
2553
|
+
documentVersion,
|
|
2554
|
+
rendererFingerprint,
|
|
2555
|
+
pages,
|
|
2556
|
+
fragments
|
|
2557
|
+
};
|
|
2558
|
+
}
|
|
2559
|
+
normalizeVisiblePageFragments(pages) {
|
|
2560
|
+
const emittedFragmentCounts = /* @__PURE__ */ new Map();
|
|
2561
|
+
for (const page of pages) {
|
|
2562
|
+
this.checkpoint();
|
|
2563
|
+
const pageRect = page.element.getBoundingClientRect();
|
|
2564
|
+
for (const element of Array.from(
|
|
2565
|
+
page.element.querySelectorAll("[data-source-anchor-id]")
|
|
2566
|
+
)) {
|
|
2567
|
+
this.checkpoint();
|
|
2568
|
+
if (this.isDeliberatelyUnrenderedSource(element, page.element)) continue;
|
|
2569
|
+
const anchorId = element.dataset.sourceAnchorId;
|
|
2570
|
+
if (!anchorId) continue;
|
|
2571
|
+
const style = this.view.getComputedStyle(element);
|
|
2572
|
+
if (style.display === "none" || style.visibility === "hidden") continue;
|
|
2573
|
+
const rect = element.getBoundingClientRect();
|
|
2574
|
+
const visible = this.intersectWithClippingAncestors(element, page.element, pageRect, rect);
|
|
2575
|
+
if (rect.width <= 0 || rect.height <= 0 || visible.right <= visible.left || visible.bottom <= visible.top) continue;
|
|
2576
|
+
const fragmentIndex = emittedFragmentCounts.get(anchorId) ?? 0;
|
|
2577
|
+
emittedFragmentCounts.set(anchorId, fragmentIndex + 1);
|
|
2578
|
+
element.dataset.pageNumber = String(page.pageNumber);
|
|
2579
|
+
element.dataset.fragmentIndex = String(fragmentIndex);
|
|
2580
|
+
element.dataset.pageFragmentId = `p${page.pageNumber}-f${fragmentIndex}-${anchorId}`;
|
|
2581
|
+
}
|
|
2582
|
+
}
|
|
2583
|
+
}
|
|
2584
|
+
/**
|
|
2585
|
+
* Intersect an element with every ancestor that establishes an overflow clip before the page
|
|
2586
|
+
* root. getBoundingClientRect() reports layout outside those clips, which is not rendered and
|
|
2587
|
+
* therefore must not satisfy PageMap completeness or inflate portable geometry.
|
|
2588
|
+
*/
|
|
2589
|
+
intersectWithClippingAncestors(element, page, pageRect, rect) {
|
|
2590
|
+
let left2 = Math.max(rect.left, pageRect.left);
|
|
2591
|
+
let top2 = Math.max(rect.top, pageRect.top);
|
|
2592
|
+
let right2 = Math.min(rect.right, pageRect.right);
|
|
2593
|
+
let bottom2 = Math.min(rect.bottom, pageRect.bottom);
|
|
2594
|
+
const clips = (value) => value === "hidden" || value === "clip" || value === "scroll" || value === "auto";
|
|
2595
|
+
for (let ancestor = element.parentElement; ancestor && ancestor !== page; ancestor = ancestor.parentElement) {
|
|
2596
|
+
const style = this.view.getComputedStyle(ancestor);
|
|
2597
|
+
const clipsX = clips(style.overflowX);
|
|
2598
|
+
const clipsY = clips(style.overflowY);
|
|
2599
|
+
if (!clipsX && !clipsY) continue;
|
|
2600
|
+
const ancestorRect = ancestor.getBoundingClientRect();
|
|
2601
|
+
if (clipsX) {
|
|
2602
|
+
left2 = Math.max(left2, ancestorRect.left);
|
|
2603
|
+
right2 = Math.min(right2, ancestorRect.right);
|
|
2604
|
+
}
|
|
2605
|
+
if (clipsY) {
|
|
2606
|
+
top2 = Math.max(top2, ancestorRect.top);
|
|
2607
|
+
bottom2 = Math.min(bottom2, ancestorRect.bottom);
|
|
2608
|
+
}
|
|
2609
|
+
}
|
|
2610
|
+
return { left: left2, top: top2, right: right2, bottom: bottom2 };
|
|
2611
|
+
}
|
|
2612
|
+
storyForCanonicalAnchor(anchorId) {
|
|
2613
|
+
const first = anchorId.indexOf(":");
|
|
2614
|
+
const second = first < 0 ? -1 : anchorId.indexOf(":", first + 1);
|
|
2615
|
+
const scope = first >= 0 && second > first ? anchorId.slice(first + 1, second) : "body";
|
|
2616
|
+
if (scope.startsWith("hdr")) return "header";
|
|
2617
|
+
if (scope.startsWith("ftr")) return "footer";
|
|
2618
|
+
if (scope === "fn") return "footnote";
|
|
2619
|
+
if (scope === "en") return "endnote";
|
|
2620
|
+
if (scope === "cmt") return "comment";
|
|
2621
|
+
return "body";
|
|
2622
|
+
}
|
|
2623
|
+
/**
|
|
2624
|
+
* Add canonical IDs from an addressable source subtree to the pre-pagination inventory.
|
|
2625
|
+
* Registry wrappers are excluded when scanning staging because selectable registry contents are
|
|
2626
|
+
* inventoried separately. Producers may explicitly mark content that has no visual substrate
|
|
2627
|
+
* with `data-page-map-exclude="true"`; native hidden semantics carry the same signal.
|
|
2628
|
+
*/
|
|
2629
|
+
collectExpectedSourceAnchors(source, destination, excludeRegistries = false) {
|
|
2630
|
+
const candidates = source.matches("[data-source-anchor-id]") ? [source] : [];
|
|
2631
|
+
candidates.push(...Array.from(source.querySelectorAll("[data-source-anchor-id]")));
|
|
2632
|
+
for (const element of candidates) {
|
|
2633
|
+
if (excludeRegistries && element.closest(
|
|
2634
|
+
"#pagination-hf-registry, #pagination-footnote-registry, #pagination-comment-margin-registry"
|
|
2635
|
+
)) {
|
|
2636
|
+
continue;
|
|
2637
|
+
}
|
|
2638
|
+
if (this.isZeroHeightExplicitBreakCarrier(element)) {
|
|
2639
|
+
element.dataset.pageMapExclude = "true";
|
|
2640
|
+
}
|
|
2641
|
+
if (this.isDeliberatelyUnrenderedSource(element, source)) continue;
|
|
2642
|
+
const anchorId = element.dataset.sourceAnchorId;
|
|
2643
|
+
if (anchorId) destination.add(anchorId);
|
|
2644
|
+
}
|
|
2645
|
+
}
|
|
2646
|
+
isZeroHeightExplicitBreakCarrier(element) {
|
|
2647
|
+
const next = element.nextElementSibling;
|
|
2648
|
+
const bounds = element.getBoundingClientRect();
|
|
2649
|
+
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);
|
|
2650
|
+
}
|
|
2651
|
+
isDeliberatelyUnrenderedSource(element, sourceRoot) {
|
|
2652
|
+
for (let current = element; current; current = current.parentElement) {
|
|
2653
|
+
const zeroHeightExplicitBreakCarrier = current === element && this.isZeroHeightExplicitBreakCarrier(current);
|
|
2654
|
+
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") {
|
|
2655
|
+
return true;
|
|
2656
|
+
}
|
|
2657
|
+
if (current === sourceRoot) break;
|
|
2658
|
+
}
|
|
2659
|
+
return false;
|
|
2660
|
+
}
|
|
2661
|
+
/**
|
|
2662
|
+
* Keep exactly one active bare-Unid editor anchor per source block. Presentation clones use
|
|
2663
|
+
* canonical source identity plus page/fragment qualification instead.
|
|
2664
|
+
*/
|
|
2665
|
+
qualifyPageFragments(pages) {
|
|
2666
|
+
const fragmentCounts = /* @__PURE__ */ new Map();
|
|
2667
|
+
const activeCanonicalIds = /* @__PURE__ */ new Set();
|
|
2668
|
+
const activeBareAnchorIds = /* @__PURE__ */ new Set();
|
|
2669
|
+
const makeInactive = (element) => {
|
|
2670
|
+
element.removeAttribute("data-anchor");
|
|
2671
|
+
element.removeAttribute("data-committed-text");
|
|
2672
|
+
if (element.hasAttribute("contenteditable")) element.setAttribute("contenteditable", "false");
|
|
2673
|
+
};
|
|
2674
|
+
for (const page of pages) {
|
|
2675
|
+
const nodes = page.element.querySelectorAll("[data-source-anchor-id]");
|
|
2676
|
+
for (const element of Array.from(nodes)) {
|
|
2677
|
+
const anchorId = element.dataset.sourceAnchorId;
|
|
2678
|
+
if (!anchorId) continue;
|
|
2679
|
+
const fragmentIndex = fragmentCounts.get(anchorId) ?? 0;
|
|
2680
|
+
fragmentCounts.set(anchorId, fragmentIndex + 1);
|
|
2681
|
+
element.dataset.pageNumber = String(page.pageNumber);
|
|
2682
|
+
element.dataset.fragmentIndex = String(fragmentIndex);
|
|
2683
|
+
element.dataset.pageFragmentId = `p${page.pageNumber}-f${fragmentIndex}-${anchorId}`;
|
|
2684
|
+
const story = this.storyForCanonicalAnchor(anchorId);
|
|
2685
|
+
const mayOwnActiveEditorAnchor = story === "body" || story === "comment" || story === "footnote" || story === "endnote";
|
|
2686
|
+
if (element.hasAttribute("data-anchor")) {
|
|
2687
|
+
const bareAnchorId = element.dataset.anchor;
|
|
2688
|
+
if (mayOwnActiveEditorAnchor && !activeCanonicalIds.has(anchorId) && !activeBareAnchorIds.has(bareAnchorId)) {
|
|
2689
|
+
activeCanonicalIds.add(anchorId);
|
|
2690
|
+
activeBareAnchorIds.add(bareAnchorId);
|
|
2691
|
+
} else {
|
|
2692
|
+
makeInactive(element);
|
|
2693
|
+
}
|
|
2694
|
+
}
|
|
2695
|
+
}
|
|
2696
|
+
}
|
|
2697
|
+
const activeStagingCanonicalIds = /* @__PURE__ */ new Set();
|
|
2698
|
+
for (const element of Array.from(
|
|
2699
|
+
this.stagingElement.querySelectorAll("[data-source-anchor-id][data-anchor]")
|
|
2700
|
+
)) {
|
|
2701
|
+
const anchorId = element.dataset.sourceAnchorId;
|
|
2702
|
+
if (!anchorId) continue;
|
|
2703
|
+
const bareAnchorId = element.dataset.anchor;
|
|
2704
|
+
if (activeCanonicalIds.has(anchorId) || activeStagingCanonicalIds.has(anchorId) || activeBareAnchorIds.has(bareAnchorId)) {
|
|
2705
|
+
makeInactive(element);
|
|
2706
|
+
} else {
|
|
2707
|
+
activeStagingCanonicalIds.add(anchorId);
|
|
2708
|
+
activeBareAnchorIds.add(bareAnchorId);
|
|
2709
|
+
}
|
|
2710
|
+
}
|
|
2711
|
+
}
|
|
2712
|
+
/**
|
|
2713
|
+
* Page flow clones source blocks while the hidden staging tree stays in the document. Any HTML
|
|
2714
|
+
* fragment target copied into a visible page would therefore resolve to its earlier hidden
|
|
2715
|
+
* source. Transfer target ownership to the page presentation after flow is complete; registry
|
|
2716
|
+
* and wrapper IDs that have no visible counterpart remain available to pagination internals.
|
|
2717
|
+
*/
|
|
2718
|
+
transferVisibleFragmentTargets() {
|
|
2719
|
+
const visibleIds = new Set(Array.from(
|
|
2720
|
+
this.containerElement.querySelectorAll("[id]")
|
|
2721
|
+
).map((element) => element.id).filter(Boolean));
|
|
2722
|
+
if (visibleIds.size === 0) return;
|
|
2723
|
+
for (const source of Array.from(
|
|
2724
|
+
this.stagingElement.querySelectorAll("[id]")
|
|
2725
|
+
)) {
|
|
2726
|
+
if (visibleIds.has(source.id)) source.removeAttribute("id");
|
|
1486
2727
|
}
|
|
1487
|
-
this.stagingElement.style.display = "none";
|
|
1488
|
-
this.substitutePageNumberFields(pages.length);
|
|
1489
|
-
return { totalPages: pages.length, pages };
|
|
1490
2728
|
}
|
|
1491
2729
|
/** Read each section's `w:pgNumType` off its wrapper (see {@link SectionPageNumbering}). */
|
|
1492
2730
|
parsePageNumbering(sections) {
|
|
@@ -1530,9 +2768,8 @@ var PaginationEngine = class {
|
|
|
1530
2768
|
if (markers.length === 0) continue;
|
|
1531
2769
|
const sectionIndex = parseInt(box.dataset.sectionIndex || "0", 10);
|
|
1532
2770
|
const pageNumber = parseInt(box.dataset.pageNumber || "1", 10);
|
|
1533
|
-
const pageInSection = parseInt(box.dataset.pageInSection || "1", 10);
|
|
1534
2771
|
const numbering = this.pageNumbering.get(sectionIndex) ?? {};
|
|
1535
|
-
const displayed =
|
|
2772
|
+
const displayed = parseInt(box.dataset.displayedPageNumber || String(pageNumber), 10);
|
|
1536
2773
|
for (const marker of Array.from(markers)) {
|
|
1537
2774
|
const kind = marker.dataset.field;
|
|
1538
2775
|
if (kind !== "PAGE" && kind !== "NUMPAGES") continue;
|
|
@@ -1544,17 +2781,26 @@ var PaginationEngine = class {
|
|
|
1544
2781
|
/**
|
|
1545
2782
|
* Measures all content blocks in a section.
|
|
1546
2783
|
*/
|
|
1547
|
-
measureBlocks(section, dims) {
|
|
2784
|
+
measureBlocks(section, dims, sectionIndex) {
|
|
1548
2785
|
const blocks = [];
|
|
1549
2786
|
const children = Array.from(section.children);
|
|
1550
2787
|
for (const child of children) {
|
|
2788
|
+
this.checkpoint();
|
|
1551
2789
|
if (child.dataset.sectionIndex !== void 0) {
|
|
1552
|
-
const
|
|
2790
|
+
const nestedSectionIndex = parseInt(child.dataset.sectionIndex || String(sectionIndex), 10);
|
|
2791
|
+
const nestedBlocks = this.measureBlocks(child, dims, nestedSectionIndex);
|
|
1553
2792
|
blocks.push(...nestedBlocks);
|
|
1554
2793
|
continue;
|
|
1555
2794
|
}
|
|
2795
|
+
if (child.matches("section.endnotes")) {
|
|
2796
|
+
const endnoteBlocks = this.measureSafeEndnoteBlocks(child, dims, sectionIndex);
|
|
2797
|
+
if (endnoteBlocks) {
|
|
2798
|
+
blocks.push(...endnoteBlocks);
|
|
2799
|
+
continue;
|
|
2800
|
+
}
|
|
2801
|
+
}
|
|
1556
2802
|
const rect = child.getBoundingClientRect();
|
|
1557
|
-
const style =
|
|
2803
|
+
const style = this.view.getComputedStyle(child);
|
|
1558
2804
|
const marginTopPx = parseFloat(style.marginTop) || 0;
|
|
1559
2805
|
const marginBottomPx = parseFloat(style.marginBottom) || 0;
|
|
1560
2806
|
const heightPt = pxToPt(rect.height);
|
|
@@ -1563,14 +2809,166 @@ var PaginationEngine = class {
|
|
|
1563
2809
|
const isPageBreak = child.dataset.pageBreak === "true" || child.classList.contains(`${this.cssPrefix}break`);
|
|
1564
2810
|
blocks.push({
|
|
1565
2811
|
element: child,
|
|
2812
|
+
sectionIndex,
|
|
1566
2813
|
heightPt,
|
|
1567
2814
|
marginTopPt,
|
|
1568
2815
|
marginBottomPt,
|
|
1569
2816
|
keepWithNext: child.dataset.keepWithNext === "true",
|
|
1570
2817
|
keepLines: child.dataset.keepLines === "true",
|
|
1571
2818
|
pageBreakBefore: child.dataset.pageBreakBefore === "true",
|
|
1572
|
-
isPageBreak
|
|
2819
|
+
isPageBreak,
|
|
2820
|
+
isWordParagraph: this.isWordParagraphElement(child)
|
|
2821
|
+
});
|
|
2822
|
+
}
|
|
2823
|
+
return blocks;
|
|
2824
|
+
}
|
|
2825
|
+
/**
|
|
2826
|
+
* Flatten the converter's `section.endnotes > ol > li > p` presentation into
|
|
2827
|
+
* ordinary paragraph blocks. This preserves paragraph formatting and canonical
|
|
2828
|
+
* p:en/en:en identities while allowing the existing paragraph fragmenter to
|
|
2829
|
+
* split a long endnote across page boundaries.
|
|
2830
|
+
*/
|
|
2831
|
+
measureSafeEndnoteBlocks(section, dims, sectionIndex) {
|
|
2832
|
+
const sectionChildren = Array.from(section.children);
|
|
2833
|
+
const list = sectionChildren.find((child) => child.tagName === "OL");
|
|
2834
|
+
if (!list || sectionChildren.some((child) => child.tagName !== "HR" && child !== list) || Array.from(list.children).some((child) => child.tagName !== "LI")) {
|
|
2835
|
+
return null;
|
|
2836
|
+
}
|
|
2837
|
+
const items = Array.from(list.children);
|
|
2838
|
+
if (items.length === 0 || items.some(
|
|
2839
|
+
(item) => item.children.length === 0 || Array.from(item.children).some((child) => child.tagName !== "P")
|
|
2840
|
+
)) {
|
|
2841
|
+
return null;
|
|
2842
|
+
}
|
|
2843
|
+
const blocks = [];
|
|
2844
|
+
const sectionStyle = this.view.getComputedStyle(section);
|
|
2845
|
+
for (const rule of sectionChildren.filter((child) => child.tagName === "HR")) {
|
|
2846
|
+
this.checkpoint();
|
|
2847
|
+
const clonedRule = rule.cloneNode(true);
|
|
2848
|
+
if (blocks.length === 0) clonedRule.style.marginTop = sectionStyle.marginTop;
|
|
2849
|
+
blocks.push(this.measureElement(clonedRule, dims, sectionIndex));
|
|
2850
|
+
}
|
|
2851
|
+
const listStyle = this.view.getComputedStyle(list);
|
|
2852
|
+
for (let itemIndex = 0; itemIndex < items.length; itemIndex++) {
|
|
2853
|
+
this.checkpoint();
|
|
2854
|
+
const item = items[itemIndex];
|
|
2855
|
+
const ownerAnchorId = item.dataset.sourceAnchorId;
|
|
2856
|
+
const paragraphs = Array.from(item.children);
|
|
2857
|
+
for (let paragraphIndex = 0; paragraphIndex < paragraphs.length; paragraphIndex++) {
|
|
2858
|
+
this.checkpoint();
|
|
2859
|
+
const paragraph = paragraphs[paragraphIndex];
|
|
2860
|
+
if (!this.hasRangeFragmentSafeLayout(paragraph)) {
|
|
2861
|
+
return null;
|
|
2862
|
+
}
|
|
2863
|
+
const clone = paragraph.cloneNode(true);
|
|
2864
|
+
clone.dataset.paginationSafeEndnote = "true";
|
|
2865
|
+
clone.style.fontSize ||= sectionStyle.fontSize;
|
|
2866
|
+
clone.style.lineHeight ||= sectionStyle.lineHeight;
|
|
2867
|
+
clone.style.paddingLeft ||= listStyle.paddingLeft;
|
|
2868
|
+
if (ownerAnchorId && !Array.from(
|
|
2869
|
+
clone.querySelectorAll("[data-source-anchor-id]")
|
|
2870
|
+
).some((node) => node.dataset.sourceAnchorId === ownerAnchorId)) {
|
|
2871
|
+
const owner = this.document.createElement("span");
|
|
2872
|
+
owner.dataset.sourceAnchorId = ownerAnchorId;
|
|
2873
|
+
while (clone.firstChild) owner.appendChild(clone.firstChild);
|
|
2874
|
+
clone.appendChild(owner);
|
|
2875
|
+
}
|
|
2876
|
+
if (paragraphIndex === 0) {
|
|
2877
|
+
const itemId = item.id;
|
|
2878
|
+
if (itemId) {
|
|
2879
|
+
if (clone.id && clone.id !== itemId) return null;
|
|
2880
|
+
clone.id = itemId;
|
|
2881
|
+
}
|
|
2882
|
+
const value = parseInt(item.getAttribute("value") || String(itemIndex + 1), 10);
|
|
2883
|
+
const marker = this.formatOrderedListMarker(
|
|
2884
|
+
Number.isFinite(value) ? value : itemIndex + 1,
|
|
2885
|
+
listStyle.listStyleType
|
|
2886
|
+
);
|
|
2887
|
+
clone.insertBefore(this.document.createTextNode(`${marker}. `), clone.firstChild);
|
|
2888
|
+
}
|
|
2889
|
+
blocks.push(this.measureElement(clone, dims, sectionIndex));
|
|
2890
|
+
}
|
|
2891
|
+
}
|
|
2892
|
+
return blocks;
|
|
2893
|
+
}
|
|
2894
|
+
/** Render the CSS ordered-list formats emitted by the converter after an endnote is flattened. */
|
|
2895
|
+
formatOrderedListMarker(value, listStyleType) {
|
|
2896
|
+
const format = (() => {
|
|
2897
|
+
switch (listStyleType) {
|
|
2898
|
+
case "lower-roman":
|
|
2899
|
+
return "lowerRoman";
|
|
2900
|
+
case "upper-roman":
|
|
2901
|
+
return "upperRoman";
|
|
2902
|
+
case "lower-alpha":
|
|
2903
|
+
case "lower-latin":
|
|
2904
|
+
return "lowerLetter";
|
|
2905
|
+
case "upper-alpha":
|
|
2906
|
+
case "upper-latin":
|
|
2907
|
+
return "upperLetter";
|
|
2908
|
+
default:
|
|
2909
|
+
return "decimal";
|
|
2910
|
+
}
|
|
2911
|
+
})();
|
|
2912
|
+
return formatPageNumber(value, format);
|
|
2913
|
+
}
|
|
2914
|
+
/**
|
|
2915
|
+
* Flows a multi-column (`w:cols`) section's children into CSS-multicol container
|
|
2916
|
+
* blocks. Word lays such a section out as N columns inside the same body extent;
|
|
2917
|
+
* a balanced `column-count` container reproduces that geometry, and the paginator
|
|
2918
|
+
* then places each container as one ordinary measured block. A container grows
|
|
2919
|
+
* greedily until its balanced height would exceed the smallest page body available
|
|
2920
|
+
* to the section, so a long columned section still splits across pages at block
|
|
2921
|
+
* boundaries. Each child lands in exactly one container, so anchors never
|
|
2922
|
+
* duplicate. An explicit page break child passes through as its own block, which
|
|
2923
|
+
* ends the current container and lets the normal flow logic turn the page.
|
|
2924
|
+
*/
|
|
2925
|
+
buildColumnBlocks(section, dims, sectionIndex, columnCount, columnGapPt) {
|
|
2926
|
+
const children = Array.from(section.children);
|
|
2927
|
+
const blocks = [];
|
|
2928
|
+
const maxFragmentHeight = this.smallestEffectiveContentHeight(dims, sectionIndex);
|
|
2929
|
+
const isBreak = (child) => child.dataset.pageBreak === "true" || child.classList.contains(`${this.cssPrefix}break`);
|
|
2930
|
+
const makeContainer = (slice) => {
|
|
2931
|
+
const container = this.document.createElement("div");
|
|
2932
|
+
container.style.columnCount = String(columnCount);
|
|
2933
|
+
container.style.columnGap = `${columnGapPt}pt`;
|
|
2934
|
+
for (const child of slice) {
|
|
2935
|
+
container.appendChild(child.cloneNode(true));
|
|
2936
|
+
}
|
|
2937
|
+
return container;
|
|
2938
|
+
};
|
|
2939
|
+
let start2 = 0;
|
|
2940
|
+
while (start2 < children.length) {
|
|
2941
|
+
this.checkpoint();
|
|
2942
|
+
if (isBreak(children[start2])) {
|
|
2943
|
+
blocks.push(this.measureElement(children[start2], dims, sectionIndex));
|
|
2944
|
+
start2++;
|
|
2945
|
+
continue;
|
|
2946
|
+
}
|
|
2947
|
+
let end = start2 + 1;
|
|
2948
|
+
let container = makeContainer(children.slice(start2, end));
|
|
2949
|
+
let measured = this.measureElement(container, dims, sectionIndex);
|
|
2950
|
+
while (end < children.length && !isBreak(children[end])) {
|
|
2951
|
+
this.checkpoint();
|
|
2952
|
+
const candidate = makeContainer(children.slice(start2, end + 1));
|
|
2953
|
+
const candidateMeasured = this.measureElement(candidate, dims, sectionIndex);
|
|
2954
|
+
if (candidateMeasured.heightPt > maxFragmentHeight) break;
|
|
2955
|
+
container = candidate;
|
|
2956
|
+
measured = candidateMeasured;
|
|
2957
|
+
end++;
|
|
2958
|
+
}
|
|
2959
|
+
blocks.push({
|
|
2960
|
+
element: container,
|
|
2961
|
+
sectionIndex,
|
|
2962
|
+
heightPt: measured.heightPt,
|
|
2963
|
+
marginTopPt: measured.marginTopPt,
|
|
2964
|
+
marginBottomPt: measured.marginBottomPt,
|
|
2965
|
+
keepWithNext: false,
|
|
2966
|
+
keepLines: false,
|
|
2967
|
+
pageBreakBefore: false,
|
|
2968
|
+
isPageBreak: false,
|
|
2969
|
+
isWordParagraph: false
|
|
1573
2970
|
});
|
|
2971
|
+
start2 = end;
|
|
1574
2972
|
}
|
|
1575
2973
|
return blocks;
|
|
1576
2974
|
}
|
|
@@ -1579,8 +2977,8 @@ var PaginationEngine = class {
|
|
|
1579
2977
|
* This is intentionally DOM-based: table row heights cannot be inferred from individual
|
|
1580
2978
|
* rows because wrapping and collapsed borders change the height of a fragment.
|
|
1581
2979
|
*/
|
|
1582
|
-
measureElement(element, dims) {
|
|
1583
|
-
const measurementHost = document.createElement("div");
|
|
2980
|
+
measureElement(element, dims, sectionIndex) {
|
|
2981
|
+
const measurementHost = this.document.createElement("div");
|
|
1584
2982
|
measurementHost.style.position = "absolute";
|
|
1585
2983
|
measurementHost.style.visibility = "hidden";
|
|
1586
2984
|
measurementHost.style.left = "-9999px";
|
|
@@ -1589,16 +2987,18 @@ var PaginationEngine = class {
|
|
|
1589
2987
|
measurementHost.appendChild(measuredElement);
|
|
1590
2988
|
this.stagingElement.appendChild(measurementHost);
|
|
1591
2989
|
const rect = measuredElement.getBoundingClientRect();
|
|
1592
|
-
const style =
|
|
2990
|
+
const style = this.view.getComputedStyle(measuredElement);
|
|
1593
2991
|
const measured = {
|
|
1594
2992
|
element,
|
|
2993
|
+
sectionIndex,
|
|
1595
2994
|
heightPt: pxToPt(rect.height),
|
|
1596
2995
|
marginTopPt: pxToPt(parseFloat(style.marginTop) || 0),
|
|
1597
2996
|
marginBottomPt: pxToPt(parseFloat(style.marginBottom) || 0),
|
|
1598
2997
|
keepWithNext: element.dataset.keepWithNext === "true",
|
|
1599
2998
|
keepLines: element.dataset.keepLines === "true",
|
|
1600
2999
|
pageBreakBefore: element.dataset.pageBreakBefore === "true",
|
|
1601
|
-
isPageBreak: element.dataset.pageBreak === "true" || element.classList.contains(`${this.cssPrefix}break`)
|
|
3000
|
+
isPageBreak: element.dataset.pageBreak === "true" || element.classList.contains(`${this.cssPrefix}break`),
|
|
3001
|
+
isWordParagraph: this.isWordParagraphElement(element)
|
|
1602
3002
|
};
|
|
1603
3003
|
this.stagingElement.removeChild(measurementHost);
|
|
1604
3004
|
return measured;
|
|
@@ -1630,10 +3030,15 @@ var PaginationEngine = class {
|
|
|
1630
3030
|
* collapsed-margin rules as normal block placement. The trailing margin is
|
|
1631
3031
|
* intentionally excluded, matching the individual block fit check.
|
|
1632
3032
|
*/
|
|
1633
|
-
measureKeepWithNextChainBodyHeight(chain, previousMarginBottomPt, isFirstOnPage) {
|
|
3033
|
+
measureKeepWithNextChainBodyHeight(chain, previousMarginBottomPt, isFirstOnPage, pageInSection) {
|
|
1634
3034
|
const firstBlock = chain[0];
|
|
1635
3035
|
if (!firstBlock) return 0;
|
|
1636
|
-
const firstMarginTop =
|
|
3036
|
+
const firstMarginTop = this.effectiveBlockMarginTop(
|
|
3037
|
+
firstBlock,
|
|
3038
|
+
previousMarginBottomPt,
|
|
3039
|
+
isFirstOnPage,
|
|
3040
|
+
pageInSection
|
|
3041
|
+
);
|
|
1637
3042
|
let bodyHeight = firstMarginTop + firstBlock.heightPt;
|
|
1638
3043
|
for (let index = 1; index < chain.length; index++) {
|
|
1639
3044
|
const previousBlock = chain[index - 1];
|
|
@@ -1642,6 +3047,35 @@ var PaginationEngine = class {
|
|
|
1642
3047
|
}
|
|
1643
3048
|
return bodyHeight;
|
|
1644
3049
|
}
|
|
3050
|
+
/** Word paragraphs render as `p`, or as `h1`–`h6` when their style has an outline level. */
|
|
3051
|
+
isWordParagraphElement(element) {
|
|
3052
|
+
return element.tagName === "P" || /^H[1-6]$/.test(element.tagName);
|
|
3053
|
+
}
|
|
3054
|
+
shouldSuppressPageTopSpacing(block, isFirstOnPage, pageInSection) {
|
|
3055
|
+
return isFirstOnPage && pageInSection > 1 && block.isWordParagraph === true;
|
|
3056
|
+
}
|
|
3057
|
+
/**
|
|
3058
|
+
* Resolves the part of a block's top margin that consumes the current page.
|
|
3059
|
+
*
|
|
3060
|
+
* In Word's native DOCX layout, paragraph space-before is suppressed when a paragraph is the
|
|
3061
|
+
* first body block on a later page of the SAME section. The first page of a document/section is
|
|
3062
|
+
* the exception and keeps its spacing. Tables and other block margins are not paragraph spacing,
|
|
3063
|
+
* so they continue to use the ordinary CSS collapsing rule.
|
|
3064
|
+
*/
|
|
3065
|
+
effectiveBlockMarginTop(block, previousMarginBottomPt, isFirstOnPage, pageInSection) {
|
|
3066
|
+
if (this.shouldSuppressPageTopSpacing(block, isFirstOnPage, pageInSection)) {
|
|
3067
|
+
return 0;
|
|
3068
|
+
}
|
|
3069
|
+
return isFirstOnPage ? block.marginTopPt : Math.max(block.marginTopPt, previousMarginBottomPt) - previousMarginBottomPt;
|
|
3070
|
+
}
|
|
3071
|
+
/** Clone a source block with the same page-top spacing decision used by the height budget. */
|
|
3072
|
+
cloneBlockForPage(block, isFirstOnPage, pageInSection) {
|
|
3073
|
+
const clone = block.element.cloneNode(true);
|
|
3074
|
+
if (this.shouldSuppressPageTopSpacing(block, isFirstOnPage, pageInSection)) {
|
|
3075
|
+
clone.style.setProperty("margin-top", "0", "important");
|
|
3076
|
+
}
|
|
3077
|
+
return clone;
|
|
3078
|
+
}
|
|
1645
3079
|
/**
|
|
1646
3080
|
* Finds footnote references introduced by a sequence of blocks, preserving
|
|
1647
3081
|
* document order and excluding references already assigned to the page.
|
|
@@ -1650,6 +3084,7 @@ var PaginationEngine = class {
|
|
|
1650
3084
|
const knownIds = new Set(existingFootnoteIds);
|
|
1651
3085
|
const newIds = [];
|
|
1652
3086
|
for (const block of blocks) {
|
|
3087
|
+
this.checkpoint();
|
|
1653
3088
|
for (const id of this.extractFootnoteRefs(block.element)) {
|
|
1654
3089
|
if (!knownIds.has(id)) {
|
|
1655
3090
|
knownIds.add(id);
|
|
@@ -1667,8 +3102,8 @@ var PaginationEngine = class {
|
|
|
1667
3102
|
smallestEffectiveContentHeight(dims, sectionIndex) {
|
|
1668
3103
|
return Math.min(
|
|
1669
3104
|
this.getPageBands(dims, sectionIndex, 1, 1).bodyHeight,
|
|
1670
|
-
this.getPageBands(dims, sectionIndex, 2,
|
|
1671
|
-
this.getPageBands(dims, sectionIndex,
|
|
3105
|
+
this.getPageBands(dims, sectionIndex, 2, 2).bodyHeight,
|
|
3106
|
+
this.getPageBands(dims, sectionIndex, 3, 3).bodyHeight
|
|
1672
3107
|
);
|
|
1673
3108
|
}
|
|
1674
3109
|
/**
|
|
@@ -1706,10 +3141,11 @@ var PaginationEngine = class {
|
|
|
1706
3141
|
if (wrapper.tagName !== "DIV" || wrapper.children.length !== 1 || block.keepWithNext || block.keepLines || block.pageBreakBefore || block.isPageBreak) {
|
|
1707
3142
|
return null;
|
|
1708
3143
|
}
|
|
1709
|
-
const
|
|
1710
|
-
if (!
|
|
3144
|
+
const tableElement = wrapper.firstElementChild;
|
|
3145
|
+
if (!tableElement || tableElement.localName !== "table") {
|
|
1711
3146
|
return null;
|
|
1712
3147
|
}
|
|
3148
|
+
const table = tableElement;
|
|
1713
3149
|
const body = table.tBodies.length === 1 ? table.tBodies[0] : null;
|
|
1714
3150
|
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]")) {
|
|
1715
3151
|
return null;
|
|
@@ -1723,8 +3159,10 @@ var PaginationEngine = class {
|
|
|
1723
3159
|
const groups = [];
|
|
1724
3160
|
let start2 = 0;
|
|
1725
3161
|
while (start2 < rows.length) {
|
|
3162
|
+
this.checkpoint();
|
|
1726
3163
|
let end = start2;
|
|
1727
3164
|
while (end < rows.length) {
|
|
3165
|
+
this.checkpoint();
|
|
1728
3166
|
const candidate = this.createSimpleTableFragment(
|
|
1729
3167
|
wrapper,
|
|
1730
3168
|
table,
|
|
@@ -1732,7 +3170,7 @@ var PaginationEngine = class {
|
|
|
1732
3170
|
rows.slice(start2, end + 1),
|
|
1733
3171
|
start2 === 0
|
|
1734
3172
|
);
|
|
1735
|
-
const measured = this.measureElement(candidate, dims);
|
|
3173
|
+
const measured = this.measureElement(candidate, dims, block.sectionIndex);
|
|
1736
3174
|
if (measured.heightPt > maximumFragmentHeight) {
|
|
1737
3175
|
break;
|
|
1738
3176
|
}
|
|
@@ -1764,7 +3202,7 @@ var PaginationEngine = class {
|
|
|
1764
3202
|
if (!isLast) {
|
|
1765
3203
|
fragment.style.setProperty("margin-bottom", "0", "important");
|
|
1766
3204
|
}
|
|
1767
|
-
const measured = this.measureElement(fragment, dims);
|
|
3205
|
+
const measured = this.measureElement(fragment, dims, block.sectionIndex);
|
|
1768
3206
|
if (measured.heightPt + measured.marginTopPt + measured.marginBottomPt > minimumContentHeight) {
|
|
1769
3207
|
return null;
|
|
1770
3208
|
}
|
|
@@ -1779,35 +3217,198 @@ var PaginationEngine = class {
|
|
|
1779
3217
|
return fragments;
|
|
1780
3218
|
}
|
|
1781
3219
|
/**
|
|
1782
|
-
*
|
|
1783
|
-
*
|
|
1784
|
-
*
|
|
1785
|
-
|
|
1786
|
-
|
|
1787
|
-
|
|
1788
|
-
|
|
3220
|
+
* DOM endpoints that can finish a paragraph fragment. The flattened UTF-16
|
|
3221
|
+
* offsets are checked against the browser's Unicode grapheme segmenter, so a
|
|
3222
|
+
* formatting-run boundary can never bisect a surrogate pair, combining
|
|
3223
|
+
* sequence, or joined emoji. NBSP/word-joiner boundaries remain indivisible.
|
|
3224
|
+
* PAGE/NUMPAGES field results are atomic because splitting their marker would
|
|
3225
|
+
* make later substitution duplicate or replace only half of the field.
|
|
3226
|
+
*/
|
|
3227
|
+
paragraphFragmentEndpoints(paragraph, emergencyGraphemeBreaks = false) {
|
|
3228
|
+
const textNodes = [];
|
|
3229
|
+
const textStarts = /* @__PURE__ */ new Map();
|
|
3230
|
+
const lastTextByField = /* @__PURE__ */ new Map();
|
|
3231
|
+
const walker = this.document.createTreeWalker(paragraph, this.view.NodeFilter.SHOW_TEXT);
|
|
3232
|
+
const flattenedChunks = [];
|
|
3233
|
+
let flattenedLength = 0;
|
|
1789
3234
|
let textNode;
|
|
1790
3235
|
while (textNode = walker.nextNode()) {
|
|
1791
|
-
|
|
1792
|
-
|
|
1793
|
-
|
|
3236
|
+
this.checkpoint();
|
|
3237
|
+
textStarts.set(textNode, flattenedLength);
|
|
3238
|
+
textNodes.push(textNode);
|
|
3239
|
+
flattenedChunks.push(textNode.data);
|
|
3240
|
+
flattenedLength += textNode.data.length;
|
|
3241
|
+
let field = textNode.parentElement?.closest("[data-field]") ?? null;
|
|
3242
|
+
while (field?.parentElement?.closest("[data-field]")) {
|
|
3243
|
+
field = field.parentElement.closest("[data-field]");
|
|
3244
|
+
}
|
|
3245
|
+
if (field && paragraph.contains(field)) lastTextByField.set(field, textNode);
|
|
3246
|
+
}
|
|
3247
|
+
const flattenedText = flattenedChunks.join("");
|
|
3248
|
+
if (!this.hasVisibleText(flattenedText)) return [];
|
|
3249
|
+
const invisible = /[\s\u200B-\u200F\uFEFF]/;
|
|
3250
|
+
let firstVisibleOffset = -1;
|
|
3251
|
+
let lastVisibleOffset = -1;
|
|
3252
|
+
for (let index = 0; index < flattenedText.length; index++) {
|
|
3253
|
+
if (index % 4096 === 0) this.checkpoint();
|
|
3254
|
+
if (invisible.test(flattenedText[index])) continue;
|
|
3255
|
+
if (firstVisibleOffset < 0) firstVisibleOffset = index;
|
|
3256
|
+
lastVisibleOffset = index;
|
|
3257
|
+
}
|
|
3258
|
+
const graphemeBoundaries = this.graphemeBoundaryOffsets(flattenedText);
|
|
3259
|
+
const candidates = [];
|
|
3260
|
+
const addCandidate = (candidate) => {
|
|
3261
|
+
const { textOffset } = candidate;
|
|
3262
|
+
if (textOffset <= 0 || textOffset >= flattenedText.length) return;
|
|
3263
|
+
if (textOffset <= firstVisibleOffset || textOffset > lastVisibleOffset) return;
|
|
3264
|
+
if (this.isNonBreakingTextBoundary(flattenedText, textOffset)) return;
|
|
3265
|
+
if (!this.isLegalParagraphLineBoundary(paragraph, flattenedText, textOffset)) return;
|
|
3266
|
+
if (graphemeBoundaries) {
|
|
3267
|
+
if (!graphemeBoundaries.has(textOffset)) return;
|
|
3268
|
+
} else if (!this.isConservativeFallbackTextBoundary(flattenedText, textOffset)) {
|
|
3269
|
+
return;
|
|
3270
|
+
}
|
|
3271
|
+
candidates.push(candidate);
|
|
3272
|
+
};
|
|
3273
|
+
for (const node of textNodes) {
|
|
3274
|
+
this.checkpoint();
|
|
3275
|
+
const start2 = textStarts.get(node);
|
|
3276
|
+
const atomic = node.parentElement?.closest(
|
|
3277
|
+
"[data-field], [data-list-marker], a[data-comment-id]"
|
|
3278
|
+
);
|
|
3279
|
+
if (atomic && paragraph.contains(atomic)) continue;
|
|
3280
|
+
const whitespace = /[\u0009-\u000D\u0020]+/g;
|
|
1794
3281
|
let match;
|
|
1795
|
-
|
|
1796
|
-
|
|
3282
|
+
let matchesSinceCheckpoint = 0;
|
|
3283
|
+
while ((match = whitespace.exec(node.data)) !== null) {
|
|
3284
|
+
if (++matchesSinceCheckpoint % 256 === 0) this.checkpoint();
|
|
3285
|
+
const offset = match.index + match[0].length;
|
|
3286
|
+
addCandidate({ node, offset, textOffset: start2 + offset, priority: 0 });
|
|
1797
3287
|
}
|
|
1798
|
-
|
|
1799
|
-
|
|
3288
|
+
addCandidate({
|
|
3289
|
+
node,
|
|
3290
|
+
offset: node.data.length,
|
|
3291
|
+
textOffset: start2 + node.data.length,
|
|
3292
|
+
priority: 0
|
|
3293
|
+
});
|
|
3294
|
+
if (emergencyGraphemeBreaks) {
|
|
3295
|
+
for (let offset = 1; offset < node.data.length; offset++) {
|
|
3296
|
+
if (offset % 256 === 0) this.checkpoint();
|
|
3297
|
+
addCandidate({
|
|
3298
|
+
node,
|
|
3299
|
+
offset,
|
|
3300
|
+
textOffset: start2 + offset,
|
|
3301
|
+
priority: -1
|
|
3302
|
+
});
|
|
3303
|
+
}
|
|
3304
|
+
}
|
|
3305
|
+
}
|
|
3306
|
+
for (const field of Array.from(paragraph.querySelectorAll("[data-field]"))) {
|
|
3307
|
+
this.checkpoint();
|
|
3308
|
+
if (field.parentElement?.closest("[data-field]")) continue;
|
|
3309
|
+
const last = lastTextByField.get(field);
|
|
3310
|
+
const parent = field.parentNode;
|
|
3311
|
+
if (!last || !parent) continue;
|
|
3312
|
+
const childIndex = Array.prototype.indexOf.call(parent.childNodes, field);
|
|
3313
|
+
if (childIndex < 0) continue;
|
|
3314
|
+
addCandidate({
|
|
3315
|
+
node: parent,
|
|
3316
|
+
offset: childIndex + 1,
|
|
3317
|
+
textOffset: textStarts.get(last) + last.data.length,
|
|
3318
|
+
priority: 1
|
|
3319
|
+
});
|
|
3320
|
+
}
|
|
3321
|
+
candidates.sort((left2, right2) => left2.textOffset - right2.textOffset || right2.priority - left2.priority);
|
|
3322
|
+
const endpoints = [];
|
|
3323
|
+
for (const candidate of candidates) {
|
|
3324
|
+
if (endpoints[endpoints.length - 1]?.textOffset !== candidate.textOffset) {
|
|
3325
|
+
endpoints.push(candidate);
|
|
1800
3326
|
}
|
|
1801
3327
|
}
|
|
1802
3328
|
return endpoints;
|
|
1803
3329
|
}
|
|
3330
|
+
/** Browser-native UAX #29 boundaries; null keeps older runtimes conservative. */
|
|
3331
|
+
graphemeBoundaryOffsets(text) {
|
|
3332
|
+
const SegmenterConstructor = this.view.Intl.Segmenter;
|
|
3333
|
+
if (!SegmenterConstructor) return null;
|
|
3334
|
+
const boundaries = /* @__PURE__ */ new Set([0, text.length]);
|
|
3335
|
+
let segmentCount = 0;
|
|
3336
|
+
for (const part of new SegmenterConstructor("en", { granularity: "grapheme" }).segment(text)) {
|
|
3337
|
+
if (++segmentCount % 256 === 0) this.checkpoint();
|
|
3338
|
+
boundaries.add(part.index);
|
|
3339
|
+
boundaries.add(part.index + part.segment.length);
|
|
3340
|
+
}
|
|
3341
|
+
return boundaries;
|
|
3342
|
+
}
|
|
3343
|
+
/**
|
|
3344
|
+
* Conservative UAX #14/CSS wrapping opportunities used for synthetic page
|
|
3345
|
+
* boundaries. In particular, a formatting-run boundary is not itself a word
|
|
3346
|
+
* boundary, and Japanese opening/closing punctuation stays with its pair.
|
|
3347
|
+
* Arbitrary grapheme breaks are admitted only when the paragraph's CSS asks
|
|
3348
|
+
* the browser to wrap anywhere.
|
|
3349
|
+
*/
|
|
3350
|
+
isLegalParagraphLineBoundary(paragraph, text, offset) {
|
|
3351
|
+
const lastCodeUnit = text.charCodeAt(offset - 1);
|
|
3352
|
+
const beforeOffset = lastCodeUnit >= 56320 && lastCodeUnit <= 57343 ? Math.max(0, offset - 2) : offset - 1;
|
|
3353
|
+
const beforeCodePoint = text.codePointAt(beforeOffset);
|
|
3354
|
+
const afterCodePoint = text.codePointAt(offset);
|
|
3355
|
+
const before = beforeCodePoint === void 0 ? "" : String.fromCodePoint(beforeCodePoint);
|
|
3356
|
+
const after = afterCodePoint === void 0 ? "" : String.fromCodePoint(afterCodePoint);
|
|
3357
|
+
if (/^[\u0009-\u000D\u0020]$/.test(before) || /^[\u0009-\u000D\u0020]$/.test(after)) return true;
|
|
3358
|
+
if (/[-\u00AD\u2010]$/u.test(before)) return true;
|
|
3359
|
+
const style = this.view.getComputedStyle(paragraph);
|
|
3360
|
+
if (style.wordBreak === "break-all" || style.overflowWrap === "anywhere" || style.overflowWrap === "break-word") return true;
|
|
3361
|
+
const eastAsian = /[\p{Script=Han}\p{Script=Hiragana}\p{Script=Katakana}\p{Script=Hangul}]/u;
|
|
3362
|
+
if (!eastAsian.test(before) && !eastAsian.test(after)) return false;
|
|
3363
|
+
const opening = "([{<\u2018\u201C\u3008\u300A\u300C\u300E\u3010\u3014\u3016\u3018\u301A\uFF08\uFF3B\uFF5B";
|
|
3364
|
+
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";
|
|
3365
|
+
return !opening.includes(before) && !closing.includes(after);
|
|
3366
|
+
}
|
|
3367
|
+
/** Never fragment across characters whose line-break meaning is explicitly non-breaking. */
|
|
3368
|
+
isNonBreakingTextBoundary(text, offset) {
|
|
3369
|
+
const codePointBefore = (index) => {
|
|
3370
|
+
if (index <= 0) return void 0;
|
|
3371
|
+
const last = text.charCodeAt(index - 1);
|
|
3372
|
+
const start2 = last >= 56320 && last <= 57343 ? index - 2 : index - 1;
|
|
3373
|
+
return text.codePointAt(Math.max(0, start2));
|
|
3374
|
+
};
|
|
3375
|
+
const nonBreaking = /* @__PURE__ */ new Set([
|
|
3376
|
+
160,
|
|
3377
|
+
8206,
|
|
3378
|
+
8207,
|
|
3379
|
+
8209,
|
|
3380
|
+
8234,
|
|
3381
|
+
8235,
|
|
3382
|
+
8236,
|
|
3383
|
+
8237,
|
|
3384
|
+
8238,
|
|
3385
|
+
8239,
|
|
3386
|
+
8288,
|
|
3387
|
+
8294,
|
|
3388
|
+
8295,
|
|
3389
|
+
8296,
|
|
3390
|
+
8297,
|
|
3391
|
+
65279
|
|
3392
|
+
]);
|
|
3393
|
+
return nonBreaking.has(codePointBefore(offset) ?? -1) || nonBreaking.has(text.codePointAt(offset) ?? -1);
|
|
3394
|
+
}
|
|
3395
|
+
/**
|
|
3396
|
+
* Without Intl.Segmenter, admit only an all-ASCII boundary. This still
|
|
3397
|
+
* fragments ordinary prose while refusing to guess about Unicode clusters.
|
|
3398
|
+
*/
|
|
3399
|
+
isConservativeFallbackTextBoundary(text, offset) {
|
|
3400
|
+
return text.charCodeAt(offset - 1) <= 127 && text.charCodeAt(offset) <= 127;
|
|
3401
|
+
}
|
|
1804
3402
|
/**
|
|
1805
3403
|
* Whether a range contains visible text after ignoring bidi/zero-width marks.
|
|
1806
3404
|
* Paragraph fragmentation deliberately excludes non-textual descendants, so
|
|
1807
3405
|
* this is enough to reject empty head or tail fragments.
|
|
1808
3406
|
*/
|
|
1809
3407
|
hasVisibleFragmentText(fragment) {
|
|
1810
|
-
return (fragment.textContent || "")
|
|
3408
|
+
return this.hasVisibleText(fragment.textContent || "");
|
|
3409
|
+
}
|
|
3410
|
+
hasVisibleText(text) {
|
|
3411
|
+
return text.replace(/[\u200B-\u200F\uFEFF]/g, "").trim().length > 0;
|
|
1811
3412
|
}
|
|
1812
3413
|
/**
|
|
1813
3414
|
* Phase one only handles text-like paragraph descendants. Objects, explicit
|
|
@@ -1816,13 +3417,25 @@ var PaginationEngine = class {
|
|
|
1816
3417
|
* fallback instead of risking broken content or duplicate anchors.
|
|
1817
3418
|
*/
|
|
1818
3419
|
canFragmentParagraph(block) {
|
|
1819
|
-
if (!this.fragmentParagraphs
|
|
3420
|
+
if (!this.fragmentParagraphs) {
|
|
1820
3421
|
return false;
|
|
1821
3422
|
}
|
|
1822
3423
|
const paragraph = block.element;
|
|
1823
3424
|
if (block.keepWithNext || block.keepLines || block.pageBreakBefore || block.isPageBreak || paragraph.dataset.widowControl === "true" || paragraph.hasAttribute("contenteditable")) {
|
|
1824
3425
|
return false;
|
|
1825
3426
|
}
|
|
3427
|
+
return this.canRangeFragmentParagraph(paragraph);
|
|
3428
|
+
}
|
|
3429
|
+
/**
|
|
3430
|
+
* Shared structural gate for body and note paragraph fragmentation. Footnote
|
|
3431
|
+
* first paragraphs are inline beside their marker, so that one known layout
|
|
3432
|
+
* context may opt into an inline root while retaining every descendant and
|
|
3433
|
+
* break-safety restriction used for body text.
|
|
3434
|
+
*/
|
|
3435
|
+
canRangeFragmentParagraph(paragraph, allowInlineRoot = false) {
|
|
3436
|
+
if (paragraph.tagName !== "P" || paragraph.dataset.keepWithNext === "true" || paragraph.dataset.keepLines === "true" || paragraph.dataset.pageBreakBefore === "true" || paragraph.dataset.widowControl === "true" || paragraph.hasAttribute("contenteditable")) {
|
|
3437
|
+
return false;
|
|
3438
|
+
}
|
|
1826
3439
|
const unsupportedDescendants = [
|
|
1827
3440
|
"br",
|
|
1828
3441
|
"img",
|
|
@@ -1853,21 +3466,27 @@ var PaginationEngine = class {
|
|
|
1853
3466
|
"fieldset",
|
|
1854
3467
|
"details",
|
|
1855
3468
|
"[data-footnote-id]",
|
|
1856
|
-
"[data-list-marker]",
|
|
1857
|
-
"[data-anchor]",
|
|
1858
|
-
"[id]",
|
|
1859
3469
|
"[contenteditable]"
|
|
1860
3470
|
].join(", ");
|
|
1861
3471
|
if (paragraph.querySelector(unsupportedDescendants)) {
|
|
1862
3472
|
return false;
|
|
1863
3473
|
}
|
|
1864
|
-
const
|
|
1865
|
-
|
|
3474
|
+
const isValidatedEndnote = paragraph.dataset.paginationSafeEndnote === "true";
|
|
3475
|
+
return isValidatedEndnote || this.hasRangeFragmentSafeLayout(paragraph, allowInlineRoot);
|
|
3476
|
+
}
|
|
3477
|
+
/**
|
|
3478
|
+
* A range clone preserves nested inline formatting exactly. Anything that establishes its own
|
|
3479
|
+
* box/layout context is deferred until a future fragmenter can model it accurately. Callers
|
|
3480
|
+
* must invoke this while the paragraph is attached to the styled document.
|
|
3481
|
+
*/
|
|
3482
|
+
hasRangeFragmentSafeLayout(paragraph, allowInlineRoot = false) {
|
|
3483
|
+
const paragraphStyle = this.view.getComputedStyle(paragraph);
|
|
3484
|
+
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") {
|
|
1866
3485
|
return false;
|
|
1867
3486
|
}
|
|
1868
3487
|
for (const descendant of Array.from(paragraph.querySelectorAll("*"))) {
|
|
1869
|
-
const style =
|
|
1870
|
-
if (style.display !== "inline" || style.position !== "static" || style.float !== "none" || style.whiteSpace !== "normal") {
|
|
3488
|
+
const style = this.view.getComputedStyle(descendant);
|
|
3489
|
+
if (style.display !== "inline" || style.position !== "static" || style.float !== "none" || style.whiteSpace !== "normal" && style.whiteSpace !== "pre-wrap") {
|
|
1871
3490
|
return false;
|
|
1872
3491
|
}
|
|
1873
3492
|
}
|
|
@@ -1894,58 +3513,122 @@ var PaginationEngine = class {
|
|
|
1894
3513
|
return fragment;
|
|
1895
3514
|
}
|
|
1896
3515
|
/**
|
|
1897
|
-
*
|
|
1898
|
-
*
|
|
1899
|
-
*
|
|
3516
|
+
* Range clones repeat an inline ancestor when the split lands inside it. Keep
|
|
3517
|
+
* semantic wrappers (links, comments, formatting) on both sides, but retain a
|
|
3518
|
+
* duplicated HTML/editor identity only on the leading fragment. Targets that
|
|
3519
|
+
* occur wholly after the split are absent from `head` and remain on `tail`.
|
|
1900
3520
|
*/
|
|
1901
|
-
|
|
1902
|
-
|
|
1903
|
-
|
|
1904
|
-
|
|
1905
|
-
|
|
1906
|
-
const
|
|
1907
|
-
|
|
1908
|
-
|
|
1909
|
-
}
|
|
1910
|
-
|
|
1911
|
-
|
|
1912
|
-
|
|
1913
|
-
|
|
1914
|
-
|
|
1915
|
-
|
|
1916
|
-
|
|
1917
|
-
|
|
1918
|
-
|
|
1919
|
-
|
|
1920
|
-
|
|
1921
|
-
|
|
1922
|
-
|
|
3521
|
+
reconcileParagraphFragmentIdentities(head, tail) {
|
|
3522
|
+
const elements = (root, selector2) => [
|
|
3523
|
+
...root.matches(selector2) ? [root] : [],
|
|
3524
|
+
...Array.from(root.querySelectorAll(selector2))
|
|
3525
|
+
];
|
|
3526
|
+
const headIds = new Set(elements(head, "[id]").map((element) => element.id));
|
|
3527
|
+
for (const element of elements(tail, "[id]")) {
|
|
3528
|
+
if (headIds.has(element.id)) element.removeAttribute("id");
|
|
3529
|
+
}
|
|
3530
|
+
const headAnchors = new Set(elements(head, "[data-anchor]").map((element) => element.dataset.anchor).filter((anchor) => Boolean(anchor)));
|
|
3531
|
+
for (const element of elements(tail, "[data-anchor]")) {
|
|
3532
|
+
if (!element.dataset.anchor || !headAnchors.has(element.dataset.anchor)) continue;
|
|
3533
|
+
element.removeAttribute("data-anchor");
|
|
3534
|
+
element.removeAttribute("data-committed-text");
|
|
3535
|
+
}
|
|
3536
|
+
}
|
|
3537
|
+
/**
|
|
3538
|
+
* Range-clone the largest safe prefix accepted by `fits`. The measurement
|
|
3539
|
+
* policy stays with the caller, allowing body blocks and note bands to share
|
|
3540
|
+
* one DOM fragmenter while measuring in their respective layout contexts.
|
|
3541
|
+
*/
|
|
3542
|
+
splitParagraphAtLargestFit(paragraph, fits, options = {}) {
|
|
3543
|
+
const largestFit = (endpoints2) => {
|
|
3544
|
+
const lastEndpoint = endpoints2[endpoints2.length - 1];
|
|
3545
|
+
if (lastEndpoint) {
|
|
3546
|
+
const lastRange = this.document.createRange();
|
|
3547
|
+
lastRange.setStart(paragraph, 0);
|
|
3548
|
+
lastRange.setEnd(lastEndpoint.node, lastEndpoint.offset);
|
|
3549
|
+
const lastContents = lastRange.cloneContents();
|
|
3550
|
+
if (this.hasVisibleFragmentText(lastContents)) {
|
|
3551
|
+
const lastHead = this.createParagraphFragment(paragraph, lastRange, true, false);
|
|
3552
|
+
if (fits(lastHead)) return { endpoint: lastEndpoint, head: lastHead };
|
|
3553
|
+
}
|
|
1923
3554
|
}
|
|
1924
|
-
|
|
1925
|
-
|
|
1926
|
-
|
|
1927
|
-
|
|
1928
|
-
|
|
1929
|
-
|
|
1930
|
-
|
|
3555
|
+
let low = 0;
|
|
3556
|
+
let high = endpoints2.length - 2;
|
|
3557
|
+
let best2 = null;
|
|
3558
|
+
while (low <= high) {
|
|
3559
|
+
this.checkpoint();
|
|
3560
|
+
const middle = Math.floor((low + high) / 2);
|
|
3561
|
+
const endpoint = endpoints2[middle];
|
|
3562
|
+
const headRange = this.document.createRange();
|
|
3563
|
+
headRange.setStart(paragraph, 0);
|
|
3564
|
+
headRange.setEnd(endpoint.node, endpoint.offset);
|
|
3565
|
+
const headContents = headRange.cloneContents();
|
|
3566
|
+
if (!this.hasVisibleFragmentText(headContents)) {
|
|
3567
|
+
low = middle + 1;
|
|
3568
|
+
continue;
|
|
3569
|
+
}
|
|
3570
|
+
const head = this.createParagraphFragment(paragraph, headRange, true, false);
|
|
3571
|
+
if (fits(head)) {
|
|
3572
|
+
best2 = { endpoint, head };
|
|
3573
|
+
low = middle + 1;
|
|
3574
|
+
} else {
|
|
3575
|
+
high = middle - 1;
|
|
3576
|
+
}
|
|
1931
3577
|
}
|
|
3578
|
+
return best2;
|
|
3579
|
+
};
|
|
3580
|
+
let endpoints = this.paragraphFragmentEndpoints(paragraph);
|
|
3581
|
+
let best = largestFit(endpoints);
|
|
3582
|
+
if (!best && options.emergencyGraphemeBreaks) {
|
|
3583
|
+
endpoints = this.paragraphFragmentEndpoints(paragraph, true);
|
|
3584
|
+
best = largestFit(endpoints);
|
|
3585
|
+
}
|
|
3586
|
+
if (!best && options.forceFirstOnNoFit && endpoints.length > 0) {
|
|
3587
|
+
const endpoint = endpoints[0];
|
|
3588
|
+
const headRange = this.document.createRange();
|
|
3589
|
+
headRange.setStart(paragraph, 0);
|
|
3590
|
+
headRange.setEnd(endpoint.node, endpoint.offset);
|
|
3591
|
+
best = {
|
|
3592
|
+
endpoint,
|
|
3593
|
+
head: this.createParagraphFragment(paragraph, headRange, true, false)
|
|
3594
|
+
};
|
|
1932
3595
|
}
|
|
1933
3596
|
if (!best) {
|
|
1934
|
-
return
|
|
3597
|
+
return endpoints.length > 0 ? { kind: "no-fit" } : { kind: "indivisible" };
|
|
1935
3598
|
}
|
|
1936
|
-
const tailRange = document.createRange();
|
|
3599
|
+
const tailRange = this.document.createRange();
|
|
1937
3600
|
tailRange.setStart(best.endpoint.node, best.endpoint.offset);
|
|
1938
3601
|
tailRange.setEnd(paragraph, paragraph.childNodes.length);
|
|
1939
3602
|
const tailContents = tailRange.cloneContents();
|
|
1940
|
-
if (!this.hasVisibleFragmentText(tailContents)) {
|
|
3603
|
+
if (!this.hasVisibleFragmentText(tailContents)) return { kind: "indivisible" };
|
|
3604
|
+
const tail = this.createParagraphFragment(paragraph, tailRange, false, true);
|
|
3605
|
+
this.reconcileParagraphFragmentIdentities(best.head, tail);
|
|
3606
|
+
return {
|
|
3607
|
+
kind: "split",
|
|
3608
|
+
head: best.head,
|
|
3609
|
+
tail
|
|
3610
|
+
};
|
|
3611
|
+
}
|
|
3612
|
+
/**
|
|
3613
|
+
* Splits a simple paragraph at the largest DOM Range endpoint that fits the
|
|
3614
|
+
* currently available body space. The caller then processes the tail normally,
|
|
3615
|
+
* allowing it to fragment again on later pages when necessary.
|
|
3616
|
+
*/
|
|
3617
|
+
tryFragmentParagraph(block, dims, availableHeightPt, effectiveMarginTopPt) {
|
|
3618
|
+
if (!this.canFragmentParagraph(block) || availableHeightPt <= effectiveMarginTopPt) {
|
|
1941
3619
|
return null;
|
|
1942
3620
|
}
|
|
1943
|
-
const
|
|
1944
|
-
|
|
3621
|
+
const split = this.splitParagraphAtLargestFit(block.element, (head) => {
|
|
3622
|
+
const measured = this.measureElement(head, dims, block.sectionIndex);
|
|
3623
|
+
return effectiveMarginTopPt + measured.heightPt <= availableHeightPt;
|
|
3624
|
+
});
|
|
3625
|
+
if (split.kind !== "split") return null;
|
|
3626
|
+
const headMeasured = this.measureElement(split.head, dims, block.sectionIndex);
|
|
3627
|
+
const tailMeasured = this.measureElement(split.tail, dims, block.sectionIndex);
|
|
1945
3628
|
return [
|
|
1946
3629
|
{
|
|
1947
|
-
...
|
|
1948
|
-
element:
|
|
3630
|
+
...headMeasured,
|
|
3631
|
+
element: split.head,
|
|
1949
3632
|
keepWithNext: false,
|
|
1950
3633
|
keepLines: false,
|
|
1951
3634
|
pageBreakBefore: false,
|
|
@@ -1953,7 +3636,7 @@ var PaginationEngine = class {
|
|
|
1953
3636
|
},
|
|
1954
3637
|
{
|
|
1955
3638
|
...tailMeasured,
|
|
1956
|
-
element: tail,
|
|
3639
|
+
element: split.tail,
|
|
1957
3640
|
keepWithNext: false,
|
|
1958
3641
|
keepLines: false,
|
|
1959
3642
|
pageBreakBefore: false,
|
|
@@ -2024,7 +3707,7 @@ var PaginationEngine = class {
|
|
|
2024
3707
|
const registry = /* @__PURE__ */ new Map();
|
|
2025
3708
|
const registryEl = this.stagingElement.querySelector("#pagination-footnote-registry");
|
|
2026
3709
|
if (!registryEl) return registry;
|
|
2027
|
-
const entries = Array.from(registryEl.
|
|
3710
|
+
const entries = Array.from(registryEl.children).filter((entry) => entry instanceof this.view.HTMLElement && entry.hasAttribute("data-footnote-id"));
|
|
2028
3711
|
for (const entry of entries) {
|
|
2029
3712
|
const footnoteId = entry.dataset.footnoteId;
|
|
2030
3713
|
if (footnoteId) {
|
|
@@ -2033,6 +3716,68 @@ var PaginationEngine = class {
|
|
|
2033
3716
|
}
|
|
2034
3717
|
return registry;
|
|
2035
3718
|
}
|
|
3719
|
+
/** Read Word's optional normal and continuation separator stories. */
|
|
3720
|
+
parseFootnoteSeparators() {
|
|
3721
|
+
const registry = this.stagingElement.querySelector("#pagination-footnote-registry");
|
|
3722
|
+
const clone = (kind) => {
|
|
3723
|
+
const source = Array.from(registry?.children ?? []).find(
|
|
3724
|
+
(entry) => entry instanceof this.view.HTMLElement && entry.getAttribute("data-footnote-separator") === kind
|
|
3725
|
+
);
|
|
3726
|
+
return source?.cloneNode(true);
|
|
3727
|
+
};
|
|
3728
|
+
return {
|
|
3729
|
+
normal: clone("normal") ?? null,
|
|
3730
|
+
continuation: clone("continuation") ?? null
|
|
3731
|
+
};
|
|
3732
|
+
}
|
|
3733
|
+
/** Append the exact separator story selected for this initial/continued note band. */
|
|
3734
|
+
appendFootnoteSeparator(container, continuation) {
|
|
3735
|
+
const kind = continuation ? "continuation" : "normal";
|
|
3736
|
+
const source = continuation ? this.footnoteContinuationSeparator : this.footnoteSeparator;
|
|
3737
|
+
if (!source) {
|
|
3738
|
+
const fallback = this.document.createElement("hr");
|
|
3739
|
+
fallback.dataset.footnoteSeparator = kind;
|
|
3740
|
+
container.appendChild(fallback);
|
|
3741
|
+
return;
|
|
3742
|
+
}
|
|
3743
|
+
const clone = source.cloneNode(true);
|
|
3744
|
+
for (const element of [clone, ...Array.from(clone.querySelectorAll("*"))]) {
|
|
3745
|
+
element.removeAttribute("id");
|
|
3746
|
+
element.removeAttribute("data-anchor");
|
|
3747
|
+
element.removeAttribute("data-committed-text");
|
|
3748
|
+
element.removeAttribute("data-source-anchor-id");
|
|
3749
|
+
element.removeAttribute("data-page-fragment-id");
|
|
3750
|
+
element.removeAttribute("data-fragment-index");
|
|
3751
|
+
element.removeAttribute("data-footnote-id");
|
|
3752
|
+
element.removeAttribute("data-comment-id");
|
|
3753
|
+
element.removeAttribute("name");
|
|
3754
|
+
if (element instanceof this.view.HTMLAnchorElement && element.getAttribute("href")?.startsWith("#")) {
|
|
3755
|
+
element.removeAttribute("href");
|
|
3756
|
+
}
|
|
3757
|
+
element.setAttribute("contenteditable", "false");
|
|
3758
|
+
}
|
|
3759
|
+
container.appendChild(clone);
|
|
3760
|
+
}
|
|
3761
|
+
/** Parses the hidden source notes used to render paginated margin comments. */
|
|
3762
|
+
parseCommentMarginRegistry() {
|
|
3763
|
+
const registry = /* @__PURE__ */ new Map();
|
|
3764
|
+
const registryEl = this.stagingElement.querySelector(
|
|
3765
|
+
"#pagination-comment-margin-registry"
|
|
3766
|
+
);
|
|
3767
|
+
if (!registryEl) return registry;
|
|
3768
|
+
for (const entry of Array.from(
|
|
3769
|
+
registryEl.querySelectorAll("[data-comment-id]")
|
|
3770
|
+
)) {
|
|
3771
|
+
const commentId = entry.dataset.commentId;
|
|
3772
|
+
if (!commentId) continue;
|
|
3773
|
+
let root = entry;
|
|
3774
|
+
for (let parent = root.parentElement?.closest("[data-comment-id]"); parent && registryEl.contains(parent); parent = root.parentElement?.closest("[data-comment-id]")) {
|
|
3775
|
+
root = parent;
|
|
3776
|
+
}
|
|
3777
|
+
registry.set(commentId, root.cloneNode(true));
|
|
3778
|
+
}
|
|
3779
|
+
return registry;
|
|
3780
|
+
}
|
|
2036
3781
|
/**
|
|
2037
3782
|
* Extracts footnote reference IDs from an element.
|
|
2038
3783
|
*/
|
|
@@ -2047,6 +3792,92 @@ var PaginationEngine = class {
|
|
|
2047
3792
|
}
|
|
2048
3793
|
return ids;
|
|
2049
3794
|
}
|
|
3795
|
+
/** Clone a note child for the continuation queue with its original selector position. */
|
|
3796
|
+
cloneFootnoteElementForContinuation(element, sourceIndex) {
|
|
3797
|
+
const clone = element.cloneNode(true);
|
|
3798
|
+
clone.setAttribute(
|
|
3799
|
+
FOOTNOTE_SOURCE_POSITION_ATTR,
|
|
3800
|
+
sourceIndex === 0 ? "first" : "later"
|
|
3801
|
+
);
|
|
3802
|
+
return clone;
|
|
3803
|
+
}
|
|
3804
|
+
/** Remove identities that belong only to the initial registry presentation shell. */
|
|
3805
|
+
makeContinuationShellInert(element) {
|
|
3806
|
+
element.removeAttribute("id");
|
|
3807
|
+
element.removeAttribute("data-anchor");
|
|
3808
|
+
element.removeAttribute("data-committed-text");
|
|
3809
|
+
}
|
|
3810
|
+
/**
|
|
3811
|
+
* Build the exact continuation shape shared by measurement and paint.
|
|
3812
|
+
*
|
|
3813
|
+
* Keep the source `.footnote-item > .footnote-content` ancestry: document
|
|
3814
|
+
* CSS, inherited direction/language, and custom classes frequently target
|
|
3815
|
+
* those shells. Only the number and initial HTML/editor identities are
|
|
3816
|
+
* omitted. A hidden sentinel preserves `p:not(:first-of-type)` for a page
|
|
3817
|
+
* whose first carried element was a later source paragraph.
|
|
3818
|
+
*/
|
|
3819
|
+
createFootnoteContinuationWrapper(continuation, elements = continuation.remainingElements) {
|
|
3820
|
+
const source = this.footnoteRegistry.get(continuation.footnoteId);
|
|
3821
|
+
const wrapper = source ? source.cloneNode(false) : this.document.createElement("div");
|
|
3822
|
+
this.makeContinuationShellInert(wrapper);
|
|
3823
|
+
wrapper.classList.add("footnote-continuation");
|
|
3824
|
+
wrapper.dataset.footnoteId = continuation.footnoteId;
|
|
3825
|
+
if (continuation.sourceAnchorId) {
|
|
3826
|
+
wrapper.dataset.sourceAnchorId = continuation.sourceAnchorId;
|
|
3827
|
+
}
|
|
3828
|
+
const sourceContent = source?.querySelector(".footnote-content");
|
|
3829
|
+
const content = sourceContent ? sourceContent.cloneNode(false) : this.document.createElement("span");
|
|
3830
|
+
this.makeContinuationShellInert(content);
|
|
3831
|
+
if (!sourceContent) content.className = "footnote-content";
|
|
3832
|
+
const first = elements[0];
|
|
3833
|
+
if (first?.tagName === "P" && first.getAttribute(FOOTNOTE_SOURCE_POSITION_ATTR) === "later") {
|
|
3834
|
+
const sentinel = this.document.createElement("p");
|
|
3835
|
+
sentinel.className = "footnote-continuation-position-sentinel";
|
|
3836
|
+
sentinel.setAttribute("aria-hidden", "true");
|
|
3837
|
+
sentinel.style.setProperty("display", "none", "important");
|
|
3838
|
+
content.appendChild(sentinel);
|
|
3839
|
+
}
|
|
3840
|
+
for (let index = 0; index < elements.length; index++) {
|
|
3841
|
+
this.checkpoint();
|
|
3842
|
+
const element = elements[index];
|
|
3843
|
+
const clone = element.cloneNode(true);
|
|
3844
|
+
clone.removeAttribute(FOOTNOTE_SOURCE_POSITION_ATTR);
|
|
3845
|
+
if (index === 0 && clone.tagName === "P") {
|
|
3846
|
+
clone.style.setProperty("display", "block", "important");
|
|
3847
|
+
}
|
|
3848
|
+
content.appendChild(clone);
|
|
3849
|
+
}
|
|
3850
|
+
wrapper.appendChild(content);
|
|
3851
|
+
return wrapper;
|
|
3852
|
+
}
|
|
3853
|
+
/** Build the exact partial-note item shape shared by measurement and paint. */
|
|
3854
|
+
createPartialFootnoteItem(footnote, fittingElements) {
|
|
3855
|
+
const item = footnote.cloneNode(false);
|
|
3856
|
+
const number = footnote.querySelector(".footnote-number");
|
|
3857
|
+
if (number) item.appendChild(number.cloneNode(true));
|
|
3858
|
+
const sourceContent = footnote.querySelector(".footnote-content");
|
|
3859
|
+
const content = sourceContent ? sourceContent.cloneNode(false) : this.document.createElement("span");
|
|
3860
|
+
if (!sourceContent) content.className = "footnote-content";
|
|
3861
|
+
for (const element of fittingElements) {
|
|
3862
|
+
this.checkpoint();
|
|
3863
|
+
content.appendChild(element.cloneNode(true));
|
|
3864
|
+
}
|
|
3865
|
+
item.appendChild(content);
|
|
3866
|
+
return item;
|
|
3867
|
+
}
|
|
3868
|
+
/**
|
|
3869
|
+
* Where a note measurement tree is attached.
|
|
3870
|
+
*
|
|
3871
|
+
* A note band is reserved from a measurement and then painted; if the two
|
|
3872
|
+
* happen under different inherited typography the painted notes overflow the
|
|
3873
|
+
* reserve, which is the invisible clipping this engine exists to avoid. The
|
|
3874
|
+
* registry lives in the staging tree but a band paints inside the output
|
|
3875
|
+
* container, so measure there. The host is never a page box, so a page's
|
|
3876
|
+
* `zoom` cannot scale a reserve that is accounted for in unscaled points.
|
|
3877
|
+
*/
|
|
3878
|
+
noteMeasurementHost() {
|
|
3879
|
+
return this.containerElement.isConnected ? this.containerElement : this.stagingElement;
|
|
3880
|
+
}
|
|
2050
3881
|
/**
|
|
2051
3882
|
* Measures the height of footnotes for given IDs (in points).
|
|
2052
3883
|
* Creates a temporary container to measure the footnotes.
|
|
@@ -2054,140 +3885,246 @@ var PaginationEngine = class {
|
|
|
2054
3885
|
* @param contentWidth - Width for measurement
|
|
2055
3886
|
* @param continuation - Optional continuation content to include first
|
|
2056
3887
|
*/
|
|
2057
|
-
measureFootnotesHeight(footnoteIds, contentWidth, continuation) {
|
|
3888
|
+
measureFootnotesHeight(footnoteIds, contentWidth, continuation, partialFootnotes) {
|
|
2058
3889
|
const hasContinuation = continuation && continuation.remainingElements.length > 0;
|
|
2059
|
-
if (footnoteIds.length === 0 && !hasContinuation
|
|
3890
|
+
if (footnoteIds.length === 0 && !hasContinuation) {
|
|
2060
3891
|
return 0;
|
|
2061
3892
|
}
|
|
2062
|
-
|
|
3893
|
+
if (this.footnoteRegistry.size === 0 && !hasContinuation) {
|
|
3894
|
+
return 0;
|
|
3895
|
+
}
|
|
3896
|
+
const measureContainer = this.document.createElement("div");
|
|
2063
3897
|
measureContainer.style.position = "absolute";
|
|
2064
3898
|
measureContainer.style.visibility = "hidden";
|
|
2065
3899
|
measureContainer.style.width = `${contentWidth}pt`;
|
|
2066
3900
|
measureContainer.style.left = "-9999px";
|
|
2067
3901
|
measureContainer.className = this.cssPrefix + "footnotes";
|
|
2068
|
-
|
|
2069
|
-
measureContainer.appendChild(hr);
|
|
3902
|
+
this.appendFootnoteSeparator(measureContainer, Boolean(hasContinuation));
|
|
2070
3903
|
if (hasContinuation) {
|
|
2071
|
-
|
|
2072
|
-
contWrapper.className = "footnote-continuation";
|
|
2073
|
-
for (const el of continuation.remainingElements) {
|
|
2074
|
-
contWrapper.appendChild(el.cloneNode(true));
|
|
2075
|
-
}
|
|
2076
|
-
measureContainer.appendChild(contWrapper);
|
|
3904
|
+
measureContainer.appendChild(this.createFootnoteContinuationWrapper(continuation));
|
|
2077
3905
|
}
|
|
3906
|
+
const partialById = new Map(
|
|
3907
|
+
partialFootnotes?.map((partial) => [partial.footnoteId, partial]) ?? []
|
|
3908
|
+
);
|
|
2078
3909
|
for (const id of footnoteIds) {
|
|
3910
|
+
this.checkpoint();
|
|
2079
3911
|
const footnote = this.footnoteRegistry.get(id);
|
|
2080
3912
|
if (footnote) {
|
|
2081
|
-
|
|
3913
|
+
const partial = partialById.get(id);
|
|
3914
|
+
measureContainer.appendChild(partial ? this.createPartialFootnoteItem(footnote, partial.fittingElements) : footnote.cloneNode(true));
|
|
2082
3915
|
}
|
|
2083
3916
|
}
|
|
2084
|
-
this.
|
|
2085
|
-
|
|
2086
|
-
|
|
2087
|
-
|
|
2088
|
-
|
|
3917
|
+
this.noteMeasurementHost().appendChild(measureContainer);
|
|
3918
|
+
try {
|
|
3919
|
+
return pxToPt(measureContainer.getBoundingClientRect().height);
|
|
3920
|
+
} finally {
|
|
3921
|
+
measureContainer.remove();
|
|
3922
|
+
}
|
|
2089
3923
|
}
|
|
2090
3924
|
/**
|
|
2091
|
-
*
|
|
3925
|
+
* Partition a continuation for one page's note band, preferring complete children
|
|
3926
|
+
* and range-fragmenting an eligible paragraph when necessary. Always advances by
|
|
3927
|
+
* at least one element so an indivisible oversized paragraph follows the established
|
|
3928
|
+
* clipped fallback without trapping pagination in a loop.
|
|
2092
3929
|
*/
|
|
2093
|
-
|
|
2094
|
-
|
|
2095
|
-
|
|
2096
|
-
|
|
2097
|
-
const measureContainer = document.createElement("div");
|
|
3930
|
+
splitContinuationForPage(continuation, availableHeightPt, contentWidth) {
|
|
3931
|
+
const fitting = [];
|
|
3932
|
+
let remaining = [];
|
|
3933
|
+
const measureContainer = this.document.createElement("div");
|
|
2098
3934
|
measureContainer.style.position = "absolute";
|
|
2099
3935
|
measureContainer.style.visibility = "hidden";
|
|
2100
3936
|
measureContainer.style.width = `${contentWidth}pt`;
|
|
2101
3937
|
measureContainer.style.left = "-9999px";
|
|
2102
|
-
measureContainer.className = this.cssPrefix + "footnotes";
|
|
2103
|
-
|
|
2104
|
-
|
|
2105
|
-
|
|
2106
|
-
|
|
2107
|
-
|
|
2108
|
-
|
|
2109
|
-
|
|
2110
|
-
|
|
2111
|
-
|
|
2112
|
-
|
|
2113
|
-
|
|
2114
|
-
|
|
2115
|
-
|
|
2116
|
-
|
|
2117
|
-
|
|
2118
|
-
|
|
2119
|
-
|
|
2120
|
-
|
|
2121
|
-
|
|
2122
|
-
|
|
2123
|
-
|
|
2124
|
-
|
|
2125
|
-
|
|
2126
|
-
|
|
2127
|
-
|
|
2128
|
-
|
|
2129
|
-
|
|
2130
|
-
|
|
2131
|
-
|
|
2132
|
-
|
|
2133
|
-
|
|
2134
|
-
|
|
2135
|
-
|
|
2136
|
-
|
|
2137
|
-
|
|
2138
|
-
|
|
2139
|
-
|
|
2140
|
-
|
|
2141
|
-
|
|
2142
|
-
|
|
2143
|
-
|
|
2144
|
-
|
|
2145
|
-
|
|
2146
|
-
|
|
2147
|
-
|
|
2148
|
-
|
|
2149
|
-
|
|
2150
|
-
|
|
2151
|
-
|
|
2152
|
-
|
|
2153
|
-
|
|
2154
|
-
|
|
2155
|
-
|
|
2156
|
-
|
|
2157
|
-
|
|
2158
|
-
|
|
2159
|
-
|
|
2160
|
-
|
|
2161
|
-
|
|
2162
|
-
|
|
2163
|
-
|
|
2164
|
-
|
|
2165
|
-
|
|
2166
|
-
|
|
3938
|
+
measureContainer.className = this.cssPrefix + "footnotes";
|
|
3939
|
+
this.appendFootnoteSeparator(measureContainer, true);
|
|
3940
|
+
const wrapper = this.createFootnoteContinuationWrapper(continuation, []);
|
|
3941
|
+
const content = wrapper.querySelector(":scope > .footnote-content");
|
|
3942
|
+
if (!content) {
|
|
3943
|
+
throw new Error("Footnote continuation is missing its content shell");
|
|
3944
|
+
}
|
|
3945
|
+
measureContainer.appendChild(wrapper);
|
|
3946
|
+
this.noteMeasurementHost().appendChild(measureContainer);
|
|
3947
|
+
try {
|
|
3948
|
+
for (let index = 0; index < continuation.remainingElements.length; index++) {
|
|
3949
|
+
this.checkpoint();
|
|
3950
|
+
const element = continuation.remainingElements[index];
|
|
3951
|
+
const sourcePosition = element.getAttribute(FOOTNOTE_SOURCE_POSITION_ATTR);
|
|
3952
|
+
if (fitting.length === 0 && element.tagName === "P" && element.getAttribute(FOOTNOTE_SOURCE_POSITION_ATTR) === "later") {
|
|
3953
|
+
const sentinel = this.document.createElement("p");
|
|
3954
|
+
sentinel.className = "footnote-continuation-position-sentinel";
|
|
3955
|
+
sentinel.setAttribute("aria-hidden", "true");
|
|
3956
|
+
sentinel.style.setProperty("display", "none", "important");
|
|
3957
|
+
content.appendChild(sentinel);
|
|
3958
|
+
}
|
|
3959
|
+
const candidate = element.cloneNode(true);
|
|
3960
|
+
candidate.removeAttribute(FOOTNOTE_SOURCE_POSITION_ATTR);
|
|
3961
|
+
if (fitting.length === 0 && candidate.tagName === "P") {
|
|
3962
|
+
candidate.style.setProperty("display", "block", "important");
|
|
3963
|
+
}
|
|
3964
|
+
content.appendChild(candidate);
|
|
3965
|
+
const candidateHeight = pxToPt(measureContainer.getBoundingClientRect().height);
|
|
3966
|
+
if (candidateHeight <= availableHeightPt) {
|
|
3967
|
+
fitting.push(element);
|
|
3968
|
+
continue;
|
|
3969
|
+
}
|
|
3970
|
+
const canSplitCandidate = candidate.tagName === "P" && this.canRangeFragmentParagraph(candidate);
|
|
3971
|
+
candidate.remove();
|
|
3972
|
+
let split = null;
|
|
3973
|
+
if (canSplitCandidate) {
|
|
3974
|
+
split = this.splitParagraphAtLargestFit(candidate, (head) => {
|
|
3975
|
+
content.appendChild(head);
|
|
3976
|
+
try {
|
|
3977
|
+
return pxToPt(measureContainer.getBoundingClientRect().height) <= availableHeightPt;
|
|
3978
|
+
} finally {
|
|
3979
|
+
head.remove();
|
|
3980
|
+
}
|
|
3981
|
+
}, {
|
|
3982
|
+
// Both fallbacks below cut where ordinary line breaking would not,
|
|
3983
|
+
// so they are only ever right for an element that owns the whole
|
|
3984
|
+
// band: if this page already carries earlier content, a paragraph
|
|
3985
|
+
// that will not start here belongs intact in the next note band.
|
|
3986
|
+
emergencyGraphemeBreaks: fitting.length === 0,
|
|
3987
|
+
// A continuation page owns the full note band. If even one legal
|
|
3988
|
+
// grapheme is taller than it, clip only that unit and keep draining.
|
|
3989
|
+
forceFirstOnNoFit: fitting.length === 0
|
|
3990
|
+
});
|
|
3991
|
+
}
|
|
3992
|
+
if (split?.kind === "split") {
|
|
3993
|
+
if (sourcePosition) {
|
|
3994
|
+
split.head.setAttribute(FOOTNOTE_SOURCE_POSITION_ATTR, sourcePosition);
|
|
3995
|
+
split.tail.setAttribute(FOOTNOTE_SOURCE_POSITION_ATTR, sourcePosition);
|
|
3996
|
+
}
|
|
3997
|
+
fitting.push(split.head);
|
|
3998
|
+
remaining = [split.tail, ...continuation.remainingElements.slice(index + 1)];
|
|
3999
|
+
} else if (fitting.length === 0) {
|
|
4000
|
+
fitting.push(element);
|
|
4001
|
+
remaining = continuation.remainingElements.slice(index + 1);
|
|
4002
|
+
} else {
|
|
4003
|
+
remaining = continuation.remainingElements.slice(index);
|
|
2167
4004
|
}
|
|
2168
4005
|
break;
|
|
2169
4006
|
}
|
|
4007
|
+
} finally {
|
|
4008
|
+
measureContainer.remove();
|
|
2170
4009
|
}
|
|
2171
|
-
return {
|
|
4010
|
+
return {
|
|
4011
|
+
current: {
|
|
4012
|
+
footnoteId: continuation.footnoteId,
|
|
4013
|
+
sourceAnchorId: continuation.sourceAnchorId,
|
|
4014
|
+
remainingElements: fitting
|
|
4015
|
+
},
|
|
4016
|
+
overflow: remaining.length > 0 ? {
|
|
4017
|
+
footnoteId: continuation.footnoteId,
|
|
4018
|
+
sourceAnchorId: continuation.sourceAnchorId,
|
|
4019
|
+
remainingElements: remaining
|
|
4020
|
+
} : null
|
|
4021
|
+
};
|
|
2172
4022
|
}
|
|
2173
4023
|
/**
|
|
2174
|
-
*
|
|
4024
|
+
* Splits a footnote element into parts that fit within the available height.
|
|
4025
|
+
* Returns the elements that fit and the elements that need to continue.
|
|
2175
4026
|
*/
|
|
2176
|
-
|
|
2177
|
-
const
|
|
2178
|
-
|
|
2179
|
-
|
|
4027
|
+
splitFootnoteToFit(footnoteElement, availableHeightPt, contentWidth, forceProgress = false, existingPayload) {
|
|
4028
|
+
const layoutContext = this.document.createElement("div");
|
|
4029
|
+
layoutContext.style.position = "absolute";
|
|
4030
|
+
layoutContext.style.visibility = "hidden";
|
|
4031
|
+
layoutContext.style.width = `${contentWidth}pt`;
|
|
4032
|
+
layoutContext.style.left = "-9999px";
|
|
4033
|
+
layoutContext.className = this.cssPrefix + "footnotes";
|
|
4034
|
+
this.appendFootnoteSeparator(layoutContext, false);
|
|
4035
|
+
const attachedFootnote = footnoteElement.cloneNode(true);
|
|
4036
|
+
layoutContext.appendChild(attachedFootnote);
|
|
4037
|
+
this.noteMeasurementHost().appendChild(layoutContext);
|
|
4038
|
+
const measureContainer = this.document.createElement("div");
|
|
2180
4039
|
measureContainer.style.position = "absolute";
|
|
2181
4040
|
measureContainer.style.visibility = "hidden";
|
|
2182
4041
|
measureContainer.style.width = `${contentWidth}pt`;
|
|
2183
4042
|
measureContainer.style.left = "-9999px";
|
|
2184
4043
|
measureContainer.className = this.cssPrefix + "footnotes";
|
|
2185
|
-
|
|
2186
|
-
this.
|
|
2187
|
-
|
|
2188
|
-
|
|
2189
|
-
|
|
2190
|
-
|
|
4044
|
+
const hasContinuation = Boolean(existingPayload?.continuation?.remainingElements.length);
|
|
4045
|
+
this.appendFootnoteSeparator(measureContainer, hasContinuation);
|
|
4046
|
+
if (hasContinuation) {
|
|
4047
|
+
measureContainer.appendChild(this.createFootnoteContinuationWrapper(
|
|
4048
|
+
existingPayload.continuation
|
|
4049
|
+
));
|
|
4050
|
+
}
|
|
4051
|
+
const partialById = new Map(
|
|
4052
|
+
existingPayload?.partialFootnotes?.map((partial) => [partial.footnoteId, partial]) ?? []
|
|
4053
|
+
);
|
|
4054
|
+
for (const id of existingPayload?.footnoteIds ?? []) {
|
|
4055
|
+
this.checkpoint();
|
|
4056
|
+
const source = this.footnoteRegistry.get(id);
|
|
4057
|
+
if (!source) continue;
|
|
4058
|
+
const partial = partialById.get(id);
|
|
4059
|
+
measureContainer.appendChild(partial ? this.createPartialFootnoteItem(source, partial.fittingElements) : source.cloneNode(true));
|
|
4060
|
+
}
|
|
4061
|
+
const measuredPartial = this.createPartialFootnoteItem(attachedFootnote, []);
|
|
4062
|
+
const measuredContent = measuredPartial.querySelector(":scope > .footnote-content");
|
|
4063
|
+
if (!measuredContent) {
|
|
4064
|
+
layoutContext.remove();
|
|
4065
|
+
throw new Error("Footnote is missing its content shell");
|
|
4066
|
+
}
|
|
4067
|
+
measureContainer.appendChild(measuredPartial);
|
|
4068
|
+
this.noteMeasurementHost().appendChild(measureContainer);
|
|
4069
|
+
try {
|
|
4070
|
+
const footnoteContent = attachedFootnote.querySelector(".footnote-content");
|
|
4071
|
+
if (!footnoteContent) {
|
|
4072
|
+
return {
|
|
4073
|
+
fits: Array.from(attachedFootnote.children).map((el) => el.cloneNode(true)),
|
|
4074
|
+
overflow: []
|
|
4075
|
+
};
|
|
4076
|
+
}
|
|
4077
|
+
const children = Array.from(footnoteContent.children);
|
|
4078
|
+
const fits = [];
|
|
4079
|
+
let overflow = [];
|
|
4080
|
+
for (let i = 0; i < children.length; i++) {
|
|
4081
|
+
this.checkpoint();
|
|
4082
|
+
const child = children[i];
|
|
4083
|
+
const measuredCandidate = child.cloneNode(true);
|
|
4084
|
+
measuredContent.appendChild(measuredCandidate);
|
|
4085
|
+
const candidateFits = pxToPt(measureContainer.getBoundingClientRect().height) <= availableHeightPt;
|
|
4086
|
+
if (candidateFits) {
|
|
4087
|
+
fits.push(child.cloneNode(true));
|
|
4088
|
+
continue;
|
|
4089
|
+
}
|
|
4090
|
+
measuredCandidate.remove();
|
|
4091
|
+
const split = this.canRangeFragmentParagraph(child, true) ? this.splitParagraphAtLargestFit(child, (head) => {
|
|
4092
|
+
measuredContent.appendChild(head);
|
|
4093
|
+
try {
|
|
4094
|
+
return pxToPt(measureContainer.getBoundingClientRect().height) <= availableHeightPt;
|
|
4095
|
+
} finally {
|
|
4096
|
+
head.remove();
|
|
4097
|
+
}
|
|
4098
|
+
}, {
|
|
4099
|
+
// Both fallbacks cut where ordinary line breaking would not, so they
|
|
4100
|
+
// are only ever right for an element that owns the whole band: with
|
|
4101
|
+
// earlier siblings already packed here, a paragraph that will not
|
|
4102
|
+
// start belongs intact in the next note band.
|
|
4103
|
+
emergencyGraphemeBreaks: fits.length === 0 && forceProgress,
|
|
4104
|
+
forceFirstOnNoFit: fits.length === 0 && forceProgress
|
|
4105
|
+
}) : null;
|
|
4106
|
+
if (split?.kind === "split") {
|
|
4107
|
+
fits.push(split.head);
|
|
4108
|
+
split.tail.setAttribute(
|
|
4109
|
+
FOOTNOTE_SOURCE_POSITION_ATTR,
|
|
4110
|
+
i === 0 ? "first" : "later"
|
|
4111
|
+
);
|
|
4112
|
+
overflow = [split.tail, ...children.slice(i + 1).map((element, offset) => this.cloneFootnoteElementForContinuation(element, i + 1 + offset))];
|
|
4113
|
+
} else if (split?.kind === "no-fit") {
|
|
4114
|
+
overflow = children.slice(i).map((element, offset) => this.cloneFootnoteElementForContinuation(element, i + offset));
|
|
4115
|
+
} else if (fits.length === 0 && forceProgress) {
|
|
4116
|
+
fits.push(child.cloneNode(true));
|
|
4117
|
+
overflow = children.slice(i + 1).map((element, offset) => this.cloneFootnoteElementForContinuation(element, i + 1 + offset));
|
|
4118
|
+
} else {
|
|
4119
|
+
overflow = children.slice(i).map((element, offset) => this.cloneFootnoteElementForContinuation(element, i + offset));
|
|
4120
|
+
}
|
|
4121
|
+
break;
|
|
4122
|
+
}
|
|
4123
|
+
return { fits, overflow };
|
|
4124
|
+
} finally {
|
|
4125
|
+
measureContainer.remove();
|
|
4126
|
+
layoutContext.remove();
|
|
4127
|
+
}
|
|
2191
4128
|
}
|
|
2192
4129
|
/**
|
|
2193
4130
|
* Adds footnotes to a page container, including continuation content.
|
|
@@ -2200,12 +4137,11 @@ var PaginationEngine = class {
|
|
|
2200
4137
|
if (this.footnoteRegistry.size === 0 && !hasContinuation) {
|
|
2201
4138
|
return;
|
|
2202
4139
|
}
|
|
2203
|
-
const partialFootnoteIds = new Set(partialFootnotes?.map((p) => p.footnoteId) || []);
|
|
2204
4140
|
const maxFootnoteHeight = Math.min(
|
|
2205
4141
|
footnoteHeight,
|
|
2206
4142
|
bands.bodyHeight * MAX_FOOTNOTE_AREA_RATIO
|
|
2207
4143
|
);
|
|
2208
|
-
const footnotesDiv = document.createElement("div");
|
|
4144
|
+
const footnotesDiv = this.document.createElement("div");
|
|
2209
4145
|
footnotesDiv.className = `${this.cssPrefix}footnotes`;
|
|
2210
4146
|
footnotesDiv.style.position = "absolute";
|
|
2211
4147
|
footnotesDiv.style.bottom = `${dims.pageHeight - (bands.bodyTop + bands.bodyHeight)}pt`;
|
|
@@ -2214,35 +4150,18 @@ var PaginationEngine = class {
|
|
|
2214
4150
|
footnotesDiv.style.boxSizing = "border-box";
|
|
2215
4151
|
footnotesDiv.style.maxHeight = `${maxFootnoteHeight}pt`;
|
|
2216
4152
|
footnotesDiv.style.overflow = "hidden";
|
|
2217
|
-
|
|
2218
|
-
footnotesDiv.appendChild(hr);
|
|
4153
|
+
this.appendFootnoteSeparator(footnotesDiv, Boolean(hasContinuation));
|
|
2219
4154
|
if (hasContinuation) {
|
|
2220
|
-
|
|
2221
|
-
contWrapper.className = "footnote-continuation";
|
|
2222
|
-
for (const el of continuation.remainingElements) {
|
|
2223
|
-
contWrapper.appendChild(el.cloneNode(true));
|
|
2224
|
-
}
|
|
2225
|
-
footnotesDiv.appendChild(contWrapper);
|
|
4155
|
+
footnotesDiv.appendChild(this.createFootnoteContinuationWrapper(continuation));
|
|
2226
4156
|
}
|
|
2227
4157
|
for (const id of footnoteIds) {
|
|
2228
4158
|
const partial = partialFootnotes?.find((p) => p.footnoteId === id);
|
|
2229
4159
|
if (partial) {
|
|
2230
4160
|
const footnote = this.footnoteRegistry.get(id);
|
|
2231
4161
|
if (footnote) {
|
|
2232
|
-
|
|
2233
|
-
|
|
2234
|
-
|
|
2235
|
-
const numberSpan = footnote.querySelector(".footnote-number");
|
|
2236
|
-
if (numberSpan) {
|
|
2237
|
-
partialDiv.appendChild(numberSpan.cloneNode(true));
|
|
2238
|
-
}
|
|
2239
|
-
const contentSpan = document.createElement("span");
|
|
2240
|
-
contentSpan.className = "footnote-content";
|
|
2241
|
-
for (const el of partial.fittingElements) {
|
|
2242
|
-
contentSpan.appendChild(el.cloneNode(true));
|
|
2243
|
-
}
|
|
2244
|
-
partialDiv.appendChild(contentSpan);
|
|
2245
|
-
footnotesDiv.appendChild(partialDiv);
|
|
4162
|
+
footnotesDiv.appendChild(
|
|
4163
|
+
this.createPartialFootnoteItem(footnote, partial.fittingElements)
|
|
4164
|
+
);
|
|
2246
4165
|
}
|
|
2247
4166
|
} else {
|
|
2248
4167
|
const footnote = this.footnoteRegistry.get(id);
|
|
@@ -2253,30 +4172,26 @@ var PaginationEngine = class {
|
|
|
2253
4172
|
}
|
|
2254
4173
|
pageBox.appendChild(footnotesDiv);
|
|
2255
4174
|
}
|
|
2256
|
-
/**
|
|
2257
|
-
|
|
2258
|
-
*/
|
|
2259
|
-
selectHeader(sectionIndex, pageInSection, globalPageNumber) {
|
|
4175
|
+
/** Select the section's first/odd/even header from its one-based page position. */
|
|
4176
|
+
selectHeader(sectionIndex, pageInSection, displayedPageNumber) {
|
|
2260
4177
|
const sectionHf = this.hfRegistry.get(sectionIndex);
|
|
2261
4178
|
if (!sectionHf) return void 0;
|
|
2262
4179
|
if (pageInSection === 1 && sectionHf.headerFirst) {
|
|
2263
4180
|
return sectionHf.headerFirst;
|
|
2264
4181
|
}
|
|
2265
|
-
if (
|
|
4182
|
+
if (displayedPageNumber % 2 === 0 && sectionHf.headerEven) {
|
|
2266
4183
|
return sectionHf.headerEven;
|
|
2267
4184
|
}
|
|
2268
4185
|
return sectionHf.headerDefault;
|
|
2269
4186
|
}
|
|
2270
|
-
/**
|
|
2271
|
-
|
|
2272
|
-
*/
|
|
2273
|
-
selectFooter(sectionIndex, pageInSection, globalPageNumber) {
|
|
4187
|
+
/** Select the section's first/odd/even footer from its one-based page position. */
|
|
4188
|
+
selectFooter(sectionIndex, pageInSection, displayedPageNumber) {
|
|
2274
4189
|
const sectionHf = this.hfRegistry.get(sectionIndex);
|
|
2275
4190
|
if (!sectionHf) return void 0;
|
|
2276
4191
|
if (pageInSection === 1 && sectionHf.footerFirst) {
|
|
2277
4192
|
return sectionHf.footerFirst;
|
|
2278
4193
|
}
|
|
2279
|
-
if (
|
|
4194
|
+
if (displayedPageNumber % 2 === 0 && sectionHf.footerEven) {
|
|
2280
4195
|
return sectionHf.footerEven;
|
|
2281
4196
|
}
|
|
2282
4197
|
return sectionHf.footerDefault;
|
|
@@ -2291,7 +4206,7 @@ var PaginationEngine = class {
|
|
|
2291
4206
|
* Deterministic: it depends only on the section's page setup and the registry's pre-measured
|
|
2292
4207
|
* story heights, never on the page's content, which is what keeps it lazy-loading compatible.
|
|
2293
4208
|
*/
|
|
2294
|
-
getPageBands(dims, sectionIndex, pageInSection,
|
|
4209
|
+
getPageBands(dims, sectionIndex, pageInSection, displayedPageNumber) {
|
|
2295
4210
|
const sectionHf = this.hfRegistry.get(sectionIndex);
|
|
2296
4211
|
return resolvePageBands(
|
|
2297
4212
|
dims,
|
|
@@ -2300,14 +4215,14 @@ var PaginationEngine = class {
|
|
|
2300
4215
|
sectionHf?.headerEvenHeight,
|
|
2301
4216
|
sectionHf?.headerDefaultHeight,
|
|
2302
4217
|
pageInSection,
|
|
2303
|
-
|
|
4218
|
+
displayedPageNumber
|
|
2304
4219
|
),
|
|
2305
4220
|
this.selectStoryHeight(
|
|
2306
4221
|
sectionHf?.footerFirstHeight,
|
|
2307
4222
|
sectionHf?.footerEvenHeight,
|
|
2308
4223
|
sectionHf?.footerDefaultHeight,
|
|
2309
4224
|
pageInSection,
|
|
2310
|
-
|
|
4225
|
+
displayedPageNumber
|
|
2311
4226
|
)
|
|
2312
4227
|
);
|
|
2313
4228
|
}
|
|
@@ -2315,9 +4230,9 @@ var PaginationEngine = class {
|
|
|
2315
4230
|
* The measured height of the running story this page position selects, mirroring
|
|
2316
4231
|
* {@link selectHeader}/{@link selectFooter}. Zero when the page has no such story.
|
|
2317
4232
|
*/
|
|
2318
|
-
selectStoryHeight(first, even, fallback, pageInSection,
|
|
4233
|
+
selectStoryHeight(first, even, fallback, pageInSection, displayedPageNumber) {
|
|
2319
4234
|
if (pageInSection === 1 && first != null) return first;
|
|
2320
|
-
if (
|
|
4235
|
+
if (displayedPageNumber % 2 === 0 && even != null) return even;
|
|
2321
4236
|
return fallback ?? 0;
|
|
2322
4237
|
}
|
|
2323
4238
|
/**
|
|
@@ -2329,7 +4244,7 @@ var PaginationEngine = class {
|
|
|
2329
4244
|
* silently starts overlapping body text.
|
|
2330
4245
|
*/
|
|
2331
4246
|
measureHeaderFooterHeight(source, contentWidth) {
|
|
2332
|
-
const measureContainer = document.createElement("div");
|
|
4247
|
+
const measureContainer = this.document.createElement("div");
|
|
2333
4248
|
measureContainer.style.position = "absolute";
|
|
2334
4249
|
measureContainer.style.visibility = "hidden";
|
|
2335
4250
|
measureContainer.style.width = `${contentWidth}pt`;
|
|
@@ -2348,79 +4263,275 @@ var PaginationEngine = class {
|
|
|
2348
4263
|
* Implements a single-pass, forward-only algorithm that is compatible with future lazy loading.
|
|
2349
4264
|
* Supports footnote continuation - long footnotes can split across pages.
|
|
2350
4265
|
*/
|
|
2351
|
-
flowToPages(blocks, dims, startPageNumber, sectionIndex) {
|
|
4266
|
+
flowToPages(blocks, dims, startPageNumber, sectionIndex, sectionDimensions) {
|
|
2352
4267
|
const pages = [];
|
|
2353
4268
|
let currentContent = [];
|
|
2354
4269
|
let pageNumber = startPageNumber;
|
|
2355
|
-
let
|
|
4270
|
+
let pageSectionIndex = sectionIndex;
|
|
4271
|
+
const sectionStartPages = /* @__PURE__ */ new Map([[sectionIndex, startPageNumber]]);
|
|
4272
|
+
const pageInSection = (owner = pageSectionIndex, physicalPage = pageNumber) => physicalPage - (sectionStartPages.get(owner) ?? physicalPage) + 1;
|
|
4273
|
+
const dimensionsFor = (owner = pageSectionIndex) => sectionDimensions.get(owner) ?? dims;
|
|
4274
|
+
const markSectionPlaced = (owner) => {
|
|
4275
|
+
if (!sectionStartPages.has(owner)) sectionStartPages.set(owner, pageNumber);
|
|
4276
|
+
};
|
|
4277
|
+
const previousBox = this.containerElement.querySelector(
|
|
4278
|
+
`.${this.cssPrefix}box:last-of-type`
|
|
4279
|
+
);
|
|
4280
|
+
let precedingDisplayedPageNumber = parseInt(
|
|
4281
|
+
previousBox?.dataset.displayedPageNumber ?? "0",
|
|
4282
|
+
10
|
|
4283
|
+
);
|
|
4284
|
+
const displayedPageNumber = (owner = pageSectionIndex, ownerPageInSection = pageInSection(owner), physicalPage = pageNumber) => {
|
|
4285
|
+
const numbering = this.pageNumbering.get(owner) ?? {};
|
|
4286
|
+
if (numbering.start !== void 0) {
|
|
4287
|
+
return numbering.start + ownerPageInSection - 1;
|
|
4288
|
+
}
|
|
4289
|
+
const ownerNumbering = this.pageNumbering.get(pageSectionIndex) ?? {};
|
|
4290
|
+
const currentPhysicalNumber = ownerNumbering.start !== void 0 ? ownerNumbering.start + pageInSection(pageSectionIndex) - 1 : precedingDisplayedPageNumber + 1;
|
|
4291
|
+
return currentPhysicalNumber + physicalPage - pageNumber;
|
|
4292
|
+
};
|
|
2356
4293
|
let { bodyHeight: effectiveContentHeight } = this.getPageBands(
|
|
2357
|
-
|
|
2358
|
-
|
|
2359
|
-
pageInSection,
|
|
2360
|
-
|
|
4294
|
+
dimensionsFor(),
|
|
4295
|
+
pageSectionIndex,
|
|
4296
|
+
pageInSection(),
|
|
4297
|
+
displayedPageNumber()
|
|
2361
4298
|
);
|
|
2362
4299
|
let remainingHeight = effectiveContentHeight;
|
|
2363
4300
|
let prevMarginBottomPt = 0;
|
|
2364
4301
|
let currentFootnoteIds = [];
|
|
4302
|
+
let currentPageHasFootnoteReference = false;
|
|
2365
4303
|
let currentFootnoteHeight = 0;
|
|
2366
4304
|
let currentContinuation = this.pendingFootnoteContinuation;
|
|
2367
4305
|
let nextPageContinuation = null;
|
|
4306
|
+
let currentContinuationPartitioned = false;
|
|
4307
|
+
let currentPageAdmitted = false;
|
|
2368
4308
|
let deferredFootnoteIds = [];
|
|
2369
4309
|
let currentPartialFootnotes = [];
|
|
2370
|
-
|
|
2371
|
-
|
|
2372
|
-
|
|
2373
|
-
|
|
4310
|
+
const adoptEmptyPageOwner = (owner) => {
|
|
4311
|
+
pageSectionIndex = owner;
|
|
4312
|
+
const bands = this.getPageBands(
|
|
4313
|
+
dimensionsFor(owner),
|
|
4314
|
+
owner,
|
|
4315
|
+
pageInSection(owner),
|
|
4316
|
+
displayedPageNumber(owner)
|
|
4317
|
+
);
|
|
4318
|
+
effectiveContentHeight = bands.bodyHeight;
|
|
4319
|
+
remainingHeight = effectiveContentHeight;
|
|
4320
|
+
};
|
|
4321
|
+
const admitCurrentPage = () => {
|
|
4322
|
+
if (currentPageAdmitted) return;
|
|
4323
|
+
this.admitPageAllocation();
|
|
4324
|
+
currentPageAdmitted = true;
|
|
4325
|
+
};
|
|
4326
|
+
const prepareCurrentContinuation = () => {
|
|
4327
|
+
if (currentContinuationPartitioned || (currentContinuation?.remainingElements.length ?? 0) === 0) return;
|
|
4328
|
+
admitCurrentPage();
|
|
4329
|
+
const ownedDimensions = dimensionsFor();
|
|
4330
|
+
const pageBands = this.getPageBands(
|
|
4331
|
+
ownedDimensions,
|
|
4332
|
+
pageSectionIndex,
|
|
4333
|
+
pageInSection(),
|
|
4334
|
+
displayedPageNumber()
|
|
4335
|
+
);
|
|
4336
|
+
const partition = this.splitContinuationForPage(
|
|
4337
|
+
currentContinuation,
|
|
4338
|
+
pageBands.bodyHeight * MAX_FOOTNOTE_AREA_RATIO,
|
|
4339
|
+
ownedDimensions.contentWidth
|
|
4340
|
+
);
|
|
4341
|
+
currentContinuation = partition.current;
|
|
4342
|
+
if (partition.overflow) nextPageContinuation = partition.overflow;
|
|
4343
|
+
currentContinuationPartitioned = true;
|
|
4344
|
+
currentFootnoteHeight = this.measureFootnotesHeight(
|
|
4345
|
+
currentFootnoteIds,
|
|
4346
|
+
ownedDimensions.contentWidth,
|
|
4347
|
+
currentContinuation,
|
|
4348
|
+
currentPartialFootnotes
|
|
4349
|
+
);
|
|
4350
|
+
};
|
|
4351
|
+
const finishPage = (nextPageSectionIndex = pageSectionIndex, forceEmptyPage = false) => {
|
|
2374
4352
|
const hasCurrentContinuation = (currentContinuation?.remainingElements.length ?? 0) > 0;
|
|
2375
|
-
if (currentContent.length === 0 && !hasCurrentContinuation)
|
|
4353
|
+
if (!forceEmptyPage && currentContent.length === 0 && currentFootnoteIds.length === 0 && !hasCurrentContinuation) {
|
|
4354
|
+
adoptEmptyPageOwner(nextPageSectionIndex);
|
|
4355
|
+
return;
|
|
4356
|
+
}
|
|
4357
|
+
prepareCurrentContinuation();
|
|
4358
|
+
admitCurrentPage();
|
|
4359
|
+
let pageContinuation = currentContinuation;
|
|
4360
|
+
const ownedPageInSection = pageInSection();
|
|
4361
|
+
const ownedDimensions = dimensionsFor();
|
|
4362
|
+
const ownedDisplayedPageNumber = displayedPageNumber(
|
|
4363
|
+
pageSectionIndex,
|
|
4364
|
+
ownedPageInSection
|
|
4365
|
+
);
|
|
4366
|
+
const pageBands = this.getPageBands(
|
|
4367
|
+
ownedDimensions,
|
|
4368
|
+
pageSectionIndex,
|
|
4369
|
+
ownedPageInSection,
|
|
4370
|
+
ownedDisplayedPageNumber
|
|
4371
|
+
);
|
|
4372
|
+
const maxFootnoteHeight = pageBands.bodyHeight * MAX_FOOTNOTE_AREA_RATIO;
|
|
4373
|
+
pageContinuation = currentContinuation;
|
|
4374
|
+
currentFootnoteHeight = this.measureFootnotesHeight(
|
|
4375
|
+
currentFootnoteIds,
|
|
4376
|
+
ownedDimensions.contentWidth,
|
|
4377
|
+
pageContinuation,
|
|
4378
|
+
currentPartialFootnotes
|
|
4379
|
+
);
|
|
4380
|
+
if (currentContent.length === 0 && currentFootnoteIds.length > 0 && currentPartialFootnotes.length === 0) {
|
|
4381
|
+
const fittingIds = [];
|
|
4382
|
+
for (let index = 0; index < currentFootnoteIds.length; index++) {
|
|
4383
|
+
const footnoteId = currentFootnoteIds[index];
|
|
4384
|
+
const candidateIds = [...fittingIds, footnoteId];
|
|
4385
|
+
const candidateHeight = this.measureFootnotesHeight(
|
|
4386
|
+
candidateIds,
|
|
4387
|
+
ownedDimensions.contentWidth,
|
|
4388
|
+
pageContinuation
|
|
4389
|
+
);
|
|
4390
|
+
const guardedCandidateHeight = candidateHeight + FOOTNOTE_MEASUREMENT_GUARD_PT;
|
|
4391
|
+
if (guardedCandidateHeight <= maxFootnoteHeight) {
|
|
4392
|
+
fittingIds.push(footnoteId);
|
|
4393
|
+
currentFootnoteHeight = guardedCandidateHeight;
|
|
4394
|
+
continue;
|
|
4395
|
+
}
|
|
4396
|
+
const hasPageContinuation = (pageContinuation?.remainingElements.length ?? 0) > 0;
|
|
4397
|
+
if (fittingIds.length === 0 && !hasPageContinuation) {
|
|
4398
|
+
const source = this.footnoteRegistry.get(footnoteId);
|
|
4399
|
+
const split = source ? this.splitFootnoteToFit(
|
|
4400
|
+
source,
|
|
4401
|
+
maxFootnoteHeight - FOOTNOTE_MEASUREMENT_GUARD_PT,
|
|
4402
|
+
ownedDimensions.contentWidth,
|
|
4403
|
+
true
|
|
4404
|
+
) : null;
|
|
4405
|
+
fittingIds.push(footnoteId);
|
|
4406
|
+
if (source && split && split.fits.length > 0 && split.overflow.length > 0) {
|
|
4407
|
+
currentPartialFootnotes.push({ footnoteId, fittingElements: split.fits });
|
|
4408
|
+
nextPageContinuation = {
|
|
4409
|
+
footnoteId,
|
|
4410
|
+
sourceAnchorId: source.dataset.sourceAnchorId,
|
|
4411
|
+
remainingElements: split.overflow
|
|
4412
|
+
};
|
|
4413
|
+
currentFootnoteHeight = maxFootnoteHeight;
|
|
4414
|
+
} else {
|
|
4415
|
+
currentFootnoteHeight = guardedCandidateHeight;
|
|
4416
|
+
}
|
|
4417
|
+
deferredFootnoteIds.push(...currentFootnoteIds.slice(index + 1));
|
|
4418
|
+
} else {
|
|
4419
|
+
deferredFootnoteIds.push(...currentFootnoteIds.slice(index));
|
|
4420
|
+
}
|
|
4421
|
+
break;
|
|
4422
|
+
}
|
|
4423
|
+
currentFootnoteIds = fittingIds;
|
|
4424
|
+
}
|
|
2376
4425
|
const page = this.createPage(
|
|
2377
|
-
|
|
4426
|
+
ownedDimensions,
|
|
2378
4427
|
pageNumber,
|
|
2379
|
-
|
|
4428
|
+
pageSectionIndex,
|
|
4429
|
+
ownedDisplayedPageNumber,
|
|
2380
4430
|
currentContent,
|
|
2381
|
-
|
|
4431
|
+
ownedPageInSection,
|
|
2382
4432
|
currentFootnoteIds,
|
|
2383
4433
|
currentFootnoteHeight,
|
|
2384
|
-
|
|
2385
|
-
currentPartialFootnotes.length > 0 ? currentPartialFootnotes : void 0
|
|
4434
|
+
pageContinuation,
|
|
4435
|
+
currentPartialFootnotes.length > 0 ? currentPartialFootnotes : void 0,
|
|
4436
|
+
false,
|
|
4437
|
+
currentPageAdmitted
|
|
2386
4438
|
);
|
|
2387
4439
|
pages.push(page);
|
|
4440
|
+
precedingDisplayedPageNumber = ownedDisplayedPageNumber;
|
|
2388
4441
|
pageNumber++;
|
|
2389
|
-
pageInSection++;
|
|
2390
4442
|
currentContent = [];
|
|
2391
|
-
|
|
4443
|
+
pageSectionIndex = nextPageSectionIndex;
|
|
4444
|
+
if (!sectionStartPages.has(pageSectionIndex)) {
|
|
4445
|
+
sectionStartPages.set(pageSectionIndex, pageNumber);
|
|
4446
|
+
}
|
|
4447
|
+
const newBands = this.getPageBands(
|
|
4448
|
+
dimensionsFor(),
|
|
4449
|
+
pageSectionIndex,
|
|
4450
|
+
pageInSection(),
|
|
4451
|
+
displayedPageNumber()
|
|
4452
|
+
);
|
|
2392
4453
|
effectiveContentHeight = newBands.bodyHeight;
|
|
2393
4454
|
remainingHeight = effectiveContentHeight;
|
|
2394
4455
|
prevMarginBottomPt = 0;
|
|
2395
4456
|
currentFootnoteIds = [];
|
|
4457
|
+
currentPageHasFootnoteReference = false;
|
|
2396
4458
|
currentPartialFootnotes = [];
|
|
2397
4459
|
currentContinuation = nextPageContinuation;
|
|
2398
4460
|
nextPageContinuation = null;
|
|
4461
|
+
currentContinuationPartitioned = false;
|
|
4462
|
+
currentPageAdmitted = false;
|
|
2399
4463
|
if (deferredFootnoteIds.length > 0) {
|
|
2400
4464
|
currentFootnoteIds = [...deferredFootnoteIds];
|
|
2401
4465
|
deferredFootnoteIds = [];
|
|
2402
4466
|
}
|
|
2403
|
-
|
|
2404
|
-
|
|
2405
|
-
} else {
|
|
2406
|
-
currentFootnoteHeight = 0;
|
|
2407
|
-
}
|
|
2408
|
-
if (currentFootnoteIds.length > 0) {
|
|
2409
|
-
currentFootnoteHeight += this.measureFootnotesHeight(
|
|
2410
|
-
currentFootnoteIds,
|
|
2411
|
-
dims.contentWidth,
|
|
2412
|
-
null
|
|
2413
|
-
);
|
|
2414
|
-
}
|
|
4467
|
+
const nextDimensions = dimensionsFor();
|
|
4468
|
+
currentFootnoteHeight = currentContinuation ? 0 : this.measureFootnotesHeight(currentFootnoteIds, nextDimensions.contentWidth);
|
|
2415
4469
|
};
|
|
4470
|
+
const pageHasPayload = () => currentContent.length > 0 || currentFootnoteIds.length > 0 || (currentContinuation?.remainingElements.length ?? 0) > 0;
|
|
2416
4471
|
for (let i = 0; i < blocks.length; i++) {
|
|
4472
|
+
this.checkpoint();
|
|
2417
4473
|
const block = blocks[i];
|
|
4474
|
+
const allBlockFootnoteIds = this.extractFootnoteRefs(block.element);
|
|
4475
|
+
if (block.sectionIndex !== pageSectionIndex && currentPageHasFootnoteReference && (!this.footnoteLayoutLikeWord8 || allBlockFootnoteIds.length > 0)) {
|
|
4476
|
+
finishPage(block.sectionIndex);
|
|
4477
|
+
}
|
|
4478
|
+
const pageIsEmpty = currentContent.length === 0 && currentFootnoteIds.length === 0 && (currentContinuation?.remainingElements.length ?? 0) === 0;
|
|
4479
|
+
if (pageIsEmpty && block.sectionIndex !== pageSectionIndex) {
|
|
4480
|
+
adoptEmptyPageOwner(block.sectionIndex);
|
|
4481
|
+
}
|
|
4482
|
+
prepareCurrentContinuation();
|
|
4483
|
+
if (currentFootnoteHeight > 0) {
|
|
4484
|
+
const ownerDimensions = dimensionsFor();
|
|
4485
|
+
const ownerBands = this.getPageBands(
|
|
4486
|
+
ownerDimensions,
|
|
4487
|
+
pageSectionIndex,
|
|
4488
|
+
pageInSection(),
|
|
4489
|
+
displayedPageNumber()
|
|
4490
|
+
);
|
|
4491
|
+
const ownerMaxFootnoteArea = ownerBands.bodyHeight * MAX_FOOTNOTE_AREA_RATIO;
|
|
4492
|
+
if (currentFootnoteHeight > ownerMaxFootnoteArea) {
|
|
4493
|
+
const fittingIds = [];
|
|
4494
|
+
let fittingHeight = 0;
|
|
4495
|
+
if ((currentContinuation?.remainingElements.length ?? 0) === 0 && currentPartialFootnotes.length === 0) {
|
|
4496
|
+
for (const footnoteId of currentFootnoteIds) {
|
|
4497
|
+
this.checkpoint();
|
|
4498
|
+
const candidateHeight = this.measureFootnotesHeight(
|
|
4499
|
+
[...fittingIds, footnoteId],
|
|
4500
|
+
ownerDimensions.contentWidth
|
|
4501
|
+
);
|
|
4502
|
+
if (candidateHeight + FOOTNOTE_MEASUREMENT_GUARD_PT > ownerMaxFootnoteArea) break;
|
|
4503
|
+
fittingIds.push(footnoteId);
|
|
4504
|
+
fittingHeight = candidateHeight;
|
|
4505
|
+
}
|
|
4506
|
+
}
|
|
4507
|
+
if (fittingIds.length > 0) {
|
|
4508
|
+
deferredFootnoteIds = [
|
|
4509
|
+
...currentFootnoteIds.slice(fittingIds.length),
|
|
4510
|
+
...deferredFootnoteIds
|
|
4511
|
+
];
|
|
4512
|
+
currentFootnoteIds = fittingIds;
|
|
4513
|
+
currentFootnoteHeight = fittingHeight;
|
|
4514
|
+
} else {
|
|
4515
|
+
finishPage(block.sectionIndex);
|
|
4516
|
+
i--;
|
|
4517
|
+
continue;
|
|
4518
|
+
}
|
|
4519
|
+
}
|
|
4520
|
+
}
|
|
4521
|
+
const blockDimensions = dimensionsFor(block.sectionIndex);
|
|
4522
|
+
const nextBlockPageInSection = pageInSection(block.sectionIndex, pageNumber + 1);
|
|
4523
|
+
const freshBlockPageBodyHeight = this.getPageBands(
|
|
4524
|
+
blockDimensions,
|
|
4525
|
+
block.sectionIndex,
|
|
4526
|
+
nextBlockPageInSection,
|
|
4527
|
+
displayedPageNumber(block.sectionIndex, nextBlockPageInSection, pageNumber + 1)
|
|
4528
|
+
).bodyHeight;
|
|
2418
4529
|
if (block.isPageBreak) {
|
|
2419
|
-
finishPage();
|
|
4530
|
+
finishPage(block.sectionIndex);
|
|
2420
4531
|
continue;
|
|
2421
4532
|
}
|
|
2422
4533
|
if (block.pageBreakBefore && currentContent.length > 0) {
|
|
2423
|
-
finishPage();
|
|
4534
|
+
finishPage(block.sectionIndex);
|
|
2424
4535
|
}
|
|
2425
4536
|
const previousBlock = blocks[i - 1];
|
|
2426
4537
|
const startsKeepChain = !previousBlock || !previousBlock.keepWithNext || previousBlock.isPageBreak || block.pageBreakBefore;
|
|
@@ -2436,7 +4547,7 @@ var PaginationEngine = class {
|
|
|
2436
4547
|
if (newChainFootnoteIds.length > 0 && this.footnoteRegistry.size > 0) {
|
|
2437
4548
|
const totalChainFootnoteHeight = this.measureFootnotesHeight(
|
|
2438
4549
|
[...currentFootnoteIds, ...newChainFootnoteIds],
|
|
2439
|
-
|
|
4550
|
+
blockDimensions.contentWidth,
|
|
2440
4551
|
currentContinuation
|
|
2441
4552
|
);
|
|
2442
4553
|
additionalChainFootnoteHeight = Math.max(
|
|
@@ -2447,58 +4558,69 @@ var PaginationEngine = class {
|
|
|
2447
4558
|
const currentChainHeight = this.measureKeepWithNextChainBodyHeight(
|
|
2448
4559
|
keepChain,
|
|
2449
4560
|
prevMarginBottomPt,
|
|
2450
|
-
currentContent.length === 0
|
|
4561
|
+
currentContent.length === 0,
|
|
4562
|
+
pageInSection(block.sectionIndex)
|
|
2451
4563
|
) + additionalChainFootnoteHeight;
|
|
2452
4564
|
const currentAvailableHeight = remainingHeight - currentFootnoteHeight;
|
|
2453
4565
|
if (currentChainHeight > currentAvailableHeight) {
|
|
2454
4566
|
const nextPageBands = this.getPageBands(
|
|
2455
|
-
|
|
2456
|
-
sectionIndex,
|
|
2457
|
-
pageInSection + 1,
|
|
2458
|
-
|
|
4567
|
+
dimensionsFor(block.sectionIndex),
|
|
4568
|
+
block.sectionIndex,
|
|
4569
|
+
pageInSection(block.sectionIndex, pageNumber + 1),
|
|
4570
|
+
displayedPageNumber(
|
|
4571
|
+
block.sectionIndex,
|
|
4572
|
+
pageInSection(block.sectionIndex, pageNumber + 1),
|
|
4573
|
+
pageNumber + 1
|
|
4574
|
+
)
|
|
2459
4575
|
);
|
|
2460
4576
|
const freshChainBodyHeight = this.measureKeepWithNextChainBodyHeight(
|
|
2461
4577
|
keepChain,
|
|
2462
4578
|
0,
|
|
2463
|
-
true
|
|
4579
|
+
true,
|
|
4580
|
+
pageInSection(block.sectionIndex, pageNumber + 1)
|
|
2464
4581
|
);
|
|
2465
4582
|
const freshChainFootnoteHeight = this.measureFootnotesHeight(
|
|
2466
4583
|
newChainFootnoteIds,
|
|
2467
|
-
|
|
4584
|
+
blockDimensions.contentWidth,
|
|
2468
4585
|
nextPageContinuation
|
|
2469
4586
|
);
|
|
2470
4587
|
if (freshChainBodyHeight + freshChainFootnoteHeight <= nextPageBands.bodyHeight) {
|
|
2471
|
-
finishPage();
|
|
4588
|
+
finishPage(block.sectionIndex);
|
|
2472
4589
|
}
|
|
2473
4590
|
}
|
|
2474
4591
|
}
|
|
2475
4592
|
}
|
|
2476
|
-
const allBlockFootnoteIds = this.extractFootnoteRefs(block.element);
|
|
2477
4593
|
const newFootnoteIds = this.collectNewFootnoteIds([block], currentFootnoteIds);
|
|
4594
|
+
let combinedFootnoteHeight = currentFootnoteHeight;
|
|
2478
4595
|
let additionalFootnoteHeight = 0;
|
|
2479
4596
|
if (newFootnoteIds.length > 0 && this.footnoteRegistry.size > 0) {
|
|
2480
4597
|
const combinedFootnoteIds = [...currentFootnoteIds, ...newFootnoteIds];
|
|
2481
|
-
|
|
4598
|
+
combinedFootnoteHeight = this.measureFootnotesHeight(
|
|
2482
4599
|
combinedFootnoteIds,
|
|
2483
|
-
|
|
2484
|
-
currentContinuation
|
|
4600
|
+
blockDimensions.contentWidth,
|
|
4601
|
+
currentContinuation,
|
|
4602
|
+
currentPartialFootnotes
|
|
4603
|
+
);
|
|
4604
|
+
additionalFootnoteHeight = Math.max(
|
|
4605
|
+
0,
|
|
4606
|
+
combinedFootnoteHeight - currentFootnoteHeight
|
|
2485
4607
|
);
|
|
2486
|
-
additionalFootnoteHeight = totalFootnoteHeight - currentFootnoteHeight;
|
|
2487
4608
|
}
|
|
2488
4609
|
const isFirstOnPage = currentContent.length === 0;
|
|
2489
|
-
|
|
2490
|
-
|
|
2491
|
-
|
|
2492
|
-
|
|
4610
|
+
const effectiveMarginTop = this.effectiveBlockMarginTop(
|
|
4611
|
+
block,
|
|
4612
|
+
prevMarginBottomPt,
|
|
4613
|
+
isFirstOnPage,
|
|
4614
|
+
pageInSection(block.sectionIndex)
|
|
4615
|
+
);
|
|
2493
4616
|
const blockSpace = effectiveMarginTop + block.heightPt + additionalFootnoteHeight;
|
|
2494
4617
|
const effectiveRemainingHeight = remainingHeight - currentFootnoteHeight;
|
|
2495
4618
|
const bodyContentUsed = effectiveContentHeight - remainingHeight;
|
|
2496
4619
|
const maxFootnoteArea = effectiveContentHeight * MAX_FOOTNOTE_AREA_RATIO;
|
|
2497
|
-
const maxFootnoteExpansion = Math.max(0, maxFootnoteArea - currentFootnoteHeight);
|
|
2498
4620
|
if (blockSpace > effectiveRemainingHeight) {
|
|
2499
4621
|
const paragraphFragments = this.tryFragmentParagraph(
|
|
2500
4622
|
block,
|
|
2501
|
-
|
|
4623
|
+
blockDimensions,
|
|
2502
4624
|
effectiveRemainingHeight,
|
|
2503
4625
|
effectiveMarginTop
|
|
2504
4626
|
);
|
|
@@ -2508,40 +4630,74 @@ var PaginationEngine = class {
|
|
|
2508
4630
|
continue;
|
|
2509
4631
|
}
|
|
2510
4632
|
}
|
|
2511
|
-
if (blockSpace <= effectiveRemainingHeight) {
|
|
2512
|
-
|
|
4633
|
+
if (blockSpace <= effectiveRemainingHeight && (combinedFootnoteHeight === 0 || combinedFootnoteHeight + FOOTNOTE_MEASUREMENT_GUARD_PT <= maxFootnoteArea)) {
|
|
4634
|
+
markSectionPlaced(block.sectionIndex);
|
|
4635
|
+
currentContent.push(this.cloneBlockForPage(
|
|
4636
|
+
block,
|
|
4637
|
+
isFirstOnPage,
|
|
4638
|
+
pageInSection(block.sectionIndex)
|
|
4639
|
+
));
|
|
2513
4640
|
remainingHeight -= effectiveMarginTop + block.heightPt + block.marginBottomPt;
|
|
2514
4641
|
prevMarginBottomPt = block.marginBottomPt;
|
|
2515
4642
|
if (newFootnoteIds.length > 0) {
|
|
2516
4643
|
currentFootnoteIds.push(...newFootnoteIds);
|
|
2517
|
-
currentFootnoteHeight
|
|
4644
|
+
currentFootnoteHeight = combinedFootnoteHeight;
|
|
2518
4645
|
}
|
|
2519
|
-
|
|
4646
|
+
currentPageHasFootnoteReference ||= allBlockFootnoteIds.length > 0;
|
|
4647
|
+
} else if (block.heightPt + this.effectiveBlockMarginTop(
|
|
4648
|
+
block,
|
|
4649
|
+
0,
|
|
4650
|
+
true,
|
|
4651
|
+
pageInSection(block.sectionIndex, pageNumber + 1)
|
|
4652
|
+
) <= freshBlockPageBodyHeight) {
|
|
2520
4653
|
const blockSpaceWithoutFootnotes = effectiveMarginTop + block.heightPt;
|
|
2521
|
-
const minBodySpaceNeeded = bodyContentUsed + blockSpaceWithoutFootnotes + MIN_BODY_CONTENT_HEIGHT;
|
|
2522
|
-
const canExpandFootnotes = minBodySpaceNeeded <= effectiveContentHeight;
|
|
2523
4654
|
if (newFootnoteIds.length > 0 && blockSpaceWithoutFootnotes <= effectiveRemainingHeight) {
|
|
2524
|
-
|
|
4655
|
+
markSectionPlaced(block.sectionIndex);
|
|
4656
|
+
currentContent.push(this.cloneBlockForPage(
|
|
4657
|
+
block,
|
|
4658
|
+
isFirstOnPage,
|
|
4659
|
+
pageInSection(block.sectionIndex)
|
|
4660
|
+
));
|
|
2525
4661
|
remainingHeight -= effectiveMarginTop + block.heightPt + block.marginBottomPt;
|
|
2526
4662
|
prevMarginBottomPt = block.marginBottomPt;
|
|
2527
4663
|
const availableForFootnotes = Math.min(
|
|
2528
4664
|
maxFootnoteArea,
|
|
2529
4665
|
effectiveContentHeight - bodyContentUsed - blockSpaceWithoutFootnotes
|
|
2530
4666
|
);
|
|
2531
|
-
|
|
4667
|
+
const nextPageMaxFootnoteArea = freshBlockPageBodyHeight * MAX_FOOTNOTE_AREA_RATIO;
|
|
4668
|
+
const deferralCannotHelp = availableForFootnotes >= nextPageMaxFootnoteArea;
|
|
4669
|
+
for (let noteIndex = 0; noteIndex < newFootnoteIds.length; noteIndex++) {
|
|
4670
|
+
this.checkpoint();
|
|
4671
|
+
const footnoteId = newFootnoteIds[noteIndex];
|
|
2532
4672
|
const footnote = this.footnoteRegistry.get(footnoteId);
|
|
2533
4673
|
if (!footnote) continue;
|
|
2534
|
-
|
|
4674
|
+
if (nextPageContinuation) {
|
|
4675
|
+
deferredFootnoteIds.push(...newFootnoteIds.slice(noteIndex));
|
|
4676
|
+
break;
|
|
4677
|
+
}
|
|
4678
|
+
const candidateIds = [...currentFootnoteIds, footnoteId];
|
|
4679
|
+
const candidateHeight = this.measureFootnotesHeight(
|
|
4680
|
+
candidateIds,
|
|
4681
|
+
blockDimensions.contentWidth,
|
|
4682
|
+
currentContinuation,
|
|
4683
|
+
currentPartialFootnotes
|
|
4684
|
+
);
|
|
2535
4685
|
const spaceLeftForFootnotes = availableForFootnotes - currentFootnoteHeight;
|
|
2536
|
-
if (
|
|
4686
|
+
if (candidateHeight + FOOTNOTE_MEASUREMENT_GUARD_PT <= availableForFootnotes) {
|
|
2537
4687
|
currentFootnoteIds.push(footnoteId);
|
|
2538
|
-
currentFootnoteHeight
|
|
4688
|
+
currentFootnoteHeight = candidateHeight;
|
|
2539
4689
|
} else {
|
|
2540
4690
|
if (spaceLeftForFootnotes > 20) {
|
|
2541
4691
|
const { fits, overflow } = this.splitFootnoteToFit(
|
|
2542
4692
|
footnote,
|
|
2543
|
-
|
|
2544
|
-
|
|
4693
|
+
availableForFootnotes,
|
|
4694
|
+
blockDimensions.contentWidth,
|
|
4695
|
+
deferralCannotHelp,
|
|
4696
|
+
{
|
|
4697
|
+
footnoteIds: currentFootnoteIds,
|
|
4698
|
+
continuation: currentContinuation,
|
|
4699
|
+
partialFootnotes: currentPartialFootnotes
|
|
4700
|
+
}
|
|
2545
4701
|
);
|
|
2546
4702
|
if (fits.length > 0) {
|
|
2547
4703
|
currentFootnoteIds.push(footnoteId);
|
|
@@ -2552,66 +4708,70 @@ var PaginationEngine = class {
|
|
|
2552
4708
|
if (overflow.length > 0) {
|
|
2553
4709
|
nextPageContinuation = {
|
|
2554
4710
|
footnoteId,
|
|
4711
|
+
sourceAnchorId: footnote.dataset.sourceAnchorId,
|
|
2555
4712
|
remainingElements: overflow
|
|
2556
4713
|
};
|
|
2557
4714
|
}
|
|
2558
|
-
currentFootnoteHeight =
|
|
4715
|
+
currentFootnoteHeight = this.measureFootnotesHeight(
|
|
4716
|
+
currentFootnoteIds,
|
|
4717
|
+
blockDimensions.contentWidth,
|
|
4718
|
+
currentContinuation,
|
|
4719
|
+
currentPartialFootnotes
|
|
4720
|
+
);
|
|
4721
|
+
if (overflow.length > 0) {
|
|
4722
|
+
deferredFootnoteIds.push(...newFootnoteIds.slice(noteIndex + 1));
|
|
4723
|
+
break;
|
|
4724
|
+
}
|
|
2559
4725
|
} else {
|
|
2560
|
-
deferredFootnoteIds.push(
|
|
4726
|
+
deferredFootnoteIds.push(...newFootnoteIds.slice(noteIndex));
|
|
4727
|
+
break;
|
|
2561
4728
|
}
|
|
2562
4729
|
} else {
|
|
2563
|
-
deferredFootnoteIds.push(
|
|
4730
|
+
deferredFootnoteIds.push(...newFootnoteIds.slice(noteIndex));
|
|
4731
|
+
break;
|
|
2564
4732
|
}
|
|
2565
4733
|
}
|
|
2566
4734
|
}
|
|
2567
|
-
|
|
2568
|
-
const expandedFootnoteSpace = Math.min(maxFootnoteArea, additionalFootnoteHeight + currentFootnoteHeight);
|
|
2569
|
-
const bodySpaceAfterExpansion = effectiveContentHeight - expandedFootnoteSpace;
|
|
2570
|
-
if (blockSpaceWithoutFootnotes <= bodySpaceAfterExpansion - bodyContentUsed) {
|
|
2571
|
-
currentContent.push(block.element.cloneNode(true));
|
|
2572
|
-
remainingHeight = bodySpaceAfterExpansion - bodyContentUsed - blockSpaceWithoutFootnotes;
|
|
2573
|
-
prevMarginBottomPt = block.marginBottomPt;
|
|
2574
|
-
currentFootnoteIds.push(...newFootnoteIds);
|
|
2575
|
-
currentFootnoteHeight = expandedFootnoteSpace;
|
|
2576
|
-
} else {
|
|
2577
|
-
finishPage();
|
|
2578
|
-
const newPageFootnoteHeight = allBlockFootnoteIds.length > 0 ? this.measureFootnotesHeight(allBlockFootnoteIds, dims.contentWidth, currentContinuation) : currentContinuation ? this.measureContinuationHeight(currentContinuation, dims.contentWidth) : 0;
|
|
2579
|
-
const newPageSpace = block.marginTopPt + block.heightPt + block.marginBottomPt;
|
|
2580
|
-
currentContent.push(block.element.cloneNode(true));
|
|
2581
|
-
remainingHeight = effectiveContentHeight - newPageSpace;
|
|
2582
|
-
prevMarginBottomPt = block.marginBottomPt;
|
|
2583
|
-
currentFootnoteIds = [...currentFootnoteIds, ...allBlockFootnoteIds];
|
|
2584
|
-
currentFootnoteHeight = newPageFootnoteHeight;
|
|
2585
|
-
}
|
|
4735
|
+
currentPageHasFootnoteReference ||= allBlockFootnoteIds.length > 0;
|
|
2586
4736
|
} else {
|
|
2587
|
-
finishPage();
|
|
2588
|
-
|
|
2589
|
-
|
|
2590
|
-
currentContent.push(block.element.cloneNode(true));
|
|
2591
|
-
remainingHeight = effectiveContentHeight - newPageSpace;
|
|
2592
|
-
prevMarginBottomPt = block.marginBottomPt;
|
|
2593
|
-
currentFootnoteIds = [...currentFootnoteIds, ...allBlockFootnoteIds];
|
|
2594
|
-
currentFootnoteHeight = newPageFootnoteHeight;
|
|
4737
|
+
finishPage(block.sectionIndex, !pageHasPayload());
|
|
4738
|
+
i--;
|
|
4739
|
+
continue;
|
|
2595
4740
|
}
|
|
2596
4741
|
} else {
|
|
2597
|
-
const tableFragments = this.trySplitSimpleOversizedTable(
|
|
4742
|
+
const tableFragments = this.trySplitSimpleOversizedTable(
|
|
4743
|
+
block,
|
|
4744
|
+
dimensionsFor(block.sectionIndex),
|
|
4745
|
+
block.sectionIndex
|
|
4746
|
+
);
|
|
2598
4747
|
if (tableFragments) {
|
|
2599
4748
|
if (currentContent.length > 0 || currentContinuation) {
|
|
2600
|
-
finishPage();
|
|
4749
|
+
finishPage(block.sectionIndex);
|
|
2601
4750
|
}
|
|
2602
4751
|
blocks.splice(i, 1, ...tableFragments);
|
|
2603
4752
|
i--;
|
|
2604
4753
|
continue;
|
|
2605
4754
|
}
|
|
2606
|
-
if (
|
|
2607
|
-
finishPage();
|
|
4755
|
+
if (pageHasPayload()) {
|
|
4756
|
+
finishPage(block.sectionIndex);
|
|
4757
|
+
i--;
|
|
4758
|
+
continue;
|
|
2608
4759
|
}
|
|
2609
|
-
|
|
2610
|
-
|
|
2611
|
-
|
|
4760
|
+
markSectionPlaced(block.sectionIndex);
|
|
4761
|
+
currentContent.push(this.cloneBlockForPage(
|
|
4762
|
+
block,
|
|
4763
|
+
true,
|
|
4764
|
+
pageInSection(block.sectionIndex)
|
|
4765
|
+
));
|
|
4766
|
+
deferredFootnoteIds.push(...newFootnoteIds);
|
|
4767
|
+
currentPageHasFootnoteReference ||= allBlockFootnoteIds.length > 0;
|
|
4768
|
+
finishPage(block.sectionIndex);
|
|
2612
4769
|
}
|
|
2613
4770
|
}
|
|
2614
4771
|
finishPage();
|
|
4772
|
+
while (currentFootnoteIds.length > 0 || deferredFootnoteIds.length > 0 || (currentContinuation?.remainingElements.length ?? 0) > 0) {
|
|
4773
|
+
finishPage();
|
|
4774
|
+
}
|
|
2615
4775
|
this.pendingFootnoteContinuation = nextPageContinuation;
|
|
2616
4776
|
return pages;
|
|
2617
4777
|
}
|
|
@@ -2636,6 +4796,19 @@ var PaginationEngine = class {
|
|
|
2636
4796
|
if (el.getAttribute("contenteditable") !== null) el.setAttribute("contenteditable", "false");
|
|
2637
4797
|
}
|
|
2638
4798
|
}
|
|
4799
|
+
/** A repeated margin note is presentation, not a second bookmark/link target. */
|
|
4800
|
+
makeClonedMarginCommentInert(root) {
|
|
4801
|
+
this.makeClonedStoryInert(root);
|
|
4802
|
+
const nodes = [root, ...Array.from(root.querySelectorAll("*"))];
|
|
4803
|
+
for (const element of nodes) {
|
|
4804
|
+
element.removeAttribute("id");
|
|
4805
|
+
if (element.localName === "a" && element.getAttribute("href")?.startsWith("#")) {
|
|
4806
|
+
element.removeAttribute("href");
|
|
4807
|
+
element.setAttribute("aria-disabled", "true");
|
|
4808
|
+
element.tabIndex = -1;
|
|
4809
|
+
}
|
|
4810
|
+
}
|
|
4811
|
+
}
|
|
2639
4812
|
/**
|
|
2640
4813
|
* Resolves floating DrawingML objects after their anchor paragraphs have landed on a page.
|
|
2641
4814
|
*
|
|
@@ -2661,7 +4834,7 @@ var PaginationEngine = class {
|
|
|
2661
4834
|
const context = {
|
|
2662
4835
|
staticLeft: toPageX(staticRect.left),
|
|
2663
4836
|
staticTop: toPageY(staticRect.top),
|
|
2664
|
-
lineHeight: paragraph ? (parseFloat(getComputedStyle(paragraph).lineHeight) || staticRect.height) / pixelsPerPoint : staticRect.height / pixelsPerPoint,
|
|
4837
|
+
lineHeight: paragraph ? (parseFloat(this.view.getComputedStyle(paragraph).lineHeight) || staticRect.height) / pixelsPerPoint : staticRect.height / pixelsPerPoint,
|
|
2665
4838
|
paragraphLeft: toPageX(paragraphRect.left),
|
|
2666
4839
|
paragraphTop: toPageY(paragraphRect.top),
|
|
2667
4840
|
paragraphWidth: paragraphRect.width / pixelsPerPoint,
|
|
@@ -2791,36 +4964,48 @@ var PaginationEngine = class {
|
|
|
2791
4964
|
else if (alignment === "outside") fraction = pageNumber % 2 === 1 ? 1 : 0;
|
|
2792
4965
|
return reference.start + (reference.size - objectSize) * fraction;
|
|
2793
4966
|
}
|
|
4967
|
+
/** Charge a physical page before any page-owned partitioning or DOM allocation. */
|
|
4968
|
+
admitPageAllocation() {
|
|
4969
|
+
const prospectivePageCount = this.createdPageCount + 1;
|
|
4970
|
+
this.pageCountCheckpoint?.(prospectivePageCount);
|
|
4971
|
+
this.createdPageCount = prospectivePageCount;
|
|
4972
|
+
}
|
|
2794
4973
|
/**
|
|
2795
4974
|
* Creates a page container element.
|
|
2796
4975
|
*/
|
|
2797
|
-
createPage(dims, pageNumber, sectionIndex, content, pageInSection, footnoteIds = [], footnoteHeight = 0, continuation, partialFootnotes) {
|
|
2798
|
-
|
|
4976
|
+
createPage(dims, pageNumber, sectionIndex, displayedPageNumber, content, pageInSection, footnoteIds = [], footnoteHeight = 0, continuation, partialFootnotes, isSectionFiller = false, pageAlreadyAdmitted = false) {
|
|
4977
|
+
if (!pageAlreadyAdmitted) this.admitPageAllocation();
|
|
4978
|
+
const pageBox = this.document.createElement("div");
|
|
2799
4979
|
pageBox.className = `${this.cssPrefix}box`;
|
|
2800
4980
|
pageBox.style.width = `${dims.pageWidth}pt`;
|
|
2801
4981
|
pageBox.style.height = `${dims.pageHeight}pt`;
|
|
2802
4982
|
pageBox.style.overflow = "hidden";
|
|
2803
4983
|
pageBox.style.position = "relative";
|
|
2804
4984
|
if (this.scale !== 1) {
|
|
2805
|
-
|
|
2806
|
-
|
|
2807
|
-
|
|
2808
|
-
|
|
2809
|
-
|
|
2810
|
-
|
|
2811
|
-
|
|
2812
|
-
|
|
2813
|
-
|
|
4985
|
+
if (this.view.CSS?.supports("zoom", "1")) {
|
|
4986
|
+
pageBox.style.zoom = String(this.scale);
|
|
4987
|
+
} else {
|
|
4988
|
+
pageBox.style.transform = `scale(${this.scale})`;
|
|
4989
|
+
pageBox.style.transformOrigin = "top left";
|
|
4990
|
+
const heightReductionPt = dims.pageHeight * (1 - this.scale);
|
|
4991
|
+
const widthReductionPt = dims.pageWidth * (1 - this.scale);
|
|
4992
|
+
const heightReductionPx = ptToPx(heightReductionPt);
|
|
4993
|
+
const widthReductionPx = ptToPx(widthReductionPt);
|
|
4994
|
+
pageBox.style.marginRight = `-${widthReductionPx}px`;
|
|
4995
|
+
pageBox.style.marginBottom = `${this.pageGap - heightReductionPx}px`;
|
|
4996
|
+
}
|
|
2814
4997
|
}
|
|
2815
4998
|
pageBox.style.willChange = "transform";
|
|
2816
4999
|
pageBox.style.contain = "layout paint";
|
|
2817
5000
|
pageBox.dataset.pageNumber = String(pageNumber);
|
|
2818
5001
|
pageBox.dataset.sectionIndex = String(sectionIndex);
|
|
5002
|
+
pageBox.dataset.displayedPageNumber = String(displayedPageNumber);
|
|
5003
|
+
if (isSectionFiller) pageBox.dataset.sectionFiller = "true";
|
|
2819
5004
|
pageBox.dataset.pageInSection = String(pageInSection);
|
|
2820
|
-
const bands = this.getPageBands(dims, sectionIndex, pageInSection,
|
|
2821
|
-
const headerSource = this.selectHeader(sectionIndex, pageInSection,
|
|
5005
|
+
const bands = this.getPageBands(dims, sectionIndex, pageInSection, displayedPageNumber);
|
|
5006
|
+
const headerSource = isSectionFiller ? void 0 : this.selectHeader(sectionIndex, pageInSection, displayedPageNumber);
|
|
2822
5007
|
if (headerSource) {
|
|
2823
|
-
const headerDiv = document.createElement("div");
|
|
5008
|
+
const headerDiv = this.document.createElement("div");
|
|
2824
5009
|
headerDiv.className = `${this.cssPrefix}header`;
|
|
2825
5010
|
headerDiv.style.position = "absolute";
|
|
2826
5011
|
headerDiv.style.top = `${bands.headerTop}pt`;
|
|
@@ -2842,7 +5027,7 @@ var PaginationEngine = class {
|
|
|
2842
5027
|
}
|
|
2843
5028
|
const contentAreaTop = bands.bodyTop;
|
|
2844
5029
|
const contentAreaHeight = bands.bodyHeight;
|
|
2845
|
-
const contentArea = document.createElement("div");
|
|
5030
|
+
const contentArea = this.document.createElement("div");
|
|
2846
5031
|
contentArea.className = `${this.cssPrefix}content`;
|
|
2847
5032
|
contentArea.style.position = "absolute";
|
|
2848
5033
|
contentArea.style.top = `${contentAreaTop}pt`;
|
|
@@ -2855,12 +5040,46 @@ var PaginationEngine = class {
|
|
|
2855
5040
|
}
|
|
2856
5041
|
pageBox.appendChild(contentArea);
|
|
2857
5042
|
const hasContinuation = continuation && continuation.remainingElements.length > 0;
|
|
2858
|
-
if (footnoteIds.length > 0 || hasContinuation) {
|
|
5043
|
+
if (!isSectionFiller && (footnoteIds.length > 0 || hasContinuation)) {
|
|
2859
5044
|
this.addPageFootnotes(pageBox, footnoteIds, dims, bands, footnoteHeight, continuation, partialFootnotes);
|
|
2860
5045
|
}
|
|
2861
|
-
const
|
|
5046
|
+
const pageCommentIds = [];
|
|
5047
|
+
for (const marker of Array.from(
|
|
5048
|
+
pageBox.querySelectorAll("[data-comment-id]")
|
|
5049
|
+
)) {
|
|
5050
|
+
if (marker.closest(`.${this.cssPrefix}comment-margin`)) continue;
|
|
5051
|
+
const id = marker.dataset.commentId;
|
|
5052
|
+
if (id && this.commentMarginRegistry.has(id) && !pageCommentIds.includes(id)) {
|
|
5053
|
+
pageCommentIds.push(id);
|
|
5054
|
+
}
|
|
5055
|
+
}
|
|
5056
|
+
if (pageCommentIds.length > 0) {
|
|
5057
|
+
const marginColumn = this.document.createElement("aside");
|
|
5058
|
+
marginColumn.className = `${this.cssPrefix}comment-margin`;
|
|
5059
|
+
marginColumn.style.position = "absolute";
|
|
5060
|
+
marginColumn.style.top = `${contentAreaTop}pt`;
|
|
5061
|
+
marginColumn.style.left = `${dims.marginLeft + dims.contentWidth + 3}pt`;
|
|
5062
|
+
marginColumn.style.width = `${Math.max(12, dims.marginRight - 6)}pt`;
|
|
5063
|
+
marginColumn.style.maxHeight = `${contentAreaHeight}pt`;
|
|
5064
|
+
marginColumn.style.overflow = "hidden";
|
|
5065
|
+
marginColumn.style.boxSizing = "border-box";
|
|
5066
|
+
const placedThreadRoots = /* @__PURE__ */ new Set();
|
|
5067
|
+
for (const id of pageCommentIds) {
|
|
5068
|
+
const source = this.commentMarginRegistry.get(id);
|
|
5069
|
+
if (source) {
|
|
5070
|
+
const rootId = source.dataset.commentId ?? id;
|
|
5071
|
+
if (placedThreadRoots.has(rootId)) continue;
|
|
5072
|
+
placedThreadRoots.add(rootId);
|
|
5073
|
+
const clone = source.cloneNode(true);
|
|
5074
|
+
this.makeClonedMarginCommentInert(clone);
|
|
5075
|
+
marginColumn.appendChild(clone);
|
|
5076
|
+
}
|
|
5077
|
+
}
|
|
5078
|
+
pageBox.appendChild(marginColumn);
|
|
5079
|
+
}
|
|
5080
|
+
const footerSource = isSectionFiller ? void 0 : this.selectFooter(sectionIndex, pageInSection, displayedPageNumber);
|
|
2862
5081
|
if (footerSource) {
|
|
2863
|
-
const footerDiv = document.createElement("div");
|
|
5082
|
+
const footerDiv = this.document.createElement("div");
|
|
2864
5083
|
footerDiv.className = `${this.cssPrefix}footer`;
|
|
2865
5084
|
footerDiv.style.position = "absolute";
|
|
2866
5085
|
footerDiv.style.top = "auto";
|
|
@@ -2880,8 +5099,8 @@ var PaginationEngine = class {
|
|
|
2880
5099
|
}
|
|
2881
5100
|
pageBox.appendChild(footerDiv);
|
|
2882
5101
|
}
|
|
2883
|
-
if (this.showPageNumbers) {
|
|
2884
|
-
const pageNum = document.createElement("div");
|
|
5102
|
+
if (this.showPageNumbers && !isSectionFiller) {
|
|
5103
|
+
const pageNum = this.document.createElement("div");
|
|
2885
5104
|
pageNum.className = `${this.cssPrefix}number`;
|
|
2886
5105
|
pageNum.textContent = String(pageNumber);
|
|
2887
5106
|
pageBox.appendChild(pageNum);
|
|
@@ -2904,7 +5123,8 @@ var PaginationEngine = class {
|
|
|
2904
5123
|
}
|
|
2905
5124
|
};
|
|
2906
5125
|
function paginateHtml(html, container, options = {}) {
|
|
2907
|
-
const
|
|
5126
|
+
const ownerDocument = typeof container === "string" ? globalThis.document : container.ownerDocument;
|
|
5127
|
+
const containerEl = typeof container === "string" ? ownerDocument.getElementById(container) : container;
|
|
2908
5128
|
if (!containerEl) {
|
|
2909
5129
|
throw new Error("Container element not found");
|
|
2910
5130
|
}
|
|
@@ -6206,6 +8426,13 @@ function fontWeightIsBold(w) {
|
|
|
6206
8426
|
function escapeInlineMarkdown(text) {
|
|
6207
8427
|
return text.replace(/([\\`*_[\]])/g, "\\$1");
|
|
6208
8428
|
}
|
|
8429
|
+
function escapeLeadingBlockMarkers(md) {
|
|
8430
|
+
return md.split("\n").map((line) => {
|
|
8431
|
+
if (/^[#>|]/.test(line) || /^[-+] /.test(line)) return "\\" + line;
|
|
8432
|
+
if (/^\d+\. /.test(line)) return line.replace(".", "\\.");
|
|
8433
|
+
return line;
|
|
8434
|
+
}).join("\n");
|
|
8435
|
+
}
|
|
6209
8436
|
function collectInlineSegments(node, out) {
|
|
6210
8437
|
node.childNodes.forEach((child) => {
|
|
6211
8438
|
if (isGeneratedChrome(child)) return;
|
|
@@ -6257,7 +8484,7 @@ function serializeInlineMarkdown(block) {
|
|
|
6257
8484
|
merged.push({ ...s });
|
|
6258
8485
|
}
|
|
6259
8486
|
}
|
|
6260
|
-
return merged.map(segToMarkdown).join("").trim();
|
|
8487
|
+
return escapeLeadingBlockMarkers(merged.map(segToMarkdown).join("").trim());
|
|
6261
8488
|
}
|
|
6262
8489
|
function isListBlock(block) {
|
|
6263
8490
|
return !!block.querySelector(":scope > [data-list-marker]");
|
|
@@ -8292,17 +10519,19 @@ var DocxEditor = class _DocxEditor {
|
|
|
8292
10519
|
this.refreshAfter(block, idx, false);
|
|
8293
10520
|
}
|
|
8294
10521
|
// ─── Table row / column editing (active block must be inside a table cell) ──────────
|
|
8295
|
-
/** Run a table-structure op on the active cell
|
|
10522
|
+
/** Run a table-structure op on the active cell's canonical tc anchor and re-render. */
|
|
8296
10523
|
tableEdit(run) {
|
|
8297
10524
|
const block = this.activeBlock;
|
|
8298
|
-
|
|
10525
|
+
const cell = block?.closest("td, th");
|
|
10526
|
+
if (this.closed || !block || !cell || !block.closest("table")) return;
|
|
8299
10527
|
const unid = block.getAttribute("data-anchor");
|
|
8300
10528
|
if (!unid) return;
|
|
8301
|
-
|
|
8302
|
-
|
|
10529
|
+
const paragraphId = this.anchorIdOf(block);
|
|
10530
|
+
const cellId = this.anchorIdOf(cell);
|
|
10531
|
+
if (!paragraphId || !cellId) return;
|
|
8303
10532
|
const idx = this.blockIndex(block);
|
|
8304
|
-
|
|
8305
|
-
const res = this.parseEdit(run(
|
|
10533
|
+
this.syncBlock(block, paragraphId);
|
|
10534
|
+
const res = this.parseEdit(run(cellId));
|
|
8306
10535
|
if (!res.success) return;
|
|
8307
10536
|
this.refreshAfter(block, idx, false);
|
|
8308
10537
|
}
|
|
@@ -8455,6 +10684,59 @@ var DocxEditor = class _DocxEditor {
|
|
|
8455
10684
|
if (!res.success) return;
|
|
8456
10685
|
this.reconcile(idx, false);
|
|
8457
10686
|
}
|
|
10687
|
+
/**
|
|
10688
|
+
* Add a native Word comment on the current selection in the active block — or on the whole
|
|
10689
|
+
* block when the selection is collapsed (a null span comments the paragraph). The definition,
|
|
10690
|
+
* threading parts, and body-side range markers are written by the session's `AddComment`
|
|
10691
|
+
* (issue #300); the re-render shows the marker chrome the converter already draws. The
|
|
10692
|
+
* annotation type legal review runs on, finally authorable from the shipped surface
|
|
10693
|
+
* (issue #580).
|
|
10694
|
+
*/
|
|
10695
|
+
addComment(markdown = "New comment.", author = "Reviewer") {
|
|
10696
|
+
const block = this.activeBlock;
|
|
10697
|
+
if (this.closed || !block) return;
|
|
10698
|
+
const bridge = this.exports.DocxSessionBridge;
|
|
10699
|
+
if (!bridge.AddComment) return;
|
|
10700
|
+
let fullId = this.anchorIdOf(block);
|
|
10701
|
+
if (!fullId) return;
|
|
10702
|
+
const idx = this.blockIndex(block);
|
|
10703
|
+
const span = selectionSpanIn(block);
|
|
10704
|
+
fullId = this.syncBlock(block, fullId);
|
|
10705
|
+
const res = this.parseEdit(
|
|
10706
|
+
bridge.AddComment(
|
|
10707
|
+
this.handle,
|
|
10708
|
+
fullId,
|
|
10709
|
+
span ? JSON.stringify(span) : "",
|
|
10710
|
+
author,
|
|
10711
|
+
"",
|
|
10712
|
+
"",
|
|
10713
|
+
markdown
|
|
10714
|
+
)
|
|
10715
|
+
);
|
|
10716
|
+
if (!res.success) return;
|
|
10717
|
+
this.reconcile(idx, false);
|
|
10718
|
+
}
|
|
10719
|
+
/** The document's native comment threads (session truth), for review UIs — flat entries
|
|
10720
|
+
* with `parentAnchorId` linking replies and `resolved` carrying thread state. */
|
|
10721
|
+
listComments() {
|
|
10722
|
+
if (this.closed) return [];
|
|
10723
|
+
const bridge = this.exports.DocxSessionBridge;
|
|
10724
|
+
if (!bridge.ListComments) return [];
|
|
10725
|
+
try {
|
|
10726
|
+
return JSON.parse(bridge.ListComments(this.handle));
|
|
10727
|
+
} catch {
|
|
10728
|
+
return [];
|
|
10729
|
+
}
|
|
10730
|
+
}
|
|
10731
|
+
/** Resolve or reopen a comment thread by its `cmt` anchor id (from {@link listComments}).
|
|
10732
|
+
* Resolution is thread metadata (`commentsExtended`), not body markup, so no re-render is
|
|
10733
|
+
* needed — a review UI re-reads {@link listComments} for the new state. */
|
|
10734
|
+
setCommentResolved(commentAnchorId, resolved) {
|
|
10735
|
+
if (this.closed) return;
|
|
10736
|
+
const bridge = this.exports.DocxSessionBridge;
|
|
10737
|
+
if (!bridge.SetCommentResolved) return;
|
|
10738
|
+
this.parseEdit(bridge.SetCommentResolved(this.handle, commentAnchorId, resolved));
|
|
10739
|
+
}
|
|
8458
10740
|
applyParagraphFormat(op) {
|
|
8459
10741
|
const block = this.activeBlock;
|
|
8460
10742
|
if (this.closed || !block) return;
|
|
@@ -8982,8 +11264,8 @@ var DocxEditor = class _DocxEditor {
|
|
|
8982
11264
|
const backref = document.createElement("a");
|
|
8983
11265
|
backref.setAttribute("class", `${prefix}-backref`);
|
|
8984
11266
|
backref.setAttribute("contenteditable", "false");
|
|
8985
|
-
backref.textContent = "\u21A9";
|
|
8986
|
-
last.append(
|
|
11267
|
+
backref.textContent = " \u21A9";
|
|
11268
|
+
last.append(backref);
|
|
8987
11269
|
}
|
|
8988
11270
|
li.querySelectorAll("[data-anchor]").forEach((b) => this.wireBlock(b));
|
|
8989
11271
|
return li;
|
|
@@ -9136,6 +11418,7 @@ var RIBBON_CSS = `
|
|
|
9136
11418
|
color: var(--dxr-ink);
|
|
9137
11419
|
background: var(--dxr-desk);
|
|
9138
11420
|
-webkit-text-size-adjust: 100%;
|
|
11421
|
+
text-size-adjust: 100%;
|
|
9139
11422
|
}
|
|
9140
11423
|
.dxr *, .dxr *::before, .dxr *::after { box-sizing: border-box; }
|
|
9141
11424
|
|
|
@@ -9539,6 +11822,25 @@ var RIBBON_CSS = `
|
|
|
9539
11822
|
box-shadow: 0 1px 3px rgba(15, 23, 42, .08), 0 12px 24px rgba(15, 23, 42, .06);
|
|
9540
11823
|
}
|
|
9541
11824
|
|
|
11825
|
+
/* \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 */
|
|
11826
|
+
.dxr-threads {
|
|
11827
|
+
display: flex; flex-direction: column; gap: 4px;
|
|
11828
|
+
max-height: 92px; overflow-y: auto; min-width: 260px; padding-right: 2px;
|
|
11829
|
+
}
|
|
11830
|
+
.dxr-thread {
|
|
11831
|
+
display: flex; align-items: center; gap: 6px;
|
|
11832
|
+
font-size: 11px; line-height: 1.3; padding: 3px 6px;
|
|
11833
|
+
border: 1px solid var(--dxr-rule); border-radius: 4px; background: var(--dxr-chrome-sunk);
|
|
11834
|
+
}
|
|
11835
|
+
.dxr-thread[data-resolved] { opacity: 0.55; }
|
|
11836
|
+
.dxr-thread[data-reply] { margin-left: 14px; }
|
|
11837
|
+
.dxr-thread .dxr-tauthor { font-weight: 600; white-space: nowrap; }
|
|
11838
|
+
.dxr-thread .dxr-ttext {
|
|
11839
|
+
flex: 1; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; max-width: 220px;
|
|
11840
|
+
}
|
|
11841
|
+
.dxr-thread button { flex: none; }
|
|
11842
|
+
.dxr-group.dxr-grow { flex: 1; min-width: 0; }
|
|
11843
|
+
|
|
9542
11844
|
/* \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 */
|
|
9543
11845
|
.dxr-pop {
|
|
9544
11846
|
display: none;
|
|
@@ -9857,6 +12159,7 @@ var RIBBON_HTML = `
|
|
|
9857
12159
|
<button type="button" class="dxr-tab" role="tab" data-tab="home" aria-selected="true">Home</button>
|
|
9858
12160
|
<button type="button" class="dxr-tab" role="tab" data-tab="insert" aria-selected="false">Insert</button>
|
|
9859
12161
|
<button type="button" class="dxr-tab" role="tab" data-tab="layout" aria-selected="false">Layout</button>
|
|
12162
|
+
<button type="button" class="dxr-tab" role="tab" data-tab="review" aria-selected="false">Review</button>
|
|
9860
12163
|
<button type="button" class="dxr-tab" role="tab" data-tab="table" data-contextual aria-selected="false" hidden>Table</button>
|
|
9861
12164
|
</div>
|
|
9862
12165
|
|
|
@@ -10022,6 +12325,31 @@ var RIBBON_HTML = `
|
|
|
10022
12325
|
</div>
|
|
10023
12326
|
</div>
|
|
10024
12327
|
|
|
12328
|
+
<!-- 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
|
|
12329
|
+
Comment authoring over the session's native comment family (issue #580):
|
|
12330
|
+
the button comments the selection (the whole block when collapsed), and
|
|
12331
|
+
the thread list is live session truth with resolve/reopen per thread. -->
|
|
12332
|
+
<div class="dxr-panel" data-panel="review">
|
|
12333
|
+
<div class="dxr-group">
|
|
12334
|
+
<span class="dxr-glabel">Comments</span>
|
|
12335
|
+
<div class="dxr-row">
|
|
12336
|
+
<input data-dxr="commenttext" type="text" placeholder="Comment text…"
|
|
12337
|
+
title="The comment body; markdown is honored" />
|
|
12338
|
+
<button type="button" data-dxr="comment"
|
|
12339
|
+
title="Comment on the selection, or on the whole block when nothing is selected">💬 Comment</button>
|
|
12340
|
+
</div>
|
|
12341
|
+
<div class="dxr-row">
|
|
12342
|
+
<span class="dxr-note">Comments the selected text; the whole block when the caret is collapsed.</span>
|
|
12343
|
+
</div>
|
|
12344
|
+
</div>
|
|
12345
|
+
<div class="dxr-group dxr-grow">
|
|
12346
|
+
<span class="dxr-glabel">Threads</span>
|
|
12347
|
+
<div class="dxr-threads" data-dxr="threads" aria-live="polite">
|
|
12348
|
+
<span class="dxr-note">No comments yet.</span>
|
|
12349
|
+
</div>
|
|
12350
|
+
</div>
|
|
12351
|
+
</div>
|
|
12352
|
+
|
|
10025
12353
|
<!-- 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
|
|
10026
12354
|
Row/column editing lives here rather than in a floating toolbar: a docked
|
|
10027
12355
|
contextual tab cannot overlap the cell you are editing. -->
|
|
@@ -10529,6 +12857,18 @@ var RibbonSurface = class {
|
|
|
10529
12857
|
if (this.live && style.value) this.run("style", () => this.live.setParagraphStyle(style.value));
|
|
10530
12858
|
style.value = "";
|
|
10531
12859
|
});
|
|
12860
|
+
const commentButton = this.control("comment");
|
|
12861
|
+
if (commentButton) {
|
|
12862
|
+
this.keepSelection(commentButton);
|
|
12863
|
+
commentButton.addEventListener("click", () => {
|
|
12864
|
+
if (!this.live) return;
|
|
12865
|
+
const input = this.control("commenttext");
|
|
12866
|
+
const text = input?.value.trim() || "New comment.";
|
|
12867
|
+
this.run("comment", () => this.live.addComment(text, this.options.revisionAuthor ?? "Reviewer"));
|
|
12868
|
+
if (input) input.value = "";
|
|
12869
|
+
this.renderThreads();
|
|
12870
|
+
});
|
|
12871
|
+
}
|
|
10532
12872
|
this.wireFileActions();
|
|
10533
12873
|
this.wireLayout();
|
|
10534
12874
|
this.wirePicker();
|
|
@@ -10651,6 +12991,52 @@ var RibbonSurface = class {
|
|
|
10651
12991
|
else panel.removeAttribute("data-active");
|
|
10652
12992
|
}
|
|
10653
12993
|
if (name === "layout") this.syncPageNumbering();
|
|
12994
|
+
if (name === "review") this.renderThreads();
|
|
12995
|
+
}
|
|
12996
|
+
/** Re-render the Review tab's thread list from session truth ({@link DocxEditor.listComments}):
|
|
12997
|
+
* one row per comment, replies indented under their thread root, resolve/reopen per root. */
|
|
12998
|
+
renderThreads() {
|
|
12999
|
+
const host = this.control("threads");
|
|
13000
|
+
if (!host || !this.live) return;
|
|
13001
|
+
const doc = this.element.ownerDocument ?? document;
|
|
13002
|
+
host.textContent = "";
|
|
13003
|
+
const comments = this.live.listComments();
|
|
13004
|
+
if (comments.length === 0) {
|
|
13005
|
+
const empty = doc.createElement("span");
|
|
13006
|
+
empty.className = "dxr-note";
|
|
13007
|
+
empty.textContent = "No comments yet.";
|
|
13008
|
+
host.appendChild(empty);
|
|
13009
|
+
return;
|
|
13010
|
+
}
|
|
13011
|
+
for (const entry of comments) {
|
|
13012
|
+
const row = doc.createElement("div");
|
|
13013
|
+
row.className = "dxr-thread";
|
|
13014
|
+
row.setAttribute("data-thread", entry.anchorId);
|
|
13015
|
+
if (entry.resolved) row.setAttribute("data-resolved", "");
|
|
13016
|
+
if (entry.parentAnchorId) row.setAttribute("data-reply", "");
|
|
13017
|
+
const author = doc.createElement("span");
|
|
13018
|
+
author.className = "dxr-tauthor";
|
|
13019
|
+
author.textContent = entry.author || "\u2014";
|
|
13020
|
+
row.appendChild(author);
|
|
13021
|
+
const text = doc.createElement("span");
|
|
13022
|
+
text.className = "dxr-ttext";
|
|
13023
|
+
text.textContent = entry.text;
|
|
13024
|
+
text.title = entry.text;
|
|
13025
|
+
row.appendChild(text);
|
|
13026
|
+
if (!entry.parentAnchorId) {
|
|
13027
|
+
const toggle = doc.createElement("button");
|
|
13028
|
+
toggle.type = "button";
|
|
13029
|
+
toggle.textContent = entry.resolved ? "Reopen" : "Resolve";
|
|
13030
|
+
toggle.title = entry.resolved ? "Reopen this comment thread" : "Mark this comment thread resolved";
|
|
13031
|
+
toggle.addEventListener("click", () => {
|
|
13032
|
+
if (!this.live) return;
|
|
13033
|
+
this.run(entry.resolved ? "reopen comment" : "resolve comment", () => this.live.setCommentResolved(entry.anchorId, !entry.resolved));
|
|
13034
|
+
this.renderThreads();
|
|
13035
|
+
});
|
|
13036
|
+
row.appendChild(toggle);
|
|
13037
|
+
}
|
|
13038
|
+
host.appendChild(row);
|
|
13039
|
+
}
|
|
10654
13040
|
}
|
|
10655
13041
|
// ── selection-driven state ──────────────────────────────────────────────────
|
|
10656
13042
|
selectionElement() {
|
|
@@ -10952,6 +13338,55 @@ async function toBytes(input) {
|
|
|
10952
13338
|
const buffer = await input.arrayBuffer();
|
|
10953
13339
|
return new Uint8Array(buffer);
|
|
10954
13340
|
}
|
|
13341
|
+
async function generatePackageManifest(document2) {
|
|
13342
|
+
const exports = ensureInitialized();
|
|
13343
|
+
const bytes = await toBytes(document2);
|
|
13344
|
+
await yieldToMain();
|
|
13345
|
+
return JSON.parse(
|
|
13346
|
+
exports.DocumentConverter.GeneratePackageManifest(bytes)
|
|
13347
|
+
);
|
|
13348
|
+
}
|
|
13349
|
+
async function verifyDeliverable(document2, baseline) {
|
|
13350
|
+
const exports = ensureInitialized();
|
|
13351
|
+
const bytes = await toBytes(document2);
|
|
13352
|
+
const baselineBytes = baseline === void 0 ? void 0 : await toBytes(baseline);
|
|
13353
|
+
await yieldToMain();
|
|
13354
|
+
return JSON.parse(
|
|
13355
|
+
baselineBytes === void 0 ? exports.DocumentConverter.VerifyDeliverable(bytes) : exports.DocumentConverter.VerifyDeliverableWithBaseline(
|
|
13356
|
+
baselineBytes,
|
|
13357
|
+
bytes
|
|
13358
|
+
)
|
|
13359
|
+
);
|
|
13360
|
+
}
|
|
13361
|
+
async function verifyDeliveryReceipt(receiptJson, artifacts) {
|
|
13362
|
+
const exports = ensureInitialized();
|
|
13363
|
+
const artifactsJson = artifacts === void 0 ? "" : JSON.stringify(
|
|
13364
|
+
Object.fromEntries(
|
|
13365
|
+
Object.entries(artifacts).map(([artifactId, bytes]) => [
|
|
13366
|
+
artifactId,
|
|
13367
|
+
bytesToBase64(bytes)
|
|
13368
|
+
])
|
|
13369
|
+
)
|
|
13370
|
+
);
|
|
13371
|
+
await yieldToMain();
|
|
13372
|
+
return JSON.parse(
|
|
13373
|
+
exports.DocumentConverter.VerifyDeliveryReceipt(receiptJson, artifactsJson)
|
|
13374
|
+
);
|
|
13375
|
+
}
|
|
13376
|
+
async function proveRedlineReversibility(baseline, intendedFinal, redline) {
|
|
13377
|
+
const exports = ensureInitialized();
|
|
13378
|
+
const baselineBytes = await toBytes(baseline);
|
|
13379
|
+
const intendedFinalBytes = await toBytes(intendedFinal);
|
|
13380
|
+
const redlineBytes = await toBytes(redline);
|
|
13381
|
+
await yieldToMain();
|
|
13382
|
+
return JSON.parse(
|
|
13383
|
+
exports.DocumentConverter.ProveRedlineReversibility(
|
|
13384
|
+
baselineBytes,
|
|
13385
|
+
intendedFinalBytes,
|
|
13386
|
+
redlineBytes
|
|
13387
|
+
)
|
|
13388
|
+
);
|
|
13389
|
+
}
|
|
10955
13390
|
async function renderBlockHtml(document2, anchorId, options) {
|
|
10956
13391
|
const exports = ensureInitialized();
|
|
10957
13392
|
const bytes = await toBytes(document2);
|
|
@@ -11212,21 +13647,30 @@ async function docxDiffGetRevisions(left2, right2, settings) {
|
|
|
11212
13647
|
throw new Error(`Failed to get DocxDiff revisions: ${error.error}`);
|
|
11213
13648
|
}
|
|
11214
13649
|
const parsed = JSON.parse(result);
|
|
11215
|
-
return (parsed.revisions || parsed.Revisions || []).map(
|
|
11216
|
-
|
|
11217
|
-
|
|
11218
|
-
|
|
11219
|
-
|
|
11220
|
-
|
|
11221
|
-
|
|
11222
|
-
|
|
11223
|
-
|
|
11224
|
-
|
|
11225
|
-
|
|
11226
|
-
|
|
11227
|
-
|
|
11228
|
-
|
|
11229
|
-
|
|
13650
|
+
return (parsed.revisions || parsed.Revisions || []).map(mapDocxDiffRevision);
|
|
13651
|
+
}
|
|
13652
|
+
async function docxDiffCompareProducts(left2, right2, settings, products) {
|
|
13653
|
+
const exports = ensureInitialized();
|
|
13654
|
+
const leftBytes = await toBytes(left2);
|
|
13655
|
+
const rightBytes = await toBytes(right2);
|
|
13656
|
+
await yieldToMain();
|
|
13657
|
+
const result = exports.DocxDiffBridge.CompareProductsJson(
|
|
13658
|
+
leftBytes,
|
|
13659
|
+
rightBytes,
|
|
13660
|
+
docxDiffSettingsJson(settings),
|
|
13661
|
+
products ? JSON.stringify(products) : ""
|
|
13662
|
+
);
|
|
13663
|
+
if (isErrorResponse(result)) {
|
|
13664
|
+
const error = parseError(result);
|
|
13665
|
+
throw new Error(`Failed to compare products: ${error.error}`);
|
|
13666
|
+
}
|
|
13667
|
+
const parsed = JSON.parse(result);
|
|
13668
|
+
return {
|
|
13669
|
+
redline: typeof parsed.redlineB64 === "string" ? Uint8Array.from(atob(parsed.redlineB64), (c) => c.charCodeAt(0)) : void 0,
|
|
13670
|
+
revisions: Array.isArray(parsed.revisions) ? parsed.revisions.map(mapDocxDiffRevision) : void 0,
|
|
13671
|
+
editScript: parsed.editScript !== void 0 ? parsed.editScript : void 0,
|
|
13672
|
+
semanticChanges: parsed.semanticChanges !== void 0 ? parsed.semanticChanges : void 0
|
|
13673
|
+
};
|
|
11230
13674
|
}
|
|
11231
13675
|
async function docxDiffGetEditScript(left2, right2, settings) {
|
|
11232
13676
|
const exports = ensureInitialized();
|
|
@@ -11244,6 +13688,22 @@ async function docxDiffGetEditScript(left2, right2, settings) {
|
|
|
11244
13688
|
}
|
|
11245
13689
|
return result;
|
|
11246
13690
|
}
|
|
13691
|
+
async function docxDiffGetSemanticChanges(left2, right2, settings) {
|
|
13692
|
+
const exports = ensureInitialized();
|
|
13693
|
+
const leftBytes = await toBytes(left2);
|
|
13694
|
+
const rightBytes = await toBytes(right2);
|
|
13695
|
+
await yieldToMain();
|
|
13696
|
+
const result = exports.DocxDiffBridge.GetSemanticChangesJson(
|
|
13697
|
+
leftBytes,
|
|
13698
|
+
rightBytes,
|
|
13699
|
+
docxDiffSettingsJson(settings)
|
|
13700
|
+
);
|
|
13701
|
+
if (isErrorResponse(result)) {
|
|
13702
|
+
const error = parseError(result);
|
|
13703
|
+
throw new Error(`Failed to get semantic changes: ${error.error}`);
|
|
13704
|
+
}
|
|
13705
|
+
return JSON.parse(result);
|
|
13706
|
+
}
|
|
11247
13707
|
async function docxDiffAcceptRevisions(redline) {
|
|
11248
13708
|
const exports = ensureInitialized();
|
|
11249
13709
|
const bytes = await toBytes(redline);
|
|
@@ -11497,6 +13957,7 @@ async function getDocumentStructure(document2) {
|
|
|
11497
13957
|
const parsed = JSON.parse(result);
|
|
11498
13958
|
const convertElement = (el) => ({
|
|
11499
13959
|
id: el.Id || el.id,
|
|
13960
|
+
anchorId: el.AnchorId || el.anchorId,
|
|
11500
13961
|
type: el.Type || el.type,
|
|
11501
13962
|
textPreview: el.TextPreview || el.textPreview,
|
|
11502
13963
|
index: el.Index ?? el.index,
|
|
@@ -11508,8 +13969,12 @@ async function getDocumentStructure(document2) {
|
|
|
11508
13969
|
});
|
|
11509
13970
|
const convertTableColumn = (col) => ({
|
|
11510
13971
|
tableId: col.TableId || col.tableId,
|
|
13972
|
+
anchorId: col.AnchorId || col.anchorId,
|
|
13973
|
+
tableAnchorId: col.TableAnchorId || col.tableAnchorId,
|
|
13974
|
+
isVirtual: col.IsVirtual ?? col.isVirtual ?? false,
|
|
11511
13975
|
columnIndex: col.ColumnIndex ?? col.columnIndex,
|
|
11512
13976
|
cellIds: col.CellIds || col.cellIds || [],
|
|
13977
|
+
cellAnchorIds: col.CellAnchorIds || col.cellAnchorIds || [],
|
|
11513
13978
|
rowCount: col.RowCount ?? col.rowCount
|
|
11514
13979
|
});
|
|
11515
13980
|
const root = convertElement(parsed.Root || parsed.root);
|
|
@@ -11572,7 +14037,8 @@ async function getDocumentMetadata(document2) {
|
|
|
11572
14037
|
hasEndnotes: parsed.HasEndnotes ?? parsed.hasEndnotes,
|
|
11573
14038
|
hasTrackedChanges: parsed.HasTrackedChanges ?? parsed.hasTrackedChanges,
|
|
11574
14039
|
hasComments: parsed.HasComments ?? parsed.hasComments,
|
|
11575
|
-
estimatedPageCount: parsed.EstimatedPageCount ?? parsed.estimatedPageCount
|
|
14040
|
+
estimatedPageCount: parsed.EstimatedPageCount ?? parsed.estimatedPageCount,
|
|
14041
|
+
estimatedPageCountSource: parsed.EstimatedPageCountSource ?? parsed.estimatedPageCountSource ?? "heuristic"
|
|
11576
14042
|
};
|
|
11577
14043
|
}
|
|
11578
14044
|
async function exportToOpenContract(document2) {
|
|
@@ -12368,6 +14834,7 @@ export {
|
|
|
12368
14834
|
addAnnotation,
|
|
12369
14835
|
addAnnotationToHtml,
|
|
12370
14836
|
addAnnotationWithTarget,
|
|
14837
|
+
clearPageCitationHighlight,
|
|
12371
14838
|
compareDocuments,
|
|
12372
14839
|
compareDocumentsToHtml,
|
|
12373
14840
|
compareDocumentsToHtmlWithLog,
|
|
@@ -12382,15 +14849,18 @@ export {
|
|
|
12382
14849
|
createEditor,
|
|
12383
14850
|
createExternalAnnotationSet,
|
|
12384
14851
|
createRibbonEditor,
|
|
14852
|
+
createUnavailablePageMap,
|
|
12385
14853
|
createViewer,
|
|
12386
14854
|
docxDiffAcceptRevisions,
|
|
12387
14855
|
docxDiffCompare,
|
|
14856
|
+
docxDiffCompareProducts,
|
|
12388
14857
|
docxDiffConsolidate,
|
|
12389
14858
|
docxDiffGetConflicts,
|
|
12390
14859
|
docxDiffGetConsolidatedEditScript,
|
|
12391
14860
|
docxDiffGetConsolidatedRevisions,
|
|
12392
14861
|
docxDiffGetEditScript,
|
|
12393
14862
|
docxDiffGetRevisions,
|
|
14863
|
+
docxDiffGetSemanticChanges,
|
|
12394
14864
|
docxDiffRejectRevisions,
|
|
12395
14865
|
exportToOpenContract,
|
|
12396
14866
|
findElementById,
|
|
@@ -12400,6 +14870,7 @@ export {
|
|
|
12400
14870
|
fitScale,
|
|
12401
14871
|
generateAnnotationCss,
|
|
12402
14872
|
generateAnnotationVisibilityCss,
|
|
14873
|
+
generatePackageManifest,
|
|
12403
14874
|
getAnnotations,
|
|
12404
14875
|
getDocumentMetadata,
|
|
12405
14876
|
getDocumentStructure,
|
|
@@ -12419,10 +14890,12 @@ export {
|
|
|
12419
14890
|
isMoveDestination,
|
|
12420
14891
|
isMoveSource,
|
|
12421
14892
|
mountRibbon,
|
|
14893
|
+
navigateToPageCitation,
|
|
12422
14894
|
openDocxSession2 as openDocxSession,
|
|
12423
14895
|
paginateHtml,
|
|
12424
14896
|
parseSectionDimensions,
|
|
12425
14897
|
projectAnnotationsOntoHtml,
|
|
14898
|
+
proveRedlineReversibility,
|
|
12426
14899
|
ptToPx,
|
|
12427
14900
|
pxToPt,
|
|
12428
14901
|
removeAnnotation,
|
|
@@ -12442,5 +14915,7 @@ export {
|
|
|
12442
14915
|
targetTableRow,
|
|
12443
14916
|
toDocumentBytes,
|
|
12444
14917
|
validateExternalAnnotations,
|
|
14918
|
+
verifyDeliverable,
|
|
14919
|
+
verifyDeliveryReceipt,
|
|
12445
14920
|
wasmBasePath
|
|
12446
14921
|
};
|