@beyondwork/docx-react-component 1.0.108 → 1.0.110

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.
@@ -25,15 +25,60 @@ export interface AdjacentGeometryPageSpan {
25
25
  readonly endPage?: number | null;
26
26
  }
27
27
 
28
+ export type AdjacentGeometryPageIndexStatus =
29
+ | "matches-runtime"
30
+ | "differs-from-runtime"
31
+ | "runtime-missing"
32
+ | "word-page-missing";
33
+
34
+ export interface AdjacentGeometryL04FrameProjection {
35
+ readonly pageIndex?: number;
36
+ readonly pageId?: string;
37
+ readonly frameId?: string;
38
+ readonly frameCompleteness?: string;
39
+ readonly physicalBoundsTwips?: AdjacentGeometryTwipsRect;
40
+ readonly markerLane?: AdjacentGeometryTwipsRect;
41
+ readonly textColumn?: AdjacentGeometryTwipsRect;
42
+ readonly fieldStartAnchorTwips?: {
43
+ readonly x?: number;
44
+ readonly y?: number;
45
+ };
46
+ readonly fieldEndAnchorTwips?: {
47
+ readonly x?: number;
48
+ readonly y?: number;
49
+ };
50
+ readonly coordinateSpace: "l04-frame-twips";
51
+ readonly precision: "word-page-local-calibration";
52
+ readonly status:
53
+ | "projected-l04-frame"
54
+ | "page-index-differs-from-runtime"
55
+ | "runtime-page-missing"
56
+ | "word-page-missing"
57
+ | "missing-l04-page-frame"
58
+ | "missing-word-page-local-geometry";
59
+ }
60
+
61
+ interface AdjacentGeometryPageFrame {
62
+ readonly pageId?: string;
63
+ readonly pageIndex: number;
64
+ readonly frameId?: string;
65
+ readonly completeness?: string;
66
+ readonly physicalBoundsTwips?: AdjacentGeometryTwipsRect;
67
+ }
68
+
28
69
  export interface AdjacentGeometryPageLocalNumberingGeometry {
29
70
  readonly wordPage?: number;
71
+ readonly normalizedWordPageIndex?: number;
30
72
  readonly runtimePageIndex?: number;
73
+ readonly pageIndexStatus: AdjacentGeometryPageIndexStatus;
31
74
  readonly runtimeFragmentId?: string;
32
75
  readonly markerLane?: AdjacentGeometryTwipsRect;
33
76
  readonly textColumn?: AdjacentGeometryTwipsRect;
77
+ readonly l04FrameProjection: AdjacentGeometryL04FrameProjection;
34
78
  readonly coordinateSpace: "word-page-twips";
35
79
  readonly precision: "word-page-local-calibration";
36
80
  readonly normalizationStatus:
81
+ | "l04-frame-twips-projected"
37
82
  | "word-page-local-awaiting-l04-frame-projection"
38
83
  | "missing-word-page-local-lane";
39
84
  }
@@ -56,9 +101,11 @@ export interface AdjacentGeometryPageLocalFieldGeometry {
56
101
  readonly x?: number;
57
102
  readonly y?: number;
58
103
  };
104
+ readonly l04FrameProjection: AdjacentGeometryL04FrameProjection;
59
105
  readonly coordinateSpace: "word-page-twips";
60
106
  readonly precision: "word-page-local-calibration";
61
107
  readonly normalizationStatus:
108
+ | "l04-frame-twips-projected"
62
109
  | "word-page-local-awaiting-l04-frame-projection"
63
110
  | "missing-word-placement";
64
111
  }
