@beyondwork/docx-react-component 1.0.35 → 1.0.37

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (65) hide show
  1. package/README.md +103 -13
  2. package/package.json +1 -1
  3. package/src/api/package-version.ts +13 -0
  4. package/src/api/public-types.ts +84 -1
  5. package/src/core/commands/index.ts +19 -2
  6. package/src/core/selection/mapping.ts +6 -0
  7. package/src/io/docx-session.ts +24 -9
  8. package/src/io/export/build-app-properties-xml.ts +88 -0
  9. package/src/io/export/serialize-comments.ts +6 -1
  10. package/src/io/export/serialize-footnotes.ts +10 -9
  11. package/src/io/export/serialize-headers-footers.ts +11 -10
  12. package/src/io/export/serialize-main-document.ts +337 -50
  13. package/src/io/export/serialize-numbering.ts +115 -24
  14. package/src/io/export/serialize-tables.ts +13 -11
  15. package/src/io/export/table-properties-xml.ts +35 -16
  16. package/src/io/export/twip.ts +66 -0
  17. package/src/io/normalize/normalize-text.ts +5 -0
  18. package/src/io/ooxml/parse-footnotes.ts +2 -1
  19. package/src/io/ooxml/parse-headers-footers.ts +2 -1
  20. package/src/io/ooxml/parse-main-document.ts +21 -1
  21. package/src/legal/bookmarks.ts +78 -0
  22. package/src/model/canonical-document.ts +11 -0
  23. package/src/review/store/scope-tag-diff.ts +130 -0
  24. package/src/runtime/document-navigation.ts +1 -305
  25. package/src/runtime/document-runtime.ts +178 -16
  26. package/src/runtime/layout/docx-font-loader.ts +143 -0
  27. package/src/runtime/layout/index.ts +188 -0
  28. package/src/runtime/layout/inert-layout-facet.ts +45 -0
  29. package/src/runtime/layout/layout-engine-instance.ts +618 -0
  30. package/src/runtime/layout/layout-invalidation.ts +257 -0
  31. package/src/runtime/layout/layout-measurement-provider.ts +175 -0
  32. package/src/runtime/layout/measurement-backend-canvas.ts +307 -0
  33. package/src/runtime/layout/measurement-backend-empirical.ts +208 -0
  34. package/src/runtime/layout/page-fragment-mapper.ts +179 -0
  35. package/src/runtime/layout/page-graph.ts +433 -0
  36. package/src/runtime/layout/page-layout-snapshot-adapter.ts +70 -0
  37. package/src/runtime/layout/page-story-resolver.ts +195 -0
  38. package/src/runtime/layout/paginated-layout-engine.ts +788 -0
  39. package/src/runtime/layout/public-facet.ts +705 -0
  40. package/src/runtime/layout/resolved-formatting-document.ts +317 -0
  41. package/src/runtime/layout/resolved-formatting-state.ts +430 -0
  42. package/src/runtime/scope-tag-registry.ts +95 -0
  43. package/src/runtime/session-capabilities.ts +7 -4
  44. package/src/runtime/surface-projection.ts +1 -0
  45. package/src/runtime/text-ack-range.ts +49 -0
  46. package/src/ui/WordReviewEditor.tsx +15 -0
  47. package/src/ui/editor-runtime-boundary.ts +10 -1
  48. package/src/ui/editor-surface-controller.tsx +3 -0
  49. package/src/ui/headless/chrome-registry.ts +235 -0
  50. package/src/ui/headless/scoped-chrome-policy.ts +164 -0
  51. package/src/ui/headless/selection-tool-context.ts +2 -0
  52. package/src/ui/headless/selection-tool-resolver.ts +36 -17
  53. package/src/ui-tailwind/editor-surface/fast-text-edit-lane.ts +333 -0
  54. package/src/ui-tailwind/editor-surface/local-edit-session-state.ts +89 -0
  55. package/src/ui-tailwind/editor-surface/perf-probe.ts +21 -1
  56. package/src/ui-tailwind/editor-surface/pm-command-bridge.ts +8 -1
  57. package/src/ui-tailwind/editor-surface/pm-decorations.ts +73 -13
  58. package/src/ui-tailwind/editor-surface/predicted-position-map.ts +78 -0
  59. package/src/ui-tailwind/editor-surface/predicted-tag-preflight.ts +63 -0
  60. package/src/ui-tailwind/editor-surface/predicted-tx-gate.ts +39 -0
  61. package/src/ui-tailwind/editor-surface/tw-prosemirror-surface.tsx +173 -6
  62. package/src/ui-tailwind/theme/editor-theme.css +40 -14
  63. package/src/ui-tailwind/toolbar/tw-toolbar-icon-button.tsx +2 -2
  64. package/src/ui-tailwind/toolbar/tw-toolbar.tsx +235 -166
  65. package/src/ui-tailwind/tw-review-workspace.tsx +27 -1
