@beyondwork/docx-react-component 1.0.110 → 1.0.111
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 +3 -0
- package/src/model/layout/page-graph-types.ts +33 -0
- package/src/runtime/geometry/adjacent-geometry-intake.ts +373 -5
- package/src/runtime/geometry/caret-geometry.ts +219 -7
- package/src/runtime/geometry/geometry-index.ts +35 -10
- package/src/runtime/geometry/object-handles.ts +42 -1
- package/src/runtime/layout/index.ts +3 -0
- package/src/runtime/layout/inert-layout-facet.ts +13 -0
- package/src/runtime/layout/layout-engine-instance.ts +2 -0
- package/src/runtime/layout/layout-engine-version.ts +32 -2
- package/src/runtime/layout/layout-facet-types.ts +3 -0
- package/src/runtime/layout/page-graph.ts +81 -7
- package/src/runtime/layout/project-block-fragments.ts +144 -1
- package/src/runtime/layout/public-facet.ts +160 -0
- package/src/runtime/scopes/adjacent-geometry-evidence.ts +456 -0
- package/src/runtime/scopes/compile-scope-bundle.ts +8 -0
- package/src/runtime/scopes/evidence.ts +16 -0
- package/src/runtime/scopes/index.ts +13 -0
- package/src/runtime/scopes/semantic-scope-types.ts +67 -0
- package/src/ui-tailwind/debug/layer11-consumer-readiness.ts +104 -0
- package/src/ui-tailwind/editor-surface/pm-page-break-decorations.ts +50 -5
- package/src/ui-tailwind/editor-surface/tw-prosemirror-surface.tsx +26 -0
- package/src/README.md +0 -85
- package/src/api/README.md +0 -26
- package/src/api/v3/README.md +0 -91
- package/src/component-inventory.md +0 -99
- package/src/core/README.md +0 -10
- package/src/core/commands/README.md +0 -3
- package/src/core/schema/README.md +0 -3
- package/src/core/selection/README.md +0 -3
- package/src/core/state/README.md +0 -3
- package/src/io/README.md +0 -10
- package/src/io/export/README.md +0 -3
- package/src/io/normalize/README.md +0 -3
- package/src/io/ooxml/README.md +0 -3
- package/src/io/opc/README.md +0 -3
- package/src/model/README.md +0 -3
- package/src/preservation/README.md +0 -3
- package/src/review/README.md +0 -16
- package/src/review/store/README.md +0 -3
- package/src/runtime/README.md +0 -3
- package/src/ui/README.md +0 -30
- package/src/ui/comments/README.md +0 -3
- package/src/ui/compatibility/README.md +0 -3
- package/src/ui/editor-surface/README.md +0 -3
- package/src/ui/review/README.md +0 -3
- package/src/ui/status/README.md +0 -3
- package/src/ui/theme/README.md +0 -3
- package/src/ui/toolbar/README.md +0 -3
- package/src/ui-tailwind/debug/README.md +0 -22
- package/src/validation/README.md +0 -3
|
@@ -34,9 +34,14 @@
|
|
|
34
34
|
*/
|
|
35
35
|
|
|
36
36
|
import type { EditorStoryTarget } from "../../api/public-types";
|
|
37
|
+
import type {
|
|
38
|
+
PublicLineRunAnchor,
|
|
39
|
+
PublicTwipsRect,
|
|
40
|
+
} from "../layout/public-facet.ts";
|
|
37
41
|
import type {
|
|
38
42
|
RenderFrame,
|
|
39
43
|
RenderFrameRect,
|
|
44
|
+
RenderLine,
|
|
40
45
|
RenderStoryRegion,
|
|
41
46
|
} from "../render/index.ts";
|
|
42
47
|
import type {
|
|
@@ -58,6 +63,9 @@ export function resolveCaretGeometry(
|
|
|
58
63
|
if (!frame) return null;
|
|
59
64
|
if (!Number.isFinite(offset)) return null;
|
|
60
65
|
|
|
66
|
+
const projectedCaret = resolveProducerCaretGeometry(frame, offset, story);
|
|
67
|
+
if (projectedCaret) return projectedCaret;
|
|
68
|
+
|
|
61
69
|
const anchorRect = frame.anchorIndex.byRuntimeOffset(offset, story);
|
|
62
70
|
if (!anchorRect) return null;
|
|
63
71
|
|
|
@@ -113,12 +121,7 @@ export function resolveSelectionRects(
|
|
|
113
121
|
if (lo === hi) {
|
|
114
122
|
const caret = resolveCaretGeometry(frame, lo, range.story);
|
|
115
123
|
if (!caret) return [];
|
|
116
|
-
|
|
117
|
-
// read. The placeholder-grade metadata on `CaretGeometry` (direction,
|
|
118
|
-
// baseline) applies to the caret as a whole, not to the rect's
|
|
119
|
-
// position, so make the rect precision explicit for downstream
|
|
120
|
-
// compositor consumers.
|
|
121
|
-
return [{ ...caret.rect, precision: "exact" }];
|
|
124
|
+
return [{ ...caret.rect, precision: caret.rect.precision ?? "exact" }];
|
|
122
125
|
}
|
|
123
126
|
|
|
124
127
|
const lineRects = resolveLineSelectionRects(frame, lo, hi, range.story);
|
|
@@ -178,7 +181,10 @@ function resolveLineSelectionRects(
|
|
|
178
181
|
: blockFrom +
|
|
179
182
|
Math.floor((span * (i + 1)) / Math.max(1, lineCount));
|
|
180
183
|
if (lineTo <= from || lineFrom >= to) continue;
|
|
181
|
-
rects.push(
|
|
184
|
+
rects.push(
|
|
185
|
+
resolveProducerSelectionRect(line, lineFrom, lineTo, from, to) ??
|
|
186
|
+
toGeometryRect(line.frame, "within-tolerance"),
|
|
187
|
+
);
|
|
182
188
|
}
|
|
183
189
|
}
|
|
184
190
|
}
|
|
@@ -186,6 +192,212 @@ function resolveLineSelectionRects(
|
|
|
186
192
|
return rects;
|
|
187
193
|
}
|
|
188
194
|
|
|
195
|
+
function resolveProducerCaretGeometry(
|
|
196
|
+
frame: RenderFrame,
|
|
197
|
+
offset: number,
|
|
198
|
+
story?: EditorStoryTarget,
|
|
199
|
+
): CaretGeometry | null {
|
|
200
|
+
const located = findProducerLineForOffset(frame, offset, story);
|
|
201
|
+
if (!located) return null;
|
|
202
|
+
const { line, lineFrom, lineTo } = located;
|
|
203
|
+
const lineRectTwips = producerLineRectTwips(line);
|
|
204
|
+
if (!lineRectTwips) return null;
|
|
205
|
+
const contentRectTwips = producerLineContentRectTwips(line) ?? lineRectTwips;
|
|
206
|
+
const direction = producerLineDirection(line);
|
|
207
|
+
const span = Math.max(1, lineTo - lineFrom);
|
|
208
|
+
const ratio = clamp((offset - lineFrom) / span, 0, 1);
|
|
209
|
+
const inlineRatio = direction === "rtl" ? 1 - ratio : ratio;
|
|
210
|
+
const caretXTwips =
|
|
211
|
+
contentRectTwips.xTwips + contentRectTwips.widthTwips * inlineRatio;
|
|
212
|
+
const caretFrame = projectLineTwipsRect(line, lineRectTwips, {
|
|
213
|
+
xTwips: caretXTwips,
|
|
214
|
+
yTwips: lineRectTwips.yTwips,
|
|
215
|
+
widthTwips: 0,
|
|
216
|
+
heightTwips: lineRectTwips.heightTwips,
|
|
217
|
+
});
|
|
218
|
+
if (!caretFrame) return null;
|
|
219
|
+
const baseline =
|
|
220
|
+
line.line.baselinePageYTwips !== undefined
|
|
221
|
+
? projectPageYToLineOffsetPx(
|
|
222
|
+
line,
|
|
223
|
+
lineRectTwips,
|
|
224
|
+
line.line.baselinePageYTwips,
|
|
225
|
+
)
|
|
226
|
+
: producerRunBaselinePageYTwips(line) !== undefined
|
|
227
|
+
? projectPageYToLineOffsetPx(
|
|
228
|
+
line,
|
|
229
|
+
lineRectTwips,
|
|
230
|
+
producerRunBaselinePageYTwips(line)!,
|
|
231
|
+
)
|
|
232
|
+
: resolveBaselinePx(caretFrame);
|
|
233
|
+
return {
|
|
234
|
+
rect: {
|
|
235
|
+
...toGeometryRect(caretFrame, "within-tolerance"),
|
|
236
|
+
widthPx: 0,
|
|
237
|
+
},
|
|
238
|
+
baseline,
|
|
239
|
+
height: caretFrame.heightPx,
|
|
240
|
+
direction,
|
|
241
|
+
precision: "within-tolerance",
|
|
242
|
+
};
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
function findProducerLineForOffset(
|
|
246
|
+
frame: RenderFrame,
|
|
247
|
+
offset: number,
|
|
248
|
+
story?: EditorStoryTarget,
|
|
249
|
+
):
|
|
250
|
+
| {
|
|
251
|
+
line: RenderLine;
|
|
252
|
+
lineFrom: number;
|
|
253
|
+
lineTo: number;
|
|
254
|
+
}
|
|
255
|
+
| null {
|
|
256
|
+
if (!Array.isArray(frame.pages)) return null;
|
|
257
|
+
for (const page of frame.pages) {
|
|
258
|
+
for (const region of collectRegions(page.regions)) {
|
|
259
|
+
if (!storyMatches(region.storyTarget, story)) continue;
|
|
260
|
+
for (const block of region.blocks) {
|
|
261
|
+
const blockFrom = block.fragment.from;
|
|
262
|
+
const blockTo = block.fragment.to;
|
|
263
|
+
if (offset < blockFrom || offset > blockTo) continue;
|
|
264
|
+
if (block.lines.length === 0) continue;
|
|
265
|
+
const span = Math.max(1, blockTo - blockFrom);
|
|
266
|
+
const lineCount = block.lines.length;
|
|
267
|
+
for (let i = 0; i < lineCount; i += 1) {
|
|
268
|
+
const lineFrom =
|
|
269
|
+
blockFrom + Math.floor((span * i) / Math.max(1, lineCount));
|
|
270
|
+
const lineTo =
|
|
271
|
+
i === lineCount - 1
|
|
272
|
+
? blockTo
|
|
273
|
+
: blockFrom +
|
|
274
|
+
Math.floor((span * (i + 1)) / Math.max(1, lineCount));
|
|
275
|
+
const contains =
|
|
276
|
+
i === lineCount - 1
|
|
277
|
+
? offset >= lineFrom && offset <= lineTo
|
|
278
|
+
: offset >= lineFrom && offset < lineTo;
|
|
279
|
+
if (!contains) continue;
|
|
280
|
+
const line = block.lines[i]!;
|
|
281
|
+
if (!producerLineRectTwips(line)) continue;
|
|
282
|
+
return { line, lineFrom, lineTo };
|
|
283
|
+
}
|
|
284
|
+
}
|
|
285
|
+
}
|
|
286
|
+
}
|
|
287
|
+
return null;
|
|
288
|
+
}
|
|
289
|
+
|
|
290
|
+
function resolveProducerSelectionRect(
|
|
291
|
+
line: RenderLine,
|
|
292
|
+
lineFrom: number,
|
|
293
|
+
lineTo: number,
|
|
294
|
+
from: number,
|
|
295
|
+
to: number,
|
|
296
|
+
): GeometryRect | null {
|
|
297
|
+
const lineRectTwips = producerLineRectTwips(line);
|
|
298
|
+
if (!lineRectTwips) return null;
|
|
299
|
+
const contentRectTwips = producerLineContentRectTwips(line) ?? lineRectTwips;
|
|
300
|
+
const span = Math.max(1, lineTo - lineFrom);
|
|
301
|
+
const startRatio = clamp((Math.max(from, lineFrom) - lineFrom) / span, 0, 1);
|
|
302
|
+
const endRatio = clamp((Math.min(to, lineTo) - lineFrom) / span, 0, 1);
|
|
303
|
+
if (endRatio <= startRatio) return null;
|
|
304
|
+
const direction = producerLineDirection(line);
|
|
305
|
+
const leftRatio = direction === "rtl" ? 1 - endRatio : startRatio;
|
|
306
|
+
const rightRatio = direction === "rtl" ? 1 - startRatio : endRatio;
|
|
307
|
+
const leftTwips =
|
|
308
|
+
contentRectTwips.xTwips + contentRectTwips.widthTwips * leftRatio;
|
|
309
|
+
const rightTwips =
|
|
310
|
+
contentRectTwips.xTwips + contentRectTwips.widthTwips * rightRatio;
|
|
311
|
+
const frameRect = projectLineTwipsRect(line, lineRectTwips, {
|
|
312
|
+
xTwips: leftTwips,
|
|
313
|
+
yTwips: contentRectTwips.yTwips,
|
|
314
|
+
widthTwips: Math.max(0, rightTwips - leftTwips),
|
|
315
|
+
heightTwips: contentRectTwips.heightTwips,
|
|
316
|
+
});
|
|
317
|
+
return frameRect ? toGeometryRect(frameRect, "within-tolerance") : null;
|
|
318
|
+
}
|
|
319
|
+
|
|
320
|
+
function producerLineRectTwips(line: RenderLine): PublicTwipsRect | undefined {
|
|
321
|
+
return line.line.rectTwips ?? line.line.runAnchors?.[0]?.lineRectTwips;
|
|
322
|
+
}
|
|
323
|
+
|
|
324
|
+
function producerLineContentRectTwips(
|
|
325
|
+
line: RenderLine,
|
|
326
|
+
): PublicTwipsRect | undefined {
|
|
327
|
+
const anchors = line.line.runAnchors ?? [];
|
|
328
|
+
if (anchors.length === 0) return undefined;
|
|
329
|
+
return unionRunRectTwips(anchors);
|
|
330
|
+
}
|
|
331
|
+
|
|
332
|
+
function producerLineDirection(line: RenderLine): "ltr" | "rtl" {
|
|
333
|
+
return line.line.direction ?? line.line.runAnchors?.[0]?.direction ?? "ltr";
|
|
334
|
+
}
|
|
335
|
+
|
|
336
|
+
function producerRunBaselinePageYTwips(
|
|
337
|
+
line: RenderLine,
|
|
338
|
+
): number | undefined {
|
|
339
|
+
return line.line.runAnchors?.[0]?.baselinePageYTwips;
|
|
340
|
+
}
|
|
341
|
+
|
|
342
|
+
function unionRunRectTwips(
|
|
343
|
+
anchors: readonly PublicLineRunAnchor[],
|
|
344
|
+
): PublicTwipsRect | undefined {
|
|
345
|
+
let left = Number.POSITIVE_INFINITY;
|
|
346
|
+
let top = Number.POSITIVE_INFINITY;
|
|
347
|
+
let right = Number.NEGATIVE_INFINITY;
|
|
348
|
+
let bottom = Number.NEGATIVE_INFINITY;
|
|
349
|
+
for (const anchor of anchors) {
|
|
350
|
+
const rect = anchor.runRectTwips;
|
|
351
|
+
left = Math.min(left, rect.xTwips);
|
|
352
|
+
top = Math.min(top, rect.yTwips);
|
|
353
|
+
right = Math.max(right, rect.xTwips + rect.widthTwips);
|
|
354
|
+
bottom = Math.max(bottom, rect.yTwips + rect.heightTwips);
|
|
355
|
+
}
|
|
356
|
+
if (!Number.isFinite(left) || !Number.isFinite(top)) return undefined;
|
|
357
|
+
return {
|
|
358
|
+
xTwips: left,
|
|
359
|
+
yTwips: top,
|
|
360
|
+
widthTwips: Math.max(0, right - left),
|
|
361
|
+
heightTwips: Math.max(0, bottom - top),
|
|
362
|
+
};
|
|
363
|
+
}
|
|
364
|
+
|
|
365
|
+
function projectLineTwipsRect(
|
|
366
|
+
line: RenderLine,
|
|
367
|
+
lineRectTwips: PublicTwipsRect,
|
|
368
|
+
rect: PublicTwipsRect,
|
|
369
|
+
): RenderFrameRect | null {
|
|
370
|
+
if (lineRectTwips.widthTwips <= 0 || lineRectTwips.heightTwips <= 0) {
|
|
371
|
+
return null;
|
|
372
|
+
}
|
|
373
|
+
const scaleX = line.frame.widthPx / lineRectTwips.widthTwips;
|
|
374
|
+
const scaleY = line.frame.heightPx / lineRectTwips.heightTwips;
|
|
375
|
+
return {
|
|
376
|
+
leftPx: line.frame.leftPx + (rect.xTwips - lineRectTwips.xTwips) * scaleX,
|
|
377
|
+
topPx: line.frame.topPx + (rect.yTwips - lineRectTwips.yTwips) * scaleY,
|
|
378
|
+
widthPx: rect.widthTwips * scaleX,
|
|
379
|
+
heightPx: rect.heightTwips * scaleY,
|
|
380
|
+
};
|
|
381
|
+
}
|
|
382
|
+
|
|
383
|
+
function projectPageYToLineOffsetPx(
|
|
384
|
+
line: RenderLine,
|
|
385
|
+
lineRectTwips: PublicTwipsRect,
|
|
386
|
+
pageYTwips: number,
|
|
387
|
+
): number {
|
|
388
|
+
if (lineRectTwips.heightTwips <= 0) return resolveBaselinePx(line.frame);
|
|
389
|
+
const scaleY = line.frame.heightPx / lineRectTwips.heightTwips;
|
|
390
|
+
return clamp(
|
|
391
|
+
(pageYTwips - lineRectTwips.yTwips) * scaleY,
|
|
392
|
+
0,
|
|
393
|
+
line.frame.heightPx,
|
|
394
|
+
);
|
|
395
|
+
}
|
|
396
|
+
|
|
397
|
+
function clamp(value: number, min: number, max: number): number {
|
|
398
|
+
return Math.min(Math.max(value, min), max);
|
|
399
|
+
}
|
|
400
|
+
|
|
189
401
|
function collectRegions(
|
|
190
402
|
regions: RenderFrame["pages"][number]["regions"],
|
|
191
403
|
): readonly RenderStoryRegion[] {
|
|
@@ -27,6 +27,7 @@ import {
|
|
|
27
27
|
type CanonicalTableCellLayoutInput,
|
|
28
28
|
type CanonicalTableRowLayoutInput,
|
|
29
29
|
} from "../../model/canonical-layout-inputs.ts";
|
|
30
|
+
import type { PublicTwipsRect } from "../layout/public-facet.ts";
|
|
30
31
|
import type {
|
|
31
32
|
RenderFrame,
|
|
32
33
|
RenderFrameRect,
|
|
@@ -1309,25 +1310,32 @@ function appendPageLocalObjectHandleEntries(input: {
|
|
|
1309
1310
|
story.kind === "header" ? page.regions.header?.frame : page.regions.footer?.frame;
|
|
1310
1311
|
if (!regionFrame) continue;
|
|
1311
1312
|
for (const object of story.anchoredObjects) {
|
|
1312
|
-
const objectFrame =
|
|
1313
|
-
|
|
1314
|
-
object.extentTwips,
|
|
1315
|
-
|
|
1313
|
+
const objectFrame = object.anchorRectTwips
|
|
1314
|
+
? pageLocalObjectAnchorFrame(page, object.anchorRectTwips)
|
|
1315
|
+
: pageLocalObjectFrame(regionFrame, object.extentTwips, pxPerTwip);
|
|
1316
|
+
const entryPrecision: GeometryPrecision = object.anchorRectTwips
|
|
1317
|
+
? "within-tolerance"
|
|
1318
|
+
: "heuristic";
|
|
1319
|
+
const entryStatus: GeometryRehydrationStatus = object.anchorRectTwips
|
|
1320
|
+
? "realized"
|
|
1321
|
+
: "requires-rehydration";
|
|
1322
|
+
const handleRects = buildObjectHandleRectsFromRect(
|
|
1323
|
+
objectFrame,
|
|
1324
|
+
entryPrecision,
|
|
1316
1325
|
);
|
|
1317
|
-
const handleRects = buildObjectHandleRectsFromRect(objectFrame, "heuristic");
|
|
1318
1326
|
const sourceIdentity: GeometrySourceIdentity = {
|
|
1319
1327
|
storyKey: story.storyKey,
|
|
1320
1328
|
objectKey: object.objectId,
|
|
1321
1329
|
objectKind: object.sourceType,
|
|
1322
1330
|
editPosture: object.preserveOnly ? "preserve-only" : "editable",
|
|
1323
|
-
joinKind: "block-scoped",
|
|
1331
|
+
joinKind: object.anchorRectTwips ? "direct" : "block-scoped",
|
|
1324
1332
|
};
|
|
1325
1333
|
const existing = entries.get(object.objectId);
|
|
1326
1334
|
if (existing) {
|
|
1327
1335
|
appendUnique(existing.pageIds, page.page.pageId);
|
|
1328
1336
|
existing.rects.push(...handleRects);
|
|
1329
1337
|
appendDivergenceIds(existing, divergenceIdsByObjectId.get(object.objectId));
|
|
1330
|
-
if (existing.precision !== "heuristic") {
|
|
1338
|
+
if (existing.precision !== "heuristic" && entryPrecision === "heuristic") {
|
|
1331
1339
|
existing.precision = "heuristic";
|
|
1332
1340
|
existing.status = "requires-rehydration";
|
|
1333
1341
|
existing.sourceIdentity = sourceIdentity;
|
|
@@ -1338,16 +1346,33 @@ function appendPageLocalObjectHandleEntries(input: {
|
|
|
1338
1346
|
objectId: object.objectId,
|
|
1339
1347
|
pageIds: [page.page.pageId],
|
|
1340
1348
|
rects: [...handleRects],
|
|
1341
|
-
status:
|
|
1342
|
-
precision:
|
|
1349
|
+
status: entryStatus,
|
|
1350
|
+
precision: entryPrecision,
|
|
1343
1351
|
...mutableLayoutDivergenceIds(divergenceIdsByObjectId.get(object.objectId)),
|
|
1344
1352
|
sourceIdentity,
|
|
1345
1353
|
});
|
|
1346
|
-
recordPrecision(precision,
|
|
1354
|
+
recordPrecision(precision, entryPrecision);
|
|
1347
1355
|
}
|
|
1348
1356
|
}
|
|
1349
1357
|
}
|
|
1350
1358
|
|
|
1359
|
+
function pageLocalObjectAnchorFrame(
|
|
1360
|
+
page: RenderPage,
|
|
1361
|
+
rect: PublicTwipsRect,
|
|
1362
|
+
): RenderFrameRect {
|
|
1363
|
+
const pageWidthTwips = page.page.layout.pageWidth;
|
|
1364
|
+
const pageHeightTwips = page.page.layout.pageHeight;
|
|
1365
|
+
const scaleX = pageWidthTwips > 0 ? page.frame.widthPx / pageWidthTwips : 1;
|
|
1366
|
+
const scaleY =
|
|
1367
|
+
pageHeightTwips > 0 ? page.frame.heightPx / pageHeightTwips : scaleX;
|
|
1368
|
+
return {
|
|
1369
|
+
leftPx: page.frame.leftPx + rect.xTwips * scaleX,
|
|
1370
|
+
topPx: page.frame.topPx + rect.yTwips * scaleY,
|
|
1371
|
+
widthPx: rect.widthTwips * scaleX,
|
|
1372
|
+
heightPx: rect.heightTwips * scaleY,
|
|
1373
|
+
};
|
|
1374
|
+
}
|
|
1375
|
+
|
|
1351
1376
|
function pageLocalObjectFrame(
|
|
1352
1377
|
regionFrame: RenderFrameRect,
|
|
1353
1378
|
extentTwips:
|
|
@@ -26,7 +26,12 @@
|
|
|
26
26
|
* index 8 — rotate anchor (20 px above the top-center handle)
|
|
27
27
|
*/
|
|
28
28
|
|
|
29
|
-
import type {
|
|
29
|
+
import type { PublicTwipsRect } from "../layout/public-facet.ts";
|
|
30
|
+
import type {
|
|
31
|
+
RenderFrame,
|
|
32
|
+
RenderFrameRect,
|
|
33
|
+
RenderPage,
|
|
34
|
+
} from "../render/index.ts";
|
|
30
35
|
import type { GeometryPrecision, GeometryRect } from "./geometry-types.ts";
|
|
31
36
|
|
|
32
37
|
const ROTATE_HANDLE_OFFSET_PX = 20;
|
|
@@ -36,6 +41,10 @@ export function resolveObjectHandles(
|
|
|
36
41
|
objectId: string,
|
|
37
42
|
): readonly GeometryRect[] {
|
|
38
43
|
if (!frame) return [];
|
|
44
|
+
const pageLocalBBox = resolvePageLocalObjectFrame(frame, objectId);
|
|
45
|
+
if (pageLocalBBox) {
|
|
46
|
+
return buildObjectHandleRectsFromRect(pageLocalBBox, "within-tolerance");
|
|
47
|
+
}
|
|
39
48
|
const bbox = frame.anchorIndex.byObjectId(objectId);
|
|
40
49
|
if (!bbox) return [];
|
|
41
50
|
return buildObjectHandleRectsFromRect(bbox);
|
|
@@ -78,3 +87,35 @@ export function buildObjectHandleRectsFromRect(
|
|
|
78
87
|
point(midX, topPx - ROTATE_HANDLE_OFFSET_PX), // 8 rotate
|
|
79
88
|
];
|
|
80
89
|
}
|
|
90
|
+
|
|
91
|
+
function resolvePageLocalObjectFrame(
|
|
92
|
+
frame: RenderFrame,
|
|
93
|
+
objectId: string,
|
|
94
|
+
): RenderFrameRect | null {
|
|
95
|
+
if (!Array.isArray(frame.pages)) return null;
|
|
96
|
+
for (const page of frame.pages) {
|
|
97
|
+
for (const story of page.page.frame?.pageLocalStories ?? []) {
|
|
98
|
+
for (const object of story.anchoredObjects) {
|
|
99
|
+
if (object.objectId !== objectId || !object.anchorRectTwips) continue;
|
|
100
|
+
return projectPageTwipsRect(page, object.anchorRectTwips);
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
return null;
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
function projectPageTwipsRect(
|
|
108
|
+
page: RenderPage,
|
|
109
|
+
rect: PublicTwipsRect,
|
|
110
|
+
): RenderFrameRect {
|
|
111
|
+
const pageWidthTwips = page.page.layout.pageWidth;
|
|
112
|
+
const pageHeightTwips = page.page.layout.pageHeight;
|
|
113
|
+
const scaleX = pageWidthTwips > 0 ? page.frame.widthPx / pageWidthTwips : 1;
|
|
114
|
+
const scaleY = pageHeightTwips > 0 ? page.frame.heightPx / pageHeightTwips : scaleX;
|
|
115
|
+
return {
|
|
116
|
+
leftPx: page.frame.leftPx + rect.xTwips * scaleX,
|
|
117
|
+
topPx: page.frame.topPx + rect.yTwips * scaleY,
|
|
118
|
+
widthPx: rect.widthTwips * scaleX,
|
|
119
|
+
heightPx: rect.heightTwips * scaleY,
|
|
120
|
+
};
|
|
121
|
+
}
|
|
@@ -181,11 +181,14 @@ export {
|
|
|
181
181
|
type PublicRegionKind,
|
|
182
182
|
type PublicBlockFragment,
|
|
183
183
|
type PublicLineBox,
|
|
184
|
+
type PublicLineRunAnchor,
|
|
184
185
|
type PublicNoteAllocation,
|
|
185
186
|
type PublicPageAnchor,
|
|
186
187
|
type PublicPageFrame,
|
|
187
188
|
type PublicPageLocalStoryInstance,
|
|
189
|
+
type PublicPagePaginationTelemetry,
|
|
188
190
|
type PublicPageSpan,
|
|
191
|
+
type PublicPaginationTelemetry,
|
|
189
192
|
type PublicSectionNode,
|
|
190
193
|
type PublicResolvedPageStories,
|
|
191
194
|
type PublicResolvedStoryField,
|
|
@@ -10,6 +10,7 @@ import type {
|
|
|
10
10
|
LayoutFacetEvent,
|
|
11
11
|
PublicFieldDirtinessReport,
|
|
12
12
|
PublicMeasurementFidelity,
|
|
13
|
+
PublicPaginationTelemetry,
|
|
13
14
|
WordReviewEditorLayoutFacet,
|
|
14
15
|
} from "./public-facet.ts";
|
|
15
16
|
import { MARGIN_PRESET_CATALOG } from "./margin-preset-catalog.ts";
|
|
@@ -20,6 +21,17 @@ export function createInertLayoutFacet(): WordReviewEditorLayoutFacet {
|
|
|
20
21
|
families: [],
|
|
21
22
|
revision: 0,
|
|
22
23
|
};
|
|
24
|
+
const emptyPaginationTelemetry: PublicPaginationTelemetry = {
|
|
25
|
+
revision: 0,
|
|
26
|
+
pageCount: 0,
|
|
27
|
+
materializedPageCount: 0,
|
|
28
|
+
unpaginatedPageCount: 0,
|
|
29
|
+
bodyFragmentCount: 0,
|
|
30
|
+
bodyBlockReferenceCount: 0,
|
|
31
|
+
averageBodyFragmentsPerMaterializedPage: 0,
|
|
32
|
+
averageBodyBlockReferencesPerMaterializedPage: 0,
|
|
33
|
+
pages: [],
|
|
34
|
+
};
|
|
23
35
|
const fidelity: PublicMeasurementFidelity = "empirical";
|
|
24
36
|
return {
|
|
25
37
|
getPageCount: () => 0,
|
|
@@ -39,6 +51,7 @@ export function createInertLayoutFacet(): WordReviewEditorLayoutFacet {
|
|
|
39
51
|
getStoryBlocksForRegion: () => [],
|
|
40
52
|
getDocumentEndnoteBlocks: () => [],
|
|
41
53
|
getFragmentsForPage: () => [],
|
|
54
|
+
getPaginationTelemetry: () => emptyPaginationTelemetry,
|
|
42
55
|
getPageFormatCatalog: () => PAGE_FORMAT_CATALOG,
|
|
43
56
|
getActivePageFormat: () => null,
|
|
44
57
|
getMarginPresetCatalog: () => MARGIN_PRESET_CATALOG,
|
|
@@ -693,6 +693,7 @@ export function createLayoutEngine(
|
|
|
693
693
|
pages,
|
|
694
694
|
bodyFragmentsByPageIndex,
|
|
695
695
|
pageStack.fragmentMeasurementsByPageIndex,
|
|
696
|
+
mainSurface,
|
|
696
697
|
);
|
|
697
698
|
// P8.1b — merge per-note fragments (regionKind: "footnote-area") into the
|
|
698
699
|
// main fragments map so buildPageGraph sees them alongside body fragments.
|
|
@@ -881,6 +882,7 @@ export function createLayoutEngine(
|
|
|
881
882
|
freshSnapshotsToRebuild,
|
|
882
883
|
freshBodyFragmentsByPageIndex,
|
|
883
884
|
freshResult.fragmentMeasurementsByPageIndex,
|
|
885
|
+
mainSurface,
|
|
884
886
|
);
|
|
885
887
|
// P8.1b — merge per-note fragments into the fresh fragments map.
|
|
886
888
|
const freshFragmentsByPageIndex = new Map(freshBodyFragmentsByPageIndex);
|
|
@@ -1248,8 +1248,34 @@
|
|
|
1248
1248
|
* to v88. Cache envelopes from v87 invalidate because page graph
|
|
1249
1249
|
* materialization state, cache-key semantics, geometry projections, and
|
|
1250
1250
|
* surface preview shapes can change.
|
|
1251
|
+
*
|
|
1252
|
+
* 89 — Ship-side rollup: pe2 commit covering L04 pagination telemetry read
|
|
1253
|
+
* model — `WordReviewEditorLayoutFacet` now exposes
|
|
1254
|
+
* `getPaginationTelemetry()`, with per-page materialization, body-fragment
|
|
1255
|
+
* counts, body block-reference counts, line-box counts, and
|
|
1256
|
+
* materialized-page averages. Pagination output and cached graph payloads
|
|
1257
|
+
* are unchanged; cache envelopes from v88 invalidate because the public
|
|
1258
|
+
* layout facet interface grew a method used by viewport-window consumers.
|
|
1259
|
+
* The pe2 history-block bumped to v83 in isolation; ship-preview's rollup
|
|
1260
|
+
* advances to v89.
|
|
1261
|
+
*
|
|
1262
|
+
* 90 — Ship-side rollup: pe2 commit 2fcc15a10 ("feat(04): publish precision
|
|
1263
|
+
* producer layout facts") landed L04 precision producer facts — runtime
|
|
1264
|
+
* line boxes now carry page-local rects, baseline page coordinates,
|
|
1265
|
+
* paragraph direction, and per-run layout-estimate anchors with first/last
|
|
1266
|
+
* glyph bounds. Page-local anchored-object ledgers can carry
|
|
1267
|
+
* `anchorRectTwips` when canonical anchor metadata gives L04 a source/frame
|
|
1268
|
+
* placement. Pe2 commits a22c2e4ab ("feat(05): consume l04 producer
|
|
1269
|
+
* geometry facts") + 6e71ee2ad ("feat(05): publish frame-pixel adjacent
|
|
1270
|
+
* geometry") additionally brushed `src/runtime/geometry/**`, and pe2
|
|
1271
|
+
* commits 883e46bb7 + 8519112d0 ("fix(11): suppress table-internal page
|
|
1272
|
+
* chrome / anchors") brushed `src/runtime/layout/page-graph.ts` without
|
|
1273
|
+
* separate pe2 bumps. The pe2 history-block bumped to v84 in isolation;
|
|
1274
|
+
* ship-preview's rollup advances to v90. Pagination output is unchanged,
|
|
1275
|
+
* but public graph/read-model semantics grew for L05 exact caret/object
|
|
1276
|
+
* projection consumers and for L11 page-anchor suppression in tables.
|
|
1251
1277
|
*/
|
|
1252
|
-
export const LAYOUT_ENGINE_VERSION =
|
|
1278
|
+
export const LAYOUT_ENGINE_VERSION = 90 as const;
|
|
1253
1279
|
|
|
1254
1280
|
/**
|
|
1255
1281
|
* Serialization schema version for the LayCache envelope and cached payload
|
|
@@ -1290,5 +1316,9 @@ export const LAYOUT_ENGINE_VERSION = 88 as const;
|
|
|
1290
1316
|
* `numberingRows[]` for all numbered paragraphs represented by the
|
|
1291
1317
|
* fragment, including table/SDT-nested paragraphs. Pagination output is
|
|
1292
1318
|
* unchanged, but cached graph payload shape grows.
|
|
1319
|
+
* 7 — L04 precision producer graph payload: line boxes carry page-local
|
|
1320
|
+
* rect/baseline/direction/run-anchor facts, and page-local anchored
|
|
1321
|
+
* object ledgers can carry source-anchor `anchorRectTwips`. Pagination
|
|
1322
|
+
* output is unchanged, but cached graph payload shape grows.
|
|
1293
1323
|
*/
|
|
1294
|
-
export const LAYCACHE_SCHEMA_VERSION =
|
|
1324
|
+
export const LAYCACHE_SCHEMA_VERSION = 7 as const;
|
|
@@ -42,11 +42,14 @@ export type {
|
|
|
42
42
|
PublicRegionKind,
|
|
43
43
|
PublicBlockFragment,
|
|
44
44
|
PublicLineBox,
|
|
45
|
+
PublicLineRunAnchor,
|
|
45
46
|
PublicNoteAllocation,
|
|
46
47
|
PublicPageAnchor,
|
|
47
48
|
PublicPageFrame,
|
|
48
49
|
PublicPageLocalStoryInstance,
|
|
50
|
+
PublicPagePaginationTelemetry,
|
|
49
51
|
PublicPageSpan,
|
|
52
|
+
PublicPaginationTelemetry,
|
|
50
53
|
PublicSectionNode,
|
|
51
54
|
PublicResolvedPageStories,
|
|
52
55
|
PublicResolvedStoryField,
|
|
@@ -77,6 +77,7 @@ export type {
|
|
|
77
77
|
RuntimeFragmentLayoutObject,
|
|
78
78
|
RuntimeBlockFragment,
|
|
79
79
|
RuntimeLineBox,
|
|
80
|
+
RuntimeLineRunAnchor,
|
|
80
81
|
RuntimeNoteAllocation,
|
|
81
82
|
RuntimePageAnchor,
|
|
82
83
|
} from "../../model/layout/page-graph-types.ts";
|
|
@@ -566,6 +567,7 @@ function buildPageLocalStoryInstance(
|
|
|
566
567
|
kind: target.kind,
|
|
567
568
|
variant: target.variant,
|
|
568
569
|
relationshipId: target.relationshipId,
|
|
570
|
+
region,
|
|
569
571
|
})
|
|
570
572
|
: { objects: [], divergences: [] };
|
|
571
573
|
const signature = buildPageLocalStorySignature({
|
|
@@ -621,6 +623,10 @@ function buildPageLocalStorySignature(input: {
|
|
|
621
623
|
object.display,
|
|
622
624
|
object.extentTwips?.widthTwips ?? "",
|
|
623
625
|
object.extentTwips?.heightTwips ?? "",
|
|
626
|
+
object.anchorRectTwips?.xTwips ?? "",
|
|
627
|
+
object.anchorRectTwips?.yTwips ?? "",
|
|
628
|
+
object.anchorRectTwips?.widthTwips ?? "",
|
|
629
|
+
object.anchorRectTwips?.heightTwips ?? "",
|
|
624
630
|
object.relationshipIds?.join(",") ?? "",
|
|
625
631
|
object.mediaIds?.join(",") ?? "",
|
|
626
632
|
object.wrapMode ?? "",
|
|
@@ -732,6 +738,7 @@ interface StoryObjectContext {
|
|
|
732
738
|
kind: "header" | "footer";
|
|
733
739
|
variant: RuntimePageLocalStoryInstance["variant"];
|
|
734
740
|
relationshipId: string;
|
|
741
|
+
region?: RuntimePageRegion;
|
|
735
742
|
}
|
|
736
743
|
|
|
737
744
|
function collectStoryAnchoredObjects(
|
|
@@ -836,13 +843,20 @@ function collectStoryAnchoredObjects(
|
|
|
836
843
|
const preserveHint = getDrawingFramePreserveHint(inline);
|
|
837
844
|
const relationshipIds = collectDrawingRelationshipIds(inline);
|
|
838
845
|
const display = inline.anchor.display;
|
|
846
|
+
const extentTwips = extentTwipsFromEmu(
|
|
847
|
+
inline.anchor.extent.widthEmu,
|
|
848
|
+
inline.anchor.extent.heightEmu,
|
|
849
|
+
);
|
|
839
850
|
pushObject({
|
|
840
851
|
objectId: getDrawingFrameObjectId(inline, context.storyKey, ordinal),
|
|
841
852
|
sourceType: "drawing-frame",
|
|
842
853
|
display,
|
|
843
|
-
extentTwips
|
|
844
|
-
|
|
845
|
-
|
|
854
|
+
extentTwips,
|
|
855
|
+
anchorRectTwips: resolveObjectAnchorRectTwips(
|
|
856
|
+
context.region,
|
|
857
|
+
extentTwips,
|
|
858
|
+
inline.anchor.positionH,
|
|
859
|
+
inline.anchor.positionV,
|
|
846
860
|
),
|
|
847
861
|
...(relationshipIds.length > 0 ? { relationshipIds } : {}),
|
|
848
862
|
...(inline.content.type === "picture" && inline.content.mediaId
|
|
@@ -863,15 +877,22 @@ function collectStoryAnchoredObjects(
|
|
|
863
877
|
case "wordart":
|
|
864
878
|
case "vml_shape": {
|
|
865
879
|
const preserveHint = inline.preserveOnlyObject;
|
|
880
|
+
const extentTwips = preserveHint?.extentEmu
|
|
881
|
+
? extentTwipsFromEmu(
|
|
882
|
+
preserveHint.extentEmu.widthEmu,
|
|
883
|
+
preserveHint.extentEmu.heightEmu,
|
|
884
|
+
)
|
|
885
|
+
: undefined;
|
|
866
886
|
pushObject({
|
|
867
887
|
objectId: getPreserveOnlyObjectId(inline, context.storyKey, ordinal),
|
|
868
888
|
sourceType: sourceTypeForInlineObject(inline.type),
|
|
869
889
|
display: preserveHint?.display ?? "unknown",
|
|
870
|
-
...(
|
|
890
|
+
...(extentTwips
|
|
871
891
|
? {
|
|
872
|
-
extentTwips
|
|
873
|
-
|
|
874
|
-
|
|
892
|
+
extentTwips,
|
|
893
|
+
anchorRectTwips: resolveObjectAnchorRectTwips(
|
|
894
|
+
context.region,
|
|
895
|
+
extentTwips,
|
|
875
896
|
),
|
|
876
897
|
}
|
|
877
898
|
: {}),
|
|
@@ -986,6 +1007,59 @@ function extentTwipsFromEmu(
|
|
|
986
1007
|
};
|
|
987
1008
|
}
|
|
988
1009
|
|
|
1010
|
+
function resolveObjectAnchorRectTwips(
|
|
1011
|
+
region: RuntimePageRegion | undefined,
|
|
1012
|
+
extentTwips: RuntimeStoryAnchoredObject["extentTwips"],
|
|
1013
|
+
positionH?: { relativeFrom: string; align?: string; offset?: number },
|
|
1014
|
+
positionV?: { relativeFrom: string; align?: string; offset?: number },
|
|
1015
|
+
): RuntimeTwipsRect | undefined {
|
|
1016
|
+
if (!region?.rectTwips || !extentTwips) return undefined;
|
|
1017
|
+
const regionRect = region.rectTwips;
|
|
1018
|
+
const widthTwips = Math.max(0, extentTwips.widthTwips);
|
|
1019
|
+
const heightTwips = Math.max(0, extentTwips.heightTwips);
|
|
1020
|
+
return rect(
|
|
1021
|
+
resolveAxisTwips(
|
|
1022
|
+
regionRect.xTwips,
|
|
1023
|
+
regionRect.widthTwips,
|
|
1024
|
+
widthTwips,
|
|
1025
|
+
positionH?.align,
|
|
1026
|
+
positionH?.offset,
|
|
1027
|
+
),
|
|
1028
|
+
resolveAxisTwips(
|
|
1029
|
+
regionRect.yTwips,
|
|
1030
|
+
regionRect.heightTwips,
|
|
1031
|
+
heightTwips,
|
|
1032
|
+
positionV?.align,
|
|
1033
|
+
positionV?.offset,
|
|
1034
|
+
),
|
|
1035
|
+
widthTwips,
|
|
1036
|
+
heightTwips,
|
|
1037
|
+
);
|
|
1038
|
+
}
|
|
1039
|
+
|
|
1040
|
+
function resolveAxisTwips(
|
|
1041
|
+
originTwips: number,
|
|
1042
|
+
spanTwips: number,
|
|
1043
|
+
objectSpanTwips: number,
|
|
1044
|
+
align?: string,
|
|
1045
|
+
offsetEmu?: number,
|
|
1046
|
+
): number {
|
|
1047
|
+
if (offsetEmu !== undefined) {
|
|
1048
|
+
return originTwips + Math.round(offsetEmu / EMUS_PER_TWIP);
|
|
1049
|
+
}
|
|
1050
|
+
switch (align) {
|
|
1051
|
+
case "center":
|
|
1052
|
+
return originTwips + Math.round((spanTwips - objectSpanTwips) / 2);
|
|
1053
|
+
case "right":
|
|
1054
|
+
case "bottom":
|
|
1055
|
+
return originTwips + Math.max(0, spanTwips - objectSpanTwips);
|
|
1056
|
+
case "left":
|
|
1057
|
+
case "top":
|
|
1058
|
+
default:
|
|
1059
|
+
return originTwips;
|
|
1060
|
+
}
|
|
1061
|
+
}
|
|
1062
|
+
|
|
989
1063
|
function resolvePageInstanceFieldDisplayText(
|
|
990
1064
|
family: string,
|
|
991
1065
|
cachedDisplayText: string,
|