@beyondwork/docx-react-component 1.0.102 → 1.0.104

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 (57) hide show
  1. package/package.json +1 -1
  2. package/src/api/public-types.ts +63 -1
  3. package/src/api/v3/_runtime-handle.ts +2 -0
  4. package/src/api/v3/ai/outline.ts +2 -7
  5. package/src/api/v3/runtime/geometry.ts +79 -0
  6. package/src/core/commands/formatting-commands.ts +8 -7
  7. package/src/core/commands/paragraph-layout-commands.ts +11 -10
  8. package/src/core/commands/section-layout-commands.ts +7 -6
  9. package/src/core/commands/style-commands.ts +3 -2
  10. package/src/io/normalize/normalize-text.ts +6 -5
  11. package/src/io/ooxml/parse-anchor.ts +15 -15
  12. package/src/io/ooxml/parse-drawing.ts +103 -5
  13. package/src/io/ooxml/parse-fields.ts +43 -21
  14. package/src/io/ooxml/parse-font-table.ts +2 -1
  15. package/src/io/ooxml/parse-footnotes.ts +3 -2
  16. package/src/io/ooxml/parse-headers-footers.ts +7 -6
  17. package/src/io/ooxml/parse-main-document.ts +41 -40
  18. package/src/io/ooxml/parse-numbering.ts +3 -2
  19. package/src/io/ooxml/parse-object.ts +6 -6
  20. package/src/io/ooxml/parse-paragraph-formatting.ts +12 -11
  21. package/src/io/ooxml/parse-picture.ts +16 -16
  22. package/src/io/ooxml/parse-run-formatting.ts +11 -10
  23. package/src/io/ooxml/parse-settings.ts +2 -1
  24. package/src/io/ooxml/parse-shapes.ts +148 -17
  25. package/src/io/ooxml/parse-styles.ts +16 -16
  26. package/src/io/ooxml/parse-theme.ts +5 -4
  27. package/src/model/canonical-document.ts +869 -836
  28. package/src/model/canonical-layout-inputs.ts +979 -0
  29. package/src/model/layout/index.ts +6 -0
  30. package/src/model/layout/page-graph-types.ts +61 -0
  31. package/src/model/layout/runtime-page-graph-types.ts +10 -0
  32. package/src/runtime/collab/runtime-collab-sync.ts +3 -3
  33. package/src/runtime/debug/build-debug-inspector-snapshot.ts +17 -4
  34. package/src/runtime/document-runtime.ts +30 -14
  35. package/src/runtime/event-refresh-hints.ts +3 -0
  36. package/src/runtime/formatting/document-lookup.ts +3 -2
  37. package/src/runtime/formatting/formatting-context.ts +176 -34
  38. package/src/runtime/formatting/index.ts +20 -0
  39. package/src/runtime/formatting/layout-inputs.ts +320 -0
  40. package/src/runtime/formatting/numbering/geometry.ts +13 -12
  41. package/src/runtime/formatting/style-cascade.ts +2 -1
  42. package/src/runtime/formatting/table-style-resolver.ts +8 -7
  43. package/src/runtime/geometry/caret-geometry.ts +82 -10
  44. package/src/runtime/geometry/geometry-facet.ts +36 -0
  45. package/src/runtime/geometry/geometry-index.ts +891 -0
  46. package/src/runtime/geometry/geometry-types.ts +221 -1
  47. package/src/runtime/geometry/index.ts +26 -0
  48. package/src/runtime/geometry/inert-geometry-facet.ts +3 -0
  49. package/src/runtime/geometry/replacement-envelope.ts +41 -2
  50. package/src/runtime/layout/layout-engine-version.ts +16 -1
  51. package/src/runtime/layout/page-graph.ts +191 -1
  52. package/src/runtime/prerender/graph-canonicalize.ts +30 -0
  53. package/src/runtime/surface-projection.ts +74 -39
  54. package/src/runtime/workflow/coordinator.ts +57 -11
  55. package/src/session/import/normalize.ts +2 -1
  56. package/src/session/import/source-package-evidence.ts +612 -1
  57. package/src/ui-tailwind/chrome-overlay/tw-page-stack-overlay-layer.tsx +3 -0