@@ -69,6 +116,9 @@ export interface AdjacentGeometryNumberingRowSummary {
69
116
  readonly canonicalBlockId?: string;
70
117
  readonly runtimeBlockId?: string;
71
118
  readonly runtimeFragmentId?: string;
119
+ readonly runtimeNumberingId?: string;
120
+ readonly layoutObjectId?: string;
121
+ readonly runtimeJoinSource: "adjacent-live" | "l04-numbering-readback";
72
122
  readonly runtimePageIndex?: number;
73
123
  readonly sourceParagraphPath?: string;
74
124
  readonly canonicalNumberingInstanceId?: string;
@@ -141,6 +191,7 @@ export interface AdjacentGeometryDocumentSummary {
141
191
  readonly docId: string;
142
192
  readonly numberingRows: number;
143
193
  readonly numberingRowsWithRuntimeFragment: number;
194
+ readonly numberingRowsJoinedFromL04Readback: number;
144
195
  readonly numberingParagraphLineRects: number;
145
196
  readonly numberingTextOnlyLineRects: number;
146
197
  readonly numberingRawMarkerRectRows: number;
@@ -150,6 +201,10 @@ export interface AdjacentGeometryDocumentSummary {
150
201
  readonly numberingRowsWithUiaCapture: number;
151
202
  readonly numberingRowsWithPageLocalMarkerLane: number;
152
203
  readonly numberingRowsWithPageLocalTextColumn: number;
204
+ readonly numberingRowsWithRuntimePageMatch: number;
205
+ readonly numberingUiaRowsWithRuntimePageMatch: number;
206
+ readonly numberingRowsWithL04FrameProjection: number;
207
+ readonly numberingUiaRowsWithL04FrameProjection: number;
153
208
  readonly fieldRegionRows: number;
154
209
  readonly fieldRegionRowsWithRuntimeId: number;
155
210
  readonly fieldRegionResultLineRects: number;
@@ -157,10 +212,12 @@ export interface AdjacentGeometryDocumentSummary {
157
212
  readonly fieldRegionRowsWithWordPlacement: number;
158
213
  readonly fieldRegionRowsWithRuntimePageMatch: number;
159
214
  readonly fieldRegionRowsWithRuntimePageContainment: number;
215
+ readonly fieldRegionRowsWithL04FrameProjection: number;
216
+ readonly fieldRegionUiaRowsWithL04FrameProjection: number;
160
217
  }
161
218
 
162
219
  export interface AdjacentGeometryIntakeSummary {
163
- readonly schemaVersion: "layer-05-adjacent-geometry-intake/v0";
220
+ readonly schemaVersion: "layer-05-adjacent-geometry-intake/v1";
164
221
  readonly generatedAtUtc: string;
165
222
  readonly sourceRuns: {
166
223
  readonly adjacentLiveWordJoins: string;
@@ -170,6 +227,7 @@ export interface AdjacentGeometryIntakeSummary {
170
227
  readonly totals: {
171
228
  readonly numberingRows: number;
172
229
  readonly numberingRowsWithRuntimeFragment: number;
230
+ readonly numberingRowsJoinedFromL04Readback: number;
173
231
  readonly numberingParagraphLineRects: number;
174
232
  readonly numberingTextOnlyLineRects: number;
175
233
  readonly numberingRawMarkerRectRows: number;
@@ -179,6 +237,10 @@ export interface AdjacentGeometryIntakeSummary {
179
237
  readonly numberingRowsWithUiaCapture: number;
180
238
  readonly numberingRowsWithPageLocalMarkerLane: number;
181
239
  readonly numberingRowsWithPageLocalTextColumn: number;
240
+ readonly numberingRowsWithRuntimePageMatch: number;
241
+ readonly numberingUiaRowsWithRuntimePageMatch: number;
242
+ readonly numberingRowsWithL04FrameProjection: number;
243
+ readonly numberingUiaRowsWithL04FrameProjection: number;
182
244
  readonly fieldRegionRows: number;
183
245
  readonly fieldRegionRowsWithRuntimeId: number;
184
246
  readonly fieldRegionResultLineRects: number;
@@ -186,6 +248,8 @@ export interface AdjacentGeometryIntakeSummary {
186
248
  readonly fieldRegionRowsWithWordPlacement: number;
187
249
  readonly fieldRegionRowsWithRuntimePageMatch: number;
188
250
  readonly fieldRegionRowsWithRuntimePageContainment: number;
251
+ readonly fieldRegionRowsWithL04FrameProjection: number;
252
+ readonly fieldRegionUiaRowsWithL04FrameProjection: number;
189
253
  };
190
254
  readonly routing: {
191
255
  readonly routedTo: "L05";
@@ -198,6 +262,8 @@ export interface AdjacentGeometryIntakeSummary {
198
262
  readonly coordinateSpace: "word-page-twips";
199
263
  readonly precision: "word-page-local-calibration";
200
264
  readonly requiredUpstreamFact: "L04 frame-to-pixel projection";
265
+ readonly l04FrameProjectionCoordinateSpace: "l04-frame-twips";
266
+ readonly l04FrameProjectionPrecision: "word-page-local-calibration";
201
267
  readonly compositorReady: false;
202
268
  };
203
269
  readonly documents: readonly AdjacentGeometryDocumentSummary[];
@@ -210,16 +276,23 @@ export function summarizeAdjacentGeometryIntake(input: {
210
276
  readonly sourceRuns: AdjacentGeometryIntakeSummary["sourceRuns"];
211
277
  readonly numberingArtifact: unknown;
212
278
  readonly numberingLiveJoinArtifacts: readonly unknown[];
279
+ readonly numberingRuntimeReadbackArtifacts?: readonly unknown[];
213
280
  readonly fieldRegionRuntimeReadback: unknown;
214
281
  readonly fieldRegionLiveJoinArtifacts: readonly unknown[];
215
282
  }): AdjacentGeometryIntakeSummary {
283
+ const pageFramesByDocId = collectPageFramesByDocId(
284
+ input.numberingRuntimeReadbackArtifacts ?? [],
285
+ );
216
286
  const numberingRows = summarizeNumberingRows(
217
287
  input.numberingArtifact,
218
288
  input.numberingLiveJoinArtifacts,
289
+ input.numberingRuntimeReadbackArtifacts ?? [],
290
+ pageFramesByDocId,
219
291
  );
220
292
  const fieldRegionRows = summarizeFieldRegionRows(
221
293
  input.fieldRegionRuntimeReadback,
222
294
  input.fieldRegionLiveJoinArtifacts,
295
+ pageFramesByDocId,
223
296
  );
224
297
  const docIds = [
225
298
  ...new Set([
@@ -228,7 +301,7 @@ export function summarizeAdjacentGeometryIntake(input: {
228
301
  ]),
229
302
  ].sort();
230
303
  return {
231
- schemaVersion: "layer-05-adjacent-geometry-intake/v0",
304
+ schemaVersion: "layer-05-adjacent-geometry-intake/v1",
232
305
  generatedAtUtc: input.generatedAtUtc,
233
306
  sourceRuns: input.sourceRuns,
234
307
  totals: {
@@ -236,6 +309,9 @@ export function summarizeAdjacentGeometryIntake(input: {
236
309
  numberingRowsWithRuntimeFragment: numberingRows.filter(
237
310
  (row) => row.runtimeFragmentId !== undefined,
238
311
  ).length,
312
+ numberingRowsJoinedFromL04Readback: numberingRows.filter(
313
+ (row) => row.runtimeJoinSource === "l04-numbering-readback",
314
+ ).length,
239
315
  numberingParagraphLineRects: sum(
240
316
  numberingRows,
241
317
  (row) => row.paragraphLineRectCount,
@@ -258,6 +334,19 @@ export function summarizeAdjacentGeometryIntake(input: {
258
334
  numberingRowsWithPageLocalTextColumn: numberingRows.filter(
259
335
  (row) => row.pageLocalGeometry.textColumn !== undefined,
260
336
  ).length,
337
+ numberingRowsWithRuntimePageMatch: numberingRows.filter(
338
+ (row) => row.pageLocalGeometry.pageIndexStatus === "matches-runtime",
339
+ ).length,
340
+ numberingUiaRowsWithRuntimePageMatch: numberingRows.filter(
341
+ (row) =>
342
+ hasNumberingUiaCapture(row) &&
343
+ row.pageLocalGeometry.pageIndexStatus === "matches-runtime",
344
+ ).length,
345
+ numberingRowsWithL04FrameProjection: numberingRows.filter(hasL04FrameProjection)
346
+ .length,
347
+ numberingUiaRowsWithL04FrameProjection: numberingRows.filter(
348
+ (row) => hasNumberingUiaCapture(row) && hasL04FrameProjection(row),
349
+ ).length,
261
350
  fieldRegionRows: fieldRegionRows.length,
262
351
  fieldRegionRowsWithRuntimeId: fieldRegionRows.filter(
263
352
  (row) => row.runtimeFieldRegionId !== undefined,
@@ -278,6 +367,12 @@ export function summarizeAdjacentGeometryIntake(input: {
278
367
  fieldRegionRowsWithRuntimePageContainment: fieldRegionRows.filter(
279
368
  (row) => row.pageLocalGeometry.pageSpanStatus === "contained-in-runtime",
280
369
  ).length,
370
+ fieldRegionRowsWithL04FrameProjection: fieldRegionRows.filter(
371
+ hasL04FrameProjection,
372
+ ).length,
373
+ fieldRegionUiaRowsWithL04FrameProjection: fieldRegionRows.filter(
374
+ (row) => row.resultLineRectCount > 0 && hasL04FrameProjection(row),
375
+ ).length,
281
376
  },
282
377
  routing: {
283
378
  routedTo: "L05",
@@ -290,6 +385,8 @@ export function summarizeAdjacentGeometryIntake(input: {
290
385
  coordinateSpace: "word-page-twips",
291
386
  precision: "word-page-local-calibration",
292
387
  requiredUpstreamFact: "L04 frame-to-pixel projection",
388
+ l04FrameProjectionCoordinateSpace: "l04-frame-twips",
389
+ l04FrameProjectionPrecision: "word-page-local-calibration",
293
390
  compositorReady: false,
294
391
  },
295
392
  documents: docIds.map((docId) =>
@@ -307,6 +404,8 @@ export function summarizeAdjacentGeometryIntake(input: {
307
404
  function summarizeNumberingRows(
308
405
  artifact: unknown,
309
406
  liveJoinArtifacts: readonly unknown[],
407
+ runtimeReadbackArtifacts: readonly unknown[],
408
+ pageFramesByDocId: ReadonlyMap<string, ReadonlyMap<number, AdjacentGeometryPageFrame>>,
310
409
  ): readonly AdjacentGeometryNumberingRowSummary[] {
311
410
  const uiaRowsByKey = new Map<string, Record<string, unknown>>();
312
411
  for (const raw of array(record(artifact).rows)) {
@@ -322,8 +421,16 @@ function summarizeNumberingRows(
322
421
  const liveRows = liveJoinArtifacts.flatMap((artifact) =>
323
422
  array(record(artifact).numberingRows).map(record),
324
423
  );
424
+ const runtimeReadbacksByKey = collectNumberingRuntimeReadbacks(
425
+ runtimeReadbackArtifacts,
426
+ );
325
427
  const rows = liveRows.length > 0
326
- ? liveRows.filter((row) => stringValue(row.runtimeFragmentId) !== undefined)
428
+ ? liveRows.filter((row) => {
429
+ if (stringValue(row.runtimeFragmentId) !== undefined) return true;
430
+ return (
431
+ numberingRuntimeReadbackForRow(row, runtimeReadbacksByKey) !== undefined
432
+ );
433
+ })
327
434
  : array(record(artifact).rows).map(record);
328
435
 
329
436
  return rows
@@ -338,6 +445,10 @@ function summarizeNumberingRows(
338
445
  const uiaRow =
339
446
  uiaRowsByKey.get(numberingKey(docId, wordParagraphOrdinalGlobal) ?? "") ??
340
447
  {};
448
+ const runtimeReadback = numberingRuntimeReadbackForRow(
449
+ row,
450
+ runtimeReadbacksByKey,
451
+ );
341
452
  const uiaTargetJoin = record(uiaRow.targetJoin);
342
453
  const paragraphLineRects = chooseRects(
343
454
  uiaRow.paragraphLineRects,
@@ -350,28 +461,60 @@ function summarizeNumberingRows(
350
461
  const rawMarkerRects = chooseRawArray(uiaRow.markerRects, row.markerRects);
351
462
  const markerRects = chooseRects(uiaRow.markerRects, row.markerRects);
352
463
  const markerLane =
353
- normalizeTwipsRect(row.markerLane) ?? normalizeTwipsRect(uiaTargetJoin.markerLane);
464
+ normalizeTwipsRect(row.markerLane) ??
465
+ normalizeTwipsRect(runtimeReadback?.markerLane) ??
466
+ normalizeTwipsRect(uiaTargetJoin.markerLane);
354
467
  const textColumn =
355
- normalizeTwipsRect(row.textColumn) ?? normalizeTwipsRect(uiaTargetJoin.textColumn);
468
+ normalizeTwipsRect(row.textColumn) ??
469
+ normalizeTwipsRect(runtimeReadback?.textColumn) ??
470
+ normalizeTwipsRect(uiaTargetJoin.textColumn);
356
471
  const runtimeFragmentId =
357
- stringValue(row.runtimeFragmentId) ?? stringValue(uiaTargetJoin.runtimeFragmentId);
472
+ stringValue(row.runtimeFragmentId) ??
473
+ stringValue(runtimeReadback?.runtimeFragmentId) ??
474
+ stringValue(uiaTargetJoin.runtimeFragmentId);
475
+ const runtimeJoinSource: AdjacentGeometryNumberingRowSummary["runtimeJoinSource"] =
476
+ stringValue(row.runtimeFragmentId) !== undefined
477
+ ? "adjacent-live"
478
+ : "l04-numbering-readback";
358
479
  const runtimePageIndex =
359
- numberValue(row.runtimePageIndex) ?? numberValue(uiaTargetJoin.runtimePageIndex);
480
+ numberValue(row.runtimePageIndex) ??
481
+ numberValue(runtimeReadback?.pageIndex) ??
482
+ numberValue(uiaTargetJoin.runtimePageIndex);
360
483
  const wordStartPage =
361
484
  numberValue(record(row.pageSpan).startPage) ??
362
485
  numberValue(row.wordStartPage) ??
363
486
  numberValue(uiaRow.wordStartPage);
487
+ const normalizedWordPageIndex =
488
+ wordStartPage === undefined ? undefined : wordStartPage - 1;
489
+ const pageIndexStatus = classifyPageIndex(
490
+ normalizedWordPageIndex,
491
+ runtimePageIndex,
492
+ );
493
+ const l04FrameProjection = projectNumberingToL04Frame({
494
+ docId,
495
+ normalizedWordPageIndex,
496
+ runtimePageIndex,
497
+ pageIndexStatus,
498
+ markerLane,
499
+ textColumn,
500
+ pageFramesByDocId,
501
+ });
364
502
  const pageLocalGeometry: AdjacentGeometryPageLocalNumberingGeometry = {
365
503
  wordPage: wordStartPage,
504
+ normalizedWordPageIndex,
366
505
  runtimePageIndex,
506
+ pageIndexStatus,
367
507
  runtimeFragmentId,
368
508
  markerLane,
369
509
  textColumn,
510
+ l04FrameProjection,
370
511
  coordinateSpace: "word-page-twips",
371
512
  precision: "word-page-local-calibration",
372
513
  normalizationStatus:
373
- markerLane || textColumn
374
- ? "word-page-local-awaiting-l04-frame-projection"
514
+ l04FrameProjection.status === "projected-l04-frame"
515
+ ? "l04-frame-twips-projected"
516
+ : markerLane || textColumn
517
+ ? "word-page-local-awaiting-l04-frame-projection"
375
518
  : "missing-word-page-local-lane",
376
519
  };
377
520
  return {
@@ -380,11 +523,20 @@ function summarizeNumberingRows(
380
523
  canonicalBlockId:
381
524
  stringValue(row.canonicalBlockId) ?? stringValue(uiaTargetJoin.canonicalBlockId),
382
525
  runtimeBlockId:
383
- stringValue(row.runtimeBlockId) ?? stringValue(uiaTargetJoin.runtimeBlockId),
526
+ stringValue(row.runtimeBlockId) ??
527
+ stringValue(runtimeReadback?.numberingSourceBlockId) ??
528
+ stringValue(runtimeReadback?.sourceBlockId) ??
529
+ stringValue(uiaTargetJoin.runtimeBlockId),
384
530
  runtimeFragmentId,
531
+ runtimeNumberingId:
532
+ stringValue(runtimeReadback?.runtimeNumberingId) ??
533
+ stringValue(record(row.runtimeJoinAttempt).runtimeNumberingId),
534
+ layoutObjectId: stringValue(runtimeReadback?.layoutObjectId),
535
+ runtimeJoinSource,
385
536
  runtimePageIndex,
386
537
  sourceParagraphPath:
387
538
  stringValue(row.sourceParagraphPath) ??
539
+ stringValue(runtimeReadback?.sourceBlockPath) ??
388
540
  stringValue(uiaTargetJoin.sourceParagraphPath),
389
541
  canonicalNumberingInstanceId:
390
542
  stringValue(row.canonicalNumberingInstanceId) ??
@@ -423,6 +575,7 @@ function summarizeNumberingRows(
423
575
  function summarizeFieldRegionRows(
424
576
  runtimeReadback: unknown,
425
577
  liveJoinArtifacts: readonly unknown[],
578
+ pageFramesByDocId: ReadonlyMap<string, ReadonlyMap<number, AdjacentGeometryPageFrame>>,
426
579
  ): readonly AdjacentGeometryFieldRegionRowSummary[] {
427
580
  const liveJoinByOrdinalKey = new Map<string, Record<string, unknown>>();
428
581
  const uniqueLiveJoinRows: Record<string, unknown>[] = [];
@@ -500,18 +653,30 @@ function summarizeFieldRegionRows(
500
653
  const wordPlacement = normalizeWordPlacement(
501
654
  record(readbackRow.wordResultPlacement),
502
655
  );
656
+ const pageSpanStatus = classifyPageSpan(normalizedWordPageIndexSpan, runtimePageSpan);
657
+ const l04FrameProjection = projectFieldToL04Frame({
658
+ docId,
659
+ normalizedWordPageIndexSpan,
660
+ runtimePageSpan,
661
+ pageSpanStatus,
662
+ wordPlacement,
663
+ pageFramesByDocId,
664
+ });
503
665
  const pageLocalGeometry: AdjacentGeometryPageLocalFieldGeometry = {
504
666
  expectedPageSpan,
505
667
  normalizedWordPageIndexSpan,
506
668
  runtimePageSpan,
507
- pageSpanStatus: classifyPageSpan(normalizedWordPageIndexSpan, runtimePageSpan),
669
+ pageSpanStatus,
508
670
  startAnchorTwips: wordPlacement?.startXYTwips,
509
671
  endAnchorTwips: wordPlacement?.endXYTwips,
672
+ l04FrameProjection,
510
673
  coordinateSpace: "word-page-twips",
511
674
  precision: "word-page-local-calibration",
512
675
  normalizationStatus:
513
- wordPlacement?.startXYTwips || wordPlacement?.endXYTwips
514
- ? "word-page-local-awaiting-l04-frame-projection"
676
+ l04FrameProjection.status === "projected-l04-frame"
677
+ ? "l04-frame-twips-projected"
678
+ : wordPlacement?.startXYTwips || wordPlacement?.endXYTwips
679
+ ? "word-page-local-awaiting-l04-frame-projection"
515
680
  : "missing-word-placement",
516
681
  };
517
682
  return {
@@ -562,6 +727,9 @@ function summarizeDocument(
562
727
  numberingRowsWithRuntimeFragment: numberingRows.filter(
563
728
  (row) => row.runtimeFragmentId !== undefined,
564
729
  ).length,
730
+ numberingRowsJoinedFromL04Readback: numberingRows.filter(
731
+ (row) => row.runtimeJoinSource === "l04-numbering-readback",
732
+ ).length,
565
733
  numberingParagraphLineRects: sum(
566
734
  numberingRows,
567
735
  (row) => row.paragraphLineRectCount,
@@ -584,6 +752,19 @@ function summarizeDocument(
584
752
  numberingRowsWithPageLocalTextColumn: numberingRows.filter(
585
753
  (row) => row.pageLocalGeometry.textColumn !== undefined,
586
754
  ).length,
755
+ numberingRowsWithRuntimePageMatch: numberingRows.filter(
756
+ (row) => row.pageLocalGeometry.pageIndexStatus === "matches-runtime",
757
+ ).length,
758
+ numberingUiaRowsWithRuntimePageMatch: numberingRows.filter(
759
+ (row) =>
760
+ hasNumberingUiaCapture(row) &&
761
+ row.pageLocalGeometry.pageIndexStatus === "matches-runtime",
762
+ ).length,
763
+ numberingRowsWithL04FrameProjection: numberingRows.filter(hasL04FrameProjection)
764
+ .length,
765
+ numberingUiaRowsWithL04FrameProjection: numberingRows.filter(
766
+ (row) => hasNumberingUiaCapture(row) && hasL04FrameProjection(row),
767
+ ).length,
587
768
  fieldRegionRows: fieldRegionRows.length,
588
769
  fieldRegionRowsWithRuntimeId: fieldRegionRows.filter(
589
770
  (row) => row.runtimeFieldRegionId !== undefined,
@@ -604,9 +785,21 @@ function summarizeDocument(
604
785
  fieldRegionRowsWithRuntimePageContainment: fieldRegionRows.filter(
605
786
  (row) => row.pageLocalGeometry.pageSpanStatus === "contained-in-runtime",
606
787
  ).length,
788
+ fieldRegionRowsWithL04FrameProjection: fieldRegionRows.filter(
789
+ hasL04FrameProjection,
790
+ ).length,
791
+ fieldRegionUiaRowsWithL04FrameProjection: fieldRegionRows.filter(
792
+ (row) => row.resultLineRectCount > 0 && hasL04FrameProjection(row),
793
+ ).length,
607
794
  };
608
795
  }
609
796
 
797
+ function hasL04FrameProjection(
798
+ row: AdjacentGeometryNumberingRowSummary | AdjacentGeometryFieldRegionRowSummary,
799
+ ): boolean {
800
+ return row.pageLocalGeometry.l04FrameProjection.status === "projected-l04-frame";
801
+ }
802
+
610
803
  function hasNumberingUiaCapture(row: AdjacentGeometryNumberingRowSummary): boolean {
611
804
  return (
612
805
  row.paragraphLineRectCount > 0 ||
@@ -615,6 +808,250 @@ function hasNumberingUiaCapture(row: AdjacentGeometryNumberingRowSummary): boole
615
808
  );
616
809
  }
617
810
 
811
+ function collectPageFramesByDocId(
812
+ artifacts: readonly unknown[],
813
+ ): Map<string, Map<number, AdjacentGeometryPageFrame>> {
814
+ const byDocId = new Map<string, Map<number, AdjacentGeometryPageFrame>>();
815
+ for (const artifact of artifacts) {
816
+ const artifactRecord = record(artifact);
817
+ const docId = stringValue(artifactRecord.docId);
818
+ if (!docId) continue;
819
+ for (const raw of array(artifactRecord.pages)) {
820
+ const page = record(raw);
821
+ const pageIndex = numberValue(page.pageIndex);
822
+ if (pageIndex === undefined) continue;
823
+ const frame = record(page.frame);
824
+ const physicalBoundsTwips = normalizeTwipsRect(frame.physicalBoundsTwips);
825
+ const pageFrame: AdjacentGeometryPageFrame = {
826
+ pageId: stringValue(page.pageId),
827
+ pageIndex,
828
+ frameId: stringValue(frame.frameId),
829
+ completeness: stringValue(frame.completeness),
830
+ physicalBoundsTwips,
831
+ };
832
+ let docFrames = byDocId.get(docId);
833
+ if (!docFrames) {
834
+ docFrames = new Map<number, AdjacentGeometryPageFrame>();
835
+ byDocId.set(docId, docFrames);
836
+ }
837
+ if (!docFrames.has(pageIndex)) docFrames.set(pageIndex, pageFrame);
838
+ }
839
+ }
840
+ return byDocId;
841
+ }
842
+
843
+ function collectNumberingRuntimeReadbacks(
844
+ artifacts: readonly unknown[],
845
+ ): Map<string, Record<string, unknown>> {
846
+ const byKey = new Map<string, Record<string, unknown>>();
847
+ for (const artifact of artifacts) {
848
+ const artifactRecord = record(artifact);
849
+ const docId = stringValue(artifactRecord.docId);
850
+ for (const raw of array(artifactRecord.numberingFragments)) {
851
+ const row = record(raw);
852
+ const key = numberingRuntimeReadbackKey(
853
+ docId ?? stringValue(row.docId),
854
+ stringValue(row.numberingKey) ?? stringValue(row.runtimeNumberingId),
855
+ stringValue(row.numberingInstanceId),
856
+ numberValue(row.level),
857
+ );
858
+ if (key && !byKey.has(key)) byKey.set(key, row);
859
+ }
860
+ }
861
+ return byKey;
862
+ }
863
+
864
+ function numberingRuntimeReadbackForRow(
865
+ row: Record<string, unknown>,
866
+ runtimeReadbacksByKey: ReadonlyMap<string, Record<string, unknown>>,
867
+ ): Record<string, unknown> | undefined {
868
+ const key = numberingRuntimeReadbackKey(
869
+ stringValue(row.docId),
870
+ stringValue(record(row.canonicalJoinAttempt).numberingKey),
871
+ stringValue(row.canonicalNumberingInstanceId),
872
+ numberValue(row.canonicalLevel),
873
+ );
874
+ return key ? runtimeReadbacksByKey.get(key) : undefined;
875
+ }
876
+
877
+ function projectNumberingToL04Frame(input: {
878
+ readonly docId: string;
879
+ readonly normalizedWordPageIndex: number | undefined;
880
+ readonly runtimePageIndex: number | undefined;
881
+ readonly pageIndexStatus: AdjacentGeometryPageIndexStatus;
882
+ readonly markerLane: AdjacentGeometryTwipsRect | undefined;
883
+ readonly textColumn: AdjacentGeometryTwipsRect | undefined;
884
+ readonly pageFramesByDocId: ReadonlyMap<string, ReadonlyMap<number, AdjacentGeometryPageFrame>>;
885
+ }): AdjacentGeometryL04FrameProjection {
886
+ if (input.pageIndexStatus === "word-page-missing") {
887
+ return baseFrameProjection("word-page-missing");
888
+ }
889
+ if (input.pageIndexStatus === "runtime-missing") {
890
+ return baseFrameProjection("runtime-page-missing", input.normalizedWordPageIndex);
891
+ }
892
+ if (input.pageIndexStatus === "differs-from-runtime") {
893
+ return baseFrameProjection(
894
+ "page-index-differs-from-runtime",
895
+ input.normalizedWordPageIndex,
896
+ );
897
+ }
898
+ if (!input.markerLane && !input.textColumn) {
899
+ return baseFrameProjection(
900
+ "missing-word-page-local-geometry",
901
+ input.normalizedWordPageIndex,
902
+ );
903
+ }
904
+ const frame = pageFrameFor(input.pageFramesByDocId, input.docId, input.runtimePageIndex);
905
+ if (!frame?.physicalBoundsTwips) {
906
+ return baseFrameProjection("missing-l04-page-frame", input.runtimePageIndex);
907
+ }
908
+ return {
909
+ pageIndex: input.runtimePageIndex,
910
+ pageId: frame.pageId,
911
+ frameId: frame.frameId,
912
+ frameCompleteness: frame.completeness,
913
+ physicalBoundsTwips: frame.physicalBoundsTwips,
914
+ markerLane: addFrameOrigin(input.markerLane, frame.physicalBoundsTwips),
915
+ textColumn: addFrameOrigin(input.textColumn, frame.physicalBoundsTwips),
916
+ coordinateSpace: "l04-frame-twips",
917
+ precision: "word-page-local-calibration",
918
+ status: "projected-l04-frame",
919
+ };
920
+ }
921
+
922
+ function projectFieldToL04Frame(input: {
923
+ readonly docId: string;
924
+ readonly normalizedWordPageIndexSpan: AdjacentGeometryPageSpan | undefined;
925
+ readonly runtimePageSpan: AdjacentGeometryPageSpan | undefined;
926
+ readonly pageSpanStatus: AdjacentGeometryPageLocalFieldGeometry["pageSpanStatus"];
927
+ readonly wordPlacement:
928
+ | AdjacentGeometryFieldRegionRowSummary["wordPlacement"]
929
+ | undefined;
930
+ readonly pageFramesByDocId: ReadonlyMap<string, ReadonlyMap<number, AdjacentGeometryPageFrame>>;
931
+ }): AdjacentGeometryL04FrameProjection {
932
+ const normalizedStart = input.normalizedWordPageIndexSpan?.startPage;
933
+ if (normalizedStart == null) return baseFrameProjection("word-page-missing");
934
+ if (!input.runtimePageSpan || input.runtimePageSpan.startPage == null) {
935
+ return baseFrameProjection("runtime-page-missing", normalizedStart);
936
+ }
937
+ if (
938
+ input.pageSpanStatus !== "matches-runtime" &&
939
+ input.pageSpanStatus !== "contained-in-runtime"
940
+ ) {
941
+ return baseFrameProjection("page-index-differs-from-runtime", normalizedStart);
942
+ }
943
+ if (!input.wordPlacement?.startXYTwips && !input.wordPlacement?.endXYTwips) {
944
+ return baseFrameProjection("missing-word-page-local-geometry", normalizedStart);
945
+ }
946
+ const frame = pageFrameFor(
947
+ input.pageFramesByDocId,
948
+ input.docId,
949
+ input.runtimePageSpan.startPage,
950
+ );
951
+ if (!frame?.physicalBoundsTwips) {
952
+ return baseFrameProjection("missing-l04-page-frame", input.runtimePageSpan.startPage);
953
+ }
954
+ return {
955
+ pageIndex: input.runtimePageSpan.startPage,
956
+ pageId: frame.pageId,
957
+ frameId: frame.frameId,
958
+ frameCompleteness: frame.completeness,
959
+ physicalBoundsTwips: frame.physicalBoundsTwips,
960
+ fieldStartAnchorTwips: addFrameOriginToPoint(
961
+ input.wordPlacement.startXYTwips,
962
+ frame.physicalBoundsTwips,
963
+ ),
964
+ fieldEndAnchorTwips: addFrameOriginToPoint(
965
+ input.wordPlacement.endXYTwips,
966
+ frame.physicalBoundsTwips,
967
+ ),
968
+ coordinateSpace: "l04-frame-twips",
969
+ precision: "word-page-local-calibration",
970
+ status: "projected-l04-frame",
971
+ };
972
+ }
973
+
974
+ function baseFrameProjection(
975
+ status: AdjacentGeometryL04FrameProjection["status"],
976
+ pageIndex?: number,
977
+ ): AdjacentGeometryL04FrameProjection {
978
+ return {
979
+ pageIndex,
980
+ coordinateSpace: "l04-frame-twips",
981
+ precision: "word-page-local-calibration",
982
+ status,
983
+ };
984
+ }
985
+
986
+ function pageFrameFor(
987
+ pageFramesByDocId: ReadonlyMap<string, ReadonlyMap<number, AdjacentGeometryPageFrame>>,
988
+ docId: string,
989
+ pageIndex: number | undefined,
990
+ ): AdjacentGeometryPageFrame | undefined {
991
+ return pageIndex === undefined ? undefined : pageFramesByDocId.get(docId)?.get(pageIndex);
992
+ }
993
+
994
+ function addFrameOrigin(
995
+ rect: AdjacentGeometryTwipsRect | undefined,
996
+ frame: AdjacentGeometryTwipsRect,
997
+ ): AdjacentGeometryTwipsRect | undefined {
998
+ if (!rect) return undefined;
999
+ return {
1000
+ xTwips:
1001
+ rect.xTwips === undefined || frame.xTwips === undefined
1002
+ ? rect.xTwips
1003
+ : rect.xTwips + frame.xTwips,
1004
+ yTwips:
1005
+ rect.yTwips === undefined || frame.yTwips === undefined
1006
+ ? rect.yTwips
1007
+ : rect.yTwips + frame.yTwips,
1008
+ widthTwips: rect.widthTwips,
1009
+ heightTwips: rect.heightTwips,
1010
+ };
1011
+ }
1012
+
1013
+ function addFrameOriginToPoint(
1014
+ point: { readonly x?: number; readonly y?: number } | undefined,
1015
+ frame: AdjacentGeometryTwipsRect,
1016
+ ): { readonly x?: number; readonly y?: number } | undefined {
1017
+ if (!point) return undefined;
1018
+ return {
1019
+ x:
1020
+ point.x === undefined || frame.xTwips === undefined
1021
+ ? point.x
1022
+ : point.x + frame.xTwips,
1023
+ y:
1024
+ point.y === undefined || frame.yTwips === undefined
1025
+ ? point.y
1026
+ : point.y + frame.yTwips,
1027
+ };
1028
+ }
1029
+
1030
+ function classifyPageIndex(
1031
+ normalizedWordPageIndex: number | undefined,
1032
+ runtimePageIndex: number | undefined,
1033
+ ): AdjacentGeometryPageIndexStatus {
1034
+ if (normalizedWordPageIndex === undefined) return "word-page-missing";
1035
+ if (runtimePageIndex === undefined) return "runtime-missing";
1036
+ return normalizedWordPageIndex === runtimePageIndex
1037
+ ? "matches-runtime"
1038
+ : "differs-from-runtime";
1039
+ }
1040
+
1041
+ function numberingRuntimeReadbackKey(
1042
+ docId: string | undefined,
1043
+ numberingKeyValue: string | undefined,
1044
+ numberingInstanceId: string | undefined,
1045
+ level: number | undefined,
1046
+ ): string | undefined {
1047
+ return docId &&
1048
+ numberingKeyValue &&
1049
+ numberingInstanceId &&
1050
+ level !== undefined
1051
+ ? `${docId}\0${numberingKeyValue}\0${numberingInstanceId}\0${level}`
1052
+ : undefined;
1053
+ }
1054
+
618
1055
  function normalizeRects(raw: unknown): readonly AdjacentGeometryRect[] {
619
1056
  if (Array.isArray(raw)) return raw.map(normalizeRect).filter(isRect);
620
1057
  const maybeRect = normalizeRect(raw);