@beyondwork/docx-react-component 1.0.104 → 1.0.106
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/api/v3/_create.ts +9 -2
- package/src/api/v3/ai/_audit-reference.ts +28 -0
- package/src/api/v3/ai/_pe2-evidence.ts +419 -0
- package/src/api/v3/ai/attach.ts +22 -2
- package/src/api/v3/ai/bundle.ts +18 -6
- package/src/api/v3/ai/inspect.ts +12 -2
- package/src/api/v3/ai/replacement.ts +124 -0
- package/src/api/v3/index.ts +7 -0
- package/src/api/v3/ui/_types.ts +139 -0
- package/src/api/v3/ui/index.ts +9 -0
- package/src/api/v3/ui/overlays.ts +104 -0
- package/src/api/v3/ui/viewport.ts +97 -0
- package/src/model/layout/index.ts +3 -0
- package/src/model/layout/page-graph-types.ts +118 -0
- package/src/model/layout/runtime-page-graph-types.ts +13 -0
- package/src/runtime/document-runtime.ts +39 -18
- package/src/runtime/event-refresh-hints.ts +33 -6
- package/src/runtime/geometry/geometry-facet.ts +9 -1
- package/src/runtime/geometry/geometry-index.ts +461 -10
- package/src/runtime/geometry/geometry-types.ts +6 -0
- package/src/runtime/geometry/object-handles.ts +7 -4
- package/src/runtime/layout/layout-engine-instance.ts +2 -0
- package/src/runtime/layout/layout-engine-version.ts +36 -1
- package/src/runtime/layout/page-graph.ts +697 -10
- package/src/runtime/layout/paginated-layout-engine.ts +10 -0
- package/src/runtime/layout/project-block-fragments.ts +187 -8
- package/src/runtime/layout/public-facet.ts +236 -0
- package/src/runtime/prerender/graph-canonicalize.ts +14 -0
- package/src/runtime/workflow/index.ts +1 -0
- package/src/runtime/workflow/overlay-lanes.ts +228 -0
- package/src/ui/presence-overlay-lane.ts +131 -0
- package/src/ui/ui-controller-factory.ts +21 -0
|
@@ -22,6 +22,7 @@ import {
|
|
|
22
22
|
MAIN_STORY_KEY,
|
|
23
23
|
type CanonicalAnchorLayoutInput,
|
|
24
24
|
type CanonicalLayoutInputs,
|
|
25
|
+
type CanonicalScopeLayoutInput,
|
|
25
26
|
type CanonicalTableLayoutInput,
|
|
26
27
|
type CanonicalTableCellLayoutInput,
|
|
27
28
|
type CanonicalTableRowLayoutInput,
|
|
@@ -43,12 +44,15 @@ import type {
|
|
|
43
44
|
GeometryIndexRegion,
|
|
44
45
|
GeometryIndexSlice,
|
|
45
46
|
GeometryObjectHandleEntry,
|
|
47
|
+
GeometryPrecision,
|
|
46
48
|
GeometryPrecisionCounts,
|
|
47
49
|
GeometryRect,
|
|
50
|
+
GeometryRehydrationStatus,
|
|
48
51
|
GeometryReplacementEnvelopeEntry,
|
|
49
52
|
GeometrySourceIdentity,
|
|
50
53
|
SemanticDisplayEntry,
|
|
51
54
|
} from "./geometry-types.ts";
|
|
55
|
+
import { buildObjectHandleRectsFromRect } from "./object-handles.ts";
|
|
52
56
|
|
|
53
57
|
export interface GeometryIndexProjectionOptions {
|
|
54
58
|
readonly canonicalDocument?: CanonicalDocument | null;
|
|
@@ -86,7 +90,8 @@ export function projectGeometryIndexFromFrame(
|
|
|
86
90
|
const hitTargets: GeometryHitTarget[] = [];
|
|
87
91
|
const semanticEntries: SemanticDisplayEntry[] = [];
|
|
88
92
|
const replacementEnvelopes: GeometryReplacementEnvelopeEntry[] = [];
|
|
89
|
-
const
|
|
93
|
+
const objectHandleEntries = new Map<string, MutableObjectHandleEntry>();
|
|
94
|
+
const projectedBlocksByStory = new Map<string, ProjectedScopeBlock[]>();
|
|
90
95
|
const precision = createPrecisionCounts();
|
|
91
96
|
|
|
92
97
|
for (const page of frame.pages) {
|
|
@@ -95,7 +100,9 @@ export function projectGeometryIndexFromFrame(
|
|
|
95
100
|
|
|
96
101
|
for (const [region, ordinal] of regionEntries) {
|
|
97
102
|
const regionId = makeRegionId(page, region, ordinal);
|
|
98
|
-
const storyKey =
|
|
103
|
+
const storyKey =
|
|
104
|
+
identities?.storyKeyForTarget(region.storyTarget) ??
|
|
105
|
+
canonicalStoryKeyFromTarget(region.storyTarget);
|
|
99
106
|
regionIds.push(regionId);
|
|
100
107
|
const sliceIds: string[] = [];
|
|
101
108
|
|
|
@@ -200,6 +207,25 @@ export function projectGeometryIndexFromFrame(
|
|
|
200
207
|
...(sliceIdentity ? { sourceIdentity: sliceIdentity } : {}),
|
|
201
208
|
});
|
|
202
209
|
recordPrecision(precision, "exact");
|
|
210
|
+
recordProjectedScopeBlock(projectedBlocksByStory, {
|
|
211
|
+
storyKey,
|
|
212
|
+
blockId: block.fragment.blockId,
|
|
213
|
+
blockPath: identities?.blockPathForBlockId(
|
|
214
|
+
storyKey,
|
|
215
|
+
block.fragment.blockId,
|
|
216
|
+
),
|
|
217
|
+
pageId: page.page.pageId,
|
|
218
|
+
rect: toGeometryRect(block.frame),
|
|
219
|
+
});
|
|
220
|
+
appendCanonicalObjectHandleEntries({
|
|
221
|
+
frame,
|
|
222
|
+
page,
|
|
223
|
+
block,
|
|
224
|
+
identities,
|
|
225
|
+
storyKey,
|
|
226
|
+
entries: objectHandleEntries,
|
|
227
|
+
precision,
|
|
228
|
+
});
|
|
203
229
|
appendBlockSemanticEntries({
|
|
204
230
|
page,
|
|
205
231
|
region,
|
|
@@ -209,6 +235,7 @@ export function projectGeometryIndexFromFrame(
|
|
|
209
235
|
identities,
|
|
210
236
|
storyKey,
|
|
211
237
|
entries: semanticEntries,
|
|
238
|
+
projectedBlocksByStory,
|
|
212
239
|
precision,
|
|
213
240
|
});
|
|
214
241
|
}
|
|
@@ -232,8 +259,21 @@ export function projectGeometryIndexFromFrame(
|
|
|
232
259
|
regionIds,
|
|
233
260
|
});
|
|
234
261
|
recordPrecision(precision, "exact");
|
|
262
|
+
appendPageLocalObjectHandleEntries({
|
|
263
|
+
page,
|
|
264
|
+
entries: objectHandleEntries,
|
|
265
|
+
precision,
|
|
266
|
+
});
|
|
235
267
|
}
|
|
236
268
|
|
|
269
|
+
appendScopeReplacementEnvelopeEntries({
|
|
270
|
+
identities,
|
|
271
|
+
projectedBlocksByStory,
|
|
272
|
+
entries: replacementEnvelopes,
|
|
273
|
+
precision,
|
|
274
|
+
});
|
|
275
|
+
|
|
276
|
+
const objectHandles = finalizeObjectHandleEntries(objectHandleEntries);
|
|
237
277
|
const coverage: GeometryIndexCoverage = {
|
|
238
278
|
status: "realized",
|
|
239
279
|
pageCount: pages.length,
|
|
@@ -336,6 +376,7 @@ function appendBlockSemanticEntries(input: {
|
|
|
336
376
|
identities: GeometryIdentityLookup | null;
|
|
337
377
|
storyKey: string;
|
|
338
378
|
entries: SemanticDisplayEntry[];
|
|
379
|
+
projectedBlocksByStory: Map<string, ProjectedScopeBlock[]>;
|
|
339
380
|
precision: GeometryPrecisionCounts;
|
|
340
381
|
}): void {
|
|
341
382
|
const {
|
|
@@ -347,6 +388,7 @@ function appendBlockSemanticEntries(input: {
|
|
|
347
388
|
identities,
|
|
348
389
|
storyKey,
|
|
349
390
|
entries,
|
|
391
|
+
projectedBlocksByStory,
|
|
350
392
|
precision,
|
|
351
393
|
} = input;
|
|
352
394
|
const tableIdentity = identities?.tableIdentity(
|
|
@@ -379,6 +421,16 @@ function appendBlockSemanticEntries(input: {
|
|
|
379
421
|
|
|
380
422
|
const plan = block.tablePlan;
|
|
381
423
|
if (!plan) return;
|
|
424
|
+
if (tableIdentity) {
|
|
425
|
+
recordTableCellScopeBlocks({
|
|
426
|
+
table: tableIdentity,
|
|
427
|
+
block,
|
|
428
|
+
pageId: page.page.pageId,
|
|
429
|
+
storyKey,
|
|
430
|
+
identities,
|
|
431
|
+
projectedBlocksByStory,
|
|
432
|
+
});
|
|
433
|
+
}
|
|
382
434
|
const rowCount = resolveTableRowCount(block);
|
|
383
435
|
const rowHeightPx =
|
|
384
436
|
rowCount > 0 ? block.frame.heightPx / rowCount : block.frame.heightPx;
|
|
@@ -551,9 +603,14 @@ function pageFrameCellRect(
|
|
|
551
603
|
}
|
|
552
604
|
|
|
553
605
|
interface GeometryIdentityLookup {
|
|
606
|
+
storyKeyForTarget(target: EditorStoryTarget): string;
|
|
554
607
|
sliceIdentity(storyKey: string, blockId: string): GeometrySourceIdentity | undefined;
|
|
555
608
|
tableIdentity(storyKey: string, blockId: string): CanonicalTableLayoutInput | undefined;
|
|
556
609
|
anchorIdentity(storyKey: string, blockId: string): GeometrySourceIdentity | undefined;
|
|
610
|
+
objectAnchors(storyKey: string, blockId: string): readonly CanonicalAnchorLayoutInput[];
|
|
611
|
+
blockIdForPath(storyKey: string, blockPath: string): string | undefined;
|
|
612
|
+
blockPathForBlockId(storyKey: string, blockId: string): string | undefined;
|
|
613
|
+
scopeInputs(): readonly CanonicalScopeLayoutInput[];
|
|
557
614
|
}
|
|
558
615
|
|
|
559
616
|
function createIdentityLookup(
|
|
@@ -566,6 +623,7 @@ function createIdentityLookup(
|
|
|
566
623
|
: null);
|
|
567
624
|
if (!layoutInputs) return null;
|
|
568
625
|
|
|
626
|
+
const storyKeys = new Set(layoutInputs.stories.map((story) => story.storyKey));
|
|
569
627
|
const blockIdByPath = options?.canonicalDocument
|
|
570
628
|
? collectProjectedBlockIdsByPath(options.canonicalDocument)
|
|
571
629
|
: new Map<string, string>();
|
|
@@ -603,6 +661,9 @@ function createIdentityLookup(
|
|
|
603
661
|
}
|
|
604
662
|
|
|
605
663
|
return {
|
|
664
|
+
storyKeyForTarget(target) {
|
|
665
|
+
return resolveCanonicalStoryKeyForTarget(target, storyKeys);
|
|
666
|
+
},
|
|
606
667
|
sliceIdentity(storyKey, blockId) {
|
|
607
668
|
const table = tableByStoryBlockId.get(storyBlockKey(storyKey, blockId));
|
|
608
669
|
if (table) return tableSourceIdentity(table);
|
|
@@ -627,16 +688,406 @@ function createIdentityLookup(
|
|
|
627
688
|
};
|
|
628
689
|
}
|
|
629
690
|
const anchor = anchors[0]!;
|
|
630
|
-
return
|
|
631
|
-
|
|
632
|
-
|
|
633
|
-
|
|
634
|
-
|
|
635
|
-
|
|
636
|
-
|
|
691
|
+
return anchorSourceIdentity(anchor, "block-scoped");
|
|
692
|
+
},
|
|
693
|
+
objectAnchors(storyKey, blockId) {
|
|
694
|
+
return anchorsByStoryBlockId.get(storyBlockKey(storyKey, blockId)) ?? [];
|
|
695
|
+
},
|
|
696
|
+
blockIdForPath(storyKey, blockPath) {
|
|
697
|
+
return blockIdByPath.get(storyPathKey(storyKey, blockPath));
|
|
698
|
+
},
|
|
699
|
+
blockPathForBlockId(storyKey, blockId) {
|
|
700
|
+
return blockPathByStoryBlockId.get(storyBlockKey(storyKey, blockId));
|
|
701
|
+
},
|
|
702
|
+
scopeInputs() {
|
|
703
|
+
return layoutInputs.scopes;
|
|
704
|
+
},
|
|
705
|
+
};
|
|
706
|
+
}
|
|
707
|
+
|
|
708
|
+
interface MutableObjectHandleEntry {
|
|
709
|
+
objectId: string;
|
|
710
|
+
pageIds: string[];
|
|
711
|
+
rects: GeometryRect[];
|
|
712
|
+
status: GeometryRehydrationStatus;
|
|
713
|
+
precision: GeometryPrecision;
|
|
714
|
+
sourceIdentity?: GeometrySourceIdentity;
|
|
715
|
+
}
|
|
716
|
+
|
|
717
|
+
function appendCanonicalObjectHandleEntries(input: {
|
|
718
|
+
frame: RenderFrame;
|
|
719
|
+
page: RenderPage;
|
|
720
|
+
block: RenderBlock;
|
|
721
|
+
identities: GeometryIdentityLookup | null;
|
|
722
|
+
storyKey: string;
|
|
723
|
+
entries: Map<string, MutableObjectHandleEntry>;
|
|
724
|
+
precision: GeometryPrecisionCounts;
|
|
725
|
+
}): void {
|
|
726
|
+
const { frame, page, block, identities, storyKey, entries, precision } = input;
|
|
727
|
+
if (!identities) return;
|
|
728
|
+
|
|
729
|
+
for (const anchor of identities.objectAnchors(
|
|
730
|
+
storyKey,
|
|
731
|
+
block.fragment.blockId,
|
|
732
|
+
)) {
|
|
733
|
+
if (anchor.hidden === true) continue;
|
|
734
|
+
const exactObjectRect = frame.anchorIndex.byObjectId(anchor.objectKey);
|
|
735
|
+
const rect = exactObjectRect ?? block.frame;
|
|
736
|
+
const entryPrecision: GeometryPrecision = exactObjectRect
|
|
737
|
+
? "within-tolerance"
|
|
738
|
+
: "heuristic";
|
|
739
|
+
const status: GeometryRehydrationStatus = exactObjectRect
|
|
740
|
+
? "realized"
|
|
741
|
+
: "requires-rehydration";
|
|
742
|
+
const handleRects = buildObjectHandleRectsFromRect(rect, entryPrecision);
|
|
743
|
+
const existing = entries.get(anchor.objectKey);
|
|
744
|
+
if (existing) {
|
|
745
|
+
appendUnique(existing.pageIds, page.page.pageId);
|
|
746
|
+
existing.rects.push(...handleRects);
|
|
747
|
+
if (existing.precision !== "heuristic" && entryPrecision === "heuristic") {
|
|
748
|
+
existing.precision = "heuristic";
|
|
749
|
+
existing.status = "requires-rehydration";
|
|
750
|
+
existing.sourceIdentity = anchorSourceIdentity(anchor, "block-scoped");
|
|
751
|
+
}
|
|
752
|
+
continue;
|
|
753
|
+
}
|
|
754
|
+
|
|
755
|
+
entries.set(anchor.objectKey, {
|
|
756
|
+
objectId: anchor.objectKey,
|
|
757
|
+
pageIds: [page.page.pageId],
|
|
758
|
+
rects: [...handleRects],
|
|
759
|
+
status,
|
|
760
|
+
precision: entryPrecision,
|
|
761
|
+
sourceIdentity: anchorSourceIdentity(
|
|
762
|
+
anchor,
|
|
763
|
+
exactObjectRect ? "direct" : "block-scoped",
|
|
764
|
+
),
|
|
765
|
+
});
|
|
766
|
+
recordPrecision(precision, entryPrecision);
|
|
767
|
+
}
|
|
768
|
+
}
|
|
769
|
+
|
|
770
|
+
function appendPageLocalObjectHandleEntries(input: {
|
|
771
|
+
page: RenderPage;
|
|
772
|
+
entries: Map<string, MutableObjectHandleEntry>;
|
|
773
|
+
precision: GeometryPrecisionCounts;
|
|
774
|
+
}): void {
|
|
775
|
+
const { page, entries, precision } = input;
|
|
776
|
+
const stories = page.page.frame?.pageLocalStories ?? [];
|
|
777
|
+
for (const story of stories) {
|
|
778
|
+
const regionFrame =
|
|
779
|
+
story.kind === "header" ? page.regions.header?.frame : page.regions.footer?.frame;
|
|
780
|
+
if (!regionFrame) continue;
|
|
781
|
+
for (const object of story.anchoredObjects) {
|
|
782
|
+
const objectFrame = pageLocalObjectFrame(
|
|
783
|
+
regionFrame,
|
|
784
|
+
object.extentTwips,
|
|
785
|
+
page.page.layout.pageWidth > 0
|
|
786
|
+
? page.frame.widthPx / page.page.layout.pageWidth
|
|
787
|
+
: 1,
|
|
788
|
+
);
|
|
789
|
+
const handleRects = buildObjectHandleRectsFromRect(objectFrame, "heuristic");
|
|
790
|
+
const sourceIdentity: GeometrySourceIdentity = {
|
|
791
|
+
storyKey: story.storyKey,
|
|
792
|
+
objectKey: object.objectId,
|
|
793
|
+
objectKind: object.sourceType,
|
|
794
|
+
editPosture: object.preserveOnly ? "preserve-only" : "editable",
|
|
637
795
|
joinKind: "block-scoped",
|
|
638
796
|
};
|
|
639
|
-
|
|
797
|
+
const existing = entries.get(object.objectId);
|
|
798
|
+
if (existing) {
|
|
799
|
+
appendUnique(existing.pageIds, page.page.pageId);
|
|
800
|
+
existing.rects.push(...handleRects);
|
|
801
|
+
if (existing.precision !== "heuristic") {
|
|
802
|
+
existing.precision = "heuristic";
|
|
803
|
+
existing.status = "requires-rehydration";
|
|
804
|
+
existing.sourceIdentity = sourceIdentity;
|
|
805
|
+
}
|
|
806
|
+
continue;
|
|
807
|
+
}
|
|
808
|
+
entries.set(object.objectId, {
|
|
809
|
+
objectId: object.objectId,
|
|
810
|
+
pageIds: [page.page.pageId],
|
|
811
|
+
rects: [...handleRects],
|
|
812
|
+
status: "requires-rehydration",
|
|
813
|
+
precision: "heuristic",
|
|
814
|
+
sourceIdentity,
|
|
815
|
+
});
|
|
816
|
+
recordPrecision(precision, "heuristic");
|
|
817
|
+
}
|
|
818
|
+
}
|
|
819
|
+
}
|
|
820
|
+
|
|
821
|
+
function pageLocalObjectFrame(
|
|
822
|
+
regionFrame: RenderFrameRect,
|
|
823
|
+
extentTwips:
|
|
824
|
+
| { readonly widthTwips: number; readonly heightTwips: number }
|
|
825
|
+
| undefined,
|
|
826
|
+
pxPerTwip: number,
|
|
827
|
+
): RenderFrameRect {
|
|
828
|
+
if (!extentTwips) return regionFrame;
|
|
829
|
+
const widthPx = Math.max(0, extentTwips.widthTwips * pxPerTwip);
|
|
830
|
+
const heightPx = Math.max(0, extentTwips.heightTwips * pxPerTwip);
|
|
831
|
+
return {
|
|
832
|
+
leftPx: regionFrame.leftPx,
|
|
833
|
+
topPx: regionFrame.topPx,
|
|
834
|
+
widthPx: widthPx > 0 ? Math.min(widthPx, regionFrame.widthPx) : regionFrame.widthPx,
|
|
835
|
+
heightPx: heightPx > 0 ? Math.min(heightPx, regionFrame.heightPx) : regionFrame.heightPx,
|
|
836
|
+
};
|
|
837
|
+
}
|
|
838
|
+
|
|
839
|
+
function finalizeObjectHandleEntries(
|
|
840
|
+
entries: ReadonlyMap<string, MutableObjectHandleEntry>,
|
|
841
|
+
): GeometryObjectHandleEntry[] {
|
|
842
|
+
return Array.from(entries.values()).map((entry) => ({
|
|
843
|
+
objectId: entry.objectId,
|
|
844
|
+
pageIds: entry.pageIds,
|
|
845
|
+
rects: entry.rects,
|
|
846
|
+
status: entry.status,
|
|
847
|
+
precision: entry.precision,
|
|
848
|
+
...(entry.sourceIdentity ? { sourceIdentity: entry.sourceIdentity } : {}),
|
|
849
|
+
}));
|
|
850
|
+
}
|
|
851
|
+
|
|
852
|
+
function appendUnique(target: string[], value: string): void {
|
|
853
|
+
if (!target.includes(value)) target.push(value);
|
|
854
|
+
}
|
|
855
|
+
|
|
856
|
+
function resolveCanonicalStoryKeyForTarget(
|
|
857
|
+
target: EditorStoryTarget,
|
|
858
|
+
storyKeys: ReadonlySet<string>,
|
|
859
|
+
): string {
|
|
860
|
+
const exact = canonicalStoryKeyFromTarget(target);
|
|
861
|
+
if (storyKeys.has(exact)) return exact;
|
|
862
|
+
if (
|
|
863
|
+
(target.kind === "header" || target.kind === "footer") &&
|
|
864
|
+
target.sectionIndex !== undefined
|
|
865
|
+
) {
|
|
866
|
+
const unscoped = createHeaderFooterStoryKey({
|
|
867
|
+
kind: target.kind,
|
|
868
|
+
relationshipId: target.relationshipId,
|
|
869
|
+
variant: target.variant,
|
|
870
|
+
});
|
|
871
|
+
if (storyKeys.has(unscoped)) return unscoped;
|
|
872
|
+
}
|
|
873
|
+
return exact;
|
|
874
|
+
}
|
|
875
|
+
|
|
876
|
+
interface ProjectedScopeBlock {
|
|
877
|
+
readonly storyKey: string;
|
|
878
|
+
readonly blockId: string;
|
|
879
|
+
readonly blockPath?: string;
|
|
880
|
+
readonly pageId: string;
|
|
881
|
+
readonly rect: GeometryRect;
|
|
882
|
+
}
|
|
883
|
+
|
|
884
|
+
function recordProjectedScopeBlock(
|
|
885
|
+
blocksByStory: Map<string, ProjectedScopeBlock[]>,
|
|
886
|
+
block: ProjectedScopeBlock,
|
|
887
|
+
): void {
|
|
888
|
+
const list = blocksByStory.get(block.storyKey);
|
|
889
|
+
if (list) {
|
|
890
|
+
list.push(block);
|
|
891
|
+
} else {
|
|
892
|
+
blocksByStory.set(block.storyKey, [block]);
|
|
893
|
+
}
|
|
894
|
+
}
|
|
895
|
+
|
|
896
|
+
function recordTableCellScopeBlocks(input: {
|
|
897
|
+
table: CanonicalTableLayoutInput;
|
|
898
|
+
block: RenderBlock;
|
|
899
|
+
pageId: string;
|
|
900
|
+
storyKey: string;
|
|
901
|
+
identities: GeometryIdentityLookup | null;
|
|
902
|
+
projectedBlocksByStory: Map<string, ProjectedScopeBlock[]>;
|
|
903
|
+
}): void {
|
|
904
|
+
const {
|
|
905
|
+
table,
|
|
906
|
+
block,
|
|
907
|
+
pageId,
|
|
908
|
+
storyKey,
|
|
909
|
+
identities,
|
|
910
|
+
projectedBlocksByStory,
|
|
911
|
+
} = input;
|
|
912
|
+
if (!identities) return;
|
|
913
|
+
|
|
914
|
+
for (const row of table.rows) {
|
|
915
|
+
for (const cell of row.cells) {
|
|
916
|
+
const rect = pageFrameCellRect(block, row.rowIndex, cell.gridColumnStart);
|
|
917
|
+
if (!rect) continue;
|
|
918
|
+
for (let blockIndex = 0; blockIndex < cell.blockCount; blockIndex += 1) {
|
|
919
|
+
const blockPath =
|
|
920
|
+
`${table.blockPath}/row[${row.rowIndex}]/cell[${cell.cellIndex}]` +
|
|
921
|
+
`/block[${blockIndex}]`;
|
|
922
|
+
const blockId = identities.blockIdForPath(storyKey, blockPath);
|
|
923
|
+
if (!blockId) continue;
|
|
924
|
+
recordProjectedScopeBlock(projectedBlocksByStory, {
|
|
925
|
+
storyKey,
|
|
926
|
+
blockId,
|
|
927
|
+
blockPath,
|
|
928
|
+
pageId,
|
|
929
|
+
rect: {
|
|
930
|
+
...toGeometryRect(rect),
|
|
931
|
+
precision: "within-tolerance",
|
|
932
|
+
},
|
|
933
|
+
});
|
|
934
|
+
}
|
|
935
|
+
}
|
|
936
|
+
}
|
|
937
|
+
}
|
|
938
|
+
|
|
939
|
+
function appendScopeReplacementEnvelopeEntries(input: {
|
|
940
|
+
identities: GeometryIdentityLookup | null;
|
|
941
|
+
projectedBlocksByStory: ReadonlyMap<string, readonly ProjectedScopeBlock[]>;
|
|
942
|
+
entries: GeometryReplacementEnvelopeEntry[];
|
|
943
|
+
precision: GeometryPrecisionCounts;
|
|
944
|
+
}): void {
|
|
945
|
+
const { identities, projectedBlocksByStory, entries, precision } = input;
|
|
946
|
+
if (!identities) return;
|
|
947
|
+
|
|
948
|
+
for (const scope of identities.scopeInputs()) {
|
|
949
|
+
const entry = projectScopeReplacementEnvelopeEntry(
|
|
950
|
+
scope,
|
|
951
|
+
identities,
|
|
952
|
+
projectedBlocksByStory.get(scope.storyKey) ?? [],
|
|
953
|
+
);
|
|
954
|
+
entries.push(entry);
|
|
955
|
+
recordPrecision(precision, entry.precision);
|
|
956
|
+
}
|
|
957
|
+
}
|
|
958
|
+
|
|
959
|
+
function projectScopeReplacementEnvelopeEntry(
|
|
960
|
+
scope: CanonicalScopeLayoutInput,
|
|
961
|
+
identities: GeometryIdentityLookup,
|
|
962
|
+
storyBlocks: readonly ProjectedScopeBlock[],
|
|
963
|
+
): GeometryReplacementEnvelopeEntry {
|
|
964
|
+
const startBlockId = scope.start
|
|
965
|
+
? identities.blockIdForPath(scope.storyKey, scope.start.blockPath)
|
|
966
|
+
: undefined;
|
|
967
|
+
const endBlockId = scope.end
|
|
968
|
+
? identities.blockIdForPath(scope.storyKey, scope.end.blockPath)
|
|
969
|
+
: undefined;
|
|
970
|
+
|
|
971
|
+
let blocks: readonly ProjectedScopeBlock[] = [];
|
|
972
|
+
let status: GeometryReplacementEnvelopeEntry["status"] = "unavailable";
|
|
973
|
+
let precision: GeometryReplacementEnvelopeEntry["precision"] = "heuristic";
|
|
974
|
+
|
|
975
|
+
if (scope.status === "paired" && startBlockId && endBlockId) {
|
|
976
|
+
blocks = blocksInCanonicalPathRange(scope, storyBlocks);
|
|
977
|
+
if (blocks.length === 0) {
|
|
978
|
+
const startIndex = firstBlockIndex(storyBlocks, startBlockId);
|
|
979
|
+
const endIndex = lastBlockIndex(storyBlocks, endBlockId);
|
|
980
|
+
if (startIndex >= 0 && endIndex >= 0) {
|
|
981
|
+
const from = Math.min(startIndex, endIndex);
|
|
982
|
+
const to = Math.max(startIndex, endIndex);
|
|
983
|
+
blocks = storyBlocks.slice(from, to + 1);
|
|
984
|
+
}
|
|
985
|
+
}
|
|
986
|
+
if (blocks.length > 0) {
|
|
987
|
+
status = "realized";
|
|
988
|
+
precision = "within-tolerance";
|
|
989
|
+
}
|
|
990
|
+
}
|
|
991
|
+
|
|
992
|
+
if (blocks.length === 0) {
|
|
993
|
+
const fallbackBlockId = startBlockId ?? endBlockId;
|
|
994
|
+
if (fallbackBlockId) {
|
|
995
|
+
blocks = storyBlocks.filter((block) => block.blockId === fallbackBlockId);
|
|
996
|
+
if (blocks.length > 0) status = "requires-rehydration";
|
|
997
|
+
}
|
|
998
|
+
}
|
|
999
|
+
|
|
1000
|
+
return {
|
|
1001
|
+
scopeId: scope.scopeId,
|
|
1002
|
+
pageIds: uniquePageIds(blocks),
|
|
1003
|
+
rects: blocks.map((block) => ({
|
|
1004
|
+
...block.rect,
|
|
1005
|
+
precision,
|
|
1006
|
+
})),
|
|
1007
|
+
status,
|
|
1008
|
+
precision,
|
|
1009
|
+
sourceIdentity: scopeSourceIdentity(scope),
|
|
1010
|
+
};
|
|
1011
|
+
}
|
|
1012
|
+
|
|
1013
|
+
function blocksInCanonicalPathRange(
|
|
1014
|
+
scope: CanonicalScopeLayoutInput,
|
|
1015
|
+
storyBlocks: readonly ProjectedScopeBlock[],
|
|
1016
|
+
): readonly ProjectedScopeBlock[] {
|
|
1017
|
+
if (!scope.start || !scope.end) return [];
|
|
1018
|
+
const orderByPath = new Map<string, number>();
|
|
1019
|
+
for (const block of storyBlocks) {
|
|
1020
|
+
if (!block.blockPath || orderByPath.has(block.blockPath)) continue;
|
|
1021
|
+
orderByPath.set(block.blockPath, orderByPath.size);
|
|
1022
|
+
}
|
|
1023
|
+
const startOrder = orderByPath.get(scope.start.blockPath);
|
|
1024
|
+
const endOrder = orderByPath.get(scope.end.blockPath);
|
|
1025
|
+
if (startOrder === undefined || endOrder === undefined) return [];
|
|
1026
|
+
|
|
1027
|
+
const from = Math.min(startOrder, endOrder);
|
|
1028
|
+
const to = Math.max(startOrder, endOrder);
|
|
1029
|
+
return storyBlocks.filter((block) => {
|
|
1030
|
+
if (!block.blockPath) return false;
|
|
1031
|
+
const order = orderByPath.get(block.blockPath);
|
|
1032
|
+
return order !== undefined && order >= from && order <= to;
|
|
1033
|
+
});
|
|
1034
|
+
}
|
|
1035
|
+
|
|
1036
|
+
function firstBlockIndex(
|
|
1037
|
+
blocks: readonly ProjectedScopeBlock[],
|
|
1038
|
+
blockId: string,
|
|
1039
|
+
): number {
|
|
1040
|
+
return blocks.findIndex((block) => block.blockId === blockId);
|
|
1041
|
+
}
|
|
1042
|
+
|
|
1043
|
+
function lastBlockIndex(
|
|
1044
|
+
blocks: readonly ProjectedScopeBlock[],
|
|
1045
|
+
blockId: string,
|
|
1046
|
+
): number {
|
|
1047
|
+
for (let index = blocks.length - 1; index >= 0; index -= 1) {
|
|
1048
|
+
if (blocks[index]?.blockId === blockId) return index;
|
|
1049
|
+
}
|
|
1050
|
+
return -1;
|
|
1051
|
+
}
|
|
1052
|
+
|
|
1053
|
+
function uniquePageIds(
|
|
1054
|
+
blocks: readonly ProjectedScopeBlock[],
|
|
1055
|
+
): readonly string[] {
|
|
1056
|
+
const ids: string[] = [];
|
|
1057
|
+
const seen = new Set<string>();
|
|
1058
|
+
for (const block of blocks) {
|
|
1059
|
+
if (seen.has(block.pageId)) continue;
|
|
1060
|
+
seen.add(block.pageId);
|
|
1061
|
+
ids.push(block.pageId);
|
|
1062
|
+
}
|
|
1063
|
+
return ids;
|
|
1064
|
+
}
|
|
1065
|
+
|
|
1066
|
+
function scopeSourceIdentity(
|
|
1067
|
+
scope: CanonicalScopeLayoutInput,
|
|
1068
|
+
): GeometrySourceIdentity {
|
|
1069
|
+
const marker = scope.start ?? scope.end;
|
|
1070
|
+
return {
|
|
1071
|
+
storyKey: scope.storyKey,
|
|
1072
|
+
...(marker ? { blockPath: marker.blockPath } : {}),
|
|
1073
|
+
scopeKey: scope.scopeKey,
|
|
1074
|
+
scopeId: scope.scopeId,
|
|
1075
|
+
joinKind: "block-scoped",
|
|
1076
|
+
};
|
|
1077
|
+
}
|
|
1078
|
+
|
|
1079
|
+
function anchorSourceIdentity(
|
|
1080
|
+
anchor: CanonicalAnchorLayoutInput,
|
|
1081
|
+
joinKind: GeometrySourceIdentity["joinKind"],
|
|
1082
|
+
): GeometrySourceIdentity {
|
|
1083
|
+
return {
|
|
1084
|
+
storyKey: anchor.storyKey,
|
|
1085
|
+
blockPath: anchor.blockPath,
|
|
1086
|
+
objectKey: anchor.objectKey,
|
|
1087
|
+
inlinePath: anchor.inlinePath,
|
|
1088
|
+
objectKind: anchor.objectKind,
|
|
1089
|
+
editPosture: anchor.editPosture,
|
|
1090
|
+
joinKind,
|
|
640
1091
|
};
|
|
641
1092
|
}
|
|
642
1093
|
|
|
@@ -147,6 +147,8 @@ export interface GeometrySourceIdentity {
|
|
|
147
147
|
tableKey?: string;
|
|
148
148
|
rowKey?: string;
|
|
149
149
|
cellKey?: string;
|
|
150
|
+
scopeKey?: string;
|
|
151
|
+
scopeId?: string;
|
|
150
152
|
objectKey?: string;
|
|
151
153
|
inlinePath?: string;
|
|
152
154
|
objectKind?: string;
|
|
@@ -288,16 +290,20 @@ export interface SemanticDisplayEntry {
|
|
|
288
290
|
|
|
289
291
|
export interface GeometryReplacementEnvelopeEntry {
|
|
290
292
|
scopeId: string;
|
|
293
|
+
pageIds?: readonly string[];
|
|
291
294
|
rects: readonly GeometryRect[];
|
|
292
295
|
status: GeometryRehydrationStatus;
|
|
293
296
|
precision: GeometryPrecision;
|
|
297
|
+
sourceIdentity?: GeometrySourceIdentity;
|
|
294
298
|
}
|
|
295
299
|
|
|
296
300
|
export interface GeometryObjectHandleEntry {
|
|
297
301
|
objectId: string;
|
|
302
|
+
pageIds?: readonly string[];
|
|
298
303
|
rects: readonly GeometryRect[];
|
|
299
304
|
status: GeometryRehydrationStatus;
|
|
300
305
|
precision: GeometryPrecision;
|
|
306
|
+
sourceIdentity?: GeometrySourceIdentity;
|
|
301
307
|
}
|
|
302
308
|
|
|
303
309
|
// ---------------------------------------------------------------------------
|
|
@@ -27,7 +27,7 @@
|
|
|
27
27
|
*/
|
|
28
28
|
|
|
29
29
|
import type { RenderFrame, RenderFrameRect } from "../render/index.ts";
|
|
30
|
-
import type { GeometryRect } from "./geometry-types.ts";
|
|
30
|
+
import type { GeometryPrecision, GeometryRect } from "./geometry-types.ts";
|
|
31
31
|
|
|
32
32
|
const ROTATE_HANDLE_OFFSET_PX = 20;
|
|
33
33
|
|
|
@@ -38,10 +38,13 @@ export function resolveObjectHandles(
|
|
|
38
38
|
if (!frame) return [];
|
|
39
39
|
const bbox = frame.anchorIndex.byObjectId(objectId);
|
|
40
40
|
if (!bbox) return [];
|
|
41
|
-
return
|
|
41
|
+
return buildObjectHandleRectsFromRect(bbox);
|
|
42
42
|
}
|
|
43
43
|
|
|
44
|
-
function
|
|
44
|
+
export function buildObjectHandleRectsFromRect(
|
|
45
|
+
bbox: RenderFrameRect,
|
|
46
|
+
precision: GeometryPrecision = "heuristic",
|
|
47
|
+
): readonly GeometryRect[] {
|
|
45
48
|
const { leftPx, topPx, widthPx, heightPx } = bbox;
|
|
46
49
|
const right = leftPx + widthPx;
|
|
47
50
|
const bottom = topPx + heightPx;
|
|
@@ -61,7 +64,7 @@ function buildHandles(bbox: RenderFrameRect): readonly GeometryRect[] {
|
|
|
61
64
|
widthPx: 0,
|
|
62
65
|
heightPx: 0,
|
|
63
66
|
space: "frame",
|
|
64
|
-
precision
|
|
67
|
+
precision,
|
|
65
68
|
});
|
|
66
69
|
return [
|
|
67
70
|
point(leftPx, topPx), // 0 top-left
|
|
@@ -490,6 +490,7 @@ export function createLayoutEngine(
|
|
|
490
490
|
fragmentsByPageIndex,
|
|
491
491
|
lineBoxesByPageIndex,
|
|
492
492
|
noteAllocationsByPageIndex: pageStack.noteAllocationsByPageIndex,
|
|
493
|
+
subParts: document.subParts,
|
|
493
494
|
});
|
|
494
495
|
|
|
495
496
|
// Field dirtiness diff from previous graph
|
|
@@ -669,6 +670,7 @@ export function createLayoutEngine(
|
|
|
669
670
|
fragmentsByPageIndex: freshFragmentsByPageIndex,
|
|
670
671
|
lineBoxesByPageIndex: freshLineBoxesByPageIndex,
|
|
671
672
|
noteAllocationsByPageIndex: freshResult.noteAllocationsByPageIndex,
|
|
673
|
+
subParts: document.subParts,
|
|
672
674
|
});
|
|
673
675
|
const freshNodes = freshGraph.pages;
|
|
674
676
|
|
|
@@ -1046,8 +1046,43 @@
|
|
|
1046
1046
|
* Existing `regions` remain the compatibility surface, but cache envelopes
|
|
1047
1047
|
* from v63 invalidate because the page graph payload shape changed.
|
|
1048
1048
|
* Shipped via pe2 commit `24a316af2`.
|
|
1049
|
+
*
|
|
1050
|
+
* 65 — PE2 Slice 2 page-local story instances (Layer 04). `RuntimePageFrame`
|
|
1051
|
+
* now carries stable header/footer `pageLocalStories` records with story
|
|
1052
|
+
* keys, variants, relationship ids, measured frame heights, and empty
|
|
1053
|
+
* field/object ledgers for later Slice 2/4 population. Cache envelopes
|
|
1054
|
+
* from v64 invalidate because the page-frame payload shape changed again.
|
|
1055
|
+
* Shipped via pe2 commit `06fe6f692`.
|
|
1056
|
+
*
|
|
1057
|
+
* 66 — PE2 Slice 2 page-local field ledgers (Layer 04). Page graph
|
|
1058
|
+
* construction now walks canonical header/footer subparts for each
|
|
1059
|
+
* page-local story instance and records resolved PAGE / NUMPAGES /
|
|
1060
|
+
* SECTIONPAGES display text in the story's `resolvedFields` ledger.
|
|
1061
|
+
* Cache envelopes from v65 invalidate because page-frame story payloads
|
|
1062
|
+
* now carry field-resolution data. Shipped via pe2 commit `a3a42bec9`.
|
|
1063
|
+
*
|
|
1064
|
+
* 67 — PE2 Slice 2 page-local object ledgers (Layer 04). Header/footer
|
|
1065
|
+
* page-local story instances now record canonical image/drawing/preserve-only
|
|
1066
|
+
* objects as twips/plain anchored-object ledger rows and emit typed
|
|
1067
|
+
* unsupported-wrap / preserve-only-placeholder divergences. Cache envelopes
|
|
1068
|
+
* from v66 invalidate because page-frame story payloads and divergence ids
|
|
1069
|
+
* can change. Shipped via pe2 commit `7d8bb94ac`.
|
|
1070
|
+
*
|
|
1071
|
+
* 68 — PE2 Slice 3 typed continuation cursors (Layer 04). Paragraph and
|
|
1072
|
+
* table slice fragments now carry a twips/plain continuation cursor with
|
|
1073
|
+
* sequence position, line/row range, continuation direction, repeated
|
|
1074
|
+
* table header rows, and vertical-merge carry metadata. Cache envelopes
|
|
1075
|
+
* from v67 invalidate because fragment payloads can now include
|
|
1076
|
+
* continuation state. Shipped via pe2 commit `33fbf45ac`.
|
|
1077
|
+
*
|
|
1078
|
+
* 69 — PE2 Slice 3 measured layout-object descriptors (Layer 04). Body
|
|
1079
|
+
* fragments and footnote-area fragments now carry a twips/plain
|
|
1080
|
+
* `layoutObject` row with stable object id, source block id, object kind,
|
|
1081
|
+
* pagination role, measured extent, and field-family hints where applicable.
|
|
1082
|
+
* Cache envelopes from v68 invalidate because fragment payloads can now
|
|
1083
|
+
* include layout-object descriptors. Shipped via pe2 commit `9c4417418`.
|
|
1049
1084
|
*/
|
|
1050
|
-
export const LAYOUT_ENGINE_VERSION =
|
|
1085
|
+
export const LAYOUT_ENGINE_VERSION = 69 as const;
|
|
1051
1086
|
|
|
1052
1087
|
/**
|
|
1053
1088
|
* Serialization schema version for the LayCache payload (the cache envelope
|