@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
@@ -34,9 +34,14 @@
34
34
  */
35
35
 
36
36
  import type { EditorStoryTarget } from "../../api/public-types";
37
+ import type {
38
+ PublicLineRunAnchor,
39
+ PublicTwipsRect,
40
+ } from "../layout/public-facet.ts";
37
41
  import type {
38
42
  RenderFrame,
39
43
  RenderFrameRect,
44
+ RenderLine,
40
45
  RenderStoryRegion,
41
46
  } from "../render/index.ts";
42
47
  import type {
@@ -58,6 +63,9 @@ export function resolveCaretGeometry(
58
63
  if (!frame) return null;
59
64
  if (!Number.isFinite(offset)) return null;
60
65
 
66
+ const projectedCaret = resolveProducerCaretGeometry(frame, offset, story);
67
+ if (projectedCaret) return projectedCaret;
68
+
61
69
  const anchorRect = frame.anchorIndex.byRuntimeOffset(offset, story);
62
70
  if (!anchorRect) return null;
63
71
 
@@ -113,12 +121,7 @@ export function resolveSelectionRects(
113
121
  if (lo === hi) {
114
122
  const caret = resolveCaretGeometry(frame, lo, range.story);
115
123
  if (!caret) return [];
116
- // Caret rect position for a collapsed range is an exact anchor-index
117
- // read. The placeholder-grade metadata on `CaretGeometry` (direction,
118
- // baseline) applies to the caret as a whole, not to the rect's
119
- // position, so make the rect precision explicit for downstream
120
- // compositor consumers.
121
- return [{ ...caret.rect, precision: "exact" }];
124
+ return [{ ...caret.rect, precision: caret.rect.precision ?? "exact" }];
122
125
  }
123
126
 
124
127
  const lineRects = resolveLineSelectionRects(frame, lo, hi, range.story);
@@ -178,7 +181,10 @@ function resolveLineSelectionRects(
178
181
  : blockFrom +
179
182
  Math.floor((span * (i + 1)) / Math.max(1, lineCount));
180
183
  if (lineTo <= from || lineFrom >= to) continue;
181
- rects.push(toGeometryRect(line.frame, "within-tolerance"));
184
+ rects.push(
185
+ resolveProducerSelectionRect(line, lineFrom, lineTo, from, to) ??
186
+ toGeometryRect(line.frame, "within-tolerance"),
187
+ );
182
188
  }
183
189
  }
184
190
  }
@@ -186,6 +192,212 @@ function resolveLineSelectionRects(
186
192
  return rects;
187
193
  }
188
194
 
195
+ function resolveProducerCaretGeometry(
196
+ frame: RenderFrame,
197
+ offset: number,
198
+ story?: EditorStoryTarget,
199
+ ): CaretGeometry | null {
200
+ const located = findProducerLineForOffset(frame, offset, story);
201
+ if (!located) return null;
202
+ const { line, lineFrom, lineTo } = located;
203
+ const lineRectTwips = producerLineRectTwips(line);
204
+ if (!lineRectTwips) return null;
205
+ const contentRectTwips = producerLineContentRectTwips(line) ?? lineRectTwips;
206
+ const direction = producerLineDirection(line);
207
+ const span = Math.max(1, lineTo - lineFrom);
208
+ const ratio = clamp((offset - lineFrom) / span, 0, 1);
209
+ const inlineRatio = direction === "rtl" ? 1 - ratio : ratio;
210
+ const caretXTwips =
211
+ contentRectTwips.xTwips + contentRectTwips.widthTwips * inlineRatio;
212
+ const caretFrame = projectLineTwipsRect(line, lineRectTwips, {
213
+ xTwips: caretXTwips,
214
+ yTwips: lineRectTwips.yTwips,
215
+ widthTwips: 0,
216
+ heightTwips: lineRectTwips.heightTwips,
217
+ });
218
+ if (!caretFrame) return null;
219
+ const baseline =
220
+ line.line.baselinePageYTwips !== undefined
221
+ ? projectPageYToLineOffsetPx(
222
+ line,
223
+ lineRectTwips,
224
+ line.line.baselinePageYTwips,
225
+ )
226
+ : producerRunBaselinePageYTwips(line) !== undefined
227
+ ? projectPageYToLineOffsetPx(
228
+ line,
229
+ lineRectTwips,
230
+ producerRunBaselinePageYTwips(line)!,
231
+ )
232
+ : resolveBaselinePx(caretFrame);
233
+ return {
234
+ rect: {
235
+ ...toGeometryRect(caretFrame, "within-tolerance"),
236
+ widthPx: 0,
237
+ },
238
+ baseline,
239
+ height: caretFrame.heightPx,
240
+ direction,
241
+ precision: "within-tolerance",
242
+ };
243
+ }
244
+
245
+ function findProducerLineForOffset(
246
+ frame: RenderFrame,
247
+ offset: number,
248
+ story?: EditorStoryTarget,
249
+ ):
250
+ | {
251
+ line: RenderLine;
252
+ lineFrom: number;
253
+ lineTo: number;
254
+ }
255
+ | null {
256
+ if (!Array.isArray(frame.pages)) return null;
257
+ for (const page of frame.pages) {
258
+ for (const region of collectRegions(page.regions)) {
259
+ if (!storyMatches(region.storyTarget, story)) continue;
260
+ for (const block of region.blocks) {
261
+ const blockFrom = block.fragment.from;
262
+ const blockTo = block.fragment.to;
263
+ if (offset < blockFrom || offset > blockTo) continue;
264
+ if (block.lines.length === 0) continue;
265
+ const span = Math.max(1, blockTo - blockFrom);
266
+ const lineCount = block.lines.length;
267
+ for (let i = 0; i < lineCount; i += 1) {
268
+ const lineFrom =
269
+ blockFrom + Math.floor((span * i) / Math.max(1, lineCount));
270
+ const lineTo =
271
+ i === lineCount - 1
272
+ ? blockTo
273
+ : blockFrom +
274
+ Math.floor((span * (i + 1)) / Math.max(1, lineCount));
275
+ const contains =
276
+ i === lineCount - 1
277
+ ? offset >= lineFrom && offset <= lineTo
278
+ : offset >= lineFrom && offset < lineTo;
279
+ if (!contains) continue;
280
+ const line = block.lines[i]!;
281
+ if (!producerLineRectTwips(line)) continue;
282
+ return { line, lineFrom, lineTo };
283
+ }
284
+ }
285
+ }
286
+ }
287
+ return null;
288
+ }
289
+
290
+ function resolveProducerSelectionRect(
291
+ line: RenderLine,
292
+ lineFrom: number,
293
+ lineTo: number,
294
+ from: number,
295
+ to: number,
296
+ ): GeometryRect | null {
297
+ const lineRectTwips = producerLineRectTwips(line);
298
+ if (!lineRectTwips) return null;
299
+ const contentRectTwips = producerLineContentRectTwips(line) ?? lineRectTwips;
300
+ const span = Math.max(1, lineTo - lineFrom);
301
+ const startRatio = clamp((Math.max(from, lineFrom) - lineFrom) / span, 0, 1);
302
+ const endRatio = clamp((Math.min(to, lineTo) - lineFrom) / span, 0, 1);
303
+ if (endRatio <= startRatio) return null;
304
+ const direction = producerLineDirection(line);
305
+ const leftRatio = direction === "rtl" ? 1 - endRatio : startRatio;
306
+ const rightRatio = direction === "rtl" ? 1 - startRatio : endRatio;
307
+ const leftTwips =
308
+ contentRectTwips.xTwips + contentRectTwips.widthTwips * leftRatio;
309
+ const rightTwips =
310
+ contentRectTwips.xTwips + contentRectTwips.widthTwips * rightRatio;
311
+ const frameRect = projectLineTwipsRect(line, lineRectTwips, {
312
+ xTwips: leftTwips,
313
+ yTwips: contentRectTwips.yTwips,
314
+ widthTwips: Math.max(0, rightTwips - leftTwips),
315
+ heightTwips: contentRectTwips.heightTwips,
316
+ });
317
+ return frameRect ? toGeometryRect(frameRect, "within-tolerance") : null;
318
+ }
319
+
320
+ function producerLineRectTwips(line: RenderLine): PublicTwipsRect | undefined {
321
+ return line.line.rectTwips ?? line.line.runAnchors?.[0]?.lineRectTwips;
322
+ }
323
+
324
+ function producerLineContentRectTwips(
325
+ line: RenderLine,
326
+ ): PublicTwipsRect | undefined {
327
+ const anchors = line.line.runAnchors ?? [];
328
+ if (anchors.length === 0) return undefined;
329
+ return unionRunRectTwips(anchors);
330
+ }
331
+
332
+ function producerLineDirection(line: RenderLine): "ltr" | "rtl" {
333
+ return line.line.direction ?? line.line.runAnchors?.[0]?.direction ?? "ltr";
334
+ }
335
+
336
+ function producerRunBaselinePageYTwips(
337
+ line: RenderLine,
338
+ ): number | undefined {
339
+ return line.line.runAnchors?.[0]?.baselinePageYTwips;
340
+ }
341
+
342
+ function unionRunRectTwips(
343
+ anchors: readonly PublicLineRunAnchor[],
344
+ ): PublicTwipsRect | undefined {
345
+ let left = Number.POSITIVE_INFINITY;
346
+ let top = Number.POSITIVE_INFINITY;
347
+ let right = Number.NEGATIVE_INFINITY;
348
+ let bottom = Number.NEGATIVE_INFINITY;
349
+ for (const anchor of anchors) {
350
+ const rect = anchor.runRectTwips;
351
+ left = Math.min(left, rect.xTwips);
352
+ top = Math.min(top, rect.yTwips);
353
+ right = Math.max(right, rect.xTwips + rect.widthTwips);
354
+ bottom = Math.max(bottom, rect.yTwips + rect.heightTwips);
355
+ }
356
+ if (!Number.isFinite(left) || !Number.isFinite(top)) return undefined;
357
+ return {
358
+ xTwips: left,
359
+ yTwips: top,
360
+ widthTwips: Math.max(0, right - left),
361
+ heightTwips: Math.max(0, bottom - top),
362
+ };
363
+ }
364
+
365
+ function projectLineTwipsRect(
366
+ line: RenderLine,
367
+ lineRectTwips: PublicTwipsRect,
368
+ rect: PublicTwipsRect,
369
+ ): RenderFrameRect | null {
370
+ if (lineRectTwips.widthTwips <= 0 || lineRectTwips.heightTwips <= 0) {
371
+ return null;
372
+ }
373
+ const scaleX = line.frame.widthPx / lineRectTwips.widthTwips;
374
+ const scaleY = line.frame.heightPx / lineRectTwips.heightTwips;
375
+ return {
376
+ leftPx: line.frame.leftPx + (rect.xTwips - lineRectTwips.xTwips) * scaleX,
377
+ topPx: line.frame.topPx + (rect.yTwips - lineRectTwips.yTwips) * scaleY,
378
+ widthPx: rect.widthTwips * scaleX,
379
+ heightPx: rect.heightTwips * scaleY,
380
+ };
381
+ }
382
+
383
+ function projectPageYToLineOffsetPx(
384
+ line: RenderLine,
385
+ lineRectTwips: PublicTwipsRect,
386
+ pageYTwips: number,
387
+ ): number {
388
+ if (lineRectTwips.heightTwips <= 0) return resolveBaselinePx(line.frame);
389
+ const scaleY = line.frame.heightPx / lineRectTwips.heightTwips;
390
+ return clamp(
391
+ (pageYTwips - lineRectTwips.yTwips) * scaleY,
392
+ 0,
393
+ line.frame.heightPx,
394
+ );
395
+ }
396
+
397
+ function clamp(value: number, min: number, max: number): number {
398
+ return Math.min(Math.max(value, min), max);
399
+ }
400
+
189
401
  function collectRegions(
190
402
  regions: RenderFrame["pages"][number]["regions"],
191
403
  ): readonly RenderStoryRegion[] {
@@ -27,6 +27,7 @@ import {
27
27
  type CanonicalTableCellLayoutInput,
28
28
  type CanonicalTableRowLayoutInput,
29
29
  } from "../../model/canonical-layout-inputs.ts";
30
+ import type { PublicTwipsRect } from "../layout/public-facet.ts";
30
31
  import type {
31
32
  RenderFrame,
32
33
  RenderFrameRect,
@@ -106,6 +107,7 @@ export function projectGeometryIndexFromFrame(
106
107
  let splitRowCarryCount = 0;
107
108
 
108
109
  for (const page of frame.pages) {
110
+ if (!isPageGeometryMaterialized(page)) continue;
109
111
  const pageMetadata = pageFrameMetadata(page);
110
112
  pageFrameCompleteness[pageMetadata.frameCompleteness] += 1;
111
113
  layoutDivergenceObjectCount += pageMetadata.layoutDivergenceObjectIds.length;
@@ -313,7 +315,7 @@ export function projectGeometryIndexFromFrame(
313
315
 
314
316
  const objectHandles = finalizeObjectHandleEntries(objectHandleEntries);
315
317
  const coverage: GeometryIndexCoverage = {
316
- status: "realized",
318
+ status: coverageStatusForProjectedPages(frame, pages.length),
317
319
  pageCount: pages.length,
318
320
  pageFrameCompleteness,
319
321
  regionCount: regions.length,
@@ -367,6 +369,7 @@ export function summarizeGeometryCoverageFromFrame(
367
369
  let splitRowCarryCount = 0;
368
370
 
369
371
  for (const page of frame.pages) {
372
+ if (!isPageGeometryMaterialized(page)) continue;
370
373
  const pageMetadata = pageFrameMetadata(page);
371
374
  pageCount += 1;
372
375
  pageFrameCompleteness[pageMetadata.frameCompleteness] += 1;
@@ -407,7 +410,7 @@ export function summarizeGeometryCoverageFromFrame(
407
410
  }
408
411
 
409
412
  return {
410
- status: "realized",
413
+ status: coverageStatusForProjectedPages(frame, pageCount),
411
414
  pageCount,
412
415
  pageFrameCompleteness,
413
416
  regionCount,
@@ -433,6 +436,19 @@ interface PageFrameMetadata {
433
436
  divergenceIdsByObjectId: ReadonlyMap<string, readonly string[]>;
434
437
  }
435
438
 
439
+ function isPageGeometryMaterialized(page: RenderPage): boolean {
440
+ return page.page.materialization !== "unpaginated";
441
+ }
442
+
443
+ function coverageStatusForProjectedPages(
444
+ frame: RenderFrame,
445
+ projectedPageCount: number,
446
+ ): GeometryRehydrationStatus {
447
+ return frame.pages.length > 0 && projectedPageCount === 0
448
+ ? "unavailable"
449
+ : "realized";
450
+ }
451
+
436
452
  function pageFrameMetadata(page: RenderPage): PageFrameMetadata {
437
453
  const frame = page.page.frame;
438
454
  const divergenceIds = frame?.divergenceIds ? [...frame.divergenceIds] : [];
@@ -1294,25 +1310,32 @@ function appendPageLocalObjectHandleEntries(input: {
1294
1310
  story.kind === "header" ? page.regions.header?.frame : page.regions.footer?.frame;
1295
1311
  if (!regionFrame) continue;
1296
1312
  for (const object of story.anchoredObjects) {
1297
- const objectFrame = pageLocalObjectFrame(
1298
- regionFrame,
1299
- object.extentTwips,
1300
- pxPerTwip,
1313
+ const objectFrame = object.anchorRectTwips
1314
+ ? pageLocalObjectAnchorFrame(page, object.anchorRectTwips)
1315
+ : pageLocalObjectFrame(regionFrame, object.extentTwips, pxPerTwip);
1316
+ const entryPrecision: GeometryPrecision = object.anchorRectTwips
1317
+ ? "within-tolerance"
1318
+ : "heuristic";
1319
+ const entryStatus: GeometryRehydrationStatus = object.anchorRectTwips
1320
+ ? "realized"
1321
+ : "requires-rehydration";
1322
+ const handleRects = buildObjectHandleRectsFromRect(
1323
+ objectFrame,
1324
+ entryPrecision,
1301
1325
  );
1302
- const handleRects = buildObjectHandleRectsFromRect(objectFrame, "heuristic");
1303
1326
  const sourceIdentity: GeometrySourceIdentity = {
1304
1327
  storyKey: story.storyKey,
1305
1328
  objectKey: object.objectId,
1306
1329
  objectKind: object.sourceType,
1307
1330
  editPosture: object.preserveOnly ? "preserve-only" : "editable",
1308
- joinKind: "block-scoped",
1331
+ joinKind: object.anchorRectTwips ? "direct" : "block-scoped",
1309
1332
  };
1310
1333
  const existing = entries.get(object.objectId);
1311
1334
  if (existing) {
1312
1335
  appendUnique(existing.pageIds, page.page.pageId);
1313
1336
  existing.rects.push(...handleRects);
1314
1337
  appendDivergenceIds(existing, divergenceIdsByObjectId.get(object.objectId));
1315
- if (existing.precision !== "heuristic") {
1338
+ if (existing.precision !== "heuristic" && entryPrecision === "heuristic") {
1316
1339
  existing.precision = "heuristic";
1317
1340
  existing.status = "requires-rehydration";
1318
1341
  existing.sourceIdentity = sourceIdentity;
@@ -1323,16 +1346,33 @@ function appendPageLocalObjectHandleEntries(input: {
1323
1346
  objectId: object.objectId,
1324
1347
  pageIds: [page.page.pageId],
1325
1348
  rects: [...handleRects],
1326
- status: "requires-rehydration",
1327
- precision: "heuristic",
1349
+ status: entryStatus,
1350
+ precision: entryPrecision,
1328
1351
  ...mutableLayoutDivergenceIds(divergenceIdsByObjectId.get(object.objectId)),
1329
1352
  sourceIdentity,
1330
1353
  });
1331
- recordPrecision(precision, "heuristic");
1354
+ recordPrecision(precision, entryPrecision);
1332
1355
  }
1333
1356
  }
1334
1357
  }
1335
1358
 
1359
+ function pageLocalObjectAnchorFrame(
1360
+ page: RenderPage,
1361
+ rect: PublicTwipsRect,
1362
+ ): RenderFrameRect {
1363
+ const pageWidthTwips = page.page.layout.pageWidth;
1364
+ const pageHeightTwips = page.page.layout.pageHeight;
1365
+ const scaleX = pageWidthTwips > 0 ? page.frame.widthPx / pageWidthTwips : 1;
1366
+ const scaleY =
1367
+ pageHeightTwips > 0 ? page.frame.heightPx / pageHeightTwips : scaleX;
1368
+ return {
1369
+ leftPx: page.frame.leftPx + rect.xTwips * scaleX,
1370
+ topPx: page.frame.topPx + rect.yTwips * scaleY,
1371
+ widthPx: rect.widthTwips * scaleX,
1372
+ heightPx: rect.heightTwips * scaleY,
1373
+ };
1374
+ }
1375
+
1336
1376
  function pageLocalObjectFrame(
1337
1377
  regionFrame: RenderFrameRect,
1338
1378
  extentTwips:
@@ -26,7 +26,12 @@
26
26
  * index 8 — rotate anchor (20 px above the top-center handle)
27
27
  */
28
28
 
29
- import type { RenderFrame, RenderFrameRect } from "../render/index.ts";
29
+ import type { PublicTwipsRect } from "../layout/public-facet.ts";
30
+ import type {
31
+ RenderFrame,
32
+ RenderFrameRect,
33
+ RenderPage,
34
+ } from "../render/index.ts";
30
35
  import type { GeometryPrecision, GeometryRect } from "./geometry-types.ts";
31
36
 
32
37
  const ROTATE_HANDLE_OFFSET_PX = 20;
@@ -36,6 +41,10 @@ export function resolveObjectHandles(
36
41
  objectId: string,
37
42
  ): readonly GeometryRect[] {
38
43
  if (!frame) return [];
44
+ const pageLocalBBox = resolvePageLocalObjectFrame(frame, objectId);
45
+ if (pageLocalBBox) {
46
+ return buildObjectHandleRectsFromRect(pageLocalBBox, "within-tolerance");
47
+ }
39
48
  const bbox = frame.anchorIndex.byObjectId(objectId);
40
49
  if (!bbox) return [];
41
50
  return buildObjectHandleRectsFromRect(bbox);
@@ -78,3 +87,35 @@ export function buildObjectHandleRectsFromRect(
78
87
  point(midX, topPx - ROTATE_HANDLE_OFFSET_PX), // 8 rotate
79
88
  ];
80
89
  }
90
+
91
+ function resolvePageLocalObjectFrame(
92
+ frame: RenderFrame,
93
+ objectId: string,
94
+ ): RenderFrameRect | null {
95
+ if (!Array.isArray(frame.pages)) return null;
96
+ for (const page of frame.pages) {
97
+ for (const story of page.page.frame?.pageLocalStories ?? []) {
98
+ for (const object of story.anchoredObjects) {
99
+ if (object.objectId !== objectId || !object.anchorRectTwips) continue;
100
+ return projectPageTwipsRect(page, object.anchorRectTwips);
101
+ }
102
+ }
103
+ }
104
+ return null;
105
+ }
106
+
107
+ function projectPageTwipsRect(
108
+ page: RenderPage,
109
+ rect: PublicTwipsRect,
110
+ ): RenderFrameRect {
111
+ const pageWidthTwips = page.page.layout.pageWidth;
112
+ const pageHeightTwips = page.page.layout.pageHeight;
113
+ const scaleX = pageWidthTwips > 0 ? page.frame.widthPx / pageWidthTwips : 1;
114
+ const scaleY = pageHeightTwips > 0 ? page.frame.heightPx / pageHeightTwips : scaleX;
115
+ return {
116
+ leftPx: page.frame.leftPx + rect.xTwips * scaleX,
117
+ topPx: page.frame.topPx + rect.yTwips * scaleY,
118
+ widthPx: rect.widthTwips * scaleX,
119
+ heightPx: rect.heightTwips * scaleY,
120
+ };
121
+ }
@@ -181,11 +181,14 @@ export {
181
181
  type PublicRegionKind,
182
182
  type PublicBlockFragment,
183
183
  type PublicLineBox,
184
+ type PublicLineRunAnchor,
184
185
  type PublicNoteAllocation,
185
186
  type PublicPageAnchor,
186
187
  type PublicPageFrame,
187
188
  type PublicPageLocalStoryInstance,
189
+ type PublicPagePaginationTelemetry,
188
190
  type PublicPageSpan,
191
+ type PublicPaginationTelemetry,
189
192
  type PublicSectionNode,
190
193
  type PublicResolvedPageStories,
191
194
  type PublicResolvedStoryField,
@@ -10,6 +10,7 @@ import type {
10
10
  LayoutFacetEvent,
11
11
  PublicFieldDirtinessReport,
12
12
  PublicMeasurementFidelity,
13
+ PublicPaginationTelemetry,
13
14
  WordReviewEditorLayoutFacet,
14
15
  } from "./public-facet.ts";
15
16
  import { MARGIN_PRESET_CATALOG } from "./margin-preset-catalog.ts";
@@ -20,6 +21,17 @@ export function createInertLayoutFacet(): WordReviewEditorLayoutFacet {
20
21
  families: [],
21
22
  revision: 0,
22
23
  };
24
+ const emptyPaginationTelemetry: PublicPaginationTelemetry = {
25
+ revision: 0,
26
+ pageCount: 0,
27
+ materializedPageCount: 0,
28
+ unpaginatedPageCount: 0,
29
+ bodyFragmentCount: 0,
30
+ bodyBlockReferenceCount: 0,
31
+ averageBodyFragmentsPerMaterializedPage: 0,
32
+ averageBodyBlockReferencesPerMaterializedPage: 0,
33
+ pages: [],
34
+ };
23
35
  const fidelity: PublicMeasurementFidelity = "empirical";
24
36
  return {
25
37
  getPageCount: () => 0,
@@ -39,6 +51,7 @@ export function createInertLayoutFacet(): WordReviewEditorLayoutFacet {
39
51
  getStoryBlocksForRegion: () => [],
40
52
  getDocumentEndnoteBlocks: () => [],
41
53
  getFragmentsForPage: () => [],
54
+ getPaginationTelemetry: () => emptyPaginationTelemetry,
42
55
  getPageFormatCatalog: () => PAGE_FORMAT_CATALOG,
43
56
  getActivePageFormat: () => null,
44
57
  getMarginPresetCatalog: () => MARGIN_PRESET_CATALOG,