@beyondwork/docx-react-component 1.0.103 → 1.0.105
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.
- package/package.json +1 -1
- package/src/api/public-types.ts +66 -1
- package/src/api/v3/_runtime-handle.ts +2 -0
- package/src/api/v3/ai/_pe2-evidence.ts +153 -0
- package/src/api/v3/ai/bundle.ts +13 -5
- package/src/api/v3/ai/inspect.ts +7 -1
- package/src/api/v3/ai/outline.ts +2 -7
- package/src/api/v3/ai/replacement.ts +113 -0
- package/src/api/v3/runtime/geometry.ts +79 -0
- package/src/api/v3/ui/_types.ts +86 -0
- package/src/api/v3/ui/index.ts +5 -0
- package/src/api/v3/ui/overlays.ts +104 -0
- package/src/io/ooxml/parse-drawing.ts +99 -1
- package/src/io/ooxml/parse-fields.ts +27 -6
- package/src/io/ooxml/parse-shapes.ts +130 -0
- package/src/model/canonical-document.ts +34 -3
- package/src/model/canonical-layout-inputs.ts +979 -0
- package/src/model/layout/index.ts +9 -0
- package/src/model/layout/page-graph-types.ts +150 -0
- package/src/model/layout/runtime-page-graph-types.ts +23 -0
- package/src/runtime/collab/runtime-collab-sync.ts +3 -3
- package/src/runtime/debug/build-debug-inspector-snapshot.ts +17 -4
- package/src/runtime/document-runtime.ts +30 -14
- package/src/runtime/event-refresh-hints.ts +35 -5
- package/src/runtime/formatting/formatting-context.ts +110 -9
- package/src/runtime/formatting/index.ts +2 -0
- package/src/runtime/formatting/layout-inputs.ts +67 -3
- package/src/runtime/geometry/caret-geometry.ts +82 -10
- package/src/runtime/geometry/geometry-facet.ts +44 -0
- package/src/runtime/geometry/geometry-index.ts +1268 -0
- package/src/runtime/geometry/geometry-types.ts +227 -1
- package/src/runtime/geometry/index.ts +26 -0
- package/src/runtime/geometry/inert-geometry-facet.ts +3 -0
- package/src/runtime/geometry/object-handles.ts +7 -4
- package/src/runtime/geometry/replacement-envelope.ts +41 -2
- package/src/runtime/layout/layout-engine-instance.ts +2 -0
- package/src/runtime/layout/layout-engine-version.ts +44 -1
- package/src/runtime/layout/page-graph.ts +877 -2
- package/src/runtime/layout/project-block-fragments.ts +101 -1
- package/src/runtime/layout/public-facet.ts +152 -0
- package/src/runtime/prerender/graph-canonicalize.ts +44 -0
- package/src/runtime/surface-projection.ts +43 -3
- package/src/runtime/workflow/coordinator.ts +57 -11
- package/src/ui/ui-controller-factory.ts +11 -0
- package/src/ui-tailwind/chrome-overlay/tw-page-stack-overlay-layer.tsx +3 -0
|
@@ -0,0 +1,1268 @@
|
|
|
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 CanonicalScopeLayoutInput,
|
|
26
|
+
type CanonicalTableLayoutInput,
|
|
27
|
+
type CanonicalTableCellLayoutInput,
|
|
28
|
+
type CanonicalTableRowLayoutInput,
|
|
29
|
+
} from "../../model/canonical-layout-inputs.ts";
|
|
30
|
+
import type {
|
|
31
|
+
RenderFrame,
|
|
32
|
+
RenderFrameRect,
|
|
33
|
+
RenderBlock,
|
|
34
|
+
RenderPage,
|
|
35
|
+
RenderStoryRegion,
|
|
36
|
+
} from "../render/index.ts";
|
|
37
|
+
import type {
|
|
38
|
+
AnchorGeometry,
|
|
39
|
+
GeometryHitTarget,
|
|
40
|
+
GeometryIndex,
|
|
41
|
+
GeometryIndexCoverage,
|
|
42
|
+
GeometryIndexLine,
|
|
43
|
+
GeometryIndexPage,
|
|
44
|
+
GeometryIndexRegion,
|
|
45
|
+
GeometryIndexSlice,
|
|
46
|
+
GeometryObjectHandleEntry,
|
|
47
|
+
GeometryPrecision,
|
|
48
|
+
GeometryPrecisionCounts,
|
|
49
|
+
GeometryRect,
|
|
50
|
+
GeometryRehydrationStatus,
|
|
51
|
+
GeometryReplacementEnvelopeEntry,
|
|
52
|
+
GeometrySourceIdentity,
|
|
53
|
+
SemanticDisplayEntry,
|
|
54
|
+
} from "./geometry-types.ts";
|
|
55
|
+
import { buildObjectHandleRectsFromRect } from "./object-handles.ts";
|
|
56
|
+
|
|
57
|
+
export interface GeometryIndexProjectionOptions {
|
|
58
|
+
readonly canonicalDocument?: CanonicalDocument | null;
|
|
59
|
+
readonly layoutInputs?: CanonicalLayoutInputs | null;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
export function createUnavailableGeometryCoverage(): GeometryIndexCoverage {
|
|
63
|
+
return {
|
|
64
|
+
status: "unavailable",
|
|
65
|
+
pageCount: 0,
|
|
66
|
+
regionCount: 0,
|
|
67
|
+
sliceCount: 0,
|
|
68
|
+
lineCount: 0,
|
|
69
|
+
anchorCount: 0,
|
|
70
|
+
hitTargetCount: 0,
|
|
71
|
+
semanticEntryCount: 0,
|
|
72
|
+
replacementEnvelopeCount: 0,
|
|
73
|
+
objectHandleCount: 0,
|
|
74
|
+
precision: createPrecisionCounts(),
|
|
75
|
+
};
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
export function projectGeometryIndexFromFrame(
|
|
79
|
+
frame: RenderFrame | null,
|
|
80
|
+
options?: GeometryIndexProjectionOptions,
|
|
81
|
+
): GeometryIndex | null {
|
|
82
|
+
if (!frame) return null;
|
|
83
|
+
|
|
84
|
+
const identities = createIdentityLookup(options);
|
|
85
|
+
const pages: GeometryIndexPage[] = [];
|
|
86
|
+
const regions: GeometryIndexRegion[] = [];
|
|
87
|
+
const slices: GeometryIndexSlice[] = [];
|
|
88
|
+
const lines: GeometryIndexLine[] = [];
|
|
89
|
+
const anchors: AnchorGeometry[] = [];
|
|
90
|
+
const hitTargets: GeometryHitTarget[] = [];
|
|
91
|
+
const semanticEntries: SemanticDisplayEntry[] = [];
|
|
92
|
+
const replacementEnvelopes: GeometryReplacementEnvelopeEntry[] = [];
|
|
93
|
+
const objectHandleEntries = new Map<string, MutableObjectHandleEntry>();
|
|
94
|
+
const projectedBlocksByStory = new Map<string, ProjectedScopeBlock[]>();
|
|
95
|
+
const precision = createPrecisionCounts();
|
|
96
|
+
|
|
97
|
+
for (const page of frame.pages) {
|
|
98
|
+
const regionEntries = collectRegionEntries(page);
|
|
99
|
+
const regionIds: string[] = [];
|
|
100
|
+
|
|
101
|
+
for (const [region, ordinal] of regionEntries) {
|
|
102
|
+
const regionId = makeRegionId(page, region, ordinal);
|
|
103
|
+
const storyKey =
|
|
104
|
+
identities?.storyKeyForTarget(region.storyTarget) ??
|
|
105
|
+
canonicalStoryKeyFromTarget(region.storyTarget);
|
|
106
|
+
regionIds.push(regionId);
|
|
107
|
+
const sliceIds: string[] = [];
|
|
108
|
+
|
|
109
|
+
for (const block of region.blocks) {
|
|
110
|
+
const sliceId = makeSliceId(regionId, block.fragment.fragmentId);
|
|
111
|
+
const sliceIdentity = identities?.sliceIdentity(
|
|
112
|
+
storyKey,
|
|
113
|
+
block.fragment.blockId,
|
|
114
|
+
);
|
|
115
|
+
sliceIds.push(sliceId);
|
|
116
|
+
const lineIds: string[] = [];
|
|
117
|
+
|
|
118
|
+
for (const line of block.lines) {
|
|
119
|
+
const lineId = makeLineId(
|
|
120
|
+
regionId,
|
|
121
|
+
block.fragment.fragmentId,
|
|
122
|
+
line.line.lineIndex,
|
|
123
|
+
);
|
|
124
|
+
lineIds.push(lineId);
|
|
125
|
+
const anchorIds: string[] = [];
|
|
126
|
+
|
|
127
|
+
for (let index = 0; index < line.anchors.length; index += 1) {
|
|
128
|
+
const anchor = line.anchors[index]!;
|
|
129
|
+
const anchorId = makeAnchorId(lineId, index);
|
|
130
|
+
const anchorIdentity = identities?.anchorIdentity(
|
|
131
|
+
storyKey,
|
|
132
|
+
anchor.blockId ?? block.fragment.blockId,
|
|
133
|
+
);
|
|
134
|
+
anchorIds.push(anchorId);
|
|
135
|
+
anchors.push({
|
|
136
|
+
anchorId,
|
|
137
|
+
pageId: page.page.pageId,
|
|
138
|
+
pageIndex: page.page.pageIndex,
|
|
139
|
+
regionId,
|
|
140
|
+
regionKind: region.region.kind,
|
|
141
|
+
blockId: anchor.blockId,
|
|
142
|
+
fragmentId: anchor.fragmentId,
|
|
143
|
+
lineId,
|
|
144
|
+
runtimeOffset: anchor.runtimeOffset,
|
|
145
|
+
rect: toGeometryRect(anchor.frame),
|
|
146
|
+
precision: "exact",
|
|
147
|
+
...(anchorIdentity ? { sourceIdentity: anchorIdentity } : {}),
|
|
148
|
+
});
|
|
149
|
+
recordPrecision(precision, "exact");
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
lines.push({
|
|
153
|
+
lineId,
|
|
154
|
+
pageId: page.page.pageId,
|
|
155
|
+
pageIndex: page.page.pageIndex,
|
|
156
|
+
regionId,
|
|
157
|
+
regionKind: region.region.kind,
|
|
158
|
+
blockId: block.fragment.blockId,
|
|
159
|
+
fragmentId: block.fragment.fragmentId,
|
|
160
|
+
lineIndex: line.line.lineIndex,
|
|
161
|
+
rect: toGeometryRect(line.frame),
|
|
162
|
+
anchorIds,
|
|
163
|
+
});
|
|
164
|
+
recordPrecision(precision, "exact");
|
|
165
|
+
hitTargets.push({
|
|
166
|
+
targetId: `hit:${lineId}`,
|
|
167
|
+
pageId: page.page.pageId,
|
|
168
|
+
pageIndex: page.page.pageIndex,
|
|
169
|
+
regionId,
|
|
170
|
+
regionKind: region.region.kind,
|
|
171
|
+
blockId: block.fragment.blockId,
|
|
172
|
+
fragmentId: block.fragment.fragmentId,
|
|
173
|
+
lineIndex: line.line.lineIndex,
|
|
174
|
+
rect: toGeometryRect(line.frame),
|
|
175
|
+
precision: "exact",
|
|
176
|
+
});
|
|
177
|
+
recordPrecision(precision, "exact");
|
|
178
|
+
semanticEntries.push({
|
|
179
|
+
entryId: `semantic:text-line:${lineId}`,
|
|
180
|
+
kind: "text-line",
|
|
181
|
+
pageId: page.page.pageId,
|
|
182
|
+
pageIndex: page.page.pageIndex,
|
|
183
|
+
regionId,
|
|
184
|
+
regionKind: region.region.kind,
|
|
185
|
+
sliceId,
|
|
186
|
+
lineId,
|
|
187
|
+
blockId: block.fragment.blockId,
|
|
188
|
+
fragmentId: block.fragment.fragmentId,
|
|
189
|
+
rect: toGeometryRect(line.frame),
|
|
190
|
+
status: "realized",
|
|
191
|
+
precision: "exact",
|
|
192
|
+
});
|
|
193
|
+
recordPrecision(precision, "exact");
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
slices.push({
|
|
197
|
+
sliceId,
|
|
198
|
+
pageId: page.page.pageId,
|
|
199
|
+
pageIndex: page.page.pageIndex,
|
|
200
|
+
regionId,
|
|
201
|
+
regionKind: region.region.kind,
|
|
202
|
+
blockId: block.fragment.blockId,
|
|
203
|
+
fragmentId: block.fragment.fragmentId,
|
|
204
|
+
kind: block.kind,
|
|
205
|
+
rect: toGeometryRect(block.frame),
|
|
206
|
+
lineIds,
|
|
207
|
+
...(sliceIdentity ? { sourceIdentity: sliceIdentity } : {}),
|
|
208
|
+
});
|
|
209
|
+
recordPrecision(precision, "exact");
|
|
210
|
+
recordProjectedScopeBlock(projectedBlocksByStory, {
|
|
211
|
+
storyKey,
|
|
212
|
+
blockId: block.fragment.blockId,
|
|
213
|
+
blockPath: identities?.blockPathForBlockId(
|
|
214
|
+
storyKey,
|
|
215
|
+
block.fragment.blockId,
|
|
216
|
+
),
|
|
217
|
+
pageId: page.page.pageId,
|
|
218
|
+
rect: toGeometryRect(block.frame),
|
|
219
|
+
});
|
|
220
|
+
appendCanonicalObjectHandleEntries({
|
|
221
|
+
frame,
|
|
222
|
+
page,
|
|
223
|
+
block,
|
|
224
|
+
identities,
|
|
225
|
+
storyKey,
|
|
226
|
+
entries: objectHandleEntries,
|
|
227
|
+
precision,
|
|
228
|
+
});
|
|
229
|
+
appendBlockSemanticEntries({
|
|
230
|
+
page,
|
|
231
|
+
region,
|
|
232
|
+
regionId,
|
|
233
|
+
sliceId,
|
|
234
|
+
block,
|
|
235
|
+
identities,
|
|
236
|
+
storyKey,
|
|
237
|
+
entries: semanticEntries,
|
|
238
|
+
projectedBlocksByStory,
|
|
239
|
+
precision,
|
|
240
|
+
});
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
regions.push({
|
|
244
|
+
regionId,
|
|
245
|
+
pageId: page.page.pageId,
|
|
246
|
+
pageIndex: page.page.pageIndex,
|
|
247
|
+
kind: region.region.kind,
|
|
248
|
+
ordinal,
|
|
249
|
+
rect: toGeometryRect(region.frame),
|
|
250
|
+
sliceIds,
|
|
251
|
+
});
|
|
252
|
+
recordPrecision(precision, "exact");
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
pages.push({
|
|
256
|
+
pageId: page.page.pageId,
|
|
257
|
+
pageIndex: page.page.pageIndex,
|
|
258
|
+
rect: toGeometryRect(page.frame),
|
|
259
|
+
regionIds,
|
|
260
|
+
});
|
|
261
|
+
recordPrecision(precision, "exact");
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
appendScopeReplacementEnvelopeEntries({
|
|
265
|
+
identities,
|
|
266
|
+
projectedBlocksByStory,
|
|
267
|
+
entries: replacementEnvelopes,
|
|
268
|
+
precision,
|
|
269
|
+
});
|
|
270
|
+
|
|
271
|
+
const objectHandles = finalizeObjectHandleEntries(objectHandleEntries);
|
|
272
|
+
const coverage: GeometryIndexCoverage = {
|
|
273
|
+
status: "realized",
|
|
274
|
+
pageCount: pages.length,
|
|
275
|
+
regionCount: regions.length,
|
|
276
|
+
sliceCount: slices.length,
|
|
277
|
+
lineCount: lines.length,
|
|
278
|
+
anchorCount: anchors.length,
|
|
279
|
+
hitTargetCount: hitTargets.length,
|
|
280
|
+
semanticEntryCount: semanticEntries.length,
|
|
281
|
+
replacementEnvelopeCount: replacementEnvelopes.length,
|
|
282
|
+
objectHandleCount: objectHandles.length,
|
|
283
|
+
precision,
|
|
284
|
+
};
|
|
285
|
+
|
|
286
|
+
return {
|
|
287
|
+
source: "render-frame-adapter",
|
|
288
|
+
revision: frame.revision,
|
|
289
|
+
measurementFidelity: frame.measurementFidelity,
|
|
290
|
+
activeStory: frame.activeStory,
|
|
291
|
+
pages,
|
|
292
|
+
regions,
|
|
293
|
+
slices,
|
|
294
|
+
lines,
|
|
295
|
+
anchors,
|
|
296
|
+
hitTargets,
|
|
297
|
+
semanticEntries,
|
|
298
|
+
replacementEnvelopes,
|
|
299
|
+
objectHandles,
|
|
300
|
+
coverage,
|
|
301
|
+
};
|
|
302
|
+
}
|
|
303
|
+
|
|
304
|
+
export function summarizeGeometryCoverageFromFrame(
|
|
305
|
+
frame: RenderFrame | null,
|
|
306
|
+
): GeometryIndexCoverage {
|
|
307
|
+
if (!frame) return createUnavailableGeometryCoverage();
|
|
308
|
+
|
|
309
|
+
const precision = createPrecisionCounts();
|
|
310
|
+
let pageCount = 0;
|
|
311
|
+
let regionCount = 0;
|
|
312
|
+
let sliceCount = 0;
|
|
313
|
+
let lineCount = 0;
|
|
314
|
+
let anchorCount = 0;
|
|
315
|
+
let hitTargetCount = 0;
|
|
316
|
+
let semanticEntryCount = 0;
|
|
317
|
+
|
|
318
|
+
for (const page of frame.pages) {
|
|
319
|
+
pageCount += 1;
|
|
320
|
+
recordPrecision(precision, "exact");
|
|
321
|
+
for (const [region] of collectRegionEntries(page)) {
|
|
322
|
+
regionCount += 1;
|
|
323
|
+
recordPrecision(precision, "exact");
|
|
324
|
+
for (const block of region.blocks) {
|
|
325
|
+
sliceCount += 1;
|
|
326
|
+
recordPrecision(precision, "exact");
|
|
327
|
+
for (const line of block.lines) {
|
|
328
|
+
lineCount += 1;
|
|
329
|
+
hitTargetCount += 1;
|
|
330
|
+
recordPrecision(precision, "exact"); // line rect
|
|
331
|
+
recordPrecision(precision, "exact"); // matching hit-target rect
|
|
332
|
+
semanticEntryCount += 1;
|
|
333
|
+
recordPrecision(precision, "exact"); // text-line semantic entry
|
|
334
|
+
anchorCount += line.anchors.length;
|
|
335
|
+
for (let i = 0; i < line.anchors.length; i += 1) {
|
|
336
|
+
recordPrecision(precision, "exact");
|
|
337
|
+
}
|
|
338
|
+
}
|
|
339
|
+
const blockSemantic = countBlockSemanticEntries(block);
|
|
340
|
+
semanticEntryCount += blockSemantic.exact;
|
|
341
|
+
semanticEntryCount += blockSemantic["within-tolerance"];
|
|
342
|
+
semanticEntryCount += blockSemantic.heuristic;
|
|
343
|
+
precision.exact += blockSemantic.exact;
|
|
344
|
+
precision["within-tolerance"] += blockSemantic["within-tolerance"];
|
|
345
|
+
precision.heuristic += blockSemantic.heuristic;
|
|
346
|
+
}
|
|
347
|
+
}
|
|
348
|
+
}
|
|
349
|
+
|
|
350
|
+
return {
|
|
351
|
+
status: "realized",
|
|
352
|
+
pageCount,
|
|
353
|
+
regionCount,
|
|
354
|
+
sliceCount,
|
|
355
|
+
lineCount,
|
|
356
|
+
anchorCount,
|
|
357
|
+
hitTargetCount,
|
|
358
|
+
semanticEntryCount,
|
|
359
|
+
replacementEnvelopeCount: 0,
|
|
360
|
+
objectHandleCount: 0,
|
|
361
|
+
precision,
|
|
362
|
+
};
|
|
363
|
+
}
|
|
364
|
+
|
|
365
|
+
function appendBlockSemanticEntries(input: {
|
|
366
|
+
page: RenderPage;
|
|
367
|
+
region: RenderStoryRegion;
|
|
368
|
+
regionId: string;
|
|
369
|
+
sliceId: string;
|
|
370
|
+
block: RenderBlock;
|
|
371
|
+
identities: GeometryIdentityLookup | null;
|
|
372
|
+
storyKey: string;
|
|
373
|
+
entries: SemanticDisplayEntry[];
|
|
374
|
+
projectedBlocksByStory: Map<string, ProjectedScopeBlock[]>;
|
|
375
|
+
precision: GeometryPrecisionCounts;
|
|
376
|
+
}): void {
|
|
377
|
+
const {
|
|
378
|
+
page,
|
|
379
|
+
region,
|
|
380
|
+
regionId,
|
|
381
|
+
sliceId,
|
|
382
|
+
block,
|
|
383
|
+
identities,
|
|
384
|
+
storyKey,
|
|
385
|
+
entries,
|
|
386
|
+
projectedBlocksByStory,
|
|
387
|
+
precision,
|
|
388
|
+
} = input;
|
|
389
|
+
const tableIdentity = identities?.tableIdentity(
|
|
390
|
+
storyKey,
|
|
391
|
+
block.fragment.blockId,
|
|
392
|
+
);
|
|
393
|
+
const base = {
|
|
394
|
+
pageId: page.page.pageId,
|
|
395
|
+
pageIndex: page.page.pageIndex,
|
|
396
|
+
regionId,
|
|
397
|
+
regionKind: region.region.kind,
|
|
398
|
+
sliceId,
|
|
399
|
+
blockId: block.fragment.blockId,
|
|
400
|
+
fragmentId: block.fragment.fragmentId,
|
|
401
|
+
} as const;
|
|
402
|
+
|
|
403
|
+
if (block.kind === "table") {
|
|
404
|
+
entries.push({
|
|
405
|
+
...base,
|
|
406
|
+
entryId: `semantic:table-frame:${sliceId}`,
|
|
407
|
+
kind: "table-frame",
|
|
408
|
+
rect: toGeometryRect(block.frame),
|
|
409
|
+
status: "realized",
|
|
410
|
+
precision: "exact",
|
|
411
|
+
...(tableIdentity
|
|
412
|
+
? { sourceIdentity: tableSourceIdentity(tableIdentity) }
|
|
413
|
+
: {}),
|
|
414
|
+
});
|
|
415
|
+
recordPrecision(precision, "exact");
|
|
416
|
+
|
|
417
|
+
const plan = block.tablePlan;
|
|
418
|
+
if (!plan) return;
|
|
419
|
+
if (tableIdentity) {
|
|
420
|
+
recordTableCellScopeBlocks({
|
|
421
|
+
table: tableIdentity,
|
|
422
|
+
block,
|
|
423
|
+
pageId: page.page.pageId,
|
|
424
|
+
storyKey,
|
|
425
|
+
identities,
|
|
426
|
+
projectedBlocksByStory,
|
|
427
|
+
});
|
|
428
|
+
}
|
|
429
|
+
const rowCount = resolveTableRowCount(block);
|
|
430
|
+
const rowHeightPx =
|
|
431
|
+
rowCount > 0 ? block.frame.heightPx / rowCount : block.frame.heightPx;
|
|
432
|
+
for (let rowIndex = 0; rowIndex < rowCount; rowIndex += 1) {
|
|
433
|
+
entries.push({
|
|
434
|
+
...base,
|
|
435
|
+
entryId: `semantic:table-row:${sliceId}:${rowIndex}`,
|
|
436
|
+
kind: "table-row",
|
|
437
|
+
rowIndex,
|
|
438
|
+
rect: {
|
|
439
|
+
leftPx: block.frame.leftPx,
|
|
440
|
+
topPx: block.frame.topPx + rowIndex * rowHeightPx,
|
|
441
|
+
widthPx: block.frame.widthPx,
|
|
442
|
+
heightPx: rowHeightPx,
|
|
443
|
+
space: "frame",
|
|
444
|
+
precision: "within-tolerance",
|
|
445
|
+
},
|
|
446
|
+
status: "realized",
|
|
447
|
+
precision: "within-tolerance",
|
|
448
|
+
...(tableIdentity
|
|
449
|
+
? { sourceIdentity: tableRowSourceIdentity(tableIdentity, rowIndex) }
|
|
450
|
+
: {}),
|
|
451
|
+
});
|
|
452
|
+
recordPrecision(precision, "within-tolerance");
|
|
453
|
+
}
|
|
454
|
+
|
|
455
|
+
for (const cell of plan.bandClasses.cells) {
|
|
456
|
+
const rect = pageFrameCellRect(block, cell.rowIndex, cell.columnIndex);
|
|
457
|
+
if (!rect) continue;
|
|
458
|
+
entries.push({
|
|
459
|
+
...base,
|
|
460
|
+
entryId: `semantic:table-cell:${sliceId}:${cell.rowIndex}:${cell.columnIndex}`,
|
|
461
|
+
kind: "table-cell",
|
|
462
|
+
rowIndex: cell.rowIndex,
|
|
463
|
+
columnIndex: cell.columnIndex,
|
|
464
|
+
rect: {
|
|
465
|
+
...toGeometryRect(rect),
|
|
466
|
+
precision: "within-tolerance",
|
|
467
|
+
},
|
|
468
|
+
status: "realized",
|
|
469
|
+
precision: "within-tolerance",
|
|
470
|
+
...(tableIdentity
|
|
471
|
+
? {
|
|
472
|
+
sourceIdentity: tableCellSourceIdentity(
|
|
473
|
+
tableIdentity,
|
|
474
|
+
cell.rowIndex,
|
|
475
|
+
cell.columnIndex,
|
|
476
|
+
),
|
|
477
|
+
}
|
|
478
|
+
: {}),
|
|
479
|
+
});
|
|
480
|
+
recordPrecision(precision, "within-tolerance");
|
|
481
|
+
}
|
|
482
|
+
return;
|
|
483
|
+
}
|
|
484
|
+
|
|
485
|
+
if (block.kind === "image-float") {
|
|
486
|
+
entries.push({
|
|
487
|
+
...base,
|
|
488
|
+
entryId: `semantic:floating-object:${sliceId}`,
|
|
489
|
+
kind: "floating-object",
|
|
490
|
+
rect: {
|
|
491
|
+
...toGeometryRect(block.frame),
|
|
492
|
+
precision: "heuristic",
|
|
493
|
+
},
|
|
494
|
+
status: "realized",
|
|
495
|
+
precision: "heuristic",
|
|
496
|
+
});
|
|
497
|
+
recordPrecision(precision, "heuristic");
|
|
498
|
+
return;
|
|
499
|
+
}
|
|
500
|
+
|
|
501
|
+
if (block.kind === "opaque") {
|
|
502
|
+
entries.push({
|
|
503
|
+
...base,
|
|
504
|
+
entryId: `semantic:placeholder:${sliceId}`,
|
|
505
|
+
kind: "placeholder",
|
|
506
|
+
rect: toGeometryRect(block.frame),
|
|
507
|
+
status: "realized",
|
|
508
|
+
precision: "exact",
|
|
509
|
+
});
|
|
510
|
+
recordPrecision(precision, "exact");
|
|
511
|
+
return;
|
|
512
|
+
}
|
|
513
|
+
|
|
514
|
+
if (block.kind === "synthetic") {
|
|
515
|
+
entries.push({
|
|
516
|
+
...base,
|
|
517
|
+
entryId: `semantic:debug-classification:${sliceId}`,
|
|
518
|
+
kind: "debug-classification",
|
|
519
|
+
rect: toGeometryRect(block.frame),
|
|
520
|
+
status: "realized",
|
|
521
|
+
precision: "exact",
|
|
522
|
+
});
|
|
523
|
+
recordPrecision(precision, "exact");
|
|
524
|
+
}
|
|
525
|
+
}
|
|
526
|
+
|
|
527
|
+
function countBlockSemanticEntries(block: RenderBlock): GeometryPrecisionCounts {
|
|
528
|
+
const counts = createPrecisionCounts();
|
|
529
|
+
if (block.kind === "table") {
|
|
530
|
+
counts.exact += 1;
|
|
531
|
+
if (block.tablePlan) {
|
|
532
|
+
counts["within-tolerance"] += resolveTableRowCount(block);
|
|
533
|
+
counts["within-tolerance"] += block.tablePlan.bandClasses.cells.length;
|
|
534
|
+
}
|
|
535
|
+
} else if (block.kind === "image-float") {
|
|
536
|
+
counts.heuristic += 1;
|
|
537
|
+
} else if (block.kind === "opaque" || block.kind === "synthetic") {
|
|
538
|
+
counts.exact += 1;
|
|
539
|
+
}
|
|
540
|
+
return counts;
|
|
541
|
+
}
|
|
542
|
+
|
|
543
|
+
function resolveTableRowCount(block: RenderBlock): number {
|
|
544
|
+
const plan = block.tablePlan;
|
|
545
|
+
if (!plan) return 0;
|
|
546
|
+
return Math.max(
|
|
547
|
+
1,
|
|
548
|
+
plan.bandClasses.rows.length ||
|
|
549
|
+
plan.bandClasses.cells.reduce(
|
|
550
|
+
(max, c) => Math.max(max, c.rowIndex + 1),
|
|
551
|
+
0,
|
|
552
|
+
),
|
|
553
|
+
);
|
|
554
|
+
}
|
|
555
|
+
|
|
556
|
+
function pageFrameCellRect(
|
|
557
|
+
block: RenderBlock,
|
|
558
|
+
rowIndex: number,
|
|
559
|
+
columnIndex: number,
|
|
560
|
+
): RenderFrameRect | null {
|
|
561
|
+
const plan = block.tablePlan;
|
|
562
|
+
if (!plan || plan.columnsTwips.length === 0) return null;
|
|
563
|
+
const columnCount = plan.columnsTwips.length;
|
|
564
|
+
const totalWidthTwips = plan.columnsTwips.reduce(
|
|
565
|
+
(sum, value) => sum + Math.max(0, value),
|
|
566
|
+
0,
|
|
567
|
+
);
|
|
568
|
+
const pxPerTwip =
|
|
569
|
+
totalWidthTwips > 0 ? block.frame.widthPx / totalWidthTwips : 0;
|
|
570
|
+
let leftPx = block.frame.leftPx;
|
|
571
|
+
for (let i = 0; i < columnIndex; i += 1) {
|
|
572
|
+
leftPx += (plan.columnsTwips[i] ?? 0) * pxPerTwip;
|
|
573
|
+
}
|
|
574
|
+
const sameRow = plan.bandClasses.cells
|
|
575
|
+
.filter((cell) => cell.rowIndex === rowIndex)
|
|
576
|
+
.sort((a, b) => a.columnIndex - b.columnIndex);
|
|
577
|
+
const currentIndex = sameRow.findIndex(
|
|
578
|
+
(cell) => cell.columnIndex === columnIndex,
|
|
579
|
+
);
|
|
580
|
+
const next = sameRow[currentIndex + 1];
|
|
581
|
+
const columnSpan = Math.max(
|
|
582
|
+
1,
|
|
583
|
+
(next?.columnIndex ?? columnCount) - columnIndex,
|
|
584
|
+
);
|
|
585
|
+
let widthPx = 0;
|
|
586
|
+
for (let i = columnIndex; i < columnIndex + columnSpan; i += 1) {
|
|
587
|
+
widthPx += (plan.columnsTwips[i] ?? 0) * pxPerTwip;
|
|
588
|
+
}
|
|
589
|
+
const rowCount = resolveTableRowCount(block);
|
|
590
|
+
const rowHeightPx =
|
|
591
|
+
rowCount > 0 ? block.frame.heightPx / rowCount : block.frame.heightPx;
|
|
592
|
+
return {
|
|
593
|
+
leftPx,
|
|
594
|
+
topPx: block.frame.topPx + rowIndex * rowHeightPx,
|
|
595
|
+
widthPx,
|
|
596
|
+
heightPx: rowHeightPx,
|
|
597
|
+
};
|
|
598
|
+
}
|
|
599
|
+
|
|
600
|
+
interface GeometryIdentityLookup {
|
|
601
|
+
storyKeyForTarget(target: EditorStoryTarget): string;
|
|
602
|
+
sliceIdentity(storyKey: string, blockId: string): GeometrySourceIdentity | undefined;
|
|
603
|
+
tableIdentity(storyKey: string, blockId: string): CanonicalTableLayoutInput | undefined;
|
|
604
|
+
anchorIdentity(storyKey: string, blockId: string): GeometrySourceIdentity | undefined;
|
|
605
|
+
objectAnchors(storyKey: string, blockId: string): readonly CanonicalAnchorLayoutInput[];
|
|
606
|
+
blockIdForPath(storyKey: string, blockPath: string): string | undefined;
|
|
607
|
+
blockPathForBlockId(storyKey: string, blockId: string): string | undefined;
|
|
608
|
+
scopeInputs(): readonly CanonicalScopeLayoutInput[];
|
|
609
|
+
}
|
|
610
|
+
|
|
611
|
+
function createIdentityLookup(
|
|
612
|
+
options: GeometryIndexProjectionOptions | undefined,
|
|
613
|
+
): GeometryIdentityLookup | null {
|
|
614
|
+
const layoutInputs =
|
|
615
|
+
options?.layoutInputs ??
|
|
616
|
+
(options?.canonicalDocument
|
|
617
|
+
? collectCanonicalLayoutInputs(options.canonicalDocument)
|
|
618
|
+
: null);
|
|
619
|
+
if (!layoutInputs) return null;
|
|
620
|
+
|
|
621
|
+
const storyKeys = new Set(layoutInputs.stories.map((story) => story.storyKey));
|
|
622
|
+
const blockIdByPath = options?.canonicalDocument
|
|
623
|
+
? collectProjectedBlockIdsByPath(options.canonicalDocument)
|
|
624
|
+
: new Map<string, string>();
|
|
625
|
+
const tableByStoryBlockId = new Map<string, CanonicalTableLayoutInput>();
|
|
626
|
+
const tableOrdinalByStory = new Map<string, number>();
|
|
627
|
+
for (const table of layoutInputs.tables) {
|
|
628
|
+
const blockId =
|
|
629
|
+
blockIdByPath.get(storyPathKey(table.storyKey, table.blockPath)) ??
|
|
630
|
+
nextTableBlockId(tableOrdinalByStory, table.storyKey);
|
|
631
|
+
tableByStoryBlockId.set(storyBlockKey(table.storyKey, blockId), table);
|
|
632
|
+
}
|
|
633
|
+
|
|
634
|
+
const blockPathByStoryBlockId = new Map<string, string>();
|
|
635
|
+
for (const [pathKey, blockId] of blockIdByPath) {
|
|
636
|
+
const splitAt = pathKey.indexOf("\u0000");
|
|
637
|
+
if (splitAt <= 0) continue;
|
|
638
|
+
const storyKey = pathKey.slice(0, splitAt);
|
|
639
|
+
const blockPath = pathKey.slice(splitAt + 1);
|
|
640
|
+
blockPathByStoryBlockId.set(storyBlockKey(storyKey, blockId), blockPath);
|
|
641
|
+
}
|
|
642
|
+
|
|
643
|
+
const anchorsByStoryBlockId = new Map<string, CanonicalAnchorLayoutInput[]>();
|
|
644
|
+
for (const anchor of layoutInputs.anchors) {
|
|
645
|
+
const blockId = blockIdByPath.get(
|
|
646
|
+
storyPathKey(anchor.storyKey, anchor.blockPath),
|
|
647
|
+
);
|
|
648
|
+
if (!blockId) continue;
|
|
649
|
+
const key = storyBlockKey(anchor.storyKey, blockId);
|
|
650
|
+
const list = anchorsByStoryBlockId.get(key);
|
|
651
|
+
if (list) {
|
|
652
|
+
list.push(anchor);
|
|
653
|
+
} else {
|
|
654
|
+
anchorsByStoryBlockId.set(key, [anchor]);
|
|
655
|
+
}
|
|
656
|
+
}
|
|
657
|
+
|
|
658
|
+
return {
|
|
659
|
+
storyKeyForTarget(target) {
|
|
660
|
+
return resolveCanonicalStoryKeyForTarget(target, storyKeys);
|
|
661
|
+
},
|
|
662
|
+
sliceIdentity(storyKey, blockId) {
|
|
663
|
+
const table = tableByStoryBlockId.get(storyBlockKey(storyKey, blockId));
|
|
664
|
+
if (table) return tableSourceIdentity(table);
|
|
665
|
+
const blockPath = blockPathByStoryBlockId.get(storyBlockKey(storyKey, blockId));
|
|
666
|
+
return blockPath
|
|
667
|
+
? { storyKey, blockPath, joinKind: "direct" }
|
|
668
|
+
: undefined;
|
|
669
|
+
},
|
|
670
|
+
tableIdentity(storyKey, blockId) {
|
|
671
|
+
return tableByStoryBlockId.get(storyBlockKey(storyKey, blockId));
|
|
672
|
+
},
|
|
673
|
+
anchorIdentity(storyKey, blockId) {
|
|
674
|
+
const key = storyBlockKey(storyKey, blockId);
|
|
675
|
+
const anchors = anchorsByStoryBlockId.get(key);
|
|
676
|
+
if (!anchors || anchors.length === 0) return undefined;
|
|
677
|
+
const blockPath = blockPathByStoryBlockId.get(key) ?? anchors[0]!.blockPath;
|
|
678
|
+
if (anchors.length !== 1) {
|
|
679
|
+
return {
|
|
680
|
+
storyKey,
|
|
681
|
+
blockPath,
|
|
682
|
+
joinKind: "block-scoped",
|
|
683
|
+
};
|
|
684
|
+
}
|
|
685
|
+
const anchor = anchors[0]!;
|
|
686
|
+
return anchorSourceIdentity(anchor, "block-scoped");
|
|
687
|
+
},
|
|
688
|
+
objectAnchors(storyKey, blockId) {
|
|
689
|
+
return anchorsByStoryBlockId.get(storyBlockKey(storyKey, blockId)) ?? [];
|
|
690
|
+
},
|
|
691
|
+
blockIdForPath(storyKey, blockPath) {
|
|
692
|
+
return blockIdByPath.get(storyPathKey(storyKey, blockPath));
|
|
693
|
+
},
|
|
694
|
+
blockPathForBlockId(storyKey, blockId) {
|
|
695
|
+
return blockPathByStoryBlockId.get(storyBlockKey(storyKey, blockId));
|
|
696
|
+
},
|
|
697
|
+
scopeInputs() {
|
|
698
|
+
return layoutInputs.scopes;
|
|
699
|
+
},
|
|
700
|
+
};
|
|
701
|
+
}
|
|
702
|
+
|
|
703
|
+
interface MutableObjectHandleEntry {
|
|
704
|
+
objectId: string;
|
|
705
|
+
pageIds: string[];
|
|
706
|
+
rects: GeometryRect[];
|
|
707
|
+
status: GeometryRehydrationStatus;
|
|
708
|
+
precision: GeometryPrecision;
|
|
709
|
+
sourceIdentity?: GeometrySourceIdentity;
|
|
710
|
+
}
|
|
711
|
+
|
|
712
|
+
function appendCanonicalObjectHandleEntries(input: {
|
|
713
|
+
frame: RenderFrame;
|
|
714
|
+
page: RenderPage;
|
|
715
|
+
block: RenderBlock;
|
|
716
|
+
identities: GeometryIdentityLookup | null;
|
|
717
|
+
storyKey: string;
|
|
718
|
+
entries: Map<string, MutableObjectHandleEntry>;
|
|
719
|
+
precision: GeometryPrecisionCounts;
|
|
720
|
+
}): void {
|
|
721
|
+
const { frame, page, block, identities, storyKey, entries, precision } = input;
|
|
722
|
+
if (!identities) return;
|
|
723
|
+
|
|
724
|
+
for (const anchor of identities.objectAnchors(
|
|
725
|
+
storyKey,
|
|
726
|
+
block.fragment.blockId,
|
|
727
|
+
)) {
|
|
728
|
+
if (anchor.hidden === true) continue;
|
|
729
|
+
const exactObjectRect = frame.anchorIndex.byObjectId(anchor.objectKey);
|
|
730
|
+
const rect = exactObjectRect ?? block.frame;
|
|
731
|
+
const entryPrecision: GeometryPrecision = exactObjectRect
|
|
732
|
+
? "within-tolerance"
|
|
733
|
+
: "heuristic";
|
|
734
|
+
const status: GeometryRehydrationStatus = exactObjectRect
|
|
735
|
+
? "realized"
|
|
736
|
+
: "requires-rehydration";
|
|
737
|
+
const handleRects = buildObjectHandleRectsFromRect(rect, entryPrecision);
|
|
738
|
+
const existing = entries.get(anchor.objectKey);
|
|
739
|
+
if (existing) {
|
|
740
|
+
appendUnique(existing.pageIds, page.page.pageId);
|
|
741
|
+
existing.rects.push(...handleRects);
|
|
742
|
+
if (existing.precision !== "heuristic" && entryPrecision === "heuristic") {
|
|
743
|
+
existing.precision = "heuristic";
|
|
744
|
+
existing.status = "requires-rehydration";
|
|
745
|
+
existing.sourceIdentity = anchorSourceIdentity(anchor, "block-scoped");
|
|
746
|
+
}
|
|
747
|
+
continue;
|
|
748
|
+
}
|
|
749
|
+
|
|
750
|
+
entries.set(anchor.objectKey, {
|
|
751
|
+
objectId: anchor.objectKey,
|
|
752
|
+
pageIds: [page.page.pageId],
|
|
753
|
+
rects: [...handleRects],
|
|
754
|
+
status,
|
|
755
|
+
precision: entryPrecision,
|
|
756
|
+
sourceIdentity: anchorSourceIdentity(
|
|
757
|
+
anchor,
|
|
758
|
+
exactObjectRect ? "direct" : "block-scoped",
|
|
759
|
+
),
|
|
760
|
+
});
|
|
761
|
+
recordPrecision(precision, entryPrecision);
|
|
762
|
+
}
|
|
763
|
+
}
|
|
764
|
+
|
|
765
|
+
function finalizeObjectHandleEntries(
|
|
766
|
+
entries: ReadonlyMap<string, MutableObjectHandleEntry>,
|
|
767
|
+
): GeometryObjectHandleEntry[] {
|
|
768
|
+
return Array.from(entries.values()).map((entry) => ({
|
|
769
|
+
objectId: entry.objectId,
|
|
770
|
+
pageIds: entry.pageIds,
|
|
771
|
+
rects: entry.rects,
|
|
772
|
+
status: entry.status,
|
|
773
|
+
precision: entry.precision,
|
|
774
|
+
...(entry.sourceIdentity ? { sourceIdentity: entry.sourceIdentity } : {}),
|
|
775
|
+
}));
|
|
776
|
+
}
|
|
777
|
+
|
|
778
|
+
function appendUnique(target: string[], value: string): void {
|
|
779
|
+
if (!target.includes(value)) target.push(value);
|
|
780
|
+
}
|
|
781
|
+
|
|
782
|
+
function resolveCanonicalStoryKeyForTarget(
|
|
783
|
+
target: EditorStoryTarget,
|
|
784
|
+
storyKeys: ReadonlySet<string>,
|
|
785
|
+
): string {
|
|
786
|
+
const exact = canonicalStoryKeyFromTarget(target);
|
|
787
|
+
if (storyKeys.has(exact)) return exact;
|
|
788
|
+
if (
|
|
789
|
+
(target.kind === "header" || target.kind === "footer") &&
|
|
790
|
+
target.sectionIndex !== undefined
|
|
791
|
+
) {
|
|
792
|
+
const unscoped = createHeaderFooterStoryKey({
|
|
793
|
+
kind: target.kind,
|
|
794
|
+
relationshipId: target.relationshipId,
|
|
795
|
+
variant: target.variant,
|
|
796
|
+
});
|
|
797
|
+
if (storyKeys.has(unscoped)) return unscoped;
|
|
798
|
+
}
|
|
799
|
+
return exact;
|
|
800
|
+
}
|
|
801
|
+
|
|
802
|
+
interface ProjectedScopeBlock {
|
|
803
|
+
readonly storyKey: string;
|
|
804
|
+
readonly blockId: string;
|
|
805
|
+
readonly blockPath?: string;
|
|
806
|
+
readonly pageId: string;
|
|
807
|
+
readonly rect: GeometryRect;
|
|
808
|
+
}
|
|
809
|
+
|
|
810
|
+
function recordProjectedScopeBlock(
|
|
811
|
+
blocksByStory: Map<string, ProjectedScopeBlock[]>,
|
|
812
|
+
block: ProjectedScopeBlock,
|
|
813
|
+
): void {
|
|
814
|
+
const list = blocksByStory.get(block.storyKey);
|
|
815
|
+
if (list) {
|
|
816
|
+
list.push(block);
|
|
817
|
+
} else {
|
|
818
|
+
blocksByStory.set(block.storyKey, [block]);
|
|
819
|
+
}
|
|
820
|
+
}
|
|
821
|
+
|
|
822
|
+
function recordTableCellScopeBlocks(input: {
|
|
823
|
+
table: CanonicalTableLayoutInput;
|
|
824
|
+
block: RenderBlock;
|
|
825
|
+
pageId: string;
|
|
826
|
+
storyKey: string;
|
|
827
|
+
identities: GeometryIdentityLookup | null;
|
|
828
|
+
projectedBlocksByStory: Map<string, ProjectedScopeBlock[]>;
|
|
829
|
+
}): void {
|
|
830
|
+
const {
|
|
831
|
+
table,
|
|
832
|
+
block,
|
|
833
|
+
pageId,
|
|
834
|
+
storyKey,
|
|
835
|
+
identities,
|
|
836
|
+
projectedBlocksByStory,
|
|
837
|
+
} = input;
|
|
838
|
+
if (!identities) return;
|
|
839
|
+
|
|
840
|
+
for (const row of table.rows) {
|
|
841
|
+
for (const cell of row.cells) {
|
|
842
|
+
const rect = pageFrameCellRect(block, row.rowIndex, cell.gridColumnStart);
|
|
843
|
+
if (!rect) continue;
|
|
844
|
+
for (let blockIndex = 0; blockIndex < cell.blockCount; blockIndex += 1) {
|
|
845
|
+
const blockPath =
|
|
846
|
+
`${table.blockPath}/row[${row.rowIndex}]/cell[${cell.cellIndex}]` +
|
|
847
|
+
`/block[${blockIndex}]`;
|
|
848
|
+
const blockId = identities.blockIdForPath(storyKey, blockPath);
|
|
849
|
+
if (!blockId) continue;
|
|
850
|
+
recordProjectedScopeBlock(projectedBlocksByStory, {
|
|
851
|
+
storyKey,
|
|
852
|
+
blockId,
|
|
853
|
+
blockPath,
|
|
854
|
+
pageId,
|
|
855
|
+
rect: {
|
|
856
|
+
...toGeometryRect(rect),
|
|
857
|
+
precision: "within-tolerance",
|
|
858
|
+
},
|
|
859
|
+
});
|
|
860
|
+
}
|
|
861
|
+
}
|
|
862
|
+
}
|
|
863
|
+
}
|
|
864
|
+
|
|
865
|
+
function appendScopeReplacementEnvelopeEntries(input: {
|
|
866
|
+
identities: GeometryIdentityLookup | null;
|
|
867
|
+
projectedBlocksByStory: ReadonlyMap<string, readonly ProjectedScopeBlock[]>;
|
|
868
|
+
entries: GeometryReplacementEnvelopeEntry[];
|
|
869
|
+
precision: GeometryPrecisionCounts;
|
|
870
|
+
}): void {
|
|
871
|
+
const { identities, projectedBlocksByStory, entries, precision } = input;
|
|
872
|
+
if (!identities) return;
|
|
873
|
+
|
|
874
|
+
for (const scope of identities.scopeInputs()) {
|
|
875
|
+
const entry = projectScopeReplacementEnvelopeEntry(
|
|
876
|
+
scope,
|
|
877
|
+
identities,
|
|
878
|
+
projectedBlocksByStory.get(scope.storyKey) ?? [],
|
|
879
|
+
);
|
|
880
|
+
entries.push(entry);
|
|
881
|
+
recordPrecision(precision, entry.precision);
|
|
882
|
+
}
|
|
883
|
+
}
|
|
884
|
+
|
|
885
|
+
function projectScopeReplacementEnvelopeEntry(
|
|
886
|
+
scope: CanonicalScopeLayoutInput,
|
|
887
|
+
identities: GeometryIdentityLookup,
|
|
888
|
+
storyBlocks: readonly ProjectedScopeBlock[],
|
|
889
|
+
): GeometryReplacementEnvelopeEntry {
|
|
890
|
+
const startBlockId = scope.start
|
|
891
|
+
? identities.blockIdForPath(scope.storyKey, scope.start.blockPath)
|
|
892
|
+
: undefined;
|
|
893
|
+
const endBlockId = scope.end
|
|
894
|
+
? identities.blockIdForPath(scope.storyKey, scope.end.blockPath)
|
|
895
|
+
: undefined;
|
|
896
|
+
|
|
897
|
+
let blocks: readonly ProjectedScopeBlock[] = [];
|
|
898
|
+
let status: GeometryReplacementEnvelopeEntry["status"] = "unavailable";
|
|
899
|
+
let precision: GeometryReplacementEnvelopeEntry["precision"] = "heuristic";
|
|
900
|
+
|
|
901
|
+
if (scope.status === "paired" && startBlockId && endBlockId) {
|
|
902
|
+
blocks = blocksInCanonicalPathRange(scope, storyBlocks);
|
|
903
|
+
if (blocks.length === 0) {
|
|
904
|
+
const startIndex = firstBlockIndex(storyBlocks, startBlockId);
|
|
905
|
+
const endIndex = lastBlockIndex(storyBlocks, endBlockId);
|
|
906
|
+
if (startIndex >= 0 && endIndex >= 0) {
|
|
907
|
+
const from = Math.min(startIndex, endIndex);
|
|
908
|
+
const to = Math.max(startIndex, endIndex);
|
|
909
|
+
blocks = storyBlocks.slice(from, to + 1);
|
|
910
|
+
}
|
|
911
|
+
}
|
|
912
|
+
if (blocks.length > 0) {
|
|
913
|
+
status = "realized";
|
|
914
|
+
precision = "within-tolerance";
|
|
915
|
+
}
|
|
916
|
+
}
|
|
917
|
+
|
|
918
|
+
if (blocks.length === 0) {
|
|
919
|
+
const fallbackBlockId = startBlockId ?? endBlockId;
|
|
920
|
+
if (fallbackBlockId) {
|
|
921
|
+
blocks = storyBlocks.filter((block) => block.blockId === fallbackBlockId);
|
|
922
|
+
if (blocks.length > 0) status = "requires-rehydration";
|
|
923
|
+
}
|
|
924
|
+
}
|
|
925
|
+
|
|
926
|
+
return {
|
|
927
|
+
scopeId: scope.scopeId,
|
|
928
|
+
pageIds: uniquePageIds(blocks),
|
|
929
|
+
rects: blocks.map((block) => ({
|
|
930
|
+
...block.rect,
|
|
931
|
+
precision,
|
|
932
|
+
})),
|
|
933
|
+
status,
|
|
934
|
+
precision,
|
|
935
|
+
sourceIdentity: scopeSourceIdentity(scope),
|
|
936
|
+
};
|
|
937
|
+
}
|
|
938
|
+
|
|
939
|
+
function blocksInCanonicalPathRange(
|
|
940
|
+
scope: CanonicalScopeLayoutInput,
|
|
941
|
+
storyBlocks: readonly ProjectedScopeBlock[],
|
|
942
|
+
): readonly ProjectedScopeBlock[] {
|
|
943
|
+
if (!scope.start || !scope.end) return [];
|
|
944
|
+
const orderByPath = new Map<string, number>();
|
|
945
|
+
for (const block of storyBlocks) {
|
|
946
|
+
if (!block.blockPath || orderByPath.has(block.blockPath)) continue;
|
|
947
|
+
orderByPath.set(block.blockPath, orderByPath.size);
|
|
948
|
+
}
|
|
949
|
+
const startOrder = orderByPath.get(scope.start.blockPath);
|
|
950
|
+
const endOrder = orderByPath.get(scope.end.blockPath);
|
|
951
|
+
if (startOrder === undefined || endOrder === undefined) return [];
|
|
952
|
+
|
|
953
|
+
const from = Math.min(startOrder, endOrder);
|
|
954
|
+
const to = Math.max(startOrder, endOrder);
|
|
955
|
+
return storyBlocks.filter((block) => {
|
|
956
|
+
if (!block.blockPath) return false;
|
|
957
|
+
const order = orderByPath.get(block.blockPath);
|
|
958
|
+
return order !== undefined && order >= from && order <= to;
|
|
959
|
+
});
|
|
960
|
+
}
|
|
961
|
+
|
|
962
|
+
function firstBlockIndex(
|
|
963
|
+
blocks: readonly ProjectedScopeBlock[],
|
|
964
|
+
blockId: string,
|
|
965
|
+
): number {
|
|
966
|
+
return blocks.findIndex((block) => block.blockId === blockId);
|
|
967
|
+
}
|
|
968
|
+
|
|
969
|
+
function lastBlockIndex(
|
|
970
|
+
blocks: readonly ProjectedScopeBlock[],
|
|
971
|
+
blockId: string,
|
|
972
|
+
): number {
|
|
973
|
+
for (let index = blocks.length - 1; index >= 0; index -= 1) {
|
|
974
|
+
if (blocks[index]?.blockId === blockId) return index;
|
|
975
|
+
}
|
|
976
|
+
return -1;
|
|
977
|
+
}
|
|
978
|
+
|
|
979
|
+
function uniquePageIds(
|
|
980
|
+
blocks: readonly ProjectedScopeBlock[],
|
|
981
|
+
): readonly string[] {
|
|
982
|
+
const ids: string[] = [];
|
|
983
|
+
const seen = new Set<string>();
|
|
984
|
+
for (const block of blocks) {
|
|
985
|
+
if (seen.has(block.pageId)) continue;
|
|
986
|
+
seen.add(block.pageId);
|
|
987
|
+
ids.push(block.pageId);
|
|
988
|
+
}
|
|
989
|
+
return ids;
|
|
990
|
+
}
|
|
991
|
+
|
|
992
|
+
function scopeSourceIdentity(
|
|
993
|
+
scope: CanonicalScopeLayoutInput,
|
|
994
|
+
): GeometrySourceIdentity {
|
|
995
|
+
const marker = scope.start ?? scope.end;
|
|
996
|
+
return {
|
|
997
|
+
storyKey: scope.storyKey,
|
|
998
|
+
...(marker ? { blockPath: marker.blockPath } : {}),
|
|
999
|
+
scopeKey: scope.scopeKey,
|
|
1000
|
+
scopeId: scope.scopeId,
|
|
1001
|
+
joinKind: "block-scoped",
|
|
1002
|
+
};
|
|
1003
|
+
}
|
|
1004
|
+
|
|
1005
|
+
function anchorSourceIdentity(
|
|
1006
|
+
anchor: CanonicalAnchorLayoutInput,
|
|
1007
|
+
joinKind: GeometrySourceIdentity["joinKind"],
|
|
1008
|
+
): GeometrySourceIdentity {
|
|
1009
|
+
return {
|
|
1010
|
+
storyKey: anchor.storyKey,
|
|
1011
|
+
blockPath: anchor.blockPath,
|
|
1012
|
+
objectKey: anchor.objectKey,
|
|
1013
|
+
inlinePath: anchor.inlinePath,
|
|
1014
|
+
objectKind: anchor.objectKind,
|
|
1015
|
+
editPosture: anchor.editPosture,
|
|
1016
|
+
joinKind,
|
|
1017
|
+
};
|
|
1018
|
+
}
|
|
1019
|
+
|
|
1020
|
+
function nextTableBlockId(
|
|
1021
|
+
ordinalByStory: Map<string, number>,
|
|
1022
|
+
storyKey: string,
|
|
1023
|
+
): string {
|
|
1024
|
+
const ordinal = ordinalByStory.get(storyKey) ?? 0;
|
|
1025
|
+
ordinalByStory.set(storyKey, ordinal + 1);
|
|
1026
|
+
return `table-${ordinal}`;
|
|
1027
|
+
}
|
|
1028
|
+
|
|
1029
|
+
function tableSourceIdentity(
|
|
1030
|
+
table: CanonicalTableLayoutInput,
|
|
1031
|
+
): GeometrySourceIdentity {
|
|
1032
|
+
return {
|
|
1033
|
+
storyKey: table.storyKey,
|
|
1034
|
+
blockPath: table.blockPath,
|
|
1035
|
+
tableKey: table.tableKey,
|
|
1036
|
+
joinKind: "direct",
|
|
1037
|
+
};
|
|
1038
|
+
}
|
|
1039
|
+
|
|
1040
|
+
function tableRowSourceIdentity(
|
|
1041
|
+
table: CanonicalTableLayoutInput,
|
|
1042
|
+
rowIndex: number,
|
|
1043
|
+
): GeometrySourceIdentity {
|
|
1044
|
+
const row = table.rows.find((entry) => entry.rowIndex === rowIndex);
|
|
1045
|
+
return {
|
|
1046
|
+
...tableSourceIdentity(table),
|
|
1047
|
+
...(row ? { rowKey: row.rowKey } : {}),
|
|
1048
|
+
};
|
|
1049
|
+
}
|
|
1050
|
+
|
|
1051
|
+
function tableCellSourceIdentity(
|
|
1052
|
+
table: CanonicalTableLayoutInput,
|
|
1053
|
+
rowIndex: number,
|
|
1054
|
+
gridColumnStart: number,
|
|
1055
|
+
): GeometrySourceIdentity {
|
|
1056
|
+
const row = table.rows.find((entry) => entry.rowIndex === rowIndex);
|
|
1057
|
+
const cell = findCanonicalCellByGridColumn(row, gridColumnStart);
|
|
1058
|
+
return {
|
|
1059
|
+
...tableRowSourceIdentity(table, rowIndex),
|
|
1060
|
+
...(cell ? { cellKey: cell.cellKey } : {}),
|
|
1061
|
+
};
|
|
1062
|
+
}
|
|
1063
|
+
|
|
1064
|
+
function findCanonicalCellByGridColumn(
|
|
1065
|
+
row: CanonicalTableRowLayoutInput | undefined,
|
|
1066
|
+
gridColumnStart: number,
|
|
1067
|
+
): CanonicalTableCellLayoutInput | undefined {
|
|
1068
|
+
if (!row) return undefined;
|
|
1069
|
+
return row.cells.find((cell) => cell.gridColumnStart === gridColumnStart);
|
|
1070
|
+
}
|
|
1071
|
+
|
|
1072
|
+
function collectProjectedBlockIdsByPath(
|
|
1073
|
+
document: CanonicalDocument,
|
|
1074
|
+
): Map<string, string> {
|
|
1075
|
+
const result = new Map<string, string>();
|
|
1076
|
+
for (const context of collectStoryBlockContexts(document)) {
|
|
1077
|
+
const counters = {
|
|
1078
|
+
paragraph: 0,
|
|
1079
|
+
table: 0,
|
|
1080
|
+
sdt: 0,
|
|
1081
|
+
customXml: 0,
|
|
1082
|
+
altChunk: 0,
|
|
1083
|
+
opaque: 0,
|
|
1084
|
+
};
|
|
1085
|
+
collectProjectedBlockIdsForBlocks(
|
|
1086
|
+
context.blocks,
|
|
1087
|
+
context.storyKey,
|
|
1088
|
+
context.basePath,
|
|
1089
|
+
counters,
|
|
1090
|
+
result,
|
|
1091
|
+
);
|
|
1092
|
+
}
|
|
1093
|
+
return result;
|
|
1094
|
+
}
|
|
1095
|
+
|
|
1096
|
+
function collectProjectedBlockIdsForBlocks(
|
|
1097
|
+
blocks: readonly BlockNode[],
|
|
1098
|
+
storyKey: string,
|
|
1099
|
+
basePath: string,
|
|
1100
|
+
counters: {
|
|
1101
|
+
paragraph: number;
|
|
1102
|
+
table: number;
|
|
1103
|
+
sdt: number;
|
|
1104
|
+
customXml: number;
|
|
1105
|
+
altChunk: number;
|
|
1106
|
+
opaque: number;
|
|
1107
|
+
},
|
|
1108
|
+
result: Map<string, string>,
|
|
1109
|
+
): void {
|
|
1110
|
+
for (let blockIndex = 0; blockIndex < blocks.length; blockIndex += 1) {
|
|
1111
|
+
const block = blocks[blockIndex];
|
|
1112
|
+
if (!block) continue;
|
|
1113
|
+
const blockPath = `${basePath}/block[${blockIndex}]`;
|
|
1114
|
+
if (block.type === "paragraph") {
|
|
1115
|
+
result.set(storyPathKey(storyKey, blockPath), `paragraph-${counters.paragraph}`);
|
|
1116
|
+
counters.paragraph += 1;
|
|
1117
|
+
continue;
|
|
1118
|
+
}
|
|
1119
|
+
if (block.type === "table") {
|
|
1120
|
+
result.set(storyPathKey(storyKey, blockPath), `table-${counters.table}`);
|
|
1121
|
+
counters.table += 1;
|
|
1122
|
+
for (let rowIndex = 0; rowIndex < block.rows.length; rowIndex += 1) {
|
|
1123
|
+
const row = block.rows[rowIndex];
|
|
1124
|
+
if (!row) continue;
|
|
1125
|
+
for (let cellIndex = 0; cellIndex < row.cells.length; cellIndex += 1) {
|
|
1126
|
+
const cell = row.cells[cellIndex];
|
|
1127
|
+
if (!cell) continue;
|
|
1128
|
+
collectProjectedBlockIdsForBlocks(
|
|
1129
|
+
cell.children,
|
|
1130
|
+
storyKey,
|
|
1131
|
+
`${blockPath}/row[${rowIndex}]/cell[${cellIndex}]`,
|
|
1132
|
+
counters,
|
|
1133
|
+
result,
|
|
1134
|
+
);
|
|
1135
|
+
}
|
|
1136
|
+
}
|
|
1137
|
+
continue;
|
|
1138
|
+
}
|
|
1139
|
+
if (block.type === "sdt") {
|
|
1140
|
+
counters.sdt += 1;
|
|
1141
|
+
collectProjectedBlockIdsForBlocks(
|
|
1142
|
+
block.children,
|
|
1143
|
+
storyKey,
|
|
1144
|
+
blockPath,
|
|
1145
|
+
counters,
|
|
1146
|
+
result,
|
|
1147
|
+
);
|
|
1148
|
+
continue;
|
|
1149
|
+
}
|
|
1150
|
+
if (block.type === "custom_xml") {
|
|
1151
|
+
counters.customXml += 1;
|
|
1152
|
+
continue;
|
|
1153
|
+
}
|
|
1154
|
+
if (block.type === "alt_chunk") {
|
|
1155
|
+
counters.altChunk += 1;
|
|
1156
|
+
continue;
|
|
1157
|
+
}
|
|
1158
|
+
if (block.type === "opaque_block" || block.type === "section_break") {
|
|
1159
|
+
counters.opaque += 1;
|
|
1160
|
+
}
|
|
1161
|
+
}
|
|
1162
|
+
}
|
|
1163
|
+
|
|
1164
|
+
function canonicalStoryKeyFromTarget(target: EditorStoryTarget): string {
|
|
1165
|
+
switch (target.kind) {
|
|
1166
|
+
case "main":
|
|
1167
|
+
return MAIN_STORY_KEY;
|
|
1168
|
+
case "header":
|
|
1169
|
+
return createHeaderFooterStoryKey({
|
|
1170
|
+
kind: "header",
|
|
1171
|
+
relationshipId: target.relationshipId,
|
|
1172
|
+
variant: target.variant,
|
|
1173
|
+
...(target.sectionIndex !== undefined
|
|
1174
|
+
? { sectionIndex: target.sectionIndex }
|
|
1175
|
+
: {}),
|
|
1176
|
+
});
|
|
1177
|
+
case "footer":
|
|
1178
|
+
return createHeaderFooterStoryKey({
|
|
1179
|
+
kind: "footer",
|
|
1180
|
+
relationshipId: target.relationshipId,
|
|
1181
|
+
variant: target.variant,
|
|
1182
|
+
...(target.sectionIndex !== undefined
|
|
1183
|
+
? { sectionIndex: target.sectionIndex }
|
|
1184
|
+
: {}),
|
|
1185
|
+
});
|
|
1186
|
+
case "footnote":
|
|
1187
|
+
return createNoteStoryKey("footnote", target.noteId);
|
|
1188
|
+
case "endnote":
|
|
1189
|
+
return createNoteStoryKey("endnote", target.noteId);
|
|
1190
|
+
}
|
|
1191
|
+
}
|
|
1192
|
+
|
|
1193
|
+
function storyBlockKey(storyKey: string, blockId: string): string {
|
|
1194
|
+
return `${storyKey}\u0000${blockId}`;
|
|
1195
|
+
}
|
|
1196
|
+
|
|
1197
|
+
function storyPathKey(storyKey: string, blockPath: string): string {
|
|
1198
|
+
return `${storyKey}\u0000${blockPath}`;
|
|
1199
|
+
}
|
|
1200
|
+
|
|
1201
|
+
function collectRegionEntries(
|
|
1202
|
+
page: RenderPage,
|
|
1203
|
+
): Array<[RenderStoryRegion, number]> {
|
|
1204
|
+
const entries: Array<[RenderStoryRegion, number]> = [];
|
|
1205
|
+
const push = (region: RenderStoryRegion | undefined) => {
|
|
1206
|
+
if (!region) return;
|
|
1207
|
+
const ordinal = entries.filter(
|
|
1208
|
+
([entry]) => entry.region.kind === region.region.kind,
|
|
1209
|
+
).length;
|
|
1210
|
+
entries.push([region, ordinal]);
|
|
1211
|
+
};
|
|
1212
|
+
push(page.regions.body);
|
|
1213
|
+
push(page.regions.header);
|
|
1214
|
+
push(page.regions.footer);
|
|
1215
|
+
for (const region of page.regions.columns ?? []) push(region);
|
|
1216
|
+
push(page.regions.footnoteArea);
|
|
1217
|
+
for (const region of page.regions.footnotes ?? []) push(region);
|
|
1218
|
+
return entries;
|
|
1219
|
+
}
|
|
1220
|
+
|
|
1221
|
+
function toGeometryRect(rect: RenderFrameRect): GeometryRect {
|
|
1222
|
+
return {
|
|
1223
|
+
leftPx: rect.leftPx,
|
|
1224
|
+
topPx: rect.topPx,
|
|
1225
|
+
widthPx: rect.widthPx,
|
|
1226
|
+
heightPx: rect.heightPx,
|
|
1227
|
+
space: "frame",
|
|
1228
|
+
};
|
|
1229
|
+
}
|
|
1230
|
+
|
|
1231
|
+
function makeRegionId(
|
|
1232
|
+
page: RenderPage,
|
|
1233
|
+
region: RenderStoryRegion,
|
|
1234
|
+
ordinal: number,
|
|
1235
|
+
): string {
|
|
1236
|
+
return `${page.page.pageId}:${region.region.kind}:${ordinal}`;
|
|
1237
|
+
}
|
|
1238
|
+
|
|
1239
|
+
function makeSliceId(regionId: string, fragmentId: string): string {
|
|
1240
|
+
return `${regionId}:slice:${fragmentId}`;
|
|
1241
|
+
}
|
|
1242
|
+
|
|
1243
|
+
function makeLineId(
|
|
1244
|
+
regionId: string,
|
|
1245
|
+
fragmentId: string,
|
|
1246
|
+
lineIndex: number,
|
|
1247
|
+
): string {
|
|
1248
|
+
return `${regionId}:line:${fragmentId}:${lineIndex}`;
|
|
1249
|
+
}
|
|
1250
|
+
|
|
1251
|
+
function makeAnchorId(lineId: string, index: number): string {
|
|
1252
|
+
return `${lineId}:anchor:${index}`;
|
|
1253
|
+
}
|
|
1254
|
+
|
|
1255
|
+
function createPrecisionCounts(): GeometryPrecisionCounts {
|
|
1256
|
+
return {
|
|
1257
|
+
exact: 0,
|
|
1258
|
+
"within-tolerance": 0,
|
|
1259
|
+
heuristic: 0,
|
|
1260
|
+
};
|
|
1261
|
+
}
|
|
1262
|
+
|
|
1263
|
+
function recordPrecision(
|
|
1264
|
+
counts: GeometryPrecisionCounts,
|
|
1265
|
+
precision: keyof GeometryPrecisionCounts,
|
|
1266
|
+
): void {
|
|
1267
|
+
counts[precision] += 1;
|
|
1268
|
+
}
|