@@ -47,12 +47,14 @@ import type {
47
47
  RuntimeContextAnalyticsQuery,
48
48
  RuntimeContextAnalyticsSnapshot,
49
49
  RuntimeRenderSnapshot,
50
+ ScopeTagTouch,
50
51
  SelectionSnapshot,
51
52
  SnapshotRefreshHints,
52
53
  SuggestionsSnapshot,
53
54
  SurfaceBlockSnapshot,
54
55
  SurfaceInlineSegment,
55
56
  StoryTextStreamSnapshot,
57
+ TextCommandAck,
56
58
  TocSnapshot,
57
59
  StyleCatalogSnapshot,
58
60
  TocRefreshOptions,
@@ -121,6 +123,12 @@ import {
121
123
  createDocumentNavigationSnapshot,
122
124
  findPageForOffset,
123
125
  } from "./document-navigation.ts";
126
+ import {
127
+ createLayoutEngine,
128
+ createLayoutFacet,
129
+ type LayoutEngineInstance,
130
+ type WordReviewEditorLayoutFacet,
131
+ } from "./layout/index.ts";
124
132
  import {
125
133
  createDocumentOutlineSnapshot,
126
134
  createDocumentSectionSnapshots,
@@ -150,6 +158,7 @@ import {
150
158
  resolveActiveSection,
151
159
  } from "./document-layout.ts";
152
160
  import { normalizeHeaderFooterTarget } from "./story-context.ts";
161
+ import { computeAdjustedRange as computeAdjustedRangeImpl } from "./text-ack-range.ts";
153
162
  import {
154
163
  getStoryBlocks,
155
164
  replaceStoryBlocks,
@@ -213,7 +222,7 @@ export interface DocumentRuntime {
213
222
  getCanonicalDocument(): CanonicalDocumentEnvelope;
214
223
  getSourcePackage(): EditorSessionState["sourcePackage"] | undefined;
215
224
  replaceText(text: string, target?: EditorAnchorProjection): void;
216
- applyActiveStoryTextCommand(command: ActiveStoryTextCommand): void;
225
+ applyActiveStoryTextCommand(command: ActiveStoryTextCommand): TextCommandAck;
217
226
  dispatch(command: EditorCommand): void;
218
227
  emitBlockedCommand(command: string, reasons: WorkflowBlockedCommandReason[]): void;
219
228
  undo(): void;
@@ -243,6 +252,12 @@ export interface DocumentRuntime {
243
252
  setZoom(level: ZoomLevel): void;
244
253
  getPageLayoutSnapshot(): PageLayoutSnapshot | null;
245
254
  getDocumentNavigationSnapshot(): DocumentNavigationSnapshot;
255
+ /**
256
+ * Runtime-owned layout facet. Provides graph-aware queries, fragment
257
+ * resolution, formatting inspection, and layout events. Prefer this over
258
+ * the opaque snapshot methods for new integration code.
259
+ */
260
+ readonly layout: WordReviewEditorLayoutFacet;
246
261
  getCurrentLocation(): DocumentLocationSnapshot | null;
247
262
  getLocationForSelection(selection: SelectionSnapshot): DocumentLocationSnapshot | null;
248
263
  getLocationForAnchor(
@@ -385,6 +400,23 @@ export function createDocumentRuntime(
385
400
  fatalError: options.fatalError as never,
386
401
  });
387
402
  storySelections.set(storyTargetKey(MAIN_STORY_TARGET), state.selection);
403
+
404
+ // Runtime-owned paginated layout engine (Phase 1+ of the layout facet work).
405
+ // The engine caches graph + resolved-formatting + fragment mapper keyed on
406
+ // (content, styles, subParts). It is the single internal source of truth
407
+ // for page composition, story resolution, and layout invalidation.
408
+ const layoutEngine: LayoutEngineInstance = createLayoutEngine();
409
+ const layoutFacet: WordReviewEditorLayoutFacet = createLayoutFacet({
410
+ engine: layoutEngine,
411
+ getQueryInput: () => ({
412
+ document: state.document,
413
+ viewState: {
414
+ activeStory,
415
+ workspaceMode: viewState.workspaceMode,
416
+ zoomLevel: viewState.zoomLevel,
417
+ },
418
+ }),
419
+ });
388
420
  let cachedSurface:
389
421
  | {
390
422
  revisionToken: string;
@@ -738,7 +770,7 @@ export function createDocumentRuntime(
738
770
  });
739
771
  }
740
772
 
741
- if (viewState.documentMode === "viewing") {
773
+ if (viewState.documentMode === "viewing" || viewState.documentMode === "commenting") {
742
774
  reasons.push({
743
775
  code: "document_viewing_mode",
744
776
  message: "Document is in viewing mode.",
@@ -822,8 +854,8 @@ export function createDocumentRuntime(
822
854
  function getEffectiveDocumentMode(
823
855
  selection: EditorState["selection"],
824
856
  ): DocumentMode {
825
- if (viewState.documentMode === "viewing") {
826
- return "viewing";
857
+ if (viewState.documentMode === "viewing" || viewState.documentMode === "commenting") {
858
+ return viewState.documentMode;
827
859
  }
828
860
  const matchingScope = getMatchingWorkflowScope(selection);
829
861
  if (matchingScope?.mode === "suggest") {
@@ -1529,13 +1561,13 @@ export function createDocumentRuntime(
1529
1561
  }
1530
1562
 
1531
1563
  if (command.type === "history.undo") {
1532
- if (viewState.documentMode === "viewing") return;
1564
+ if (viewState.documentMode === "viewing" || viewState.documentMode === "commenting") return;
1533
1565
  applyHistory("undo");
1534
1566
  return;
1535
1567
  }
1536
1568
 
1537
1569
  if (command.type === "history.redo") {
1538
- if (viewState.documentMode === "viewing") return;
1570
+ if (viewState.documentMode === "viewing" || viewState.documentMode === "commenting") return;
1539
1571
  applyHistory("redo");
1540
1572
  return;
1541
1573
  }
@@ -1604,9 +1636,21 @@ export function createDocumentRuntime(
1604
1636
  },
1605
1637
  applyActiveStoryTextCommand(command) {
1606
1638
  try {
1607
- applyTextCommandInActiveStory(command);
1639
+ return applyTextCommandInActiveStory(command);
1608
1640
  } catch (error) {
1609
- emitError(toRuntimeError(error));
1641
+ const runtimeError = toRuntimeError(error);
1642
+ emitError(runtimeError);
1643
+ return {
1644
+ kind: "rejected",
1645
+ opId: (command.origin as { opId?: string } | undefined)?.opId,
1646
+ newRevisionToken: "",
1647
+ blockedReasons: [
1648
+ {
1649
+ code: runtimeError.code ?? "runtime_error",
1650
+ message: runtimeError.message,
1651
+ },
1652
+ ],
1653
+ };
1610
1654
  }
1611
1655
  },
1612
1656
  addComment(params) {
@@ -1813,6 +1857,7 @@ export function createDocumentRuntime(
1813
1857
  getDocumentNavigationSnapshot() {
1814
1858
  return getCachedDocumentNavigationSnapshot(state, activeStory);
1815
1859
  },
1860
+ layout: layoutFacet,
1816
1861
  getCurrentLocation() {
1817
1862
  const navigation = getCachedDocumentNavigationSnapshot(state, activeStory);
1818
1863
  return createCurrentLocation({
@@ -2272,6 +2317,25 @@ export function createDocumentRuntime(
2272
2317
  protectionSnapshot = remapProtectionSnapshot(protectionSnapshot, transaction.mapping);
2273
2318
  state = finalizeState(transaction.nextState, transaction.markDirty, clock());
2274
2319
  storySelections.set(storyTargetKey(activeStory), state.selection);
2320
+
2321
+ // Signal a bounded content-edit invalidation to the layout engine so the
2322
+ // next layout query can splice rather than rebuild the full graph. The
2323
+ // engine analyzes the reason against its cached graph and falls back to
2324
+ // a full rebuild when the edit crosses section boundaries or reaches a
2325
+ // page the engine cannot safely resume from.
2326
+ if (transaction.markDirty && transaction.mapping.steps.length > 0) {
2327
+ let minFrom = Infinity;
2328
+ let maxTo = -Infinity;
2329
+ for (const step of transaction.mapping.steps) {
2330
+ if (step.from < minFrom) minFrom = step.from;
2331
+ const end = step.from + step.insertSize;
2332
+ if (end > maxTo) maxTo = end;
2333
+ }
2334
+ if (minFrom < maxTo) {
2335
+ layoutEngine.invalidate({ kind: "content-edit", from: minFrom, to: maxTo });
2336
+ }
2337
+ }
2338
+
2275
2339
  cachedRenderSnapshot = refreshRenderSnapshot();
2276
2340
  notify(previous, state, transaction);
2277
2341
  }
@@ -2455,24 +2519,31 @@ export function createDocumentRuntime(
2455
2519
  selection?: EditorState["selection"];
2456
2520
  blockedCommandName?: string;
2457
2521
  } = {},
2458
- ): void {
2522
+ ): TextCommandAck {
2523
+ const opId = (command.origin as { opId?: string } | undefined)?.opId;
2459
2524
  const selection = options.selection ?? state.selection;
2460
2525
  if (
2461
2526
  activeStory.kind !== "main" &&
2462
2527
  getEffectiveDocumentMode(selection) === "suggesting" &&
2463
2528
  command.type === "paragraph.split"
2464
2529
  ) {
2530
+ const message = `"${command.type}" is not supported in suggesting mode for this story.`;
2465
2531
  emit({
2466
2532
  type: "command_blocked",
2467
2533
  documentId: state.documentId,
2468
2534
  command: options.blockedCommandName ?? command.type,
2469
2535
  reasons: [{
2470
2536
  code: "suggesting_unsupported",
2471
- message: `"${command.type}" is not supported in suggesting mode for this story.`,
2537
+ message,
2472
2538
  storyTarget: activeStory,
2473
2539
  }],
2474
2540
  });
2475
- return;
2541
+ return {
2542
+ kind: "rejected",
2543
+ opId,
2544
+ newRevisionToken: "",
2545
+ blockedReasons: [{ code: "suggesting_unsupported", message }],
2546
+ };
2476
2547
  }
2477
2548
  const blockedReasons = evaluateWorkflowBlockedReasons(selection, command.type);
2478
2549
  if (blockedReasons.length > 0) {
@@ -2482,7 +2553,12 @@ export function createDocumentRuntime(
2482
2553
  command: options.blockedCommandName ?? command.type,
2483
2554
  reasons: blockedReasons,
2484
2555
  });
2485
- return;
2556
+ return {
2557
+ kind: "rejected",
2558
+ opId,
2559
+ newRevisionToken: "",
2560
+ blockedReasons: blockedReasons.map((r) => ({ code: r.code, message: r.message })),
2561
+ };
2486
2562
  }
2487
2563
 
2488
2564
  const timestamp = command.origin?.timestamp ?? clock();
@@ -2499,8 +2575,15 @@ export function createDocumentRuntime(
2499
2575
  };
2500
2576
 
2501
2577
  if (activeStory.kind === "main") {
2502
- commit(executeEditorCommand(baseState, command, context));
2503
- return;
2578
+ const mainTransaction = executeEditorCommand(baseState, command, context);
2579
+ commit(mainTransaction);
2580
+ return classifyAck({
2581
+ command,
2582
+ opId,
2583
+ priorState: baseState,
2584
+ transaction: mainTransaction,
2585
+ newRevisionToken: state.revisionToken,
2586
+ });
2504
2587
  }
2505
2588
 
2506
2589
  const localState = createEditorState({
@@ -2531,7 +2614,11 @@ export function createDocumentRuntime(
2531
2614
  historyBoundary: "skip",
2532
2615
  markDirty: false,
2533
2616
  });
2534
- return;
2617
+ return {
2618
+ kind: "equivalent",
2619
+ opId,
2620
+ newRevisionToken: state.revisionToken,
2621
+ };
2535
2622
  }
2536
2623
 
2537
2624
  const nextDocument = replaceStoryBlocks(
@@ -2561,12 +2648,87 @@ export function createDocumentRuntime(
2561
2648
  context,
2562
2649
  );
2563
2650
 
2564
- commit({
2651
+ const mergedTransaction: EditorTransaction = {
2565
2652
  ...fullTransaction,
2566
2653
  effects: mergeTransactionEffects(fullTransaction.effects, localTransaction.effects),
2654
+ };
2655
+ commit(mergedTransaction);
2656
+ return classifyAck({
2657
+ command,
2658
+ opId,
2659
+ priorState: baseState,
2660
+ transaction: mergedTransaction,
2661
+ newRevisionToken: state.revisionToken,
2567
2662
  });
2568
2663
  }
2569
2664
 
2665
+ function classifyAck(params: {
2666
+ command: ActiveStoryTextCommand;
2667
+ opId: string | undefined;
2668
+ priorState: EditorState;
2669
+ transaction: EditorTransaction;
2670
+ newRevisionToken: string;
2671
+ }): TextCommandAck {
2672
+ const { opId, priorState, transaction, newRevisionToken } = params;
2673
+ const meta = transaction.mapping.metadata ?? {};
2674
+ const touches: readonly ScopeTagTouch[] =
2675
+ (meta.scopeTagTouches as readonly ScopeTagTouch[] | undefined) ?? [];
2676
+
2677
+ if (meta.invalidatesStructures) {
2678
+ return {
2679
+ kind: "structural-divergence",
2680
+ opId,
2681
+ newRevisionToken,
2682
+ scopeTagTouches: touches,
2683
+ };
2684
+ }
2685
+
2686
+ // A real touch means the runtime actually changed a tag anchor — not
2687
+ // merely "a text edit happened and might conceivably have touched one".
2688
+ // The coarse `affectsComments` / `affectsRevisions` flags today are set
2689
+ // unconditionally by the text-transaction pipeline, so we cannot trust
2690
+ // them to distinguish equivalent from adjusted. `scopeTagTouches` is the
2691
+ // fine-grained truth that the predicted lane needs.
2692
+ const touchedForAdjusted = touches.some(
2693
+ (t) =>
2694
+ t.behavior === "extended" ||
2695
+ t.behavior === "trimmed" ||
2696
+ t.behavior === "split" ||
2697
+ t.behavior === "detached",
2698
+ );
2699
+
2700
+ if (touchedForAdjusted) {
2701
+ const adjustedRange = computeAdjustedRange(priorState, transaction);
2702
+ return {
2703
+ kind: "adjusted",
2704
+ opId,
2705
+ newRevisionToken,
2706
+ adjustedRange,
2707
+ scopeTagTouches: touches,
2708
+ };
2709
+ }
2710
+
2711
+ return {
2712
+ kind: "equivalent",
2713
+ opId,
2714
+ newRevisionToken,
2715
+ scopeTagTouches: touches,
2716
+ };
2717
+ }
2718
+
2719
+ function computeAdjustedRange(
2720
+ prior: EditorState,
2721
+ transaction: EditorTransaction,
2722
+ ): { fromRuntime: number; toRuntime: number } {
2723
+ return computeAdjustedRangeImpl(
2724
+ { from: prior.selection.anchor, to: prior.selection.head },
2725
+ transaction.mapping.steps.map((step) => ({
2726
+ from: step.from,
2727
+ insertSize: step.insertSize,
2728
+ })),
2729
+ );
2730
+ }
2731
+
2570
2732
  function mergeTransactionEffects(
2571
2733
  base: EditorTransaction["effects"],
2572
2734
  local: EditorTransaction["effects"],
@@ -0,0 +1,143 @@
1
+ /**
2
+ * DocxFontLoader — best-effort registration of the document's declared font
3
+ * families with the browser's FontFace registry.
4
+ *
5
+ * Scope:
6
+ * - For each font family the document uses, resolve whether the browser
7
+ * can already render it.
8
+ * - If the package ships embedded font binary data, register each face via
9
+ * `new FontFace(family, data).load()` then `document.fonts.add(face)`.
10
+ * - Wait on `document.fonts.ready` to know when layout-affecting fonts are
11
+ * available, so the Canvas backend is measuring against real metrics.
12
+ *
13
+ * Non-goals:
14
+ * - This loader does not attempt to fetch fonts from external CDNs.
15
+ * - It does not attempt style-matching with Panose; that belongs in a
16
+ * font-substitution pass if we ever need it.
17
+ * - SSR and gRPC never run this loader.
18
+ */
19
+
20
+ export interface FontLoaderInput {
21
+ /** Font family names the document actively uses. */
22
+ families: readonly string[];
23
+ /**
24
+ * Optional embedded font payloads keyed by family name (uppercase-insensitive).
25
+ * Each entry holds binary data for regular / bold / italic / bold-italic
26
+ * variants. Callers may omit any variant; the loader will register only
27
+ * what is provided.
28
+ */
29
+ embeddedFontBytes?: Map<string, EmbeddedFontBytes>;
30
+ }
31
+
32
+ export interface EmbeddedFontBytes {
33
+ regular?: ArrayBuffer;
34
+ bold?: ArrayBuffer;
35
+ italic?: ArrayBuffer;
36
+ boldItalic?: ArrayBuffer;
37
+ }
38
+
39
+ export interface DocxFontLoader {
40
+ whenReady(): Promise<void>;
41
+ isSupported(): boolean;
42
+ /** Which families are currently registered or detected as available. */
43
+ getRegisteredFamilies(): readonly string[];
44
+ /** Force re-resolution of the ready promise (e.g. after adding more fonts). */
45
+ refresh(input: FontLoaderInput): void;
46
+ }
47
+
48
+ export function createDocxFontLoader(initial: FontLoaderInput): DocxFontLoader {
49
+ const supported =
50
+ typeof document !== "undefined" &&
51
+ typeof (globalThis as { FontFace?: unknown }).FontFace !== "undefined" &&
52
+ // Guard against jsdom which exposes FontFace but not document.fonts
53
+ Boolean((document as Document & { fonts?: FontFaceSet }).fonts);
54
+
55
+ let current: FontLoaderInput = initial;
56
+ let readyPromise: Promise<void>;
57
+ const registered = new Set<string>();
58
+
59
+ function run(input: FontLoaderInput): Promise<void> {
60
+ if (!supported) return Promise.resolve();
61
+ const fontSet = (document as Document & { fonts?: FontFaceSet }).fonts;
62
+ if (!fontSet) return Promise.resolve();
63
+
64
+ const pending: Array<Promise<unknown>> = [];
65
+
66
+ if (input.embeddedFontBytes) {
67
+ for (const [familyRaw, variants] of input.embeddedFontBytes) {
68
+ const family = familyRaw.trim();
69
+ if (!family) continue;
70
+
71
+ for (const [descriptor, data] of variantsOf(variants)) {
72
+ try {
73
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
74
+ const FontFaceCtor = (globalThis as any).FontFace as {
75
+ new (family: string, source: BufferSource, descriptors?: FontFaceDescriptors): FontFace;
76
+ };
77
+ const face = new FontFaceCtor(family, data, descriptor);
78
+ pending.push(
79
+ face.load().then((loaded) => {
80
+ fontSet.add(loaded);
81
+ registered.add(family);
82
+ }),
83
+ );
84
+ } catch {
85
+ // Single-face failures should not fail the whole batch.
86
+ }
87
+ }
88
+ }
89
+ }
90
+
91
+ // Mark declared families as registered if the browser already resolves
92
+ // them (e.g. system fonts like Calibri, Arial).
93
+ for (const family of input.families) {
94
+ try {
95
+ const probe = `12px "${family.replace(/"/g, "'")}", serif`;
96
+ if (fontSet.check(probe)) {
97
+ registered.add(family);
98
+ }
99
+ } catch {
100
+ // ignore
101
+ }
102
+ }
103
+
104
+ return Promise.all(pending)
105
+ .then(() => fontSet.ready)
106
+ .then(() => undefined);
107
+ }
108
+
109
+ readyPromise = run(current);
110
+
111
+ return {
112
+ whenReady() {
113
+ return readyPromise;
114
+ },
115
+ isSupported() {
116
+ return supported;
117
+ },
118
+ getRegisteredFamilies() {
119
+ return Array.from(registered);
120
+ },
121
+ refresh(input: FontLoaderInput) {
122
+ current = input;
123
+ readyPromise = run(current);
124
+ },
125
+ };
126
+ }
127
+
128
+ function* variantsOf(
129
+ variants: EmbeddedFontBytes,
130
+ ): IterableIterator<[FontFaceDescriptors, ArrayBuffer]> {
131
+ if (variants.regular) {
132
+ yield [{ weight: "400", style: "normal" }, variants.regular];
133
+ }
134
+ if (variants.bold) {
135
+ yield [{ weight: "700", style: "normal" }, variants.bold];
136
+ }
137
+ if (variants.italic) {
138
+ yield [{ weight: "400", style: "italic" }, variants.italic];
139
+ }
140
+ if (variants.boldItalic) {
141
+ yield [{ weight: "700", style: "italic" }, variants.boldItalic];
142
+ }
143
+ }
@@ -0,0 +1,188 @@
1
+ /**
2
+ * Runtime-owned paginated layout engine — internal API surface.
3
+ *
4
+ * Runtime consumption (live, wired):
5
+ * buildPageStack, computePageStack — shipped façade
6
+ * createLayoutEngine — stateful engine owned by DocumentRuntime
7
+ * resolvePageStories — per-page header/footer variant selection
8
+ * buildPageGraph — RuntimePageGraph construction
9
+ * analyzeInvalidation / computeFieldDirtiness — invalidation + field dirtiness
10
+ * createPageFragmentMapper — offset ↔ fragment ↔ region mapping
11
+ *
12
+ * Measurement:
13
+ * LayoutMeasurementProvider + empirical and canvas backends
14
+ * DocxFontLoader — FontFace registration for canvas fidelity
15
+ *
16
+ * Nothing in this module is part of the package's public API.
17
+ */
18
+
19
+ // ---------------------------------------------------------------------------
20
+ // Façade (backward-compatible entry point)
21
+ // ---------------------------------------------------------------------------
22
+
23
+ export {
24
+ buildPageStack,
25
+ buildPageStackFrom,
26
+ computePageStack,
27
+ requiresFullRecompute,
28
+ type LayoutInvalidationReason,
29
+ type PageStackResult,
30
+ } from "./paginated-layout-engine.ts";
31
+
32
+ // ---------------------------------------------------------------------------
33
+ // Stateful engine (Phase 1)
34
+ // ---------------------------------------------------------------------------
35
+
36
+ export {
37
+ createLayoutEngine,
38
+ findActivePageNode,
39
+ type LayoutEngineInstance,
40
+ type LayoutEngineViewState,
41
+ type LayoutEngineQueryInput,
42
+ type LayoutEngineEvent,
43
+ type CreateLayoutEngineOptions,
44
+ } from "./layout-engine-instance.ts";
45
+
46
+ // ---------------------------------------------------------------------------
47
+ // Formatting resolution
48
+ // ---------------------------------------------------------------------------
49
+
50
+ export {
51
+ resolveBlockFormatting,
52
+ resolveTextWidth,
53
+ resolveCharsPerLine,
54
+ calculateParagraphHeight,
55
+ resolveNumberingPrefixLength,
56
+ type ResolvedParagraphFormatting,
57
+ type ResolvedTableRowFormatting,
58
+ type LayoutTabStop,
59
+ } from "./resolved-formatting-state.ts";
60
+
61
+ export {
62
+ buildResolvedFormattingState,
63
+ collectUsedFontFamilies,
64
+ populateNumberingInstances,
65
+ getResolvedParagraph,
66
+ getResolvedRun,
67
+ type ResolvedFormattingState,
68
+ type ResolvedRunFormatting,
69
+ type ResolvedFontCatalog,
70
+ type ResolvedFontDescriptor,
71
+ type ResolvedTabSettings,
72
+ type ResolvedNumberingGeometry,
73
+ type ResolvedDocumentSettings,
74
+ type NumberingInstanceMetadata,
75
+ type RunId,
76
+ } from "./resolved-formatting-document.ts";
77
+
78
+ // ---------------------------------------------------------------------------
79
+ // Page story resolution + graph + fragment mapping
80
+ // ---------------------------------------------------------------------------
81
+
82
+ export {
83
+ resolvePageStories,
84
+ resolveDisplayPageNumber,
85
+ resolveTotalPageCount,
86
+ type ResolvedPageStories,
87
+ } from "./page-story-resolver.ts";
88
+
89
+ export {
90
+ buildPageGraph,
91
+ spliceGraph,
92
+ findPageNodeForOffset,
93
+ findPagesForSection,
94
+ findPageForStoryTarget,
95
+ toDocumentPageSnapshots,
96
+ type RuntimePageGraph,
97
+ type RuntimePageNode,
98
+ type RuntimePageRegions,
99
+ type RuntimePageRegion,
100
+ type RuntimeBlockFragment,
101
+ type RuntimeLineBox,
102
+ type RuntimeNoteAllocation,
103
+ type RuntimePageAnchor,
104
+ type BuildPageGraphInput,
105
+ } from "./page-graph.ts";
106
+
107
+ export {
108
+ createPageFragmentMapper,
109
+ rebuildMapper,
110
+ // Deprecated alias retained for one release after §6 E.5 rename:
111
+ updateMapperIncremental,
112
+ type PageFragmentMapper,
113
+ type PageFragmentLocation,
114
+ type PageSpan,
115
+ } from "./page-fragment-mapper.ts";
116
+
117
+ export {
118
+ derivePageLayoutSnapshotFromGraph,
119
+ deriveDocumentPageSnapshots,
120
+ deriveActivePageIndex,
121
+ deriveActiveSectionIndex,
122
+ deriveActivePage,
123
+ } from "./page-layout-snapshot-adapter.ts";
124
+
125
+ // ---------------------------------------------------------------------------
126
+ // Invalidation + field dirtiness
127
+ // ---------------------------------------------------------------------------
128
+
129
+ export {
130
+ analyzeInvalidation,
131
+ computeFieldDirtiness,
132
+ type InvalidationResult,
133
+ } from "./layout-invalidation.ts";
134
+
135
+ // ---------------------------------------------------------------------------
136
+ // Measurement provider
137
+ // ---------------------------------------------------------------------------
138
+
139
+ export {
140
+ createMeasurementProvider,
141
+ createEmpiricalMeasurementProvider,
142
+ type LayoutMeasurementProvider,
143
+ type MeasurementFidelity,
144
+ type MeasurementBackendPreference,
145
+ type CreateMeasurementProviderOptions,
146
+ type MeasureLineFragmentsInput,
147
+ type MeasuredLineFragments,
148
+ type MeasureInlineObjectInput,
149
+ type MeasuredInlineObject,
150
+ type MeasureTableBlockInput,
151
+ type MeasuredTableBlock,
152
+ } from "./layout-measurement-provider.ts";
153
+
154
+ export {
155
+ createDocxFontLoader,
156
+ type DocxFontLoader,
157
+ type FontLoaderInput,
158
+ type EmbeddedFontBytes,
159
+ } from "./docx-font-loader.ts";
160
+
161
+ // ---------------------------------------------------------------------------
162
+ // Public facet (Phase 7)
163
+ // ---------------------------------------------------------------------------
164
+
165
+ export { createInertLayoutFacet } from "./inert-layout-facet.ts";
166
+
167
+ export {
168
+ createLayoutFacet,
169
+ type WordReviewEditorLayoutFacet,
170
+ type PublicPageNode,
171
+ type PublicPageRegions,
172
+ type PublicPageRegion,
173
+ type PublicBlockFragment,
174
+ type PublicLineBox,
175
+ type PublicNoteAllocation,
176
+ type PublicPageAnchor,
177
+ type PublicPageSpan,
178
+ type PublicSectionNode,
179
+ type PublicResolvedPageStories,
180
+ type PublicResolvedParagraphFormatting,
181
+ type PublicResolvedRunFormatting,
182
+ type PublicBlockMeasurement,
183
+ type PublicMeasurementFidelity,
184
+ type PublicFieldDirtinessReport,
185
+ type LayoutFacetEvent,
186
+ type LayoutFacetInvalidationReason,
187
+ type CreateLayoutFacetInput,
188
+ } from "./public-facet.ts";