@beyondwork/docx-react-component 1.0.109 → 1.0.111

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 (59) hide show
  1. package/package.json +1 -1
  2. package/src/api/public-types.ts +3 -0
  3. package/src/model/layout/page-graph-types.ts +33 -0
  4. package/src/model/layout/runtime-page-graph-types.ts +25 -0
  5. package/src/runtime/document-runtime.ts +46 -0
  6. package/src/runtime/geometry/adjacent-geometry-intake.ts +820 -15
  7. package/src/runtime/geometry/caret-geometry.ts +219 -7
  8. package/src/runtime/geometry/geometry-index.ts +52 -12
  9. package/src/runtime/geometry/object-handles.ts +42 -1
  10. package/src/runtime/layout/index.ts +3 -0
  11. package/src/runtime/layout/inert-layout-facet.ts +13 -0
  12. package/src/runtime/layout/layout-engine-instance.ts +233 -4
  13. package/src/runtime/layout/layout-engine-version.ts +47 -2
  14. package/src/runtime/layout/layout-facet-types.ts +3 -0
  15. package/src/runtime/layout/page-graph.ts +88 -7
  16. package/src/runtime/layout/paginated-layout-engine.ts +34 -0
  17. package/src/runtime/layout/project-block-fragments.ts +144 -1
  18. package/src/runtime/layout/public-facet.ts +228 -9
  19. package/src/runtime/layout/resolve-page-previews.ts +46 -8
  20. package/src/runtime/scopes/adjacent-geometry-evidence.ts +456 -0
  21. package/src/runtime/scopes/compile-scope-bundle.ts +8 -0
  22. package/src/runtime/scopes/evidence.ts +16 -0
  23. package/src/runtime/scopes/index.ts +13 -0
  24. package/src/runtime/scopes/semantic-scope-types.ts +67 -0
  25. package/src/ui-tailwind/chrome-overlay/tw-table-split-row-carry-overlay.tsx +62 -0
  26. package/src/ui-tailwind/debug/layer11-consumer-readiness.ts +104 -0
  27. package/src/ui-tailwind/editor-surface/pm-page-break-decorations.ts +50 -5
  28. package/src/ui-tailwind/editor-surface/tw-prosemirror-surface.tsx +27 -0
  29. package/src/ui-tailwind/page-stack/tw-page-chrome-entry.tsx +62 -0
  30. package/src/ui-tailwind/page-stack/tw-page-stack-chrome-layer.tsx +1 -0
  31. package/src/README.md +0 -85
  32. package/src/api/README.md +0 -26
  33. package/src/api/v3/README.md +0 -91
  34. package/src/component-inventory.md +0 -99
  35. package/src/core/README.md +0 -10
  36. package/src/core/commands/README.md +0 -3
  37. package/src/core/schema/README.md +0 -3
  38. package/src/core/selection/README.md +0 -3
  39. package/src/core/state/README.md +0 -3
  40. package/src/io/README.md +0 -10
  41. package/src/io/export/README.md +0 -3
  42. package/src/io/normalize/README.md +0 -3
  43. package/src/io/ooxml/README.md +0 -3
  44. package/src/io/opc/README.md +0 -3
  45. package/src/model/README.md +0 -3
  46. package/src/preservation/README.md +0 -3
  47. package/src/review/README.md +0 -16
  48. package/src/review/store/README.md +0 -3
  49. package/src/runtime/README.md +0 -3
  50. package/src/ui/README.md +0 -30
  51. package/src/ui/comments/README.md +0 -3
  52. package/src/ui/compatibility/README.md +0 -3
  53. package/src/ui/editor-surface/README.md +0 -3
  54. package/src/ui/review/README.md +0 -3
  55. package/src/ui/status/README.md +0 -3
  56. package/src/ui/theme/README.md +0 -3
  57. package/src/ui/toolbar/README.md +0 -3
  58. package/src/ui-tailwind/debug/README.md +0 -22
  59. package/src/validation/README.md +0 -3
@@ -49,6 +49,7 @@ import {
49
49
  buildPageStackFromWithSplits,
50
50
  buildPageStackWithSplits,
51
51
  type LayoutInvalidationReason,
52
+ type PageStackPaginationOptions,
52
53
  } from "./paginated-layout-engine.ts";