@@ -0,0 +1,891 @@
1
+ /**
2
+ * PE2 geometry index substrate.
3
+ *
4
+ * Layer 05 owns a renderer-neutral index of the geometry already projected
5
+ * for the current page frame. The current adapter consumes `RenderFrame`
6
+ * because that is the runtime's materialized page-slice view; the output is
7
+ * intentionally limited to geometry facts and precision/status metadata.
8
+ */
9
+
10
+ import type {
11
+ EditorStoryTarget,
12
+ } from "../../api/public-types.ts";
13
+ import type {
14
+ BlockNode,
15
+ CanonicalDocument,
16
+ } from "../../model/canonical-document.ts";
17
+ import {
18
+ collectCanonicalLayoutInputs,
19
+ collectStoryBlockContexts,
20
+ createHeaderFooterStoryKey,
21
+ createNoteStoryKey,
22
+ MAIN_STORY_KEY,
23
+ type CanonicalAnchorLayoutInput,
24
+ type CanonicalLayoutInputs,
25
+ type CanonicalTableLayoutInput,
26
+ type CanonicalTableCellLayoutInput,
27
+ type CanonicalTableRowLayoutInput,
28
+ } from "../../model/canonical-layout-inputs.ts";
29
+ import type {
30
+ RenderFrame,
31
+ RenderFrameRect,
32
+ RenderBlock,
33
+ RenderPage,
34
+ RenderStoryRegion,
35
+ } from "../render/index.ts";
36
+ import type {
37
+ AnchorGeometry,
38
+ GeometryHitTarget,
39
+ GeometryIndex,
40
+ GeometryIndexCoverage,
41
+ GeometryIndexLine,
42
+ GeometryIndexPage,
43
+ GeometryIndexRegion,
44
+ GeometryIndexSlice,
45
+ GeometryObjectHandleEntry,
46
+ GeometryPrecisionCounts,
47
+ GeometryRect,
48
+ GeometryReplacementEnvelopeEntry,
49
+ GeometrySourceIdentity,
50
+ SemanticDisplayEntry,
51
+ } from "./geometry-types.ts";
52
+
53
+ export interface GeometryIndexProjectionOptions {
54
+ readonly canonicalDocument?: CanonicalDocument | null;
55
+ readonly layoutInputs?: CanonicalLayoutInputs | null;
56
+ }
57
+
58
+ export function createUnavailableGeometryCoverage(): GeometryIndexCoverage {
59
+ return {
60
+ status: "unavailable",
61
+ pageCount: 0,
62
+ regionCount: 0,
63
+ sliceCount: 0,
64
+ lineCount: 0,
65
+ anchorCount: 0,
66
+ hitTargetCount: 0,
67
+ semanticEntryCount: 0,
68
+ replacementEnvelopeCount: 0,
69
+ objectHandleCount: 0,
70
+ precision: createPrecisionCounts(),
71
+ };
72
+ }
73
+
74
+ export function projectGeometryIndexFromFrame(
75
+ frame: RenderFrame | null,
76
+ options?: GeometryIndexProjectionOptions,
77
+ ): GeometryIndex | null {
78
+ if (!frame) return null;
79
+
80
+ const identities = createIdentityLookup(options);
81
+ const pages: GeometryIndexPage[] = [];
82
+ const regions: GeometryIndexRegion[] = [];
83
+ const slices: GeometryIndexSlice[] = [];
84
+ const lines: GeometryIndexLine[] = [];
85
+ const anchors: AnchorGeometry[] = [];
86
+ const hitTargets: GeometryHitTarget[] = [];
87
+ const semanticEntries: SemanticDisplayEntry[] = [];
88
+ const replacementEnvelopes: GeometryReplacementEnvelopeEntry[] = [];
89
+ const objectHandles: GeometryObjectHandleEntry[] = [];
90
+ const precision = createPrecisionCounts();
91
+
92
+ for (const page of frame.pages) {
93
+ const regionEntries = collectRegionEntries(page);
94
+ const regionIds: string[] = [];
95
+
96
+ for (const [region, ordinal] of regionEntries) {
97
+ const regionId = makeRegionId(page, region, ordinal);
98
+ const storyKey = canonicalStoryKeyFromTarget(region.storyTarget);
99
+ regionIds.push(regionId);
100
+ const sliceIds: string[] = [];
101
+
102
+ for (const block of region.blocks) {
103
+ const sliceId = makeSliceId(regionId, block.fragment.fragmentId);
104
+ const sliceIdentity = identities?.sliceIdentity(
105
+ storyKey,
106
+ block.fragment.blockId,
107
+ );
108
+ sliceIds.push(sliceId);
109
+ const lineIds: string[] = [];
110
+
111
+ for (const line of block.lines) {
112
+ const lineId = makeLineId(
113
+ regionId,
114
+ block.fragment.fragmentId,
115
+ line.line.lineIndex,
116
+ );
117
+ lineIds.push(lineId);
118
+ const anchorIds: string[] = [];
119
+
120
+ for (let index = 0; index < line.anchors.length; index += 1) {
121
+ const anchor = line.anchors[index]!;
122
+ const anchorId = makeAnchorId(lineId, index);
123
+ const anchorIdentity = identities?.anchorIdentity(
124
+ storyKey,
125
+ anchor.blockId ?? block.fragment.blockId,
126
+ );
127
+ anchorIds.push(anchorId);
128
+ anchors.push({
129
+ anchorId,
130
+ pageId: page.page.pageId,
131
+ pageIndex: page.page.pageIndex,
132
+ regionId,
133
+ regionKind: region.region.kind,
134
+ blockId: anchor.blockId,
135
+ fragmentId: anchor.fragmentId,
136
+ lineId,
137
+ runtimeOffset: anchor.runtimeOffset,
138
+ rect: toGeometryRect(anchor.frame),
139
+ precision: "exact",
140
+ ...(anchorIdentity ? { sourceIdentity: anchorIdentity } : {}),
141
+ });
142
+ recordPrecision(precision, "exact");
143
+ }
144
+
145
+ lines.push({
146
+ lineId,
147
+ pageId: page.page.pageId,
148
+ pageIndex: page.page.pageIndex,
149
+ regionId,
150
+ regionKind: region.region.kind,
151
+ blockId: block.fragment.blockId,
152
+ fragmentId: block.fragment.fragmentId,
153
+ lineIndex: line.line.lineIndex,
154
+ rect: toGeometryRect(line.frame),
155
+ anchorIds,
156
+ });
157
+ recordPrecision(precision, "exact");
158
+ hitTargets.push({
159
+ targetId: `hit:${lineId}`,
160
+ pageId: page.page.pageId,
161
+ pageIndex: page.page.pageIndex,
162
+ regionId,
163
+ regionKind: region.region.kind,
164
+ blockId: block.fragment.blockId,
165
+ fragmentId: block.fragment.fragmentId,
166
+ lineIndex: line.line.lineIndex,
167
+ rect: toGeometryRect(line.frame),
168
+ precision: "exact",
169
+ });
170
+ recordPrecision(precision, "exact");
171
+ semanticEntries.push({
172
+ entryId: `semantic:text-line:${lineId}`,
173
+ kind: "text-line",
174
+ pageId: page.page.pageId,
175
+ pageIndex: page.page.pageIndex,
176
+ regionId,
177
+ regionKind: region.region.kind,
178
+ sliceId,
179
+ lineId,
180
+ blockId: block.fragment.blockId,
181
+ fragmentId: block.fragment.fragmentId,
182
+ rect: toGeometryRect(line.frame),
183
+ status: "realized",
184
+ precision: "exact",
185
+ });
186
+ recordPrecision(precision, "exact");
187
+ }
188
+
189
+ slices.push({
190
+ sliceId,
191
+ pageId: page.page.pageId,
192
+ pageIndex: page.page.pageIndex,
193
+ regionId,
194
+ regionKind: region.region.kind,
195
+ blockId: block.fragment.blockId,
196
+ fragmentId: block.fragment.fragmentId,
197
+ kind: block.kind,
198
+ rect: toGeometryRect(block.frame),
199
+ lineIds,
200
+ ...(sliceIdentity ? { sourceIdentity: sliceIdentity } : {}),
201
+ });
202
+ recordPrecision(precision, "exact");
203
+ appendBlockSemanticEntries({
204
+ page,
205
+ region,
206
+ regionId,
207
+ sliceId,
208
+ block,
209
+ identities,
210
+ storyKey,
211
+ entries: semanticEntries,
212
+ precision,
213
+ });
214
+ }
215
+
216
+ regions.push({
217
+ regionId,
218
+ pageId: page.page.pageId,
219
+ pageIndex: page.page.pageIndex,
220
+ kind: region.region.kind,
221
+ ordinal,
222
+ rect: toGeometryRect(region.frame),
223
+ sliceIds,
224
+ });
225
+ recordPrecision(precision, "exact");
226
+ }
227
+
228
+ pages.push({
229
+ pageId: page.page.pageId,
230
+ pageIndex: page.page.pageIndex,
231
+ rect: toGeometryRect(page.frame),
232
+ regionIds,
233
+ });
234
+ recordPrecision(precision, "exact");
235
+ }
236
+
237
+ const coverage: GeometryIndexCoverage = {
238
+ status: "realized",
239
+ pageCount: pages.length,
240
+ regionCount: regions.length,
241
+ sliceCount: slices.length,
242
+ lineCount: lines.length,
243
+ anchorCount: anchors.length,
244
+ hitTargetCount: hitTargets.length,
245
+ semanticEntryCount: semanticEntries.length,
246
+ replacementEnvelopeCount: replacementEnvelopes.length,
247
+ objectHandleCount: objectHandles.length,
248
+ precision,
249
+ };
250
+
251
+ return {
252
+ source: "render-frame-adapter",
253
+ revision: frame.revision,
254
+ measurementFidelity: frame.measurementFidelity,
255
+ activeStory: frame.activeStory,
256
+ pages,
257
+ regions,
258
+ slices,
259
+ lines,
260
+ anchors,
261
+ hitTargets,
262
+ semanticEntries,
263
+ replacementEnvelopes,
264
+ objectHandles,
265
+ coverage,
266
+ };
267
+ }
268
+
269
+ export function summarizeGeometryCoverageFromFrame(
270
+ frame: RenderFrame | null,
271
+ ): GeometryIndexCoverage {
272
+ if (!frame) return createUnavailableGeometryCoverage();
273
+
274
+ const precision = createPrecisionCounts();
275
+ let pageCount = 0;
276
+ let regionCount = 0;
277
+ let sliceCount = 0;
278
+ let lineCount = 0;
279
+ let anchorCount = 0;
280
+ let hitTargetCount = 0;
281
+ let semanticEntryCount = 0;
282
+
283
+ for (const page of frame.pages) {
284
+ pageCount += 1;
285
+ recordPrecision(precision, "exact");
286
+ for (const [region] of collectRegionEntries(page)) {
287
+ regionCount += 1;
288
+ recordPrecision(precision, "exact");
289
+ for (const block of region.blocks) {
290
+ sliceCount += 1;
291
+ recordPrecision(precision, "exact");
292
+ for (const line of block.lines) {
293
+ lineCount += 1;
294
+ hitTargetCount += 1;
295
+ recordPrecision(precision, "exact"); // line rect
296
+ recordPrecision(precision, "exact"); // matching hit-target rect
297
+ semanticEntryCount += 1;
298
+ recordPrecision(precision, "exact"); // text-line semantic entry
299
+ anchorCount += line.anchors.length;
300
+ for (let i = 0; i < line.anchors.length; i += 1) {
301
+ recordPrecision(precision, "exact");
302
+ }
303
+ }
304
+ const blockSemantic = countBlockSemanticEntries(block);
305
+ semanticEntryCount += blockSemantic.exact;
306
+ semanticEntryCount += blockSemantic["within-tolerance"];
307
+ semanticEntryCount += blockSemantic.heuristic;
308
+ precision.exact += blockSemantic.exact;
309
+ precision["within-tolerance"] += blockSemantic["within-tolerance"];
310
+ precision.heuristic += blockSemantic.heuristic;
311
+ }
312
+ }
313
+ }
314
+
315
+ return {
316
+ status: "realized",
317
+ pageCount,
318
+ regionCount,
319
+ sliceCount,
320
+ lineCount,
321
+ anchorCount,
322
+ hitTargetCount,
323
+ semanticEntryCount,
324
+ replacementEnvelopeCount: 0,
325
+ objectHandleCount: 0,
326
+ precision,
327
+ };
328
+ }
329
+
330
+ function appendBlockSemanticEntries(input: {
331
+ page: RenderPage;
332
+ region: RenderStoryRegion;
333
+ regionId: string;
334
+ sliceId: string;
335
+ block: RenderBlock;
336
+ identities: GeometryIdentityLookup | null;
337
+ storyKey: string;
338
+ entries: SemanticDisplayEntry[];
339
+ precision: GeometryPrecisionCounts;
340
+ }): void {
341
+ const {
342
+ page,
343
+ region,
344
+ regionId,
345
+ sliceId,
346
+ block,
347
+ identities,
348
+ storyKey,
349
+ entries,
350
+ precision,
351
+ } = input;
352
+ const tableIdentity = identities?.tableIdentity(
353
+ storyKey,
354
+ block.fragment.blockId,
355
+ );
356
+ const base = {
357
+ pageId: page.page.pageId,
358
+ pageIndex: page.page.pageIndex,
359
+ regionId,
360
+ regionKind: region.region.kind,
361
+ sliceId,
362
+ blockId: block.fragment.blockId,
363
+ fragmentId: block.fragment.fragmentId,
364
+ } as const;
365
+
366
+ if (block.kind === "table") {
367
+ entries.push({
368
+ ...base,
369
+ entryId: `semantic:table-frame:${sliceId}`,
370
+ kind: "table-frame",
371
+ rect: toGeometryRect(block.frame),
372
+ status: "realized",
373
+ precision: "exact",
374
+ ...(tableIdentity
375
+ ? { sourceIdentity: tableSourceIdentity(tableIdentity) }
376
+ : {}),
377
+ });
378
+ recordPrecision(precision, "exact");
379
+
380
+ const plan = block.tablePlan;
381
+ if (!plan) return;
382
+ const rowCount = resolveTableRowCount(block);
383
+ const rowHeightPx =
384
+ rowCount > 0 ? block.frame.heightPx / rowCount : block.frame.heightPx;
385
+ for (let rowIndex = 0; rowIndex < rowCount; rowIndex += 1) {
386
+ entries.push({
387
+ ...base,
388
+ entryId: `semantic:table-row:${sliceId}:${rowIndex}`,
389
+ kind: "table-row",
390
+ rowIndex,
391
+ rect: {
392
+ leftPx: block.frame.leftPx,
393
+ topPx: block.frame.topPx + rowIndex * rowHeightPx,
394
+ widthPx: block.frame.widthPx,
395
+ heightPx: rowHeightPx,
396
+ space: "frame",
397
+ precision: "within-tolerance",
398
+ },
399
+ status: "realized",
400
+ precision: "within-tolerance",
401
+ ...(tableIdentity
402
+ ? { sourceIdentity: tableRowSourceIdentity(tableIdentity, rowIndex) }
403
+ : {}),
404
+ });
405
+ recordPrecision(precision, "within-tolerance");
406
+ }
407
+
408
+ for (const cell of plan.bandClasses.cells) {
409
+ const rect = pageFrameCellRect(block, cell.rowIndex, cell.columnIndex);
410
+ if (!rect) continue;
411
+ entries.push({
412
+ ...base,
413
+ entryId: `semantic:table-cell:${sliceId}:${cell.rowIndex}:${cell.columnIndex}`,
414
+ kind: "table-cell",
415
+ rowIndex: cell.rowIndex,
416
+ columnIndex: cell.columnIndex,
417
+ rect: {
418
+ ...toGeometryRect(rect),
419
+ precision: "within-tolerance",
420
+ },
421
+ status: "realized",
422
+ precision: "within-tolerance",
423
+ ...(tableIdentity
424
+ ? {
425
+ sourceIdentity: tableCellSourceIdentity(
426
+ tableIdentity,
427
+ cell.rowIndex,
428
+ cell.columnIndex,
429
+ ),
430
+ }
431
+ : {}),
432
+ });
433
+ recordPrecision(precision, "within-tolerance");
434
+ }
435
+ return;
436
+ }
437
+
438
+ if (block.kind === "image-float") {
439
+ entries.push({
440
+ ...base,
441
+ entryId: `semantic:floating-object:${sliceId}`,
442
+ kind: "floating-object",
443
+ rect: {
444
+ ...toGeometryRect(block.frame),
445
+ precision: "heuristic",
446
+ },
447
+ status: "realized",
448
+ precision: "heuristic",
449
+ });
450
+ recordPrecision(precision, "heuristic");
451
+ return;
452
+ }
453
+
454
+ if (block.kind === "opaque") {
455
+ entries.push({
456
+ ...base,
457
+ entryId: `semantic:placeholder:${sliceId}`,
458
+ kind: "placeholder",
459
+ rect: toGeometryRect(block.frame),
460
+ status: "realized",
461
+ precision: "exact",
462
+ });
463
+ recordPrecision(precision, "exact");
464
+ return;
465
+ }
466
+
467
+ if (block.kind === "synthetic") {
468
+ entries.push({
469
+ ...base,
470
+ entryId: `semantic:debug-classification:${sliceId}`,
471
+ kind: "debug-classification",
472
+ rect: toGeometryRect(block.frame),
473
+ status: "realized",
474
+ precision: "exact",
475
+ });
476
+ recordPrecision(precision, "exact");
477
+ }
478
+ }
479
+
480
+ function countBlockSemanticEntries(block: RenderBlock): GeometryPrecisionCounts {
481
+ const counts = createPrecisionCounts();
482
+ if (block.kind === "table") {
483
+ counts.exact += 1;
484
+ if (block.tablePlan) {
485
+ counts["within-tolerance"] += resolveTableRowCount(block);
486
+ counts["within-tolerance"] += block.tablePlan.bandClasses.cells.length;
487
+ }
488
+ } else if (block.kind === "image-float") {
489
+ counts.heuristic += 1;
490
+ } else if (block.kind === "opaque" || block.kind === "synthetic") {
491
+ counts.exact += 1;
492
+ }
493
+ return counts;
494
+ }
495
+
496
+ function resolveTableRowCount(block: RenderBlock): number {
497
+ const plan = block.tablePlan;
498
+ if (!plan) return 0;
499
+ return Math.max(
500
+ 1,
501
+ plan.bandClasses.rows.length ||
502
+ plan.bandClasses.cells.reduce(
503
+ (max, c) => Math.max(max, c.rowIndex + 1),
504
+ 0,
505
+ ),
506
+ );
507
+ }
508
+
509
+ function pageFrameCellRect(
510
+ block: RenderBlock,
511
+ rowIndex: number,
512
+ columnIndex: number,
513
+ ): RenderFrameRect | null {
514
+ const plan = block.tablePlan;
515
+ if (!plan || plan.columnsTwips.length === 0) return null;
516
+ const columnCount = plan.columnsTwips.length;
517
+ const totalWidthTwips = plan.columnsTwips.reduce(
518
+ (sum, value) => sum + Math.max(0, value),
519
+ 0,
520
+ );
521
+ const pxPerTwip =
522
+ totalWidthTwips > 0 ? block.frame.widthPx / totalWidthTwips : 0;
523
+ let leftPx = block.frame.leftPx;
524
+ for (let i = 0; i < columnIndex; i += 1) {
525
+ leftPx += (plan.columnsTwips[i] ?? 0) * pxPerTwip;
526
+ }
527
+ const sameRow = plan.bandClasses.cells
528
+ .filter((cell) => cell.rowIndex === rowIndex)
529
+ .sort((a, b) => a.columnIndex - b.columnIndex);
530
+ const currentIndex = sameRow.findIndex(
531
+ (cell) => cell.columnIndex === columnIndex,
532
+ );
533
+ const next = sameRow[currentIndex + 1];
534
+ const columnSpan = Math.max(
535
+ 1,
536
+ (next?.columnIndex ?? columnCount) - columnIndex,
537
+ );
538
+ let widthPx = 0;
539
+ for (let i = columnIndex; i < columnIndex + columnSpan; i += 1) {
540
+ widthPx += (plan.columnsTwips[i] ?? 0) * pxPerTwip;
541
+ }
542
+ const rowCount = resolveTableRowCount(block);
543
+ const rowHeightPx =
544
+ rowCount > 0 ? block.frame.heightPx / rowCount : block.frame.heightPx;
545
+ return {
546
+ leftPx,
547
+ topPx: block.frame.topPx + rowIndex * rowHeightPx,
548
+ widthPx,
549
+ heightPx: rowHeightPx,
550
+ };
551
+ }
552
+
553
+ interface GeometryIdentityLookup {
554
+ sliceIdentity(storyKey: string, blockId: string): GeometrySourceIdentity | undefined;
555
+ tableIdentity(storyKey: string, blockId: string): CanonicalTableLayoutInput | undefined;
556
+ anchorIdentity(storyKey: string, blockId: string): GeometrySourceIdentity | undefined;
557
+ }
558
+
559
+ function createIdentityLookup(
560
+ options: GeometryIndexProjectionOptions | undefined,
561
+ ): GeometryIdentityLookup | null {
562
+ const layoutInputs =
563
+ options?.layoutInputs ??
564
+ (options?.canonicalDocument
565
+ ? collectCanonicalLayoutInputs(options.canonicalDocument)
566
+ : null);
567
+ if (!layoutInputs) return null;
568
+
569
+ const blockIdByPath = options?.canonicalDocument
570
+ ? collectProjectedBlockIdsByPath(options.canonicalDocument)
571
+ : new Map<string, string>();
572
+ const tableByStoryBlockId = new Map<string, CanonicalTableLayoutInput>();
573
+ const tableOrdinalByStory = new Map<string, number>();
574
+ for (const table of layoutInputs.tables) {
575
+ const blockId =
576
+ blockIdByPath.get(storyPathKey(table.storyKey, table.blockPath)) ??
577
+ nextTableBlockId(tableOrdinalByStory, table.storyKey);
578
+ tableByStoryBlockId.set(storyBlockKey(table.storyKey, blockId), table);
579
+ }
580
+
581
+ const blockPathByStoryBlockId = new Map<string, string>();
582
+ for (const [pathKey, blockId] of blockIdByPath) {
583
+ const splitAt = pathKey.indexOf("\u0000");
584
+ if (splitAt <= 0) continue;
585
+ const storyKey = pathKey.slice(0, splitAt);
586
+ const blockPath = pathKey.slice(splitAt + 1);
587
+ blockPathByStoryBlockId.set(storyBlockKey(storyKey, blockId), blockPath);
588
+ }
589
+
590
+ const anchorsByStoryBlockId = new Map<string, CanonicalAnchorLayoutInput[]>();
591
+ for (const anchor of layoutInputs.anchors) {
592
+ const blockId = blockIdByPath.get(
593
+ storyPathKey(anchor.storyKey, anchor.blockPath),
594
+ );
595
+ if (!blockId) continue;
596
+ const key = storyBlockKey(anchor.storyKey, blockId);
597
+ const list = anchorsByStoryBlockId.get(key);
598
+ if (list) {
599
+ list.push(anchor);
600
+ } else {
601
+ anchorsByStoryBlockId.set(key, [anchor]);
602
+ }
603
+ }
604
+
605
+ return {
606
+ sliceIdentity(storyKey, blockId) {
607
+ const table = tableByStoryBlockId.get(storyBlockKey(storyKey, blockId));
608
+ if (table) return tableSourceIdentity(table);
609
+ const blockPath = blockPathByStoryBlockId.get(storyBlockKey(storyKey, blockId));
610
+ return blockPath
611
+ ? { storyKey, blockPath, joinKind: "direct" }
612
+ : undefined;
613
+ },
614
+ tableIdentity(storyKey, blockId) {
615
+ return tableByStoryBlockId.get(storyBlockKey(storyKey, blockId));
616
+ },
617
+ anchorIdentity(storyKey, blockId) {
618
+ const key = storyBlockKey(storyKey, blockId);
619
+ const anchors = anchorsByStoryBlockId.get(key);
620
+ if (!anchors || anchors.length === 0) return undefined;
621
+ const blockPath = blockPathByStoryBlockId.get(key) ?? anchors[0]!.blockPath;
622
+ if (anchors.length !== 1) {
623
+ return {
624
+ storyKey,
625
+ blockPath,
626
+ joinKind: "block-scoped",
627
+ };
628
+ }
629
+ const anchor = anchors[0]!;
630
+ return {
631
+ storyKey,
632
+ blockPath: anchor.blockPath,
633
+ objectKey: anchor.objectKey,
634
+ inlinePath: anchor.inlinePath,
635
+ objectKind: anchor.objectKind,
636
+ editPosture: anchor.editPosture,
637
+ joinKind: "block-scoped",
638
+ };
639
+ },
640
+ };
641
+ }
642
+
643
+ function nextTableBlockId(
644
+ ordinalByStory: Map<string, number>,
645
+ storyKey: string,
646
+ ): string {
647
+ const ordinal = ordinalByStory.get(storyKey) ?? 0;
648
+ ordinalByStory.set(storyKey, ordinal + 1);
649
+ return `table-${ordinal}`;
650
+ }
651
+
652
+ function tableSourceIdentity(
653
+ table: CanonicalTableLayoutInput,
654
+ ): GeometrySourceIdentity {
655
+ return {
656
+ storyKey: table.storyKey,
657
+ blockPath: table.blockPath,
658
+ tableKey: table.tableKey,
659
+ joinKind: "direct",
660
+ };
661
+ }
662
+
663
+ function tableRowSourceIdentity(
664
+ table: CanonicalTableLayoutInput,
665
+ rowIndex: number,
666
+ ): GeometrySourceIdentity {
667
+ const row = table.rows.find((entry) => entry.rowIndex === rowIndex);
668
+ return {
669
+ ...tableSourceIdentity(table),
670
+ ...(row ? { rowKey: row.rowKey } : {}),
671
+ };
672
+ }
673
+
674
+ function tableCellSourceIdentity(
675
+ table: CanonicalTableLayoutInput,
676
+ rowIndex: number,
677
+ gridColumnStart: number,
678
+ ): GeometrySourceIdentity {
679
+ const row = table.rows.find((entry) => entry.rowIndex === rowIndex);
680
+ const cell = findCanonicalCellByGridColumn(row, gridColumnStart);
681
+ return {
682
+ ...tableRowSourceIdentity(table, rowIndex),
683
+ ...(cell ? { cellKey: cell.cellKey } : {}),
684
+ };
685
+ }
686
+
687
+ function findCanonicalCellByGridColumn(
688
+ row: CanonicalTableRowLayoutInput | undefined,
689
+ gridColumnStart: number,
690
+ ): CanonicalTableCellLayoutInput | undefined {
691
+ if (!row) return undefined;
692
+ return row.cells.find((cell) => cell.gridColumnStart === gridColumnStart);
693
+ }
694
+
695
+ function collectProjectedBlockIdsByPath(
696
+ document: CanonicalDocument,
697
+ ): Map<string, string> {
698
+ const result = new Map<string, string>();
699
+ for (const context of collectStoryBlockContexts(document)) {
700
+ const counters = {
701
+ paragraph: 0,
702
+ table: 0,
703
+ sdt: 0,
704
+ customXml: 0,
705
+ altChunk: 0,
706
+ opaque: 0,
707
+ };
708
+ collectProjectedBlockIdsForBlocks(
709
+ context.blocks,
710
+ context.storyKey,
711
+ context.basePath,
712
+ counters,
713
+ result,
714
+ );
715
+ }
716
+ return result;
717
+ }
718
+
719
+ function collectProjectedBlockIdsForBlocks(
720
+ blocks: readonly BlockNode[],
721
+ storyKey: string,
722
+ basePath: string,
723
+ counters: {
724
+ paragraph: number;
725
+ table: number;
726
+ sdt: number;
727
+ customXml: number;
728
+ altChunk: number;
729
+ opaque: number;
730
+ },
731
+ result: Map<string, string>,
732
+ ): void {
733
+ for (let blockIndex = 0; blockIndex < blocks.length; blockIndex += 1) {
734
+ const block = blocks[blockIndex];
735
+ if (!block) continue;
736
+ const blockPath = `${basePath}/block[${blockIndex}]`;
737
+ if (block.type === "paragraph") {
738
+ result.set(storyPathKey(storyKey, blockPath), `paragraph-${counters.paragraph}`);
739
+ counters.paragraph += 1;
740
+ continue;
741
+ }
742
+ if (block.type === "table") {
743
+ result.set(storyPathKey(storyKey, blockPath), `table-${counters.table}`);
744
+ counters.table += 1;
745
+ for (let rowIndex = 0; rowIndex < block.rows.length; rowIndex += 1) {
746
+ const row = block.rows[rowIndex];
747
+ if (!row) continue;
748
+ for (let cellIndex = 0; cellIndex < row.cells.length; cellIndex += 1) {
749
+ const cell = row.cells[cellIndex];
750
+ if (!cell) continue;
751
+ collectProjectedBlockIdsForBlocks(
752
+ cell.children,
753
+ storyKey,
754
+ `${blockPath}/row[${rowIndex}]/cell[${cellIndex}]`,
755
+ counters,
756
+ result,
757
+ );
758
+ }
759
+ }
760
+ continue;
761
+ }
762
+ if (block.type === "sdt") {
763
+ counters.sdt += 1;
764
+ collectProjectedBlockIdsForBlocks(
765
+ block.children,
766
+ storyKey,
767
+ blockPath,
768
+ counters,
769
+ result,
770
+ );
771
+ continue;
772
+ }
773
+ if (block.type === "custom_xml") {
774
+ counters.customXml += 1;
775
+ continue;
776
+ }
777
+ if (block.type === "alt_chunk") {
778
+ counters.altChunk += 1;
779
+ continue;
780
+ }
781
+ if (block.type === "opaque_block" || block.type === "section_break") {
782
+ counters.opaque += 1;
783
+ }
784
+ }
785
+ }
786
+
787
+ function canonicalStoryKeyFromTarget(target: EditorStoryTarget): string {
788
+ switch (target.kind) {
789
+ case "main":
790
+ return MAIN_STORY_KEY;
791
+ case "header":
792
+ return createHeaderFooterStoryKey({
793
+ kind: "header",
794
+ relationshipId: target.relationshipId,
795
+ variant: target.variant,
796
+ ...(target.sectionIndex !== undefined
797
+ ? { sectionIndex: target.sectionIndex }
798
+ : {}),
799
+ });
800
+ case "footer":
801
+ return createHeaderFooterStoryKey({
802
+ kind: "footer",
803
+ relationshipId: target.relationshipId,
804
+ variant: target.variant,
805
+ ...(target.sectionIndex !== undefined
806
+ ? { sectionIndex: target.sectionIndex }
807
+ : {}),
808
+ });
809
+ case "footnote":
810
+ return createNoteStoryKey("footnote", target.noteId);
811
+ case "endnote":
812
+ return createNoteStoryKey("endnote", target.noteId);
813
+ }
814
+ }
815
+
816
+ function storyBlockKey(storyKey: string, blockId: string): string {
817
+ return `${storyKey}\u0000${blockId}`;
818
+ }
819
+
820
+ function storyPathKey(storyKey: string, blockPath: string): string {
821
+ return `${storyKey}\u0000${blockPath}`;
822
+ }
823
+
824
+ function collectRegionEntries(
825
+ page: RenderPage,
826
+ ): Array<[RenderStoryRegion, number]> {
827
+ const entries: Array<[RenderStoryRegion, number]> = [];
828
+ const push = (region: RenderStoryRegion | undefined) => {
829
+ if (!region) return;
830
+ const ordinal = entries.filter(
831
+ ([entry]) => entry.region.kind === region.region.kind,
832
+ ).length;
833
+ entries.push([region, ordinal]);
834
+ };
835
+ push(page.regions.body);
836
+ push(page.regions.header);
837
+ push(page.regions.footer);
838
+ for (const region of page.regions.columns ?? []) push(region);
839
+ push(page.regions.footnoteArea);
840
+ for (const region of page.regions.footnotes ?? []) push(region);
841
+ return entries;
842
+ }
843
+
844
+ function toGeometryRect(rect: RenderFrameRect): GeometryRect {
845
+ return {
846
+ leftPx: rect.leftPx,
847
+ topPx: rect.topPx,
848
+ widthPx: rect.widthPx,
849
+ heightPx: rect.heightPx,
850
+ space: "frame",
851
+ };
852
+ }
853
+
854
+ function makeRegionId(
855
+ page: RenderPage,
856
+ region: RenderStoryRegion,
857
+ ordinal: number,
858
+ ): string {
859
+ return `${page.page.pageId}:${region.region.kind}:${ordinal}`;
860
+ }
861
+
862
+ function makeSliceId(regionId: string, fragmentId: string): string {
863
+ return `${regionId}:slice:${fragmentId}`;
864
+ }
865
+
866
+ function makeLineId(
867
+ regionId: string,
868
+ fragmentId: string,
869
+ lineIndex: number,
870
+ ): string {
871
+ return `${regionId}:line:${fragmentId}:${lineIndex}`;
872
+ }
873
+
874
+ function makeAnchorId(lineId: string, index: number): string {
875
+ return `${lineId}:anchor:${index}`;
876
+ }
877
+
878
+ function createPrecisionCounts(): GeometryPrecisionCounts {
879
+ return {
880
+ exact: 0,
881
+ "within-tolerance": 0,
882
+ heuristic: 0,
883
+ };
884
+ }
885
+
886
+ function recordPrecision(
887
+ counts: GeometryPrecisionCounts,
888
+ precision: keyof GeometryPrecisionCounts,
889
+ ): void {
890
+ counts[precision] += 1;
891
+ }