@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.
- package/package.json +1 -1
- package/src/api/public-types.ts +63 -1
- package/src/api/v3/_runtime-handle.ts +2 -0
- package/src/api/v3/ai/outline.ts +2 -7
- package/src/api/v3/runtime/geometry.ts +79 -0
- package/src/core/commands/formatting-commands.ts +8 -7
- package/src/core/commands/paragraph-layout-commands.ts +11 -10
- package/src/core/commands/section-layout-commands.ts +7 -6
- package/src/core/commands/style-commands.ts +3 -2
- package/src/io/normalize/normalize-text.ts +6 -5
- package/src/io/ooxml/parse-anchor.ts +15 -15
- package/src/io/ooxml/parse-drawing.ts +103 -5
- package/src/io/ooxml/parse-fields.ts +43 -21
- package/src/io/ooxml/parse-font-table.ts +2 -1
- package/src/io/ooxml/parse-footnotes.ts +3 -2
- package/src/io/ooxml/parse-headers-footers.ts +7 -6
- package/src/io/ooxml/parse-main-document.ts +41 -40
- package/src/io/ooxml/parse-numbering.ts +3 -2
- package/src/io/ooxml/parse-object.ts +6 -6
- package/src/io/ooxml/parse-paragraph-formatting.ts +12 -11
- package/src/io/ooxml/parse-picture.ts +16 -16
- package/src/io/ooxml/parse-run-formatting.ts +11 -10
- package/src/io/ooxml/parse-settings.ts +2 -1
- package/src/io/ooxml/parse-shapes.ts +148 -17
- package/src/io/ooxml/parse-styles.ts +16 -16
- package/src/io/ooxml/parse-theme.ts +5 -4
- package/src/model/canonical-document.ts +869 -836
- package/src/model/canonical-layout-inputs.ts +979 -0
- package/src/model/layout/index.ts +6 -0
- package/src/model/layout/page-graph-types.ts +61 -0
- package/src/model/layout/runtime-page-graph-types.ts +10 -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 +3 -0
- package/src/runtime/formatting/document-lookup.ts +3 -2
- package/src/runtime/formatting/formatting-context.ts +176 -34
- package/src/runtime/formatting/index.ts +20 -0
- package/src/runtime/formatting/layout-inputs.ts +320 -0
- package/src/runtime/formatting/numbering/geometry.ts +13 -12
- package/src/runtime/formatting/style-cascade.ts +2 -1
- package/src/runtime/formatting/table-style-resolver.ts +8 -7
- package/src/runtime/geometry/caret-geometry.ts +82 -10
- package/src/runtime/geometry/geometry-facet.ts +36 -0
- package/src/runtime/geometry/geometry-index.ts +891 -0
- package/src/runtime/geometry/geometry-types.ts +221 -1
- package/src/runtime/geometry/index.ts +26 -0
- package/src/runtime/geometry/inert-geometry-facet.ts +3 -0
- package/src/runtime/geometry/replacement-envelope.ts +41 -2
- package/src/runtime/layout/layout-engine-version.ts +16 -1
- package/src/runtime/layout/page-graph.ts +191 -1
- package/src/runtime/prerender/graph-canonicalize.ts +30 -0
- package/src/runtime/surface-projection.ts +74 -39
- package/src/runtime/workflow/coordinator.ts +57 -11
- package/src/session/import/normalize.ts +2 -1
- package/src/session/import/source-package-evidence.ts +612 -1
- 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
|
+
}
|