53
54
  import {
54
55
  buildPageGraph,
@@ -102,6 +103,21 @@ export interface LayoutEngineViewState {
102
103
  export interface LayoutEngineQueryInput {
103
104
  document: CanonicalDocumentEnvelope;
104
105
  viewState?: LayoutEngineViewState;
106
+ /**
107
+ * Optional page window for lazy pagination. L04 materializes this window
108
+ * plus its buffer and returns `unpaginated` placeholders for known pages
109
+ * outside it.
110
+ */
111
+ viewportPageWindow?: LayoutViewportPageWindow;
112
+ }
113
+
114
+ export interface LayoutViewportPageWindow {
115
+ startPageIndex: number;
116
+ endPageIndex: number;
117
+ /** Extra pages to materialize before/after the requested window. */
118
+ bufferPages?: number;
119
+ /** Optional total page estimate supplied by the viewport/runtime layer. */
120
+ estimatedPageCount?: number;
105
121
  }
106
122
 
107
123
  export interface LayoutEngineEvent {
@@ -227,9 +243,206 @@ interface CacheKey {
227
243
  content: CanonicalDocumentEnvelope["content"];
228
244
  styles: CanonicalDocumentEnvelope["styles"];
229
245
  subParts: CanonicalDocumentEnvelope["subParts"];
246
+ viewportWindowKey: string;
230
247
  // Note: view state does not invalidate the graph itself (graph is global).
231
248
  }
232
249
 
250
+ interface NormalizedViewportPageWindow {
251
+ startPageIndex: number;
252
+ endPageIndex: number;
253
+ estimatedPageCount?: number;
254
+ }
255
+
256
+ const FULL_VIEWPORT_WINDOW_KEY = "full";
257
+ const DEFAULT_VIEWPORT_PAGE_WINDOW_BUFFER = 1;
258
+
259
+ function normalizeViewportPageWindow(
260
+ window: LayoutViewportPageWindow | undefined,
261
+ ): NormalizedViewportPageWindow | undefined {
262
+ if (!window) return undefined;
263
+ const buffer = Number.isFinite(window.bufferPages)
264
+ ? Math.max(0, Math.floor(window.bufferPages ?? 0))
265
+ : DEFAULT_VIEWPORT_PAGE_WINDOW_BUFFER;
266
+ const rawStart = Number.isFinite(window.startPageIndex)
267
+ ? Math.floor(window.startPageIndex)
268
+ : 0;
269
+ const rawEnd = Number.isFinite(window.endPageIndex)
270
+ ? Math.floor(window.endPageIndex)
271
+ : rawStart;
272
+ const startPageIndex = Math.max(0, Math.min(rawStart, rawEnd) - buffer);
273
+ const endPageIndex = Math.max(
274
+ startPageIndex,
275
+ Math.max(rawStart, rawEnd) + buffer,
276
+ );
277
+ const estimatedPageCount =
278
+ window.estimatedPageCount !== undefined && Number.isFinite(window.estimatedPageCount)
279
+ ? Math.max(0, Math.floor(window.estimatedPageCount))
280
+ : undefined;
281
+ return {
282
+ startPageIndex,
283
+ endPageIndex,
284
+ ...(estimatedPageCount !== undefined ? { estimatedPageCount } : {}),
285
+ };
286
+ }
287
+
288
+ function viewportWindowKey(
289
+ window: NormalizedViewportPageWindow | undefined,
290
+ ): string {
291
+ if (!window) return FULL_VIEWPORT_WINDOW_KEY;
292
+ return [
293
+ "window",
294
+ window.startPageIndex,
295
+ window.endPageIndex,
296
+ window.estimatedPageCount ?? "unknown",
297
+ ].join(":");
298
+ }
299
+
300
+ function pageStackOptionsForWindow(
301
+ window: NormalizedViewportPageWindow | undefined,
302
+ ): PageStackPaginationOptions | undefined {
303
+ if (!window) return undefined;
304
+ return { maxPageCount: window.endPageIndex + 1 };
305
+ }
306
+
307
+ function applyViewportWindowMaterialization(
308
+ graph: RuntimePageGraph,
309
+ window: NormalizedViewportPageWindow | undefined,
310
+ priorGraph: RuntimePageGraph | null,
311
+ ): RuntimePageGraph {
312
+ if (!window) {
313
+ return graph.materialization?.kind === "complete"
314
+ ? graph
315
+ : { ...graph, materialization: { kind: "complete" } };
316
+ }
317
+
318
+ const estimatedPageCount = Math.max(
319
+ graph.pages.length,
320
+ window.endPageIndex + 1,
321
+ window.estimatedPageCount ?? 0,
322
+ priorGraph?.pages.length ?? 0,
323
+ );
324
+ const hiddenPageIds = new Set<string>();
325
+ const pages: RuntimePageNode[] = graph.pages.map((page) => {
326
+ const materialized =
327
+ page.pageIndex >= window.startPageIndex &&
328
+ page.pageIndex <= window.endPageIndex;
329
+ if (materialized) {
330
+ return { ...page, materialization: "paginated" };
331
+ }
332
+ hiddenPageIds.add(page.pageId);
333
+ return toUnpaginatedPlaceholder(page);
334
+ });
335
+
336
+ const seed = pages[pages.length - 1] ?? graph.pages[graph.pages.length - 1];
337
+ if (seed) {
338
+ for (let pageIndex = pages.length; pageIndex < estimatedPageCount; pageIndex += 1) {
339
+ const placeholder = createTrailingUnpaginatedPlaceholder(
340
+ graph.revision,
341
+ pageIndex,
342
+ seed,
343
+ );
344
+ hiddenPageIds.add(placeholder.pageId);
345
+ pages.push(placeholder);
346
+ }
347
+ }
348
+
349
+ const fragments = graph.fragments.filter((fragment) => !hiddenPageIds.has(fragment.pageId));
350
+ const anchors = graph.anchors.filter((anchor) => !hiddenPageIds.has(anchor.pageId));
351
+
352
+ return {
353
+ ...graph,
354
+ pages,
355
+ fragments,
356
+ anchors,
357
+ contentPageCount: pages.filter((page) => !page.isBlankFiller).length,
358
+ materialization: {
359
+ kind: "viewport-window",
360
+ requestedWindow: {
361
+ startPageIndex: window.startPageIndex,
362
+ endPageIndex: window.endPageIndex,
363
+ },
364
+ paginatedRange: {
365
+ startPageIndex: window.startPageIndex,
366
+ endPageIndex: Math.min(window.endPageIndex, Math.max(0, graph.pages.length - 1)),
367
+ },
368
+ estimatedPageCount,
369
+ },
370
+ };
371
+ }
372
+
373
+ function toUnpaginatedPlaceholder(page: RuntimePageNode): RuntimePageNode {
374
+ const { frame: _frame, divergences: _divergences, ...rest } = page;
375
+ void _frame;
376
+ void _divergences;
377
+ return {
378
+ ...rest,
379
+ regions: buildUnpaginatedRegions(page.layout),
380
+ lineBoxes: [],
381
+ noteAllocations: [],
382
+ materialization: "unpaginated",
383
+ };
384
+ }
385
+
386
+ function createTrailingUnpaginatedPlaceholder(
387
+ revision: number,
388
+ pageIndex: number,
389
+ seed: RuntimePageNode,
390
+ ): RuntimePageNode {
391
+ const displayPageNumber =
392
+ seed.stories.displayPageNumber + Math.max(0, pageIndex - seed.pageIndex);
393
+ return {
394
+ pageId: `page-${revision}-${pageIndex}`,
395
+ pageIndex,
396
+ sectionIndex: seed.sectionIndex,
397
+ pageInSection:
398
+ seed.pageInSection >= 0
399
+ ? seed.pageInSection + Math.max(0, pageIndex - seed.pageIndex)
400
+ : pageIndex,
401
+ startOffset: seed.endOffset,
402
+ endOffset: seed.endOffset,
403
+ layout: seed.layout,
404
+ stories: {
405
+ ...seed.stories,
406
+ isFirstPage: false,
407
+ isEvenPage: displayPageNumber % 2 === 0,
408
+ displayPageNumber,
409
+ },
410
+ regions: buildUnpaginatedRegions(seed.layout),
411
+ lineBoxes: [],
412
+ noteAllocations: [],
413
+ isBlankFiller: false,
414
+ materialization: "unpaginated",
415
+ };
416
+ }
417
+
418
+ function buildUnpaginatedRegions(
419
+ layout: PageLayoutSnapshot,
420
+ ): RuntimePageNode["regions"] {
421
+ const bodyWidth = Math.max(
422
+ 0,
423
+ layout.pageWidth - layout.marginLeft - layout.marginRight - layout.gutter,
424
+ );
425
+ const bodyHeight = Math.max(
426
+ 0,
427
+ layout.pageHeight - layout.marginTop - layout.marginBottom,
428
+ );
429
+ return {
430
+ body: {
431
+ kind: "body",
432
+ originTwips: layout.marginTop,
433
+ widthTwips: bodyWidth,
434
+ heightTwips: bodyHeight,
435
+ fragmentIds: [],
436
+ rectTwips: {
437
+ xTwips: layout.marginLeft,
438
+ yTwips: layout.marginTop,
439
+ widthTwips: bodyWidth,
440
+ heightTwips: bodyHeight,
441
+ },
442
+ },
443
+ };
444
+ }
445
+
233
446
  // ---------------------------------------------------------------------------
234
447
  // Perf-probe helper (§6 E.7)
235
448
  // ---------------------------------------------------------------------------
@@ -451,6 +664,7 @@ export function createLayoutEngine(
451
664
  const recomputeStart = telemetryOn ? telemetryNow() : 0;
452
665
  const pageCountBeforeRecompute = previousPageCount;
453
666
  const document = input.document;
667
+ const viewportWindow = normalizeViewportPageWindow(input.viewportPageWindow);
454
668
  const mainSurface = createEditorSurfaceSnapshot(
455
669
  document,
456
670
  createSelectionSnapshot(0, 0),
@@ -463,6 +677,7 @@ export function createLayoutEngine(
463
677
  sections,
464
678
  mainSurface,
465
679
  measurementProvider,
680
+ pageStackOptionsForWindow(viewportWindow),
466
681
  );
467
682
  const pages = pageStack.pages;
468
683
  const stories = resolvePageStories(pages);
@@ -478,6 +693,7 @@ export function createLayoutEngine(
478
693
  pages,
479
694
  bodyFragmentsByPageIndex,
480
695
  pageStack.fragmentMeasurementsByPageIndex,
696
+ mainSurface,
481
697
  );
482
698
  // P8.1b — merge per-note fragments (regionKind: "footnote-area") into the
483
699
  // main fragments map so buildPageGraph sees them alongside body fragments.
@@ -486,7 +702,7 @@ export function createLayoutEngine(
486
702
  const existing = fragmentsByPageIndex.get(pageIndex) ?? [];
487
703
  fragmentsByPageIndex.set(pageIndex, [...existing, ...noteFragments]);
488
704
  }
489
- const graph = buildPageGraph({
705
+ const measuredGraph = buildPageGraph({
490
706
  pages,
491
707
  sections,
492
708
  stories,
@@ -495,6 +711,11 @@ export function createLayoutEngine(
495
711
  noteAllocationsByPageIndex: pageStack.noteAllocationsByPageIndex,
496
712
  subParts: document.subParts,
497
713
  });
714
+ const graph = applyViewportWindowMaterialization(
715
+ measuredGraph,
716
+ viewportWindow,
717
+ cachedGraph,
718
+ );
498
719
 
499
720
  // Field dirtiness diff from previous graph
500
721
  const dirtyFamilies = computeFieldDirtiness(cachedGraph, graph);
@@ -504,7 +725,7 @@ export function createLayoutEngine(
504
725
 
505
726
  const formatting = buildResolvedFormattingState(document, mainSurface);
506
727
 
507
- const currentPageCount = resolveTotalPageCount(pages);
728
+ const currentPageCount = graph.contentPageCount;
508
729
  // Compute the delta only; `previousPageCount` is mutated AFTER all
509
730
  // emits so every emission reads the pre-commit value through the
510
731
  // `pageCountDelta` local. `incrementalRelayout` mirrors this order
@@ -520,6 +741,7 @@ export function createLayoutEngine(
520
741
  content: document.content,
521
742
  styles: document.styles,
522
743
  subParts: document.subParts,
744
+ viewportWindowKey: viewportWindowKey(viewportWindow),
523
745
  };
524
746
  cachedGraph = graph;
525
747
  cachedFormatting = formatting;
@@ -660,6 +882,7 @@ export function createLayoutEngine(
660
882
  freshSnapshotsToRebuild,
661
883
  freshBodyFragmentsByPageIndex,
662
884
  freshResult.fragmentMeasurementsByPageIndex,
885
+ mainSurface,
663
886
  );
664
887
  // P8.1b — merge per-note fragments into the fresh fragments map.
665
888
  const freshFragmentsByPageIndex = new Map(freshBodyFragmentsByPageIndex);
@@ -752,6 +975,7 @@ export function createLayoutEngine(
752
975
  content: document.content,
753
976
  styles: document.styles,
754
977
  subParts: document.subParts,
978
+ viewportWindowKey: FULL_VIEWPORT_WINDOW_KEY,
755
979
  };
756
980
  cachedGraph = splicedGraph;
757
981
  cachedFormatting = formatting;
@@ -780,12 +1004,15 @@ export function createLayoutEngine(
780
1004
 
781
1005
  function getGraphInternal(input: LayoutEngineQueryInput): RuntimePageGraph {
782
1006
  const document = input.document;
1007
+ const normalizedWindow = normalizeViewportPageWindow(input.viewportPageWindow);
1008
+ const currentViewportWindowKey = viewportWindowKey(normalizedWindow);
783
1009
  const keyEqual =
784
1010
  cachedGraph !== null &&
785
1011
  cachedKey !== null &&
786
1012
  cachedKey.content === document.content &&
787
1013
  cachedKey.styles === document.styles &&
788
- cachedKey.subParts === document.subParts;
1014
+ cachedKey.subParts === document.subParts &&
1015
+ cachedKey.viewportWindowKey === currentViewportWindowKey;
789
1016
 
790
1017
  if (keyEqual && pendingInvalidation === null) {
791
1018
  return cachedGraph!;
@@ -797,7 +1024,8 @@ export function createLayoutEngine(
797
1024
  if (
798
1025
  pending !== null &&
799
1026
  pending.result.scope === "bounded" &&
800
- cachedGraph !== null
1027
+ cachedGraph !== null &&
1028
+ normalizedWindow === undefined
801
1029
  ) {
802
1030
  const spliced = incrementalRelayout(input, pending);
803
1031
  if (spliced !== null) {
@@ -1009,6 +1237,7 @@ export function createLayoutEngine(
1009
1237
  content: document.content,
1010
1238
  styles: document.styles,
1011
1239
  subParts: document.subParts,
1240
+ viewportWindowKey: FULL_VIEWPORT_WINDOW_KEY,
1012
1241
  };
1013
1242
  previousPageCount = graph.contentPageCount;
1014
1243
  },
@@ -1233,8 +1233,49 @@
1233
1233
  * history-block bumped to v81 in isolation; ship-preview's rollup advances
1234
1234
  * to v87. Cache envelopes from v86 invalidate because table row ranges and
1235
1235
  * continuation cursor payloads can change.
1236
+ *
1237
+ * 88 — Ship-side rollup: pe2 commit 327244cee ("Add L04 lazy pagination
1238
+ * placeholders") landed L04 lazy pagination — layout queries can now pass
1239
+ * a viewport page window; windowed reads bound pagination by the requested
1240
+ * high-water page when possible and emit `unpaginated` page placeholders
1241
+ * outside the materialized range. Pe2 commits 378e1b9c6 ("feat(05): consume
1242
+ * numbering readback joins") + 64e64567f ("feat(05): project adjacent
1243
+ * geometry into l04 frames") additionally brushed `src/runtime/geometry/**`,
1244
+ * and pe2 commit d5597ed33 ("feat(11): consume field ledgers and split row
1245
+ * carry") brushed `src/runtime/layout/public-facet.ts` +
1246
+ * `resolve-page-previews.ts`, all without separate pe2 bumps. The pe2
1247
+ * history-block bumped to v82 in isolation; ship-preview's rollup advances
1248
+ * to v88. Cache envelopes from v87 invalidate because page graph
1249
+ * materialization state, cache-key semantics, geometry projections, and
1250
+ * surface preview shapes can change.
1251
+ *
1252
+ * 89 — Ship-side rollup: pe2 commit covering L04 pagination telemetry read
1253
+ * model — `WordReviewEditorLayoutFacet` now exposes
1254
+ * `getPaginationTelemetry()`, with per-page materialization, body-fragment
1255
+ * counts, body block-reference counts, line-box counts, and
1256
+ * materialized-page averages. Pagination output and cached graph payloads
1257
+ * are unchanged; cache envelopes from v88 invalidate because the public
1258
+ * layout facet interface grew a method used by viewport-window consumers.
1259
+ * The pe2 history-block bumped to v83 in isolation; ship-preview's rollup
1260
+ * advances to v89.
1261
+ *
1262
+ * 90 — Ship-side rollup: pe2 commit 2fcc15a10 ("feat(04): publish precision
1263
+ * producer layout facts") landed L04 precision producer facts — runtime
1264
+ * line boxes now carry page-local rects, baseline page coordinates,
1265
+ * paragraph direction, and per-run layout-estimate anchors with first/last
1266
+ * glyph bounds. Page-local anchored-object ledgers can carry
1267
+ * `anchorRectTwips` when canonical anchor metadata gives L04 a source/frame
1268
+ * placement. Pe2 commits a22c2e4ab ("feat(05): consume l04 producer
1269
+ * geometry facts") + 6e71ee2ad ("feat(05): publish frame-pixel adjacent
1270
+ * geometry") additionally brushed `src/runtime/geometry/**`, and pe2
1271
+ * commits 883e46bb7 + 8519112d0 ("fix(11): suppress table-internal page
1272
+ * chrome / anchors") brushed `src/runtime/layout/page-graph.ts` without
1273
+ * separate pe2 bumps. The pe2 history-block bumped to v84 in isolation;
1274
+ * ship-preview's rollup advances to v90. Pagination output is unchanged,
1275
+ * but public graph/read-model semantics grew for L05 exact caret/object
1276
+ * projection consumers and for L11 page-anchor suppression in tables.
1236
1277
  */
1237
- export const LAYOUT_ENGINE_VERSION = 87 as const;
1278
+ export const LAYOUT_ENGINE_VERSION = 90 as const;
1238
1279
 
1239
1280
  /**
1240
1281
  * Serialization schema version for the LayCache envelope and cached payload
@@ -1275,5 +1316,9 @@ export const LAYOUT_ENGINE_VERSION = 87 as const;
1275
1316
  * `numberingRows[]` for all numbered paragraphs represented by the
1276
1317
  * fragment, including table/SDT-nested paragraphs. Pagination output is
1277
1318
  * unchanged, but cached graph payload shape grows.
1319
+ * 7 — L04 precision producer graph payload: line boxes carry page-local
1320
+ * rect/baseline/direction/run-anchor facts, and page-local anchored
1321
+ * object ledgers can carry source-anchor `anchorRectTwips`. Pagination
1322
+ * output is unchanged, but cached graph payload shape grows.
1278
1323
  */
1279
- export const LAYCACHE_SCHEMA_VERSION = 6 as const;
1324
+ export const LAYCACHE_SCHEMA_VERSION = 7 as const;
@@ -42,11 +42,14 @@ export type {
42
42
  PublicRegionKind,
43
43
  PublicBlockFragment,
44
44
  PublicLineBox,
45
+ PublicLineRunAnchor,
45
46
  PublicNoteAllocation,
46
47
  PublicPageAnchor,
47
48
  PublicPageFrame,
48
49
  PublicPageLocalStoryInstance,
50
+ PublicPagePaginationTelemetry,
49
51
  PublicPageSpan,
52
+ PublicPaginationTelemetry,
50
53
  PublicSectionNode,
51
54
  PublicResolvedPageStories,
52
55
  PublicResolvedStoryField,
@@ -77,12 +77,16 @@ export type {
77
77
  RuntimeFragmentLayoutObject,
78
78
  RuntimeBlockFragment,
79
79
  RuntimeLineBox,
80
+ RuntimeLineRunAnchor,
80
81
  RuntimeNoteAllocation,
81
82
  RuntimePageAnchor,
82
83
  } from "../../model/layout/page-graph-types.ts";
83
84
 
84
85
  export type {
85
86
  RuntimePageGraph,
87
+ RuntimePageGraphMaterialization,
88
+ RuntimePageMaterialization,
89
+ RuntimePageWindowRange,
86
90
  RuntimePageNode,
87
91
  BuildPageGraphInput,
88
92
  } from "../../model/layout/runtime-page-graph-types.ts";
@@ -253,6 +257,7 @@ export function buildPageGraph(
253
257
  [],
254
258
  noteAllocations: pageNoteAllocations,
255
259
  isBlankFiller: page.pageInSection === -1,
260
+ materialization: "paginated",
256
261
  };
257
262
  pages.push(node);
258
263
 
@@ -562,6 +567,7 @@ function buildPageLocalStoryInstance(
562
567
  kind: target.kind,
563
568
  variant: target.variant,
564
569
  relationshipId: target.relationshipId,
570
+ region,
565
571
  })
566
572
  : { objects: [], divergences: [] };
567
573
  const signature = buildPageLocalStorySignature({
@@ -617,6 +623,10 @@ function buildPageLocalStorySignature(input: {
617
623
  object.display,
618
624
  object.extentTwips?.widthTwips ?? "",
619
625
  object.extentTwips?.heightTwips ?? "",
626
+ object.anchorRectTwips?.xTwips ?? "",
627
+ object.anchorRectTwips?.yTwips ?? "",
628
+ object.anchorRectTwips?.widthTwips ?? "",
629
+ object.anchorRectTwips?.heightTwips ?? "",
620
630
  object.relationshipIds?.join(",") ?? "",
621
631
  object.mediaIds?.join(",") ?? "",
622
632
  object.wrapMode ?? "",
@@ -728,6 +738,7 @@ interface StoryObjectContext {
728
738
  kind: "header" | "footer";
729
739
  variant: RuntimePageLocalStoryInstance["variant"];
730
740
  relationshipId: string;
741
+ region?: RuntimePageRegion;
731
742
  }
732
743
 
733
744
  function collectStoryAnchoredObjects(
@@ -832,13 +843,20 @@ function collectStoryAnchoredObjects(
832
843
  const preserveHint = getDrawingFramePreserveHint(inline);
833
844
  const relationshipIds = collectDrawingRelationshipIds(inline);
834
845
  const display = inline.anchor.display;
846
+ const extentTwips = extentTwipsFromEmu(
847
+ inline.anchor.extent.widthEmu,
848
+ inline.anchor.extent.heightEmu,
849
+ );
835
850
  pushObject({
836
851
  objectId: getDrawingFrameObjectId(inline, context.storyKey, ordinal),
837
852
  sourceType: "drawing-frame",
838
853
  display,
839
- extentTwips: extentTwipsFromEmu(
840
- inline.anchor.extent.widthEmu,
841
- inline.anchor.extent.heightEmu,
854
+ extentTwips,
855
+ anchorRectTwips: resolveObjectAnchorRectTwips(
856
+ context.region,
857
+ extentTwips,
858
+ inline.anchor.positionH,
859
+ inline.anchor.positionV,
842
860
  ),
843
861
  ...(relationshipIds.length > 0 ? { relationshipIds } : {}),
844
862
  ...(inline.content.type === "picture" && inline.content.mediaId
@@ -859,15 +877,22 @@ function collectStoryAnchoredObjects(
859
877
  case "wordart":
860
878
  case "vml_shape": {
861
879
  const preserveHint = inline.preserveOnlyObject;
880
+ const extentTwips = preserveHint?.extentEmu
881
+ ? extentTwipsFromEmu(
882
+ preserveHint.extentEmu.widthEmu,
883
+ preserveHint.extentEmu.heightEmu,
884
+ )
885
+ : undefined;
862
886
  pushObject({
863
887
  objectId: getPreserveOnlyObjectId(inline, context.storyKey, ordinal),
864
888
  sourceType: sourceTypeForInlineObject(inline.type),
865
889
  display: preserveHint?.display ?? "unknown",
866
- ...(preserveHint?.extentEmu
890
+ ...(extentTwips
867
891
  ? {
868
- extentTwips: extentTwipsFromEmu(
869
- preserveHint.extentEmu.widthEmu,
870
- preserveHint.extentEmu.heightEmu,
892
+ extentTwips,
893
+ anchorRectTwips: resolveObjectAnchorRectTwips(
894
+ context.region,
895
+ extentTwips,
871
896
  ),
872
897
  }
873
898
  : {}),
@@ -982,6 +1007,59 @@ function extentTwipsFromEmu(
982
1007
  };
983
1008
  }
984
1009
 
1010
+ function resolveObjectAnchorRectTwips(
1011
+ region: RuntimePageRegion | undefined,
1012
+ extentTwips: RuntimeStoryAnchoredObject["extentTwips"],
1013
+ positionH?: { relativeFrom: string; align?: string; offset?: number },
1014
+ positionV?: { relativeFrom: string; align?: string; offset?: number },
1015
+ ): RuntimeTwipsRect | undefined {
1016
+ if (!region?.rectTwips || !extentTwips) return undefined;
1017
+ const regionRect = region.rectTwips;
1018
+ const widthTwips = Math.max(0, extentTwips.widthTwips);
1019
+ const heightTwips = Math.max(0, extentTwips.heightTwips);
1020
+ return rect(
1021
+ resolveAxisTwips(
1022
+ regionRect.xTwips,
1023
+ regionRect.widthTwips,
1024
+ widthTwips,
1025
+ positionH?.align,
1026
+ positionH?.offset,
1027
+ ),
1028
+ resolveAxisTwips(
1029
+ regionRect.yTwips,
1030
+ regionRect.heightTwips,
1031
+ heightTwips,
1032
+ positionV?.align,
1033
+ positionV?.offset,
1034
+ ),
1035
+ widthTwips,
1036
+ heightTwips,
1037
+ );
1038
+ }
1039
+
1040
+ function resolveAxisTwips(
1041
+ originTwips: number,
1042
+ spanTwips: number,
1043
+ objectSpanTwips: number,
1044
+ align?: string,
1045
+ offsetEmu?: number,
1046
+ ): number {
1047
+ if (offsetEmu !== undefined) {
1048
+ return originTwips + Math.round(offsetEmu / EMUS_PER_TWIP);
1049
+ }
1050
+ switch (align) {
1051
+ case "center":
1052
+ return originTwips + Math.round((spanTwips - objectSpanTwips) / 2);
1053
+ case "right":
1054
+ case "bottom":
1055
+ return originTwips + Math.max(0, spanTwips - objectSpanTwips);
1056
+ case "left":
1057
+ case "top":
1058
+ default:
1059
+ return originTwips;
1060
+ }
1061
+ }
1062
+
985
1063
  function resolvePageInstanceFieldDisplayText(
986
1064
  family: string,
987
1065
  cachedDisplayText: string,
@@ -1390,6 +1468,9 @@ function pageNodesStructurallyEqual(
1390
1468
  if (a.startOffset !== b.startOffset) return false;
1391
1469
  if (a.endOffset !== b.endOffset) return false;
1392
1470
  if (a.isBlankFiller !== b.isBlankFiller) return false;
1471
+ if ((a.materialization ?? "paginated") !== (b.materialization ?? "paginated")) {
1472
+ return false;
1473
+ }
1393
1474
  if (a.regions.body.fragmentIds.length !== b.regions.body.fragmentIds.length) {
1394
1475
  return false;
1395
1476
  }