@beyondwork/docx-react-component 1.0.110 → 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 (52) 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/runtime/geometry/adjacent-geometry-intake.ts +373 -5
  5. package/src/runtime/geometry/caret-geometry.ts +219 -7
  6. package/src/runtime/geometry/geometry-index.ts +35 -10
  7. package/src/runtime/geometry/object-handles.ts +42 -1
  8. package/src/runtime/layout/index.ts +3 -0
  9. package/src/runtime/layout/inert-layout-facet.ts +13 -0
  10. package/src/runtime/layout/layout-engine-instance.ts +2 -0
  11. package/src/runtime/layout/layout-engine-version.ts +32 -2
  12. package/src/runtime/layout/layout-facet-types.ts +3 -0
  13. package/src/runtime/layout/page-graph.ts +81 -7
  14. package/src/runtime/layout/project-block-fragments.ts +144 -1
  15. package/src/runtime/layout/public-facet.ts +160 -0
  16. package/src/runtime/scopes/adjacent-geometry-evidence.ts +456 -0
  17. package/src/runtime/scopes/compile-scope-bundle.ts +8 -0
  18. package/src/runtime/scopes/evidence.ts +16 -0
  19. package/src/runtime/scopes/index.ts +13 -0
  20. package/src/runtime/scopes/semantic-scope-types.ts +67 -0
  21. package/src/ui-tailwind/debug/layer11-consumer-readiness.ts +104 -0
  22. package/src/ui-tailwind/editor-surface/pm-page-break-decorations.ts +50 -5
  23. package/src/ui-tailwind/editor-surface/tw-prosemirror-surface.tsx +26 -0
  24. package/src/README.md +0 -85
  25. package/src/api/README.md +0 -26
  26. package/src/api/v3/README.md +0 -91
  27. package/src/component-inventory.md +0 -99
  28. package/src/core/README.md +0 -10
  29. package/src/core/commands/README.md +0 -3
  30. package/src/core/schema/README.md +0 -3
  31. package/src/core/selection/README.md +0 -3
  32. package/src/core/state/README.md +0 -3
  33. package/src/io/README.md +0 -10
  34. package/src/io/export/README.md +0 -3
  35. package/src/io/normalize/README.md +0 -3
  36. package/src/io/ooxml/README.md +0 -3
  37. package/src/io/opc/README.md +0 -3
  38. package/src/model/README.md +0 -3
  39. package/src/preservation/README.md +0 -3
  40. package/src/review/README.md +0 -16
  41. package/src/review/store/README.md +0 -3
  42. package/src/runtime/README.md +0 -3
  43. package/src/ui/README.md +0 -30
  44. package/src/ui/comments/README.md +0 -3
  45. package/src/ui/compatibility/README.md +0 -3
  46. package/src/ui/editor-surface/README.md +0 -3
  47. package/src/ui/review/README.md +0 -3
  48. package/src/ui/status/README.md +0 -3
  49. package/src/ui/theme/README.md +0 -3
  50. package/src/ui/toolbar/README.md +0 -3
  51. package/src/ui-tailwind/debug/README.md +0 -22
  52. package/src/validation/README.md +0 -3
@@ -20,6 +20,20 @@ export interface AdjacentGeometryTwipsRect {
20
20
  readonly heightTwips?: number;
21
21
  }
22
22
 
