@beyondwork/docx-react-component 1.0.103 → 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/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 +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/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 +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 +43 -3
- package/src/runtime/workflow/coordinator.ts +57 -11
- package/src/ui-tailwind/chrome-overlay/tw-page-stack-overlay-layer.tsx +3 -0
|
@@ -24,7 +24,10 @@
|
|
|
24
24
|
*/
|
|
25
25
|
|
|
26
26
|
import type { EditorStoryTarget } from "../../api/public-types";
|
|
27
|
-
import type {
|
|
27
|
+
import type {
|
|
28
|
+
PublicMeasurementFidelity,
|
|
29
|
+
PublicPageRegion,
|
|
30
|
+
} from "../layout/public-facet.ts";
|
|
28
31
|
|
|
29
32
|
// ---------------------------------------------------------------------------
|
|
30
33
|
// G5 · Coordinate space tags
|
|
@@ -75,6 +78,11 @@ export type GeometrySpace = "twips" | "frame" | "overlay";
|
|
|
75
78
|
*/
|
|
76
79
|
export type GeometryPrecision = "exact" | "within-tolerance" | "heuristic";
|
|
77
80
|
|
|
81
|
+
export type GeometryRehydrationStatus =
|
|
82
|
+
| "realized"
|
|
83
|
+
| "requires-rehydration"
|
|
84
|
+
| "unavailable";
|
|
85
|
+
|
|
78
86
|
// ---------------------------------------------------------------------------
|
|
79
87
|
// Core rect shape
|
|
80
88
|
// ---------------------------------------------------------------------------
|
|
@@ -98,6 +106,200 @@ export interface GeometryRect {
|
|
|
98
106
|
precision?: GeometryPrecision;
|
|
99
107
|
}
|
|
100
108
|
|
|
109
|
+
// ---------------------------------------------------------------------------
|
|
110
|
+
// PE2 geometry index (Slice 1 · page-slice projection substrate)
|
|
111
|
+
// ---------------------------------------------------------------------------
|
|
112
|
+
|
|
113
|
+
export interface GeometryPrecisionCounts {
|
|
114
|
+
exact: number;
|
|
115
|
+
"within-tolerance": number;
|
|
116
|
+
heuristic: number;
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
export interface GeometryIndexCoverage {
|
|
120
|
+
status: GeometryRehydrationStatus;
|
|
121
|
+
pageCount: number;
|
|
122
|
+
regionCount: number;
|
|
123
|
+
sliceCount: number;
|
|
124
|
+
lineCount: number;
|
|
125
|
+
anchorCount: number;
|
|
126
|
+
hitTargetCount: number;
|
|
127
|
+
semanticEntryCount: number;
|
|
128
|
+
replacementEnvelopeCount: number;
|
|
129
|
+
objectHandleCount: number;
|
|
130
|
+
precision: GeometryPrecisionCounts;
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
export type GeometrySourceIdentityJoinKind = "direct" | "block-scoped";
|
|
134
|
+
|
|
135
|
+
/**
|
|
136
|
+
* Stable canonical identity joined onto projected geometry when the current
|
|
137
|
+
* substrate can prove the correspondence without consulting renderer state.
|
|
138
|
+
*
|
|
139
|
+
* `joinKind: "direct"` means the geometry node maps to the named canonical
|
|
140
|
+
* table/row/cell. `joinKind: "block-scoped"` means the geometry rect still
|
|
141
|
+
* belongs to the enclosing rendered block/line; object fields identify the
|
|
142
|
+
* canonical object carried by that block, not a true object bbox.
|
|
143
|
+
*/
|
|
144
|
+
export interface GeometrySourceIdentity {
|
|
145
|
+
storyKey: string;
|
|
146
|
+
blockPath?: string;
|
|
147
|
+
tableKey?: string;
|
|
148
|
+
rowKey?: string;
|
|
149
|
+
cellKey?: string;
|
|
150
|
+
objectKey?: string;
|
|
151
|
+
inlinePath?: string;
|
|
152
|
+
objectKind?: string;
|
|
153
|
+
editPosture?: string;
|
|
154
|
+
joinKind: GeometrySourceIdentityJoinKind;
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
export interface GeometryIndex {
|
|
158
|
+
/**
|
|
159
|
+
* Source adapter for this index. PE2's target input is L04 page slices; the
|
|
160
|
+
* current substrate projects the render frame because it is the runtime's
|
|
161
|
+
* already materialized page-slice view.
|
|
162
|
+
*/
|
|
163
|
+
source: "render-frame-adapter";
|
|
164
|
+
revision: number;
|
|
165
|
+
measurementFidelity: PublicMeasurementFidelity;
|
|
166
|
+
activeStory: EditorStoryTarget;
|
|
167
|
+
pages: readonly GeometryIndexPage[];
|
|
168
|
+
regions: readonly GeometryIndexRegion[];
|
|
169
|
+
slices: readonly GeometryIndexSlice[];
|
|
170
|
+
lines: readonly GeometryIndexLine[];
|
|
171
|
+
anchors: readonly AnchorGeometry[];
|
|
172
|
+
hitTargets: readonly GeometryHitTarget[];
|
|
173
|
+
semanticEntries: readonly SemanticDisplayEntry[];
|
|
174
|
+
replacementEnvelopes: readonly GeometryReplacementEnvelopeEntry[];
|
|
175
|
+
objectHandles: readonly GeometryObjectHandleEntry[];
|
|
176
|
+
coverage: GeometryIndexCoverage;
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
export interface GeometryIndexPage {
|
|
180
|
+
pageId: string;
|
|
181
|
+
pageIndex: number;
|
|
182
|
+
rect: GeometryRect;
|
|
183
|
+
regionIds: readonly string[];
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
export interface GeometryIndexRegion {
|
|
187
|
+
regionId: string;
|
|
188
|
+
pageId: string;
|
|
189
|
+
pageIndex: number;
|
|
190
|
+
kind: PublicPageRegion["kind"];
|
|
191
|
+
ordinal: number;
|
|
192
|
+
rect: GeometryRect;
|
|
193
|
+
sliceIds: readonly string[];
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
export interface GeometryIndexSlice {
|
|
197
|
+
sliceId: string;
|
|
198
|
+
pageId: string;
|
|
199
|
+
pageIndex: number;
|
|
200
|
+
regionId: string;
|
|
201
|
+
regionKind: PublicPageRegion["kind"];
|
|
202
|
+
blockId: string;
|
|
203
|
+
fragmentId: string;
|
|
204
|
+
kind: "paragraph" | "table" | "opaque" | "image-float" | "synthetic";
|
|
205
|
+
rect: GeometryRect;
|
|
206
|
+
lineIds: readonly string[];
|
|
207
|
+
sourceIdentity?: GeometrySourceIdentity;
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
export interface GeometryIndexLine {
|
|
211
|
+
lineId: string;
|
|
212
|
+
pageId: string;
|
|
213
|
+
pageIndex: number;
|
|
214
|
+
regionId: string;
|
|
215
|
+
regionKind: PublicPageRegion["kind"];
|
|
216
|
+
blockId: string;
|
|
217
|
+
fragmentId: string;
|
|
218
|
+
lineIndex: number;
|
|
219
|
+
rect: GeometryRect;
|
|
220
|
+
/** Baseline offset from the line rect's top, in frame pixels. */
|
|
221
|
+
baseline?: number;
|
|
222
|
+
baselinePrecision?: GeometryPrecision;
|
|
223
|
+
anchorIds: readonly string[];
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
export interface AnchorGeometry {
|
|
227
|
+
anchorId: string;
|
|
228
|
+
pageId: string;
|
|
229
|
+
pageIndex: number;
|
|
230
|
+
regionId: string;
|
|
231
|
+
regionKind: PublicPageRegion["kind"];
|
|
232
|
+
blockId?: string;
|
|
233
|
+
fragmentId?: string;
|
|
234
|
+
lineId?: string;
|
|
235
|
+
runtimeOffset?: number;
|
|
236
|
+
rect: GeometryRect;
|
|
237
|
+
precision: GeometryPrecision;
|
|
238
|
+
sourceIdentity?: GeometrySourceIdentity;
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
export interface GeometryHitTarget {
|
|
242
|
+
targetId: string;
|
|
243
|
+
pageId: string;
|
|
244
|
+
pageIndex: number;
|
|
245
|
+
regionId: string;
|
|
246
|
+
regionKind: PublicPageRegion["kind"];
|
|
247
|
+
blockId: string;
|
|
248
|
+
fragmentId: string;
|
|
249
|
+
lineIndex: number;
|
|
250
|
+
rect: GeometryRect;
|
|
251
|
+
precision: GeometryPrecision;
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
export type SemanticDisplayEntryKind =
|
|
255
|
+
| "text-line"
|
|
256
|
+
| "numbering-marker"
|
|
257
|
+
| "table-frame"
|
|
258
|
+
| "table-row"
|
|
259
|
+
| "table-cell"
|
|
260
|
+
| "toc-leader"
|
|
261
|
+
| "toc-page-label"
|
|
262
|
+
| "field-result"
|
|
263
|
+
| "inline-object"
|
|
264
|
+
| "floating-object"
|
|
265
|
+
| "placeholder"
|
|
266
|
+
| "background"
|
|
267
|
+
| "border"
|
|
268
|
+
| "debug-classification";
|
|
269
|
+
|
|
270
|
+
export interface SemanticDisplayEntry {
|
|
271
|
+
entryId: string;
|
|
272
|
+
kind: SemanticDisplayEntryKind;
|
|
273
|
+
pageId: string;
|
|
274
|
+
pageIndex: number;
|
|
275
|
+
regionId?: string;
|
|
276
|
+
regionKind?: PublicPageRegion["kind"];
|
|
277
|
+
sliceId?: string;
|
|
278
|
+
lineId?: string;
|
|
279
|
+
blockId?: string;
|
|
280
|
+
fragmentId?: string;
|
|
281
|
+
rowIndex?: number;
|
|
282
|
+
columnIndex?: number;
|
|
283
|
+
rect: GeometryRect;
|
|
284
|
+
status: GeometryRehydrationStatus;
|
|
285
|
+
precision: GeometryPrecision;
|
|
286
|
+
sourceIdentity?: GeometrySourceIdentity;
|
|
287
|
+
}
|
|
288
|
+
|
|
289
|
+
export interface GeometryReplacementEnvelopeEntry {
|
|
290
|
+
scopeId: string;
|
|
291
|
+
rects: readonly GeometryRect[];
|
|
292
|
+
status: GeometryRehydrationStatus;
|
|
293
|
+
precision: GeometryPrecision;
|
|
294
|
+
}
|
|
295
|
+
|
|
296
|
+
export interface GeometryObjectHandleEntry {
|
|
297
|
+
objectId: string;
|
|
298
|
+
rects: readonly GeometryRect[];
|
|
299
|
+
status: GeometryRehydrationStatus;
|
|
300
|
+
precision: GeometryPrecision;
|
|
301
|
+
}
|
|
302
|
+
|
|
101
303
|
// ---------------------------------------------------------------------------
|
|
102
304
|
// Per-granularity geometry nodes (read models; Slice 2+ populates them)
|
|
103
305
|
// ---------------------------------------------------------------------------
|
|
@@ -200,6 +402,14 @@ export interface AnchorQuery {
|
|
|
200
402
|
|
|
201
403
|
export type EnvelopeConfidence = "exact" | "medium" | "detached";
|
|
202
404
|
|
|
405
|
+
export interface EnvelopeContinuationState {
|
|
406
|
+
pageIds: readonly string[];
|
|
407
|
+
pageCount: number;
|
|
408
|
+
crossesPageBoundary: boolean;
|
|
409
|
+
continuedFromPreviousPage: boolean;
|
|
410
|
+
continuesToNextPage: boolean;
|
|
411
|
+
}
|
|
412
|
+
|
|
203
413
|
export interface EnvelopeBundle {
|
|
204
414
|
/**
|
|
205
415
|
* Rect coverage for the resolved scope. Slice-5 substrate produces
|
|
@@ -211,6 +421,10 @@ export interface EnvelopeBundle {
|
|
|
211
421
|
* walk if the count is lower than expected.
|
|
212
422
|
*/
|
|
213
423
|
scopeRects: readonly GeometryRect[];
|
|
424
|
+
/** Page ids whose projected page frames intersect `scopeRects`. */
|
|
425
|
+
pageIds: readonly string[];
|
|
426
|
+
/** Continuation metadata for multi-page / partial-page replacement scopes. */
|
|
427
|
+
continuation: EnvelopeContinuationState;
|
|
214
428
|
/** Anchor point consumers attach chrome to (top-left of first rect). */
|
|
215
429
|
attachPoint: { xPx: number; yPx: number; space: GeometrySpace };
|
|
216
430
|
/**
|
|
@@ -227,6 +441,12 @@ export interface EnvelopeBundle {
|
|
|
227
441
|
* layer-05 closure.
|
|
228
442
|
*/
|
|
229
443
|
linesCrossed: number;
|
|
444
|
+
/**
|
|
445
|
+
* Realized for live range scopes. Detached scopes are projected from a
|
|
446
|
+
* last-known range and are marked `requires-rehydration` so callers do not
|
|
447
|
+
* mistake the geometry for current document truth.
|
|
448
|
+
*/
|
|
449
|
+
rehydrationStatus: GeometryRehydrationStatus;
|
|
230
450
|
/**
|
|
231
451
|
* Precision tag. `"exact"` when the envelope collapsed to a caret rect
|
|
232
452
|
* (single anchor position); `"within-tolerance"` when the envelope is
|
|
@@ -13,7 +13,24 @@
|
|
|
13
13
|
|
|
14
14
|
export type {
|
|
15
15
|
GeometrySpace,
|
|
16
|
+
GeometryPrecision,
|
|
16
17
|
GeometryRect,
|
|
18
|
+
GeometryRehydrationStatus,
|
|
19
|
+
GeometryPrecisionCounts,
|
|
20
|
+
GeometrySourceIdentityJoinKind,
|
|
21
|
+
GeometrySourceIdentity,
|
|
22
|
+
GeometryIndexCoverage,
|
|
23
|
+
GeometryIndex,
|
|
24
|
+
GeometryIndexPage,
|
|
25
|
+
GeometryIndexRegion,
|
|
26
|
+
GeometryIndexSlice,
|
|
27
|
+
GeometryIndexLine,
|
|
28
|
+
AnchorGeometry,
|
|
29
|
+
GeometryHitTarget,
|
|
30
|
+
SemanticDisplayEntryKind,
|
|
31
|
+
SemanticDisplayEntry,
|
|
32
|
+
GeometryReplacementEnvelopeEntry,
|
|
33
|
+
GeometryObjectHandleEntry,
|
|
17
34
|
PageGeometry,
|
|
18
35
|
BlockGeometry,
|
|
19
36
|
LineGeometry,
|
|
@@ -24,11 +41,20 @@ export type {
|
|
|
24
41
|
AnchorQueryKind,
|
|
25
42
|
AnchorQuery,
|
|
26
43
|
EnvelopeConfidence,
|
|
44
|
+
EnvelopeContinuationState,
|
|
27
45
|
EnvelopeBundle,
|
|
28
46
|
Viewport,
|
|
29
47
|
ViewportListener,
|
|
30
48
|
} from "./geometry-types.ts";
|
|
31
49
|
|
|
50
|
+
export {
|
|
51
|
+
createUnavailableGeometryCoverage,
|
|
52
|
+
projectGeometryIndexFromFrame,
|
|
53
|
+
summarizeGeometryCoverageFromFrame,
|
|
54
|
+
} from "./geometry-index.ts";
|
|
55
|
+
|
|
56
|
+
export type { GeometryIndexProjectionOptions } from "./geometry-index.ts";
|
|
57
|
+
|
|
32
58
|
export type {
|
|
33
59
|
GeometryFacet,
|
|
34
60
|
CreateGeometryFacetInput,
|
|
@@ -10,6 +10,7 @@
|
|
|
10
10
|
|
|
11
11
|
import type { GeometryFacet } from "./geometry-facet.ts";
|
|
12
12
|
import type { Viewport } from "./geometry-types.ts";
|
|
13
|
+
import { createUnavailableGeometryCoverage } from "./geometry-index.ts";
|
|
13
14
|
|
|
14
15
|
const INERT_VIEWPORT: Viewport = Object.freeze({
|
|
15
16
|
scrollLeftPx: 0,
|
|
@@ -20,6 +21,8 @@ const INERT_VIEWPORT: Viewport = Object.freeze({
|
|
|
20
21
|
|
|
21
22
|
export function createInertGeometryFacet(): GeometryFacet {
|
|
22
23
|
return {
|
|
24
|
+
getGeometryIndex: () => null,
|
|
25
|
+
getGeometryCoverage: () => createUnavailableGeometryCoverage(),
|
|
23
26
|
hitTest: () => null,
|
|
24
27
|
getAnchorRects: () => [],
|
|
25
28
|
getAnchor: () => null,
|
|
@@ -75,7 +75,7 @@ export function resolveReplacementEnvelope(
|
|
|
75
75
|
if (!Number.isFinite(from) || !Number.isFinite(to)) return null;
|
|
76
76
|
const rects = resolveSelectionRects(frame, { from, to, story });
|
|
77
77
|
if (rects.length === 0) return null;
|
|
78
|
-
return buildBundle(rects, "exact");
|
|
78
|
+
return buildBundle(frame, rects, "exact");
|
|
79
79
|
}
|
|
80
80
|
|
|
81
81
|
// Detached: project the last-known range and tag confidence as such.
|
|
@@ -83,10 +83,11 @@ export function resolveReplacementEnvelope(
|
|
|
83
83
|
if (!Number.isFinite(from) || !Number.isFinite(to)) return null;
|
|
84
84
|
const rects = resolveSelectionRects(frame, { from, to, story });
|
|
85
85
|
if (rects.length === 0) return null;
|
|
86
|
-
return buildBundle(rects, "detached");
|
|
86
|
+
return buildBundle(frame, rects, "detached");
|
|
87
87
|
}
|
|
88
88
|
|
|
89
89
|
function buildBundle(
|
|
90
|
+
frame: RenderFrame,
|
|
90
91
|
rects: readonly GeometryRect[],
|
|
91
92
|
confidence: EnvelopeBundle["confidence"],
|
|
92
93
|
): EnvelopeBundle {
|
|
@@ -104,8 +105,17 @@ function buildBundle(
|
|
|
104
105
|
const rectPrecision = coarsestRectPrecision(rects);
|
|
105
106
|
const envelopePrecision: EnvelopeBundle["precision"] =
|
|
106
107
|
confidence === "detached" ? "heuristic" : rectPrecision;
|
|
108
|
+
const pageIds = resolveIntersectingPageIds(frame, rects);
|
|
107
109
|
return {
|
|
108
110
|
scopeRects: rects,
|
|
111
|
+
pageIds,
|
|
112
|
+
continuation: {
|
|
113
|
+
pageIds,
|
|
114
|
+
pageCount: pageIds.length,
|
|
115
|
+
crossesPageBoundary: pageIds.length > 1,
|
|
116
|
+
continuedFromPreviousPage: false,
|
|
117
|
+
continuesToNextPage: false,
|
|
118
|
+
},
|
|
109
119
|
attachPoint: {
|
|
110
120
|
xPx: first.leftPx,
|
|
111
121
|
yPx: first.topPx,
|
|
@@ -113,10 +123,39 @@ function buildBundle(
|
|
|
113
123
|
},
|
|
114
124
|
confidence,
|
|
115
125
|
linesCrossed: rects.length,
|
|
126
|
+
rehydrationStatus:
|
|
127
|
+
confidence === "detached" ? "requires-rehydration" : "realized",
|
|
116
128
|
precision: envelopePrecision,
|
|
117
129
|
};
|
|
118
130
|
}
|
|
119
131
|
|
|
132
|
+
function resolveIntersectingPageIds(
|
|
133
|
+
frame: RenderFrame,
|
|
134
|
+
rects: readonly GeometryRect[],
|
|
135
|
+
): readonly string[] {
|
|
136
|
+
if (!Array.isArray(frame.pages)) return [];
|
|
137
|
+
const ids: string[] = [];
|
|
138
|
+
for (const page of frame.pages) {
|
|
139
|
+
if (rects.some((rect) => intersects(rect, page.frame))) {
|
|
140
|
+
ids.push(page.page.pageId);
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
return ids;
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
function intersects(rect: GeometryRect, page: { leftPx: number; topPx: number; widthPx: number; heightPx: number }): boolean {
|
|
147
|
+
const rectRight = rect.leftPx + rect.widthPx;
|
|
148
|
+
const rectBottom = rect.topPx + rect.heightPx;
|
|
149
|
+
const pageRight = page.leftPx + page.widthPx;
|
|
150
|
+
const pageBottom = page.topPx + page.heightPx;
|
|
151
|
+
return (
|
|
152
|
+
rect.leftPx <= pageRight &&
|
|
153
|
+
rectRight >= page.leftPx &&
|
|
154
|
+
rect.topPx <= pageBottom &&
|
|
155
|
+
rectBottom >= page.topPx
|
|
156
|
+
);
|
|
157
|
+
}
|
|
158
|
+
|
|
120
159
|
function coarsestRectPrecision(
|
|
121
160
|
rects: readonly GeometryRect[],
|
|
122
161
|
): "exact" | "within-tolerance" | "heuristic" {
|
|
@@ -1031,8 +1031,23 @@
|
|
|
1031
1031
|
* content instead of checking only top-level paragraphs. This honors
|
|
1032
1032
|
* `<w:br w:type="page"/>` inside cover-page SDTs such as the CCEP SOW
|
|
1033
1033
|
* template, changing page assignment from the v61 cache shape.
|
|
1034
|
+
*
|
|
1035
|
+
* 63 — PE2 geometry-projection substrate (Layer 05). Adds the new
|
|
1036
|
+
* geometry-index module (`src/runtime/geometry/geometry-index.ts`,
|
|
1037
|
+
* `geometry-facet.ts`, `caret-geometry.ts`, `replacement-envelope.ts`,
|
|
1038
|
+
* `inert-geometry-facet.ts`, `geometry-types.ts`, `index.ts`) and
|
|
1039
|
+
* advances the geometry projections that downstream caches consume.
|
|
1040
|
+
* Persisted layout caches keyed on the v62 geometry shape must
|
|
1041
|
+
* re-derive. Shipped via pe2 commits `957069161` + `1f36767d7`.
|
|
1042
|
+
*
|
|
1043
|
+
* 64 — PE2 Slice 2 page-frame graph substrate (Layer 04). `RuntimePageNode`
|
|
1044
|
+
* built by L04 now carries a twips-only `RuntimePageFrame` payload, explicit
|
|
1045
|
+
* `rectTwips` on resolved page regions, and typed layout divergence lists.
|
|
1046
|
+
* Existing `regions` remain the compatibility surface, but cache envelopes
|
|
1047
|
+
* from v63 invalidate because the page graph payload shape changed.
|
|
1048
|
+
* Shipped via pe2 commit `24a316af2`.
|
|
1034
1049
|
*/
|
|
1035
|
-
export const LAYOUT_ENGINE_VERSION =
|
|
1050
|
+
export const LAYOUT_ENGINE_VERSION = 64 as const;
|
|
1036
1051
|
|
|
1037
1052
|
/**
|
|
1038
1053
|
* Serialization schema version for the LayCache payload (the cache envelope
|
|
@@ -48,6 +48,12 @@ import type { ResolvedDocumentSection } from "../document-layout.ts";
|
|
|
48
48
|
export type {
|
|
49
49
|
RuntimePageRegions,
|
|
50
50
|
RuntimePageRegion,
|
|
51
|
+
RuntimeTwipsRect,
|
|
52
|
+
RuntimeResolvedRegions,
|
|
53
|
+
RuntimeExclusionZone,
|
|
54
|
+
RuntimeLayoutDivergenceKind,
|
|
55
|
+
RuntimeLayoutDivergence,
|
|
56
|
+
RuntimePageFrame,
|
|
51
57
|
RuntimeBlockFragment,
|
|
52
58
|
RuntimeLineBox,
|
|
53
59
|
RuntimeNoteAllocation,
|
|
@@ -62,11 +68,15 @@ export type {
|
|
|
62
68
|
|
|
63
69
|
import type {
|
|
64
70
|
RuntimeBlockFragment,
|
|
71
|
+
RuntimeLayoutDivergence,
|
|
65
72
|
RuntimeLineBox,
|
|
66
73
|
RuntimeNoteAllocation,
|
|
67
74
|
RuntimePageAnchor,
|
|
75
|
+
RuntimePageFrame,
|
|
68
76
|
RuntimePageRegion,
|
|
69
77
|
RuntimePageRegions,
|
|
78
|
+
RuntimeResolvedRegions,
|
|
79
|
+
RuntimeTwipsRect,
|
|
70
80
|
} from "../../model/layout/page-graph-types.ts";
|
|
71
81
|
import type {
|
|
72
82
|
RuntimePageGraph,
|
|
@@ -164,6 +174,24 @@ export function buildPageGraph(
|
|
|
164
174
|
input.noteAllocations?.get(pageId) ??
|
|
165
175
|
[];
|
|
166
176
|
|
|
177
|
+
const frameId = buildPageFrameId(
|
|
178
|
+
page.pageIndex,
|
|
179
|
+
page.sectionIndex,
|
|
180
|
+
stories.displayPageNumber,
|
|
181
|
+
);
|
|
182
|
+
const regions = buildRegions(page.layout, bodyPageFragments, stories, pageNoteAllocations);
|
|
183
|
+
const divergences = detectFrameDivergences(frameId, regions);
|
|
184
|
+
const frame = buildPageFrame({
|
|
185
|
+
frameId,
|
|
186
|
+
pageId,
|
|
187
|
+
pageIndex: page.pageIndex,
|
|
188
|
+
sectionIndex: page.sectionIndex,
|
|
189
|
+
displayPageNumber: stories.displayPageNumber,
|
|
190
|
+
layout: page.layout,
|
|
191
|
+
regions,
|
|
192
|
+
divergences,
|
|
193
|
+
});
|
|
194
|
+
|
|
167
195
|
const node: RuntimePageNode = {
|
|
168
196
|
pageId,
|
|
169
197
|
pageIndex: page.pageIndex,
|
|
@@ -173,7 +201,9 @@ export function buildPageGraph(
|
|
|
173
201
|
endOffset: page.endOffset,
|
|
174
202
|
layout: page.layout,
|
|
175
203
|
stories,
|
|
176
|
-
regions
|
|
204
|
+
regions,
|
|
205
|
+
frame,
|
|
206
|
+
divergences,
|
|
177
207
|
lineBoxes:
|
|
178
208
|
input.lineBoxesByPageIndex?.get(page.pageIndex) ??
|
|
179
209
|
input.lineBoxesByPageIndex?.get(index) ??
|
|
@@ -240,6 +270,12 @@ function buildRegions(
|
|
|
240
270
|
originTwips: layout.pageHeight - layout.marginBottom - totalNoteHeight,
|
|
241
271
|
widthTwips: Math.max(0, bodyWidth),
|
|
242
272
|
heightTwips: Math.max(0, totalNoteHeight),
|
|
273
|
+
rectTwips: rect(
|
|
274
|
+
layout.marginLeft,
|
|
275
|
+
layout.pageHeight - layout.marginBottom - totalNoteHeight,
|
|
276
|
+
Math.max(0, bodyWidth),
|
|
277
|
+
Math.max(0, totalNoteHeight),
|
|
278
|
+
),
|
|
243
279
|
fragmentIds,
|
|
244
280
|
};
|
|
245
281
|
bodyHeight = Math.max(0, bodyHeight - totalNoteHeight);
|
|
@@ -251,6 +287,7 @@ function buildRegions(
|
|
|
251
287
|
originTwips: layout.marginTop,
|
|
252
288
|
widthTwips: Math.max(0, bodyWidth),
|
|
253
289
|
heightTwips: Math.max(0, bodyHeight),
|
|
290
|
+
rectTwips: rect(layout.marginLeft, layout.marginTop, Math.max(0, bodyWidth), Math.max(0, bodyHeight)),
|
|
254
291
|
fragmentIds: [...bodyFragmentIds],
|
|
255
292
|
};
|
|
256
293
|
|
|
@@ -262,6 +299,12 @@ function buildRegions(
|
|
|
262
299
|
originTwips: layout.headerMargin ?? 720,
|
|
263
300
|
widthTwips: Math.max(0, bodyWidth),
|
|
264
301
|
heightTwips: Math.max(0, layout.marginTop - (layout.headerMargin ?? 720)),
|
|
302
|
+
rectTwips: rect(
|
|
303
|
+
layout.marginLeft,
|
|
304
|
+
layout.headerMargin ?? 720,
|
|
305
|
+
Math.max(0, bodyWidth),
|
|
306
|
+
Math.max(0, layout.marginTop - (layout.headerMargin ?? 720)),
|
|
307
|
+
),
|
|
265
308
|
fragmentIds: [],
|
|
266
309
|
};
|
|
267
310
|
}
|
|
@@ -271,6 +314,12 @@ function buildRegions(
|
|
|
271
314
|
originTwips: layout.pageHeight - layout.marginBottom,
|
|
272
315
|
widthTwips: Math.max(0, bodyWidth),
|
|
273
316
|
heightTwips: Math.max(0, layout.marginBottom - (layout.footerMargin ?? 720)),
|
|
317
|
+
rectTwips: rect(
|
|
318
|
+
layout.marginLeft,
|
|
319
|
+
layout.pageHeight - layout.marginBottom,
|
|
320
|
+
Math.max(0, bodyWidth),
|
|
321
|
+
Math.max(0, layout.marginBottom - (layout.footerMargin ?? 720)),
|
|
322
|
+
),
|
|
274
323
|
fragmentIds: [],
|
|
275
324
|
};
|
|
276
325
|
}
|
|
@@ -299,6 +348,12 @@ function buildRegions(
|
|
|
299
348
|
originTwips: layout.marginTop,
|
|
300
349
|
widthTwips: perColumnWidth,
|
|
301
350
|
heightTwips: Math.max(0, bodyHeight),
|
|
351
|
+
rectTwips: rect(
|
|
352
|
+
layout.marginLeft + i * (perColumnWidth + gap),
|
|
353
|
+
layout.marginTop,
|
|
354
|
+
perColumnWidth,
|
|
355
|
+
Math.max(0, bodyHeight),
|
|
356
|
+
),
|
|
302
357
|
fragmentIds: perColumnIds[i]!,
|
|
303
358
|
});
|
|
304
359
|
}
|
|
@@ -312,6 +367,141 @@ function buildRegions(
|
|
|
312
367
|
return regions;
|
|
313
368
|
}
|
|
314
369
|
|
|
370
|
+
function rect(
|
|
371
|
+
xTwips: number,
|
|
372
|
+
yTwips: number,
|
|
373
|
+
widthTwips: number,
|
|
374
|
+
heightTwips: number,
|
|
375
|
+
): RuntimeTwipsRect {
|
|
376
|
+
return { xTwips, yTwips, widthTwips, heightTwips };
|
|
377
|
+
}
|
|
378
|
+
|
|
379
|
+
function buildPageFrame(input: {
|
|
380
|
+
frameId: string;
|
|
381
|
+
pageId: string;
|
|
382
|
+
pageIndex: number;
|
|
383
|
+
sectionIndex: number;
|
|
384
|
+
displayPageNumber: number;
|
|
385
|
+
layout: PageLayoutSnapshot;
|
|
386
|
+
regions: RuntimePageRegions;
|
|
387
|
+
divergences: readonly RuntimeLayoutDivergence[];
|
|
388
|
+
}): RuntimePageFrame {
|
|
389
|
+
const regions: RuntimeResolvedRegions = {
|
|
390
|
+
body: input.regions.body,
|
|
391
|
+
exclusionZones: [],
|
|
392
|
+
...(input.regions.header ? { header: input.regions.header } : {}),
|
|
393
|
+
...(input.regions.footer ? { footer: input.regions.footer } : {}),
|
|
394
|
+
...(input.regions.columns ? { columns: input.regions.columns } : {}),
|
|
395
|
+
...(input.regions.footnotes ? { footnotes: input.regions.footnotes } : {}),
|
|
396
|
+
};
|
|
397
|
+
const divergenceIds = input.divergences.map((d) => d.divergenceId);
|
|
398
|
+
return {
|
|
399
|
+
frameId: input.frameId,
|
|
400
|
+
pageId: input.pageId,
|
|
401
|
+
pageIndex: input.pageIndex,
|
|
402
|
+
sectionIndex: input.sectionIndex,
|
|
403
|
+
displayPageNumber: input.displayPageNumber,
|
|
404
|
+
physicalBoundsTwips: rect(0, 0, input.layout.pageWidth, input.layout.pageHeight),
|
|
405
|
+
regions,
|
|
406
|
+
divergenceIds,
|
|
407
|
+
signature: buildPageFrameSignature(input, divergenceIds),
|
|
408
|
+
};
|
|
409
|
+
}
|
|
410
|
+
|
|
411
|
+
function buildPageFrameId(
|
|
412
|
+
pageIndex: number,
|
|
413
|
+
sectionIndex: number,
|
|
414
|
+
displayPageNumber: number,
|
|
415
|
+
): string {
|
|
416
|
+
return `page-frame-${pageIndex}-section-${sectionIndex}-display-${displayPageNumber}`;
|
|
417
|
+
}
|
|
418
|
+
|
|
419
|
+
function buildPageFrameSignature(
|
|
420
|
+
input: {
|
|
421
|
+
pageIndex: number;
|
|
422
|
+
sectionIndex: number;
|
|
423
|
+
displayPageNumber: number;
|
|
424
|
+
layout: PageLayoutSnapshot;
|
|
425
|
+
regions: RuntimePageRegions;
|
|
426
|
+
},
|
|
427
|
+
divergenceIds: readonly string[],
|
|
428
|
+
): string {
|
|
429
|
+
const regionParts = collectRegions(input.regions).map((region) => {
|
|
430
|
+
const r = region.rectTwips ?? rect(0, region.originTwips, region.widthTwips, region.heightTwips);
|
|
431
|
+
return [
|
|
432
|
+
region.kind,
|
|
433
|
+
r.xTwips,
|
|
434
|
+
r.yTwips,
|
|
435
|
+
r.widthTwips,
|
|
436
|
+
r.heightTwips,
|
|
437
|
+
region.fragmentIds.length,
|
|
438
|
+
].join(":");
|
|
439
|
+
});
|
|
440
|
+
return [
|
|
441
|
+
"page-frame",
|
|
442
|
+
input.pageIndex,
|
|
443
|
+
input.sectionIndex,
|
|
444
|
+
input.displayPageNumber,
|
|
445
|
+
input.layout.pageWidth,
|
|
446
|
+
input.layout.pageHeight,
|
|
447
|
+
...regionParts,
|
|
448
|
+
...divergenceIds,
|
|
449
|
+
].join("|");
|
|
450
|
+
}
|
|
451
|
+
|
|
452
|
+
function detectFrameDivergences(
|
|
453
|
+
frameId: string,
|
|
454
|
+
regions: RuntimePageRegions,
|
|
455
|
+
): RuntimeLayoutDivergence[] {
|
|
456
|
+
const candidates = collectRegions(regions).filter((region) => region.rectTwips !== undefined);
|
|
457
|
+
const divergences: RuntimeLayoutDivergence[] = [];
|
|
458
|
+
for (let i = 0; i < candidates.length; i += 1) {
|
|
459
|
+
for (let j = i + 1; j < candidates.length; j += 1) {
|
|
460
|
+
const a = candidates[i]!;
|
|
461
|
+
const b = candidates[j]!;
|
|
462
|
+
if (!shouldDetectCollision(a, b)) continue;
|
|
463
|
+
if (!rectsOverlap(a.rectTwips!, b.rectTwips!)) continue;
|
|
464
|
+
const kinds = [a.kind, b.kind].sort();
|
|
465
|
+
divergences.push({
|
|
466
|
+
divergenceId: `${frameId}:frame-collision:${kinds.join("-")}`,
|
|
467
|
+
kind: "frame-collision",
|
|
468
|
+
source: "runtime",
|
|
469
|
+
severity: "warning",
|
|
470
|
+
message: `Resolved page regions overlap: ${kinds.join(" / ")}`,
|
|
471
|
+
regionKinds: kinds as RuntimeLayoutDivergence["regionKinds"],
|
|
472
|
+
fragmentIds: [...a.fragmentIds, ...b.fragmentIds],
|
|
473
|
+
});
|
|
474
|
+
}
|
|
475
|
+
}
|
|
476
|
+
return divergences;
|
|
477
|
+
}
|
|
478
|
+
|
|
479
|
+
function collectRegions(regions: RuntimePageRegions): RuntimePageRegion[] {
|
|
480
|
+
return [
|
|
481
|
+
regions.body,
|
|
482
|
+
...(regions.header ? [regions.header] : []),
|
|
483
|
+
...(regions.footer ? [regions.footer] : []),
|
|
484
|
+
...(regions.columns ?? []),
|
|
485
|
+
...(regions.footnotes ?? []),
|
|
486
|
+
];
|
|
487
|
+
}
|
|
488
|
+
|
|
489
|
+
function shouldDetectCollision(a: RuntimePageRegion, b: RuntimePageRegion): boolean {
|
|
490
|
+
if (a.kind === "body" && b.kind === "column") return false;
|
|
491
|
+
if (a.kind === "column" && b.kind === "body") return false;
|
|
492
|
+
if (a.kind === "column" && b.kind === "column") return false;
|
|
493
|
+
return true;
|
|
494
|
+
}
|
|
495
|
+
|
|
496
|
+
function rectsOverlap(a: RuntimeTwipsRect, b: RuntimeTwipsRect): boolean {
|
|
497
|
+
return (
|
|
498
|
+
a.xTwips < b.xTwips + b.widthTwips &&
|
|
499
|
+
a.xTwips + a.widthTwips > b.xTwips &&
|
|
500
|
+
a.yTwips < b.yTwips + b.heightTwips &&
|
|
501
|
+
a.yTwips + a.heightTwips > b.yTwips
|
|
502
|
+
);
|
|
503
|
+
}
|
|
504
|
+
|
|
315
505
|
// ---------------------------------------------------------------------------
|
|
316
506
|
// Graph queries
|
|
317
507
|
// ---------------------------------------------------------------------------
|