23
+ export interface AdjacentGeometryFramePxRect {
24
+ readonly leftPx: number;
25
+ readonly topPx: number;
26
+ readonly widthPx: number;
27
+ readonly heightPx: number;
28
+ readonly coordinateSpace: "frame-px";
29
+ }
30
+
31
+ export interface AdjacentGeometryFramePxPoint {
32
+ readonly xPx: number;
33
+ readonly yPx: number;
34
+ readonly coordinateSpace: "frame-px";
35
+ }
36
+
23
37
  export interface AdjacentGeometryPageSpan {
24
38
  readonly startPage?: number | null;
25
39
  readonly endPage?: number | null;
@@ -37,6 +51,7 @@ export interface AdjacentGeometryL04FrameProjection {
37
51
  readonly frameId?: string;
38
52
  readonly frameCompleteness?: string;
39
53
  readonly physicalBoundsTwips?: AdjacentGeometryTwipsRect;
54
+ readonly framePixelScale?: AdjacentGeometryFramePixelScale;
40
55
  readonly markerLane?: AdjacentGeometryTwipsRect;
41
56
  readonly textColumn?: AdjacentGeometryTwipsRect;
42
57
  readonly fieldStartAnchorTwips?: {
@@ -58,12 +73,46 @@ export interface AdjacentGeometryL04FrameProjection {
58
73
  | "missing-word-page-local-geometry";
59
74
  }
60
75
 
76
+ export interface AdjacentGeometryFramePixelScale {
77
+ readonly source: "runtime-render-frame-page-rect";
78
+ readonly pageIndex: number;
79
+ readonly pageId?: string;
80
+ readonly frameId?: string;
81
+ readonly renderFrameRevision?: number;
82
+ readonly renderPageFrame: AdjacentGeometryFramePxRect;
83
+ readonly physicalBoundsTwips: AdjacentGeometryTwipsRect;
84
+ readonly pxPerTwipX: number;
85
+ readonly pxPerTwipY: number;
86
+ readonly zoomPxPerTwip?: number;
87
+ }
88
+
89
+ export interface AdjacentGeometryFramePixelProjection {
90
+ readonly pageIndex?: number;
91
+ readonly pageId?: string;
92
+ readonly frameId?: string;
93
+ readonly scale?: AdjacentGeometryFramePixelScale;
94
+ readonly markerLane?: AdjacentGeometryFramePxRect;
95
+ readonly textColumn?: AdjacentGeometryFramePxRect;
96
+ readonly fieldStartAnchorPx?: AdjacentGeometryFramePxPoint;
97
+ readonly fieldEndAnchorPx?: AdjacentGeometryFramePxPoint;
98
+ readonly fieldResultRangePx?: AdjacentGeometryFramePxRect;
99
+ readonly coordinateSpace: "frame-px";
100
+ readonly precision: "word-page-local-calibration";
101
+ readonly status:
102
+ | "projected-frame-pixels"
103
+ | "awaiting-l04-frame-projection"
104
+ | "missing-frame-pixel-scale"
105
+ | "missing-l04-frame-rect"
106
+ | "missing-l04-field-anchor";
107
+ }
108
+
61
109
  interface AdjacentGeometryPageFrame {
62
110
  readonly pageId?: string;
63
111
  readonly pageIndex: number;
64
112
  readonly frameId?: string;
65
113
  readonly completeness?: string;
66
114
  readonly physicalBoundsTwips?: AdjacentGeometryTwipsRect;
115
+ readonly framePixelScale?: AdjacentGeometryFramePixelScale;
67
116
  }
68
117
 
69
118
  export interface AdjacentGeometryPageLocalNumberingGeometry {
@@ -75,6 +124,8 @@ export interface AdjacentGeometryPageLocalNumberingGeometry {
75
124
  readonly markerLane?: AdjacentGeometryTwipsRect;
76
125
  readonly textColumn?: AdjacentGeometryTwipsRect;
77
126
  readonly l04FrameProjection: AdjacentGeometryL04FrameProjection;
127
+ readonly framePixelProjection: AdjacentGeometryFramePixelProjection;
128
+ readonly compositorReady: boolean;
78
129
  readonly coordinateSpace: "word-page-twips";
79
130
  readonly precision: "word-page-local-calibration";
80
131
  readonly normalizationStatus:
@@ -102,6 +153,8 @@ export interface AdjacentGeometryPageLocalFieldGeometry {
102
153
  readonly y?: number;
103
154
  };
104
155
  readonly l04FrameProjection: AdjacentGeometryL04FrameProjection;
156
+ readonly framePixelProjection: AdjacentGeometryFramePixelProjection;
157
+ readonly compositorReady: boolean;
105
158
  readonly coordinateSpace: "word-page-twips";
106
159
  readonly precision: "word-page-local-calibration";
107
160
  readonly normalizationStatus:
@@ -144,6 +197,7 @@ export interface AdjacentGeometryNumberingRowSummary {
144
197
  readonly heightTwips?: number;
145
198
  };
146
199
  readonly pageLocalGeometry: AdjacentGeometryPageLocalNumberingGeometry;
200
+ readonly compositorReady: boolean;
147
201
  readonly rangeMappingMethod?: string;
148
202
  readonly coordinateSpace: "screen-px";
149
203
  readonly precision: "screen-space-approximate";
@@ -166,6 +220,7 @@ export interface AdjacentGeometryFieldRegionRowSummary {
166
220
  readonly endPage?: number | null;
167
221
  };
168
222
  readonly pageLocalGeometry: AdjacentGeometryPageLocalFieldGeometry;
223
+ readonly compositorReady: boolean;
169
224
  readonly instructionFamily?: string;
170
225
  readonly resultLineRectCount: number;
171
226
  readonly wordPlacement?: {
@@ -205,6 +260,9 @@ export interface AdjacentGeometryDocumentSummary {
205
260
  readonly numberingUiaRowsWithRuntimePageMatch: number;
206
261
  readonly numberingRowsWithL04FrameProjection: number;
207
262
  readonly numberingUiaRowsWithL04FrameProjection: number;
263
+ readonly numberingRowsWithFramePixelProjection: number;
264
+ readonly numberingUiaRowsWithFramePixelProjection: number;
265
+ readonly numberingCompositorReadyRows: number;
208
266
  readonly fieldRegionRows: number;
209
267
  readonly fieldRegionRowsWithRuntimeId: number;
210
268
  readonly fieldRegionResultLineRects: number;
@@ -214,10 +272,13 @@ export interface AdjacentGeometryDocumentSummary {
214
272
  readonly fieldRegionRowsWithRuntimePageContainment: number;
215
273
  readonly fieldRegionRowsWithL04FrameProjection: number;
216
274
  readonly fieldRegionUiaRowsWithL04FrameProjection: number;
275
+ readonly fieldRegionRowsWithFramePixelProjection: number;
276
+ readonly fieldRegionUiaRowsWithFramePixelProjection: number;
277
+ readonly fieldRegionCompositorReadyRows: number;
217
278
  }
218
279
 
219
280
  export interface AdjacentGeometryIntakeSummary {
220
- readonly schemaVersion: "layer-05-adjacent-geometry-intake/v1";
281
+ readonly schemaVersion: "layer-05-adjacent-geometry-intake/v2";
221
282
  readonly generatedAtUtc: string;
222
283
  readonly sourceRuns: {
223
284
  readonly adjacentLiveWordJoins: string;
@@ -241,6 +302,9 @@ export interface AdjacentGeometryIntakeSummary {
241
302
  readonly numberingUiaRowsWithRuntimePageMatch: number;
242
303
  readonly numberingRowsWithL04FrameProjection: number;
243
304
  readonly numberingUiaRowsWithL04FrameProjection: number;
305
+ readonly numberingRowsWithFramePixelProjection: number;
306
+ readonly numberingUiaRowsWithFramePixelProjection: number;
307
+ readonly numberingCompositorReadyRows: number;
244
308
  readonly fieldRegionRows: number;
245
309
  readonly fieldRegionRowsWithRuntimeId: number;
246
310
  readonly fieldRegionResultLineRects: number;
@@ -250,6 +314,9 @@ export interface AdjacentGeometryIntakeSummary {
250
314
  readonly fieldRegionRowsWithRuntimePageContainment: number;
251
315
  readonly fieldRegionRowsWithL04FrameProjection: number;
252
316
  readonly fieldRegionUiaRowsWithL04FrameProjection: number;
317
+ readonly fieldRegionRowsWithFramePixelProjection: number;
318
+ readonly fieldRegionUiaRowsWithFramePixelProjection: number;
319
+ readonly fieldRegionCompositorReadyRows: number;
253
320
  };
254
321
  readonly routing: {
255
322
  readonly routedTo: "L05";
@@ -264,7 +331,10 @@ export interface AdjacentGeometryIntakeSummary {
264
331
  readonly requiredUpstreamFact: "L04 frame-to-pixel projection";
265
332
  readonly l04FrameProjectionCoordinateSpace: "l04-frame-twips";
266
333
  readonly l04FrameProjectionPrecision: "word-page-local-calibration";
267
- readonly compositorReady: false;
334
+ readonly framePixelCoordinateSpace: "frame-px";
335
+ readonly framePixelPrecision: "word-page-local-calibration";
336
+ readonly scaleSource: "runtime-render-frame-page-rect";
337
+ readonly compositorReady: boolean;
268
338
  };
269
339
  readonly documents: readonly AdjacentGeometryDocumentSummary[];
270
340
  readonly numberingRows: readonly AdjacentGeometryNumberingRowSummary[];
@@ -301,7 +371,7 @@ export function summarizeAdjacentGeometryIntake(input: {
301
371
  ]),
302
372
  ].sort();
303
373
  return {
304
- schemaVersion: "layer-05-adjacent-geometry-intake/v1",
374
+ schemaVersion: "layer-05-adjacent-geometry-intake/v2",
305
375
  generatedAtUtc: input.generatedAtUtc,
306
376
  sourceRuns: input.sourceRuns,
307
377
  totals: {
@@ -347,6 +417,15 @@ export function summarizeAdjacentGeometryIntake(input: {
347
417
  numberingUiaRowsWithL04FrameProjection: numberingRows.filter(
348
418
  (row) => hasNumberingUiaCapture(row) && hasL04FrameProjection(row),
349
419
  ).length,
420
+ numberingRowsWithFramePixelProjection: numberingRows.filter(
421
+ hasFramePixelProjection,
422
+ ).length,
423
+ numberingUiaRowsWithFramePixelProjection: numberingRows.filter(
424
+ (row) => hasNumberingUiaCapture(row) && hasFramePixelProjection(row),
425
+ ).length,
426
+ numberingCompositorReadyRows: numberingRows.filter(
427
+ (row) => row.pageLocalGeometry.compositorReady,
428
+ ).length,
350
429
  fieldRegionRows: fieldRegionRows.length,
351
430
  fieldRegionRowsWithRuntimeId: fieldRegionRows.filter(
352
431
  (row) => row.runtimeFieldRegionId !== undefined,
@@ -373,6 +452,15 @@ export function summarizeAdjacentGeometryIntake(input: {
373
452
  fieldRegionUiaRowsWithL04FrameProjection: fieldRegionRows.filter(
374
453
  (row) => row.resultLineRectCount > 0 && hasL04FrameProjection(row),
375
454
  ).length,
455
+ fieldRegionRowsWithFramePixelProjection: fieldRegionRows.filter(
456
+ hasFramePixelProjection,
457
+ ).length,
458
+ fieldRegionUiaRowsWithFramePixelProjection: fieldRegionRows.filter(
459
+ (row) => row.resultLineRectCount > 0 && hasFramePixelProjection(row),
460
+ ).length,
461
+ fieldRegionCompositorReadyRows: fieldRegionRows.filter(
462
+ (row) => row.pageLocalGeometry.compositorReady,
463
+ ).length,
376
464
  },
377
465
  routing: {
378
466
  routedTo: "L05",
@@ -387,7 +475,12 @@ export function summarizeAdjacentGeometryIntake(input: {
387
475
  requiredUpstreamFact: "L04 frame-to-pixel projection",
388
476
  l04FrameProjectionCoordinateSpace: "l04-frame-twips",
389
477
  l04FrameProjectionPrecision: "word-page-local-calibration",
390
- compositorReady: false,
478
+ framePixelCoordinateSpace: "frame-px",
479
+ framePixelPrecision: "word-page-local-calibration",
480
+ scaleSource: "runtime-render-frame-page-rect",
481
+ compositorReady:
482
+ numberingRows.some((row) => row.pageLocalGeometry.compositorReady) ||
483
+ fieldRegionRows.some((row) => row.pageLocalGeometry.compositorReady),
391
484
  },
392
485
  documents: docIds.map((docId) =>
393
486
  summarizeDocument(
@@ -499,6 +592,7 @@ function summarizeNumberingRows(
499
592
  textColumn,
500
593
  pageFramesByDocId,
501
594
  });
595
+ const framePixelProjection = projectNumberingToFramePixels(l04FrameProjection);
502
596
  const pageLocalGeometry: AdjacentGeometryPageLocalNumberingGeometry = {
503
597
  wordPage: wordStartPage,
504
598
  normalizedWordPageIndex,
@@ -508,6 +602,8 @@ function summarizeNumberingRows(
508
602
  markerLane,
509
603
  textColumn,
510
604
  l04FrameProjection,
605
+ framePixelProjection,
606
+ compositorReady: framePixelProjection.status === "projected-frame-pixels",
511
607
  coordinateSpace: "word-page-twips",
512
608
  precision: "word-page-local-calibration",
513
609
  normalizationStatus:
@@ -555,6 +651,7 @@ function summarizeNumberingRows(
555
651
  markerLane,
556
652
  textColumn,
557
653
  pageLocalGeometry,
654
+ compositorReady: pageLocalGeometry.compositorReady,
558
655
  rangeMappingMethod:
559
656
  stringValue(uiaRow.rangeMappingMethod) ?? stringValue(row.rangeMappingMethod),
560
657
  coordinateSpace: "screen-px" as const,
@@ -662,6 +759,7 @@ function summarizeFieldRegionRows(
662
759
  wordPlacement,
663
760
  pageFramesByDocId,
664
761
  });
762
+ const framePixelProjection = projectFieldToFramePixels(l04FrameProjection);
665
763
  const pageLocalGeometry: AdjacentGeometryPageLocalFieldGeometry = {
666
764
  expectedPageSpan,
667
765
  normalizedWordPageIndexSpan,
@@ -670,6 +768,8 @@ function summarizeFieldRegionRows(
670
768
  startAnchorTwips: wordPlacement?.startXYTwips,
671
769
  endAnchorTwips: wordPlacement?.endXYTwips,
672
770
  l04FrameProjection,
771
+ framePixelProjection,
772
+ compositorReady: framePixelProjection.status === "projected-frame-pixels",
673
773
  coordinateSpace: "word-page-twips",
674
774
  precision: "word-page-local-calibration",
675
775
  normalizationStatus:
@@ -699,6 +799,7 @@ function summarizeFieldRegionRows(
699
799
  runtimePageSpan,
700
800
  expectedPageSpan,
701
801
  pageLocalGeometry,
802
+ compositorReady: pageLocalGeometry.compositorReady,
702
803
  instructionFamily:
703
804
  stringValue(liveJoin.instructionFamily) ??
704
805
  stringValue(targetJoin.instructionFamily),
@@ -765,6 +866,14 @@ function summarizeDocument(
765
866
  numberingUiaRowsWithL04FrameProjection: numberingRows.filter(
766
867
  (row) => hasNumberingUiaCapture(row) && hasL04FrameProjection(row),
767
868
  ).length,
869
+ numberingRowsWithFramePixelProjection: numberingRows.filter(
870
+ hasFramePixelProjection,
871
+ ).length,
872
+ numberingUiaRowsWithFramePixelProjection: numberingRows.filter(
873
+ (row) => hasNumberingUiaCapture(row) && hasFramePixelProjection(row),
874
+ ).length,
875
+ numberingCompositorReadyRows: numberingRows.filter((row) => row.compositorReady)
876
+ .length,
768
877
  fieldRegionRows: fieldRegionRows.length,
769
878
  fieldRegionRowsWithRuntimeId: fieldRegionRows.filter(
770
879
  (row) => row.runtimeFieldRegionId !== undefined,
@@ -791,6 +900,14 @@ function summarizeDocument(
791
900
  fieldRegionUiaRowsWithL04FrameProjection: fieldRegionRows.filter(
792
901
  (row) => row.resultLineRectCount > 0 && hasL04FrameProjection(row),
793
902
  ).length,
903
+ fieldRegionRowsWithFramePixelProjection: fieldRegionRows.filter(
904
+ hasFramePixelProjection,
905
+ ).length,
906
+ fieldRegionUiaRowsWithFramePixelProjection: fieldRegionRows.filter(
907
+ (row) => row.resultLineRectCount > 0 && hasFramePixelProjection(row),
908
+ ).length,
909
+ fieldRegionCompositorReadyRows: fieldRegionRows.filter((row) => row.compositorReady)
910
+ .length,
794
911
  };
795
912
  }
796
913
 
@@ -800,6 +917,12 @@ function hasL04FrameProjection(
800
917
  return row.pageLocalGeometry.l04FrameProjection.status === "projected-l04-frame";
801
918
  }
802
919
 
920
+ function hasFramePixelProjection(
921
+ row: AdjacentGeometryNumberingRowSummary | AdjacentGeometryFieldRegionRowSummary,
922
+ ): boolean {
923
+ return row.pageLocalGeometry.framePixelProjection.status === "projected-frame-pixels";
924
+ }
925
+
803
926
  function hasNumberingUiaCapture(row: AdjacentGeometryNumberingRowSummary): boolean {
804
927
  return (
805
928
  row.paragraphLineRectCount > 0 ||
@@ -822,12 +945,28 @@ function collectPageFramesByDocId(
822
945
  if (pageIndex === undefined) continue;
823
946
  const frame = record(page.frame);
824
947
  const physicalBoundsTwips = normalizeTwipsRect(frame.physicalBoundsTwips);
948
+ const renderFrame = record(page.renderFrame);
949
+ const renderPageFrame = normalizeFramePxRect(renderFrame.frame);
950
+ const renderFrameRevision = numberValue(renderFrame.revision);
951
+ const zoomPxPerTwip =
952
+ numberValue(record(page.renderZoom).pxPerTwip) ??
953
+ numberValue(renderFrame.zoomPxPerTwip);
954
+ const frameId = stringValue(frame.frameId);
825
955
  const pageFrame: AdjacentGeometryPageFrame = {
826
956
  pageId: stringValue(page.pageId),
827
957
  pageIndex,
828
- frameId: stringValue(frame.frameId),
958
+ frameId,
829
959
  completeness: stringValue(frame.completeness),
830
960
  physicalBoundsTwips,
961
+ framePixelScale: buildFramePixelScale({
962
+ pageIndex,
963
+ pageId: stringValue(page.pageId),
964
+ frameId,
965
+ physicalBoundsTwips,
966
+ renderPageFrame,
967
+ renderFrameRevision,
968
+ zoomPxPerTwip,
969
+ }),
831
970
  };
832
971
  let docFrames = byDocId.get(docId);
833
972
  if (!docFrames) {
@@ -840,6 +979,48 @@ function collectPageFramesByDocId(
840
979
  return byDocId;
841
980
  }
842
981
 
982
+ function buildFramePixelScale(input: {
983
+ readonly pageIndex: number;
984
+ readonly pageId?: string;
985
+ readonly frameId?: string;
986
+ readonly physicalBoundsTwips?: AdjacentGeometryTwipsRect;
987
+ readonly renderPageFrame?: AdjacentGeometryFramePxRect;
988
+ readonly renderFrameRevision?: number;
989
+ readonly zoomPxPerTwip?: number;
990
+ }): AdjacentGeometryFramePixelScale | undefined {
991
+ const physicalBounds = input.physicalBoundsTwips;
992
+ const renderPageFrame = input.renderPageFrame;
993
+ if (!physicalBounds || !renderPageFrame) return undefined;
994
+ if (
995
+ physicalBounds.xTwips === undefined ||
996
+ physicalBounds.yTwips === undefined ||
997
+ physicalBounds.widthTwips === undefined ||
998
+ physicalBounds.heightTwips === undefined ||
999
+ physicalBounds.widthTwips <= 0 ||
1000
+ physicalBounds.heightTwips <= 0
1001
+ ) {
1002
+ return undefined;
1003
+ }
1004
+ const pxPerTwipX = renderPageFrame.widthPx / physicalBounds.widthTwips;
1005
+ const pxPerTwipY = renderPageFrame.heightPx / physicalBounds.heightTwips;
1006
+ if (!isFinitePositive(pxPerTwipX) || !isFinitePositive(pxPerTwipY)) {
1007
+ return undefined;
1008
+ }
1009
+ return {
1010
+ source: "runtime-render-frame-page-rect",
1011
+ pageIndex: input.pageIndex,
1012
+ pageId: input.pageId,
1013
+ frameId: input.frameId,
1014
+ renderFrameRevision: input.renderFrameRevision,
1015
+ renderPageFrame,
1016
+ physicalBoundsTwips: physicalBounds,
1017
+ pxPerTwipX,
1018
+ pxPerTwipY,
1019
+ zoomPxPerTwip:
1020
+ input.zoomPxPerTwip === undefined ? undefined : roundNumber(input.zoomPxPerTwip),
1021
+ };
1022
+ }
1023
+
843
1024
  function collectNumberingRuntimeReadbacks(
844
1025
  artifacts: readonly unknown[],
845
1026
  ): Map<string, Record<string, unknown>> {
@@ -911,6 +1092,7 @@ function projectNumberingToL04Frame(input: {
911
1092
  frameId: frame.frameId,
912
1093
  frameCompleteness: frame.completeness,
913
1094
  physicalBoundsTwips: frame.physicalBoundsTwips,
1095
+ framePixelScale: frame.framePixelScale,
914
1096
  markerLane: addFrameOrigin(input.markerLane, frame.physicalBoundsTwips),
915
1097
  textColumn: addFrameOrigin(input.textColumn, frame.physicalBoundsTwips),
916
1098
  coordinateSpace: "l04-frame-twips",
@@ -957,6 +1139,7 @@ function projectFieldToL04Frame(input: {
957
1139
  frameId: frame.frameId,
958
1140
  frameCompleteness: frame.completeness,
959
1141
  physicalBoundsTwips: frame.physicalBoundsTwips,
1142
+ framePixelScale: frame.framePixelScale,
960
1143
  fieldStartAnchorTwips: addFrameOriginToPoint(
961
1144
  input.wordPlacement.startXYTwips,
962
1145
  frame.physicalBoundsTwips,
@@ -971,6 +1154,65 @@ function projectFieldToL04Frame(input: {
971
1154
  };
972
1155
  }
973
1156
 
1157
+ function projectNumberingToFramePixels(
1158
+ projection: AdjacentGeometryL04FrameProjection,
1159
+ ): AdjacentGeometryFramePixelProjection {
1160
+ if (projection.status !== "projected-l04-frame") {
1161
+ return baseFramePixelProjection("awaiting-l04-frame-projection", projection);
1162
+ }
1163
+ const scale = projectionScale(projection);
1164
+ if (!scale) return baseFramePixelProjection("missing-frame-pixel-scale", projection);
1165
+ const markerLane = l04FrameTwipsRectToFramePx(projection.markerLane, scale);
1166
+ const textColumn = l04FrameTwipsRectToFramePx(projection.textColumn, scale);
1167
+ if (!markerLane) {
1168
+ return baseFramePixelProjection("missing-l04-frame-rect", projection, scale);
1169
+ }
1170
+ return {
1171
+ pageIndex: projection.pageIndex,
1172
+ pageId: projection.pageId,
1173
+ frameId: projection.frameId,
1174
+ scale,
1175
+ markerLane,
1176
+ textColumn,
1177
+ coordinateSpace: "frame-px",
1178
+ precision: "word-page-local-calibration",
1179
+ status: "projected-frame-pixels",
1180
+ };
1181
+ }
1182
+
1183
+ function projectFieldToFramePixels(
1184
+ projection: AdjacentGeometryL04FrameProjection,
1185
+ ): AdjacentGeometryFramePixelProjection {
1186
+ if (projection.status !== "projected-l04-frame") {
1187
+ return baseFramePixelProjection("awaiting-l04-frame-projection", projection);
1188
+ }
1189
+ const scale = projectionScale(projection);
1190
+ if (!scale) return baseFramePixelProjection("missing-frame-pixel-scale", projection);
1191
+ const fieldStartAnchorPx = l04FrameTwipsPointToFramePx(
1192
+ projection.fieldStartAnchorTwips,
1193
+ scale,
1194
+ );
1195
+ const fieldEndAnchorPx = l04FrameTwipsPointToFramePx(
1196
+ projection.fieldEndAnchorTwips,
1197
+ scale,
1198
+ );
1199
+ if (!fieldStartAnchorPx || !fieldEndAnchorPx) {
1200
+ return baseFramePixelProjection("missing-l04-field-anchor", projection, scale);
1201
+ }
1202
+ return {
1203
+ pageIndex: projection.pageIndex,
1204
+ pageId: projection.pageId,
1205
+ frameId: projection.frameId,
1206
+ scale,
1207
+ fieldStartAnchorPx,
1208
+ fieldEndAnchorPx,
1209
+ fieldResultRangePx: rangeRectFromPoints(fieldStartAnchorPx, fieldEndAnchorPx),
1210
+ coordinateSpace: "frame-px",
1211
+ precision: "word-page-local-calibration",
1212
+ status: "projected-frame-pixels",
1213
+ };
1214
+ }
1215
+
974
1216
  function baseFrameProjection(
975
1217
  status: AdjacentGeometryL04FrameProjection["status"],
976
1218
  pageIndex?: number,
@@ -983,6 +1225,22 @@ function baseFrameProjection(
983
1225
  };
984
1226
  }
985
1227
 
1228
+ function baseFramePixelProjection(
1229
+ status: AdjacentGeometryFramePixelProjection["status"],
1230
+ projection: AdjacentGeometryL04FrameProjection,
1231
+ scale?: AdjacentGeometryFramePixelScale,
1232
+ ): AdjacentGeometryFramePixelProjection {
1233
+ return {
1234
+ pageIndex: projection.pageIndex,
1235
+ pageId: projection.pageId,
1236
+ frameId: projection.frameId,
1237
+ scale,
1238
+ coordinateSpace: "frame-px",
1239
+ precision: "word-page-local-calibration",
1240
+ status,
1241
+ };
1242
+ }
1243
+
986
1244
  function pageFrameFor(
987
1245
  pageFramesByDocId: ReadonlyMap<string, ReadonlyMap<number, AdjacentGeometryPageFrame>>,
988
1246
  docId: string,
@@ -991,6 +1249,12 @@ function pageFrameFor(
991
1249
  return pageIndex === undefined ? undefined : pageFramesByDocId.get(docId)?.get(pageIndex);
992
1250
  }
993
1251
 
1252
+ function projectionScale(
1253
+ projection: AdjacentGeometryL04FrameProjection,
1254
+ ): AdjacentGeometryFramePixelScale | undefined {
1255
+ return projection.framePixelScale;
1256
+ }
1257
+
994
1258
  function addFrameOrigin(
995
1259
  rect: AdjacentGeometryTwipsRect | undefined,
996
1260
  frame: AdjacentGeometryTwipsRect,
@@ -1027,6 +1291,77 @@ function addFrameOriginToPoint(
1027
1291
  };
1028
1292
  }
1029
1293
 
1294
+ function l04FrameTwipsRectToFramePx(
1295
+ rect: AdjacentGeometryTwipsRect | undefined,
1296
+ scale: AdjacentGeometryFramePixelScale,
1297
+ ): AdjacentGeometryFramePxRect | undefined {
1298
+ if (
1299
+ !rect ||
1300
+ rect.xTwips === undefined ||
1301
+ rect.yTwips === undefined ||
1302
+ rect.widthTwips === undefined ||
1303
+ rect.heightTwips === undefined ||
1304
+ scale.physicalBoundsTwips.xTwips === undefined ||
1305
+ scale.physicalBoundsTwips.yTwips === undefined
1306
+ ) {
1307
+ return undefined;
1308
+ }
1309
+ return {
1310
+ leftPx: roundNumber(
1311
+ scale.renderPageFrame.leftPx +
1312
+ (rect.xTwips - scale.physicalBoundsTwips.xTwips) * scale.pxPerTwipX,
1313
+ ),
1314
+ topPx: roundNumber(
1315
+ scale.renderPageFrame.topPx +
1316
+ (rect.yTwips - scale.physicalBoundsTwips.yTwips) * scale.pxPerTwipY,
1317
+ ),
1318
+ widthPx: roundNumber(Math.max(0, rect.widthTwips * scale.pxPerTwipX)),
1319
+ heightPx: roundNumber(Math.max(0, rect.heightTwips * scale.pxPerTwipY)),
1320
+ coordinateSpace: "frame-px",
1321
+ };
1322
+ }
1323
+
1324
+ function l04FrameTwipsPointToFramePx(
1325
+ point: { readonly x?: number; readonly y?: number } | undefined,
1326
+ scale: AdjacentGeometryFramePixelScale,
1327
+ ): AdjacentGeometryFramePxPoint | undefined {
1328
+ if (
1329
+ !point ||
1330
+ point.x === undefined ||
1331
+ point.y === undefined ||
1332
+ scale.physicalBoundsTwips.xTwips === undefined ||
1333
+ scale.physicalBoundsTwips.yTwips === undefined
1334
+ ) {
1335
+ return undefined;
1336
+ }
1337
+ return {
1338
+ xPx: roundNumber(
1339
+ scale.renderPageFrame.leftPx +
1340
+ (point.x - scale.physicalBoundsTwips.xTwips) * scale.pxPerTwipX,
1341
+ ),
1342
+ yPx: roundNumber(
1343
+ scale.renderPageFrame.topPx +
1344
+ (point.y - scale.physicalBoundsTwips.yTwips) * scale.pxPerTwipY,
1345
+ ),
1346
+ coordinateSpace: "frame-px",
1347
+ };
1348
+ }
1349
+
1350
+ function rangeRectFromPoints(
1351
+ start: AdjacentGeometryFramePxPoint,
1352
+ end: AdjacentGeometryFramePxPoint,
1353
+ ): AdjacentGeometryFramePxRect {
1354
+ const leftPx = Math.min(start.xPx, end.xPx);
1355
+ const topPx = Math.min(start.yPx, end.yPx);
1356
+ return {
1357
+ leftPx,
1358
+ topPx,
1359
+ widthPx: roundNumber(Math.abs(end.xPx - start.xPx)),
1360
+ heightPx: roundNumber(Math.abs(end.yPx - start.yPx)),
1361
+ coordinateSpace: "frame-px",
1362
+ };
1363
+ }
1364
+
1030
1365
  function classifyPageIndex(
1031
1366
  normalizedWordPageIndex: number | undefined,
1032
1367
  runtimePageIndex: number | undefined,
@@ -1098,6 +1433,31 @@ function normalizeRect(raw: unknown): AdjacentGeometryRect | undefined {
1098
1433
  };
1099
1434
  }
1100
1435
 
1436
+ function normalizeFramePxRect(
1437
+ raw: unknown,
1438
+ ): AdjacentGeometryFramePxRect | undefined {
1439
+ const rect = record(raw);
1440
+ const leftPx = numberValue(rect.leftPx);
1441
+ const topPx = numberValue(rect.topPx);
1442
+ const widthPx = numberValue(rect.widthPx);
1443
+ const heightPx = numberValue(rect.heightPx);
1444
+ if (
1445
+ leftPx === undefined ||
1446
+ topPx === undefined ||
1447
+ widthPx === undefined ||
1448
+ heightPx === undefined
1449
+ ) {
1450
+ return undefined;
1451
+ }
1452
+ return {
1453
+ leftPx: roundNumber(leftPx),
1454
+ topPx: roundNumber(topPx),
1455
+ widthPx: roundNumber(widthPx),
1456
+ heightPx: roundNumber(heightPx),
1457
+ coordinateSpace: "frame-px",
1458
+ };
1459
+ }
1460
+
1101
1461
  function normalizeTwipsRect(raw: unknown):
1102
1462
  | {
1103
1463
  readonly xTwips?: number;
@@ -1256,6 +1616,14 @@ function numberValue(value: unknown): number | undefined {
1256
1616
  return typeof value === "number" && Number.isFinite(value) ? value : undefined;
1257
1617
  }
1258
1618
 
1619
+ function isFinitePositive(value: number): boolean {
1620
+ return Number.isFinite(value) && value > 0;
1621
+ }
1622
+
1623
+ function roundNumber(value: number): number {
1624
+ return Number.parseFloat(value.toFixed(6));
1625
+ }
1626
+
1259
1627
  function nullableNumberValue(value: unknown): number | null | undefined {
1260
1628
  return value === null ? null : numberValue(value);
1261
1629
  }