@beyondwork/docx-react-component 1.0.103 → 1.0.105
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +1 -1
- package/src/api/public-types.ts +66 -1
- package/src/api/v3/_runtime-handle.ts +2 -0
- package/src/api/v3/ai/_pe2-evidence.ts +153 -0
- package/src/api/v3/ai/bundle.ts +13 -5
- package/src/api/v3/ai/inspect.ts +7 -1
- package/src/api/v3/ai/outline.ts +2 -7
- package/src/api/v3/ai/replacement.ts +113 -0
- package/src/api/v3/runtime/geometry.ts +79 -0
- package/src/api/v3/ui/_types.ts +86 -0
- package/src/api/v3/ui/index.ts +5 -0
- package/src/api/v3/ui/overlays.ts +104 -0
- package/src/io/ooxml/parse-drawing.ts +99 -1
- package/src/io/ooxml/parse-fields.ts +27 -6
- package/src/io/ooxml/parse-shapes.ts +130 -0
- package/src/model/canonical-document.ts +34 -3
- package/src/model/canonical-layout-inputs.ts +979 -0
- package/src/model/layout/index.ts +9 -0
- package/src/model/layout/page-graph-types.ts +150 -0
- package/src/model/layout/runtime-page-graph-types.ts +23 -0
- package/src/runtime/collab/runtime-collab-sync.ts +3 -3
- package/src/runtime/debug/build-debug-inspector-snapshot.ts +17 -4
- package/src/runtime/document-runtime.ts +30 -14
- package/src/runtime/event-refresh-hints.ts +35 -5
- package/src/runtime/formatting/formatting-context.ts +110 -9
- package/src/runtime/formatting/index.ts +2 -0
- package/src/runtime/formatting/layout-inputs.ts +67 -3
- package/src/runtime/geometry/caret-geometry.ts +82 -10
- package/src/runtime/geometry/geometry-facet.ts +44 -0
- package/src/runtime/geometry/geometry-index.ts +1268 -0
- package/src/runtime/geometry/geometry-types.ts +227 -1
- package/src/runtime/geometry/index.ts +26 -0
- package/src/runtime/geometry/inert-geometry-facet.ts +3 -0
- package/src/runtime/geometry/object-handles.ts +7 -4
- package/src/runtime/geometry/replacement-envelope.ts +41 -2
- package/src/runtime/layout/layout-engine-instance.ts +2 -0
- package/src/runtime/layout/layout-engine-version.ts +44 -1
- package/src/runtime/layout/page-graph.ts +877 -2
- package/src/runtime/layout/project-block-fragments.ts +101 -1
- package/src/runtime/layout/public-facet.ts +152 -0
- package/src/runtime/prerender/graph-canonicalize.ts +44 -0
- package/src/runtime/surface-projection.ts +43 -3
- package/src/runtime/workflow/coordinator.ts +57 -11
- package/src/ui/ui-controller-factory.ts +11 -0
- package/src/ui-tailwind/chrome-overlay/tw-page-stack-overlay-layer.tsx +3 -0
|
@@ -21,6 +21,17 @@ import type {
|
|
|
21
21
|
EditorStoryTarget,
|
|
22
22
|
PageLayoutSnapshot,
|
|
23
23
|
} from "../../api/public-types";
|
|
24
|
+
import type {
|
|
25
|
+
BlockNode,
|
|
26
|
+
FooterDocument,
|
|
27
|
+
HeaderDocument,
|
|
28
|
+
InlineNode,
|
|
29
|
+
PreserveOnlyObjectSizing,
|
|
30
|
+
} from "../../model/canonical-document.ts";
|
|
31
|
+
import {
|
|
32
|
+
formatPageNumber,
|
|
33
|
+
formatPageNumberWithChapter,
|
|
34
|
+
} from "../formatting/field/page-number-format.ts";
|
|
24
35
|
import type {
|
|
25
36
|
ResolvedPageStories,
|
|
26
37
|
} from "./page-story-resolver.ts";
|
|
@@ -48,6 +59,19 @@ import type { ResolvedDocumentSection } from "../document-layout.ts";
|
|
|
48
59
|
export type {
|
|
49
60
|
RuntimePageRegions,
|
|
50
61
|
RuntimePageRegion,
|
|
62
|
+
RuntimeTwipsRect,
|
|
63
|
+
RuntimeResolvedRegions,
|
|
64
|
+
RuntimeExclusionZone,
|
|
65
|
+
RuntimeLayoutDivergenceKind,
|
|
66
|
+
RuntimeLayoutDivergence,
|
|
67
|
+
RuntimePageFrame,
|
|
68
|
+
RuntimePageLocalStoryInstance,
|
|
69
|
+
RuntimeResolvedStoryField,
|
|
70
|
+
RuntimeStoryAnchoredObject,
|
|
71
|
+
RuntimeLayoutContinuationCursor,
|
|
72
|
+
RuntimeParagraphContinuationCursor,
|
|
73
|
+
RuntimeTableContinuationCursor,
|
|
74
|
+
RuntimeTableVerticalMergeCarry,
|
|
51
75
|
RuntimeBlockFragment,
|
|
52
76
|
RuntimeLineBox,
|
|
53
77
|
RuntimeNoteAllocation,
|
|
@@ -62,11 +86,17 @@ export type {
|
|
|
62
86
|
|
|
63
87
|
import type {
|
|
64
88
|
RuntimeBlockFragment,
|
|
89
|
+
RuntimeLayoutDivergence,
|
|
65
90
|
RuntimeLineBox,
|
|
66
91
|
RuntimeNoteAllocation,
|
|
67
92
|
RuntimePageAnchor,
|
|
93
|
+
RuntimePageFrame,
|
|
94
|
+
RuntimePageLocalStoryInstance,
|
|
68
95
|
RuntimePageRegion,
|
|
69
96
|
RuntimePageRegions,
|
|
97
|
+
RuntimeResolvedRegions,
|
|
98
|
+
RuntimeStoryAnchoredObject,
|
|
99
|
+
RuntimeTwipsRect,
|
|
70
100
|
} from "../../model/layout/page-graph-types.ts";
|
|
71
101
|
import type {
|
|
72
102
|
RuntimePageGraph,
|
|
@@ -80,6 +110,19 @@ import type {
|
|
|
80
110
|
|
|
81
111
|
let graphRevision = 0;
|
|
82
112
|
|
|
113
|
+
const PAGE_INSTANCE_FIELD_FAMILIES = new Set([
|
|
114
|
+
"PAGE",
|
|
115
|
+
"NUMPAGES",
|
|
116
|
+
"SECTIONPAGES",
|
|
117
|
+
]);
|
|
118
|
+
|
|
119
|
+
const EMUS_PER_TWIP = 635;
|
|
120
|
+
|
|
121
|
+
type HeaderFooterStoryTarget = Extract<
|
|
122
|
+
EditorStoryTarget,
|
|
123
|
+
{ kind: "header" | "footer" }
|
|
124
|
+
>;
|
|
125
|
+
|
|
83
126
|
export function buildPageGraph(input: BuildPageGraphInput): RuntimePageGraph;
|
|
84
127
|
export function buildPageGraph(
|
|
85
128
|
pages: readonly DocumentPageSnapshot[],
|
|
@@ -104,6 +147,7 @@ export function buildPageGraph(
|
|
|
104
147
|
const pages: RuntimePageNode[] = [];
|
|
105
148
|
const aggregatedFragments: RuntimeBlockFragment[] = [...(input.fragments ?? [])];
|
|
106
149
|
const pageIdByGlobalPageIndex = new Map<number, string>();
|
|
150
|
+
const pageFieldCounts = buildPageFieldCounts(input.pages);
|
|
107
151
|
for (let index = 0; index < input.pages.length; index += 1) {
|
|
108
152
|
const page = input.pages[index]!;
|
|
109
153
|
pageIdByGlobalPageIndex.set(page.pageIndex, `page-${graphRevision}-${index}`);
|
|
@@ -164,6 +208,29 @@ export function buildPageGraph(
|
|
|
164
208
|
input.noteAllocations?.get(pageId) ??
|
|
165
209
|
[];
|
|
166
210
|
|
|
211
|
+
const frameId = buildPageFrameId(
|
|
212
|
+
page.pageIndex,
|
|
213
|
+
page.sectionIndex,
|
|
214
|
+
stories.displayPageNumber,
|
|
215
|
+
);
|
|
216
|
+
const regions = buildRegions(page.layout, bodyPageFragments, stories, pageNoteAllocations);
|
|
217
|
+
const frameDivergences = detectFrameDivergences(frameId, regions);
|
|
218
|
+
const builtFrame = buildPageFrame({
|
|
219
|
+
frameId,
|
|
220
|
+
pageId,
|
|
221
|
+
pageIndex: page.pageIndex,
|
|
222
|
+
sectionIndex: page.sectionIndex,
|
|
223
|
+
displayPageNumber: stories.displayPageNumber,
|
|
224
|
+
layout: page.layout,
|
|
225
|
+
stories,
|
|
226
|
+
regions,
|
|
227
|
+
divergences: frameDivergences,
|
|
228
|
+
subParts: input.subParts,
|
|
229
|
+
pageFieldCounts,
|
|
230
|
+
});
|
|
231
|
+
const divergences = builtFrame.divergences;
|
|
232
|
+
const frame = builtFrame.frame;
|
|
233
|
+
|
|
167
234
|
const node: RuntimePageNode = {
|
|
168
235
|
pageId,
|
|
169
236
|
pageIndex: page.pageIndex,
|
|
@@ -173,7 +240,9 @@ export function buildPageGraph(
|
|
|
173
240
|
endOffset: page.endOffset,
|
|
174
241
|
layout: page.layout,
|
|
175
242
|
stories,
|
|
176
|
-
regions
|
|
243
|
+
regions,
|
|
244
|
+
frame,
|
|
245
|
+
divergences,
|
|
177
246
|
lineBoxes:
|
|
178
247
|
input.lineBoxesByPageIndex?.get(page.pageIndex) ??
|
|
179
248
|
input.lineBoxesByPageIndex?.get(index) ??
|
|
@@ -240,6 +309,12 @@ function buildRegions(
|
|
|
240
309
|
originTwips: layout.pageHeight - layout.marginBottom - totalNoteHeight,
|
|
241
310
|
widthTwips: Math.max(0, bodyWidth),
|
|
242
311
|
heightTwips: Math.max(0, totalNoteHeight),
|
|
312
|
+
rectTwips: rect(
|
|
313
|
+
layout.marginLeft,
|
|
314
|
+
layout.pageHeight - layout.marginBottom - totalNoteHeight,
|
|
315
|
+
Math.max(0, bodyWidth),
|
|
316
|
+
Math.max(0, totalNoteHeight),
|
|
317
|
+
),
|
|
243
318
|
fragmentIds,
|
|
244
319
|
};
|
|
245
320
|
bodyHeight = Math.max(0, bodyHeight - totalNoteHeight);
|
|
@@ -251,6 +326,7 @@ function buildRegions(
|
|
|
251
326
|
originTwips: layout.marginTop,
|
|
252
327
|
widthTwips: Math.max(0, bodyWidth),
|
|
253
328
|
heightTwips: Math.max(0, bodyHeight),
|
|
329
|
+
rectTwips: rect(layout.marginLeft, layout.marginTop, Math.max(0, bodyWidth), Math.max(0, bodyHeight)),
|
|
254
330
|
fragmentIds: [...bodyFragmentIds],
|
|
255
331
|
};
|
|
256
332
|
|
|
@@ -262,6 +338,12 @@ function buildRegions(
|
|
|
262
338
|
originTwips: layout.headerMargin ?? 720,
|
|
263
339
|
widthTwips: Math.max(0, bodyWidth),
|
|
264
340
|
heightTwips: Math.max(0, layout.marginTop - (layout.headerMargin ?? 720)),
|
|
341
|
+
rectTwips: rect(
|
|
342
|
+
layout.marginLeft,
|
|
343
|
+
layout.headerMargin ?? 720,
|
|
344
|
+
Math.max(0, bodyWidth),
|
|
345
|
+
Math.max(0, layout.marginTop - (layout.headerMargin ?? 720)),
|
|
346
|
+
),
|
|
265
347
|
fragmentIds: [],
|
|
266
348
|
};
|
|
267
349
|
}
|
|
@@ -271,6 +353,12 @@ function buildRegions(
|
|
|
271
353
|
originTwips: layout.pageHeight - layout.marginBottom,
|
|
272
354
|
widthTwips: Math.max(0, bodyWidth),
|
|
273
355
|
heightTwips: Math.max(0, layout.marginBottom - (layout.footerMargin ?? 720)),
|
|
356
|
+
rectTwips: rect(
|
|
357
|
+
layout.marginLeft,
|
|
358
|
+
layout.pageHeight - layout.marginBottom,
|
|
359
|
+
Math.max(0, bodyWidth),
|
|
360
|
+
Math.max(0, layout.marginBottom - (layout.footerMargin ?? 720)),
|
|
361
|
+
),
|
|
274
362
|
fragmentIds: [],
|
|
275
363
|
};
|
|
276
364
|
}
|
|
@@ -299,6 +387,12 @@ function buildRegions(
|
|
|
299
387
|
originTwips: layout.marginTop,
|
|
300
388
|
widthTwips: perColumnWidth,
|
|
301
389
|
heightTwips: Math.max(0, bodyHeight),
|
|
390
|
+
rectTwips: rect(
|
|
391
|
+
layout.marginLeft + i * (perColumnWidth + gap),
|
|
392
|
+
layout.marginTop,
|
|
393
|
+
perColumnWidth,
|
|
394
|
+
Math.max(0, bodyHeight),
|
|
395
|
+
),
|
|
302
396
|
fragmentIds: perColumnIds[i]!,
|
|
303
397
|
});
|
|
304
398
|
}
|
|
@@ -312,6 +406,715 @@ function buildRegions(
|
|
|
312
406
|
return regions;
|
|
313
407
|
}
|
|
314
408
|
|
|
409
|
+
function rect(
|
|
410
|
+
xTwips: number,
|
|
411
|
+
yTwips: number,
|
|
412
|
+
widthTwips: number,
|
|
413
|
+
heightTwips: number,
|
|
414
|
+
): RuntimeTwipsRect {
|
|
415
|
+
return { xTwips, yTwips, widthTwips, heightTwips };
|
|
416
|
+
}
|
|
417
|
+
|
|
418
|
+
function buildPageFrame(input: {
|
|
419
|
+
frameId: string;
|
|
420
|
+
pageId: string;
|
|
421
|
+
pageIndex: number;
|
|
422
|
+
sectionIndex: number;
|
|
423
|
+
displayPageNumber: number;
|
|
424
|
+
layout: PageLayoutSnapshot;
|
|
425
|
+
stories: ResolvedPageStories;
|
|
426
|
+
regions: RuntimePageRegions;
|
|
427
|
+
divergences: readonly RuntimeLayoutDivergence[];
|
|
428
|
+
subParts?: BuildPageGraphInput["subParts"];
|
|
429
|
+
pageFieldCounts: PageFieldCounts;
|
|
430
|
+
}): { frame: RuntimePageFrame; divergences: RuntimeLayoutDivergence[] } {
|
|
431
|
+
const regions: RuntimeResolvedRegions = {
|
|
432
|
+
body: input.regions.body,
|
|
433
|
+
exclusionZones: [],
|
|
434
|
+
...(input.regions.header ? { header: input.regions.header } : {}),
|
|
435
|
+
...(input.regions.footer ? { footer: input.regions.footer } : {}),
|
|
436
|
+
...(input.regions.columns ? { columns: input.regions.columns } : {}),
|
|
437
|
+
...(input.regions.footnotes ? { footnotes: input.regions.footnotes } : {}),
|
|
438
|
+
};
|
|
439
|
+
const pageLocalStoryResult = buildPageLocalStoryInstances({
|
|
440
|
+
frameId: input.frameId,
|
|
441
|
+
pageId: input.pageId,
|
|
442
|
+
pageIndex: input.pageIndex,
|
|
443
|
+
sectionIndex: input.sectionIndex,
|
|
444
|
+
displayPageNumber: input.displayPageNumber,
|
|
445
|
+
layout: input.layout,
|
|
446
|
+
stories: input.stories,
|
|
447
|
+
regions,
|
|
448
|
+
subParts: input.subParts,
|
|
449
|
+
pageFieldCounts: input.pageFieldCounts,
|
|
450
|
+
});
|
|
451
|
+
const divergences = [...input.divergences, ...pageLocalStoryResult.divergences];
|
|
452
|
+
const pageLocalStories = pageLocalStoryResult.instances;
|
|
453
|
+
const divergenceIds = divergences.map((d) => d.divergenceId);
|
|
454
|
+
return {
|
|
455
|
+
frame: {
|
|
456
|
+
frameId: input.frameId,
|
|
457
|
+
pageId: input.pageId,
|
|
458
|
+
pageIndex: input.pageIndex,
|
|
459
|
+
sectionIndex: input.sectionIndex,
|
|
460
|
+
displayPageNumber: input.displayPageNumber,
|
|
461
|
+
physicalBoundsTwips: rect(0, 0, input.layout.pageWidth, input.layout.pageHeight),
|
|
462
|
+
regions,
|
|
463
|
+
pageLocalStories,
|
|
464
|
+
divergenceIds,
|
|
465
|
+
signature: buildPageFrameSignature(input, pageLocalStories, divergenceIds),
|
|
466
|
+
},
|
|
467
|
+
divergences,
|
|
468
|
+
};
|
|
469
|
+
}
|
|
470
|
+
|
|
471
|
+
function buildPageLocalStoryInstances(input: {
|
|
472
|
+
frameId: string;
|
|
473
|
+
pageId: string;
|
|
474
|
+
pageIndex: number;
|
|
475
|
+
sectionIndex: number;
|
|
476
|
+
displayPageNumber: number;
|
|
477
|
+
layout: PageLayoutSnapshot;
|
|
478
|
+
stories: ResolvedPageStories;
|
|
479
|
+
regions: RuntimeResolvedRegions;
|
|
480
|
+
subParts?: BuildPageGraphInput["subParts"];
|
|
481
|
+
pageFieldCounts: PageFieldCounts;
|
|
482
|
+
}): {
|
|
483
|
+
instances: RuntimePageLocalStoryInstance[];
|
|
484
|
+
divergences: RuntimeLayoutDivergence[];
|
|
485
|
+
} {
|
|
486
|
+
const instances: RuntimePageLocalStoryInstance[] = [];
|
|
487
|
+
const divergences: RuntimeLayoutDivergence[] = [];
|
|
488
|
+
if (isHeaderFooterStoryTarget(input.stories.header)) {
|
|
489
|
+
const built = buildPageLocalStoryInstance(
|
|
490
|
+
input.frameId,
|
|
491
|
+
input.pageId,
|
|
492
|
+
input.pageIndex,
|
|
493
|
+
input.sectionIndex,
|
|
494
|
+
input.displayPageNumber,
|
|
495
|
+
input.layout,
|
|
496
|
+
input.stories.header,
|
|
497
|
+
input.regions.header,
|
|
498
|
+
findHeaderFooterPart(input.subParts?.headers, input.stories.header),
|
|
499
|
+
input.pageFieldCounts,
|
|
500
|
+
);
|
|
501
|
+
instances.push(built.instance);
|
|
502
|
+
divergences.push(...built.divergences);
|
|
503
|
+
}
|
|
504
|
+
if (isHeaderFooterStoryTarget(input.stories.footer)) {
|
|
505
|
+
const built = buildPageLocalStoryInstance(
|
|
506
|
+
input.frameId,
|
|
507
|
+
input.pageId,
|
|
508
|
+
input.pageIndex,
|
|
509
|
+
input.sectionIndex,
|
|
510
|
+
input.displayPageNumber,
|
|
511
|
+
input.layout,
|
|
512
|
+
input.stories.footer,
|
|
513
|
+
input.regions.footer,
|
|
514
|
+
findHeaderFooterPart(input.subParts?.footers, input.stories.footer),
|
|
515
|
+
input.pageFieldCounts,
|
|
516
|
+
);
|
|
517
|
+
instances.push(built.instance);
|
|
518
|
+
divergences.push(...built.divergences);
|
|
519
|
+
}
|
|
520
|
+
return { instances, divergences };
|
|
521
|
+
}
|
|
522
|
+
|
|
523
|
+
function buildPageLocalStoryInstance(
|
|
524
|
+
frameId: string,
|
|
525
|
+
pageId: string,
|
|
526
|
+
pageIndex: number,
|
|
527
|
+
sectionIndex: number,
|
|
528
|
+
displayPageNumber: number,
|
|
529
|
+
layout: PageLayoutSnapshot,
|
|
530
|
+
target: HeaderFooterStoryTarget,
|
|
531
|
+
region: RuntimePageRegion | undefined,
|
|
532
|
+
source: HeaderDocument | FooterDocument | undefined,
|
|
533
|
+
pageFieldCounts: PageFieldCounts,
|
|
534
|
+
): {
|
|
535
|
+
instance: RuntimePageLocalStoryInstance;
|
|
536
|
+
divergences: RuntimeLayoutDivergence[];
|
|
537
|
+
} {
|
|
538
|
+
const measuredFrameHeightTwips = region?.heightTwips ?? 0;
|
|
539
|
+
const sectionPart =
|
|
540
|
+
target.sectionIndex === undefined ? "section-unknown" : `section-${target.sectionIndex}`;
|
|
541
|
+
const instanceId = `${frameId}:${target.kind}:${target.variant}:${target.relationshipId}`;
|
|
542
|
+
const storyKey = `${target.kind}:${target.relationshipId}`;
|
|
543
|
+
const resolvedFields = source
|
|
544
|
+
? collectResolvedStoryFields(source.blocks, {
|
|
545
|
+
storyKey,
|
|
546
|
+
pageIndex,
|
|
547
|
+
sectionIndex,
|
|
548
|
+
displayPageNumber,
|
|
549
|
+
layout,
|
|
550
|
+
pageFieldCounts,
|
|
551
|
+
})
|
|
552
|
+
: [];
|
|
553
|
+
const objectLedger = source
|
|
554
|
+
? collectStoryAnchoredObjects(source.blocks, {
|
|
555
|
+
frameId,
|
|
556
|
+
storyKey,
|
|
557
|
+
kind: target.kind,
|
|
558
|
+
variant: target.variant,
|
|
559
|
+
relationshipId: target.relationshipId,
|
|
560
|
+
})
|
|
561
|
+
: { objects: [], divergences: [] };
|
|
562
|
+
const signature = buildPageLocalStorySignature({
|
|
563
|
+
kind: target.kind,
|
|
564
|
+
variant: target.variant,
|
|
565
|
+
relationshipId: target.relationshipId,
|
|
566
|
+
sectionPart,
|
|
567
|
+
measuredFrameHeightTwips,
|
|
568
|
+
resolvedFields,
|
|
569
|
+
anchoredObjects: objectLedger.objects,
|
|
570
|
+
});
|
|
571
|
+
return {
|
|
572
|
+
instance: {
|
|
573
|
+
instanceId,
|
|
574
|
+
storyKey,
|
|
575
|
+
pageId,
|
|
576
|
+
kind: target.kind,
|
|
577
|
+
variant: target.variant,
|
|
578
|
+
relationshipId: target.relationshipId,
|
|
579
|
+
...(target.sectionIndex === undefined ? {} : { sectionIndex: target.sectionIndex }),
|
|
580
|
+
resolvedFields,
|
|
581
|
+
anchoredObjects: objectLedger.objects,
|
|
582
|
+
measuredFrameHeightTwips,
|
|
583
|
+
signature,
|
|
584
|
+
},
|
|
585
|
+
divergences: objectLedger.divergences,
|
|
586
|
+
};
|
|
587
|
+
}
|
|
588
|
+
|
|
589
|
+
function buildPageLocalStorySignature(input: {
|
|
590
|
+
kind: RuntimePageLocalStoryInstance["kind"];
|
|
591
|
+
variant: RuntimePageLocalStoryInstance["variant"];
|
|
592
|
+
relationshipId: string;
|
|
593
|
+
sectionPart: string;
|
|
594
|
+
measuredFrameHeightTwips: number;
|
|
595
|
+
resolvedFields: readonly RuntimePageLocalStoryInstance["resolvedFields"][number][];
|
|
596
|
+
anchoredObjects: readonly RuntimeStoryAnchoredObject[];
|
|
597
|
+
}): string {
|
|
598
|
+
return [
|
|
599
|
+
"page-local-story",
|
|
600
|
+
input.kind,
|
|
601
|
+
input.variant,
|
|
602
|
+
input.relationshipId,
|
|
603
|
+
input.sectionPart,
|
|
604
|
+
input.measuredFrameHeightTwips,
|
|
605
|
+
...input.resolvedFields.map((field) =>
|
|
606
|
+
[field.fieldId, field.family, field.displayText].join(":"),
|
|
607
|
+
),
|
|
608
|
+
...input.anchoredObjects.map((object) =>
|
|
609
|
+
[
|
|
610
|
+
object.objectId,
|
|
611
|
+
object.sourceType,
|
|
612
|
+
object.display,
|
|
613
|
+
object.extentTwips?.widthTwips ?? "",
|
|
614
|
+
object.extentTwips?.heightTwips ?? "",
|
|
615
|
+
object.relationshipIds?.join(",") ?? "",
|
|
616
|
+
object.preserveOnly ? "preserve-only" : "renderable",
|
|
617
|
+
object.divergenceIds.join(","),
|
|
618
|
+
].join(":"),
|
|
619
|
+
),
|
|
620
|
+
].join("|");
|
|
621
|
+
}
|
|
622
|
+
|
|
623
|
+
interface PageFieldCounts {
|
|
624
|
+
contentPageCount: number;
|
|
625
|
+
sectionContentPageCountByIndex: ReadonlyMap<number, number>;
|
|
626
|
+
}
|
|
627
|
+
|
|
628
|
+
interface PageFieldResolutionInput {
|
|
629
|
+
storyKey: string;
|
|
630
|
+
pageIndex: number;
|
|
631
|
+
sectionIndex: number;
|
|
632
|
+
displayPageNumber: number;
|
|
633
|
+
layout: PageLayoutSnapshot;
|
|
634
|
+
pageFieldCounts: PageFieldCounts;
|
|
635
|
+
}
|
|
636
|
+
|
|
637
|
+
function buildPageFieldCounts(
|
|
638
|
+
pages: readonly Pick<DocumentPageSnapshot, "pageInSection" | "sectionIndex">[],
|
|
639
|
+
): PageFieldCounts {
|
|
640
|
+
const sectionContentPageCountByIndex = new Map<number, number>();
|
|
641
|
+
let contentPageCount = 0;
|
|
642
|
+
for (const page of pages) {
|
|
643
|
+
if (page.pageInSection === -1) continue;
|
|
644
|
+
contentPageCount += 1;
|
|
645
|
+
sectionContentPageCountByIndex.set(
|
|
646
|
+
page.sectionIndex,
|
|
647
|
+
(sectionContentPageCountByIndex.get(page.sectionIndex) ?? 0) + 1,
|
|
648
|
+
);
|
|
649
|
+
}
|
|
650
|
+
return { contentPageCount, sectionContentPageCountByIndex };
|
|
651
|
+
}
|
|
652
|
+
|
|
653
|
+
function findHeaderFooterPart<T extends HeaderDocument | FooterDocument>(
|
|
654
|
+
parts: ReadonlyArray<T> | undefined,
|
|
655
|
+
target: HeaderFooterStoryTarget,
|
|
656
|
+
): T | undefined {
|
|
657
|
+
return parts?.find((part) => part.relationshipId === target.relationshipId);
|
|
658
|
+
}
|
|
659
|
+
|
|
660
|
+
function collectResolvedStoryFields(
|
|
661
|
+
blocks: readonly BlockNode[],
|
|
662
|
+
context: PageFieldResolutionInput,
|
|
663
|
+
): RuntimePageLocalStoryInstance["resolvedFields"] {
|
|
664
|
+
const fields: RuntimePageLocalStoryInstance["resolvedFields"] = [];
|
|
665
|
+
let ordinal = 0;
|
|
666
|
+
|
|
667
|
+
const visitBlock = (block: BlockNode): void => {
|
|
668
|
+
switch (block.type) {
|
|
669
|
+
case "paragraph":
|
|
670
|
+
for (const child of block.children) visitInline(child);
|
|
671
|
+
break;
|
|
672
|
+
case "table":
|
|
673
|
+
for (const row of block.rows) {
|
|
674
|
+
for (const cell of row.cells) {
|
|
675
|
+
for (const child of cell.children) visitBlock(child);
|
|
676
|
+
}
|
|
677
|
+
}
|
|
678
|
+
break;
|
|
679
|
+
case "sdt":
|
|
680
|
+
for (const child of block.children) visitBlock(child);
|
|
681
|
+
break;
|
|
682
|
+
default:
|
|
683
|
+
break;
|
|
684
|
+
}
|
|
685
|
+
};
|
|
686
|
+
|
|
687
|
+
const visitInline = (inline: InlineNode): void => {
|
|
688
|
+
switch (inline.type) {
|
|
689
|
+
case "field": {
|
|
690
|
+
const family = inline.fieldFamily ?? classifyFieldInstructionLocal(inline.instruction);
|
|
691
|
+
if (PAGE_INSTANCE_FIELD_FAMILIES.has(family)) {
|
|
692
|
+
const fieldId = `${context.storyKey}:field-${ordinal}:${family}`;
|
|
693
|
+
fields.push({
|
|
694
|
+
fieldId,
|
|
695
|
+
family,
|
|
696
|
+
displayText: resolvePageInstanceFieldDisplayText(
|
|
697
|
+
family,
|
|
698
|
+
flattenInline(inline.children),
|
|
699
|
+
context,
|
|
700
|
+
),
|
|
701
|
+
});
|
|
702
|
+
ordinal += 1;
|
|
703
|
+
}
|
|
704
|
+
break;
|
|
705
|
+
}
|
|
706
|
+
case "hyperlink":
|
|
707
|
+
for (const child of inline.children) visitInline(child);
|
|
708
|
+
break;
|
|
709
|
+
default:
|
|
710
|
+
break;
|
|
711
|
+
}
|
|
712
|
+
};
|
|
713
|
+
|
|
714
|
+
for (const block of blocks) visitBlock(block);
|
|
715
|
+
return fields;
|
|
716
|
+
}
|
|
717
|
+
|
|
718
|
+
interface StoryObjectContext {
|
|
719
|
+
frameId: string;
|
|
720
|
+
storyKey: string;
|
|
721
|
+
kind: "header" | "footer";
|
|
722
|
+
variant: RuntimePageLocalStoryInstance["variant"];
|
|
723
|
+
relationshipId: string;
|
|
724
|
+
}
|
|
725
|
+
|
|
726
|
+
function collectStoryAnchoredObjects(
|
|
727
|
+
blocks: readonly BlockNode[],
|
|
728
|
+
context: StoryObjectContext,
|
|
729
|
+
): {
|
|
730
|
+
objects: RuntimeStoryAnchoredObject[];
|
|
731
|
+
divergences: RuntimeLayoutDivergence[];
|
|
732
|
+
} {
|
|
733
|
+
const objects: RuntimeStoryAnchoredObject[] = [];
|
|
734
|
+
const divergences: RuntimeLayoutDivergence[] = [];
|
|
735
|
+
let ordinal = 0;
|
|
736
|
+
|
|
737
|
+
const pushObject = (
|
|
738
|
+
object: Omit<RuntimeStoryAnchoredObject, "objectId" | "divergenceIds"> & {
|
|
739
|
+
objectId?: string;
|
|
740
|
+
preserveHint?: PreserveOnlyObjectSizing;
|
|
741
|
+
wrapMode?: string;
|
|
742
|
+
},
|
|
743
|
+
): void => {
|
|
744
|
+
const objectId =
|
|
745
|
+
object.objectId ?? `${context.storyKey}:object-${ordinal}:${object.sourceType}`;
|
|
746
|
+
const objectDivergenceIds: string[] = [];
|
|
747
|
+
const objectDisplay = object.display;
|
|
748
|
+
|
|
749
|
+
if (object.preserveOnly || object.preserveHint) {
|
|
750
|
+
const divergenceId = `${context.frameId}:preserve-only-placeholder:${objectId}`;
|
|
751
|
+
objectDivergenceIds.push(divergenceId);
|
|
752
|
+
divergences.push({
|
|
753
|
+
divergenceId,
|
|
754
|
+
kind: "preserve-only-placeholder",
|
|
755
|
+
source: "runtime",
|
|
756
|
+
severity: "info",
|
|
757
|
+
message: `Page-local ${context.kind} story contains preserve-only object ${objectId}`,
|
|
758
|
+
regionKinds: [context.kind],
|
|
759
|
+
objectIds: [objectId],
|
|
760
|
+
});
|
|
761
|
+
}
|
|
762
|
+
|
|
763
|
+
const wrapMode = object.wrapMode;
|
|
764
|
+
if (
|
|
765
|
+
objectDisplay === "floating" &&
|
|
766
|
+
wrapMode !== undefined &&
|
|
767
|
+
wrapMode !== "none" &&
|
|
768
|
+
wrapMode !== "topAndBottom"
|
|
769
|
+
) {
|
|
770
|
+
const divergenceId = `${context.frameId}:unsupported-wrap:${objectId}:${wrapMode}`;
|
|
771
|
+
objectDivergenceIds.push(divergenceId);
|
|
772
|
+
divergences.push({
|
|
773
|
+
divergenceId,
|
|
774
|
+
kind: "unsupported-wrap",
|
|
775
|
+
source: "runtime",
|
|
776
|
+
severity: "warning",
|
|
777
|
+
message: `Page-local ${context.kind} story object ${objectId} uses unsupported wrap mode ${wrapMode}`,
|
|
778
|
+
regionKinds: [context.kind],
|
|
779
|
+
objectIds: [objectId],
|
|
780
|
+
});
|
|
781
|
+
}
|
|
782
|
+
|
|
783
|
+
const { preserveHint: _preserveHint, wrapMode: _wrapMode, ...ledger } = object;
|
|
784
|
+
objects.push({
|
|
785
|
+
...ledger,
|
|
786
|
+
objectId,
|
|
787
|
+
divergenceIds: objectDivergenceIds,
|
|
788
|
+
});
|
|
789
|
+
ordinal += 1;
|
|
790
|
+
};
|
|
791
|
+
|
|
792
|
+
const visitBlock = (block: BlockNode): void => {
|
|
793
|
+
switch (block.type) {
|
|
794
|
+
case "paragraph":
|
|
795
|
+
for (const child of block.children) visitInline(child);
|
|
796
|
+
break;
|
|
797
|
+
case "table":
|
|
798
|
+
for (const row of block.rows) {
|
|
799
|
+
for (const cell of row.cells) {
|
|
800
|
+
for (const child of cell.children) visitBlock(child);
|
|
801
|
+
}
|
|
802
|
+
}
|
|
803
|
+
break;
|
|
804
|
+
case "sdt":
|
|
805
|
+
for (const child of block.children) visitBlock(child);
|
|
806
|
+
break;
|
|
807
|
+
default:
|
|
808
|
+
break;
|
|
809
|
+
}
|
|
810
|
+
};
|
|
811
|
+
|
|
812
|
+
const visitInline = (inline: InlineNode): void => {
|
|
813
|
+
switch (inline.type) {
|
|
814
|
+
case "image": {
|
|
815
|
+
pushObject({
|
|
816
|
+
objectId: inline.mediaId,
|
|
817
|
+
sourceType: "image",
|
|
818
|
+
display: inline.display === "floating" ? "floating" : "inline",
|
|
819
|
+
preserveOnly: false,
|
|
820
|
+
wrapMode: inline.floating?.wrap,
|
|
821
|
+
});
|
|
822
|
+
break;
|
|
823
|
+
}
|
|
824
|
+
case "drawing_frame": {
|
|
825
|
+
const preserveHint = getDrawingFramePreserveHint(inline);
|
|
826
|
+
const relationshipIds = collectDrawingRelationshipIds(inline);
|
|
827
|
+
const display = inline.anchor.display;
|
|
828
|
+
pushObject({
|
|
829
|
+
objectId: getDrawingFrameObjectId(inline, context.storyKey, ordinal),
|
|
830
|
+
sourceType: "drawing-frame",
|
|
831
|
+
display,
|
|
832
|
+
extentTwips: extentTwipsFromEmu(
|
|
833
|
+
inline.anchor.extent.widthEmu,
|
|
834
|
+
inline.anchor.extent.heightEmu,
|
|
835
|
+
),
|
|
836
|
+
...(relationshipIds.length > 0 ? { relationshipIds } : {}),
|
|
837
|
+
preserveOnly: Boolean(preserveHint),
|
|
838
|
+
...(preserveHint ? { preserveHint } : {}),
|
|
839
|
+
wrapMode: inline.anchor.wrapMode,
|
|
840
|
+
});
|
|
841
|
+
if (inline.content.type === "shape") {
|
|
842
|
+
for (const child of inline.content.txbxBlocks ?? []) visitBlock(child);
|
|
843
|
+
}
|
|
844
|
+
break;
|
|
845
|
+
}
|
|
846
|
+
case "chart_preview":
|
|
847
|
+
case "smartart_preview":
|
|
848
|
+
case "shape":
|
|
849
|
+
case "wordart":
|
|
850
|
+
case "vml_shape": {
|
|
851
|
+
const preserveHint = inline.preserveOnlyObject;
|
|
852
|
+
pushObject({
|
|
853
|
+
objectId: getPreserveOnlyObjectId(inline, context.storyKey, ordinal),
|
|
854
|
+
sourceType: sourceTypeForInlineObject(inline.type),
|
|
855
|
+
display: preserveHint?.display ?? "unknown",
|
|
856
|
+
...(preserveHint?.extentEmu
|
|
857
|
+
? {
|
|
858
|
+
extentTwips: extentTwipsFromEmu(
|
|
859
|
+
preserveHint.extentEmu.widthEmu,
|
|
860
|
+
preserveHint.extentEmu.heightEmu,
|
|
861
|
+
),
|
|
862
|
+
}
|
|
863
|
+
: {}),
|
|
864
|
+
...(preserveHint?.relationshipIds
|
|
865
|
+
? { relationshipIds: [...preserveHint.relationshipIds] }
|
|
866
|
+
: {}),
|
|
867
|
+
preserveOnly: Boolean(preserveHint),
|
|
868
|
+
...(preserveHint ? { preserveHint } : {}),
|
|
869
|
+
});
|
|
870
|
+
if (inline.type === "shape") {
|
|
871
|
+
for (const child of inline.txbxBlocks ?? []) visitBlock(child);
|
|
872
|
+
}
|
|
873
|
+
break;
|
|
874
|
+
}
|
|
875
|
+
case "ole_embed":
|
|
876
|
+
pushObject({
|
|
877
|
+
objectId: inline.id,
|
|
878
|
+
sourceType: "ole-embed",
|
|
879
|
+
display: "unknown",
|
|
880
|
+
relationshipIds: [inline.relationshipId],
|
|
881
|
+
preserveOnly: true,
|
|
882
|
+
});
|
|
883
|
+
break;
|
|
884
|
+
case "opaque_inline":
|
|
885
|
+
pushObject({
|
|
886
|
+
objectId: inline.fragmentId,
|
|
887
|
+
sourceType: "opaque-inline",
|
|
888
|
+
display: "unknown",
|
|
889
|
+
preserveOnly: true,
|
|
890
|
+
});
|
|
891
|
+
break;
|
|
892
|
+
case "field":
|
|
893
|
+
for (const child of inline.children) visitInline(child);
|
|
894
|
+
break;
|
|
895
|
+
case "hyperlink":
|
|
896
|
+
for (const child of inline.children) visitInline(child);
|
|
897
|
+
break;
|
|
898
|
+
default:
|
|
899
|
+
break;
|
|
900
|
+
}
|
|
901
|
+
};
|
|
902
|
+
|
|
903
|
+
for (const block of blocks) visitBlock(block);
|
|
904
|
+
return { objects, divergences };
|
|
905
|
+
}
|
|
906
|
+
|
|
907
|
+
function getDrawingFramePreserveHint(
|
|
908
|
+
inline: Extract<InlineNode, { type: "drawing_frame" }>,
|
|
909
|
+
): PreserveOnlyObjectSizing | undefined {
|
|
910
|
+
const content = inline.content;
|
|
911
|
+
if (content.type === "picture") return undefined;
|
|
912
|
+
return content.preserveOnlyObject;
|
|
913
|
+
}
|
|
914
|
+
|
|
915
|
+
function collectDrawingRelationshipIds(
|
|
916
|
+
inline: Extract<InlineNode, { type: "drawing_frame" }>,
|
|
917
|
+
): string[] {
|
|
918
|
+
const content = inline.content;
|
|
919
|
+
if (content.type === "picture") return [content.blipRef];
|
|
920
|
+
const preserveIds = content.preserveOnlyObject?.relationshipIds ?? [];
|
|
921
|
+
return [...preserveIds];
|
|
922
|
+
}
|
|
923
|
+
|
|
924
|
+
function getDrawingFrameObjectId(
|
|
925
|
+
inline: Extract<InlineNode, { type: "drawing_frame" }>,
|
|
926
|
+
storyKey: string,
|
|
927
|
+
ordinal: number,
|
|
928
|
+
): string {
|
|
929
|
+
if (inline.anchor.docPr?.id) return `${storyKey}:drawing-${inline.anchor.docPr.id}`;
|
|
930
|
+
if (inline.content.type === "picture") return `${storyKey}:picture-${inline.content.blipRef}`;
|
|
931
|
+
return `${storyKey}:drawing-${ordinal}`;
|
|
932
|
+
}
|
|
933
|
+
|
|
934
|
+
function getPreserveOnlyObjectId(
|
|
935
|
+
inline: Extract<
|
|
936
|
+
InlineNode,
|
|
937
|
+
{ type: "chart_preview" | "smartart_preview" | "shape" | "wordart" | "vml_shape" }
|
|
938
|
+
>,
|
|
939
|
+
storyKey: string,
|
|
940
|
+
ordinal: number,
|
|
941
|
+
): string {
|
|
942
|
+
return inline.preserveOnlyObject?.sourceId ?? `${storyKey}:${inline.type}-${ordinal}`;
|
|
943
|
+
}
|
|
944
|
+
|
|
945
|
+
function sourceTypeForInlineObject(
|
|
946
|
+
type: Extract<
|
|
947
|
+
InlineNode,
|
|
948
|
+
{ type: "chart_preview" | "smartart_preview" | "shape" | "wordart" | "vml_shape" }
|
|
949
|
+
>["type"],
|
|
950
|
+
): RuntimeStoryAnchoredObject["sourceType"] {
|
|
951
|
+
switch (type) {
|
|
952
|
+
case "chart_preview":
|
|
953
|
+
return "chart-preview";
|
|
954
|
+
case "smartart_preview":
|
|
955
|
+
return "smartart-preview";
|
|
956
|
+
case "shape":
|
|
957
|
+
return "shape";
|
|
958
|
+
case "wordart":
|
|
959
|
+
return "wordart";
|
|
960
|
+
case "vml_shape":
|
|
961
|
+
return "vml-shape";
|
|
962
|
+
}
|
|
963
|
+
}
|
|
964
|
+
|
|
965
|
+
function extentTwipsFromEmu(
|
|
966
|
+
widthEmu: number,
|
|
967
|
+
heightEmu: number,
|
|
968
|
+
): RuntimeStoryAnchoredObject["extentTwips"] {
|
|
969
|
+
return {
|
|
970
|
+
widthTwips: Math.max(0, Math.round(widthEmu / EMUS_PER_TWIP)),
|
|
971
|
+
heightTwips: Math.max(0, Math.round(heightEmu / EMUS_PER_TWIP)),
|
|
972
|
+
};
|
|
973
|
+
}
|
|
974
|
+
|
|
975
|
+
function resolvePageInstanceFieldDisplayText(
|
|
976
|
+
family: string,
|
|
977
|
+
cachedDisplayText: string,
|
|
978
|
+
context: PageFieldResolutionInput,
|
|
979
|
+
): string {
|
|
980
|
+
switch (family) {
|
|
981
|
+
case "PAGE":
|
|
982
|
+
return formatPageNumberWithChapter(
|
|
983
|
+
context.displayPageNumber,
|
|
984
|
+
context.layout.pageNumbering,
|
|
985
|
+
);
|
|
986
|
+
case "NUMPAGES":
|
|
987
|
+
return formatPageNumber(
|
|
988
|
+
context.pageFieldCounts.contentPageCount,
|
|
989
|
+
context.layout.pageNumbering?.format,
|
|
990
|
+
);
|
|
991
|
+
case "SECTIONPAGES":
|
|
992
|
+
return formatPageNumber(
|
|
993
|
+
context.pageFieldCounts.sectionContentPageCountByIndex.get(context.sectionIndex) ?? 0,
|
|
994
|
+
context.layout.pageNumbering?.format,
|
|
995
|
+
);
|
|
996
|
+
default:
|
|
997
|
+
return cachedDisplayText;
|
|
998
|
+
}
|
|
999
|
+
}
|
|
1000
|
+
|
|
1001
|
+
function flattenInline(inlines: ReadonlyArray<InlineNode> | undefined): string {
|
|
1002
|
+
if (!inlines) return "";
|
|
1003
|
+
let buf = "";
|
|
1004
|
+
for (const inline of inlines) {
|
|
1005
|
+
if (inline.type === "text") buf += inline.text;
|
|
1006
|
+
else if (inline.type === "hard_break" || inline.type === "tab") buf += " ";
|
|
1007
|
+
}
|
|
1008
|
+
return buf;
|
|
1009
|
+
}
|
|
1010
|
+
|
|
1011
|
+
function classifyFieldInstructionLocal(instr: string): string {
|
|
1012
|
+
const match = /^\s*(\w+)/.exec(instr);
|
|
1013
|
+
return match ? match[1]!.toUpperCase() : "UNKNOWN";
|
|
1014
|
+
}
|
|
1015
|
+
|
|
1016
|
+
function isHeaderFooterStoryTarget(
|
|
1017
|
+
target: EditorStoryTarget | undefined,
|
|
1018
|
+
): target is HeaderFooterStoryTarget {
|
|
1019
|
+
return target?.kind === "header" || target?.kind === "footer";
|
|
1020
|
+
}
|
|
1021
|
+
|
|
1022
|
+
function buildPageFrameId(
|
|
1023
|
+
pageIndex: number,
|
|
1024
|
+
sectionIndex: number,
|
|
1025
|
+
displayPageNumber: number,
|
|
1026
|
+
): string {
|
|
1027
|
+
return `page-frame-${pageIndex}-section-${sectionIndex}-display-${displayPageNumber}`;
|
|
1028
|
+
}
|
|
1029
|
+
|
|
1030
|
+
function buildPageFrameSignature(
|
|
1031
|
+
input: {
|
|
1032
|
+
pageIndex: number;
|
|
1033
|
+
sectionIndex: number;
|
|
1034
|
+
displayPageNumber: number;
|
|
1035
|
+
layout: PageLayoutSnapshot;
|
|
1036
|
+
regions: RuntimePageRegions;
|
|
1037
|
+
},
|
|
1038
|
+
pageLocalStories: readonly RuntimePageLocalStoryInstance[],
|
|
1039
|
+
divergenceIds: readonly string[],
|
|
1040
|
+
): string {
|
|
1041
|
+
const regionParts = collectRegions(input.regions).map((region) => {
|
|
1042
|
+
const r = region.rectTwips ?? rect(0, region.originTwips, region.widthTwips, region.heightTwips);
|
|
1043
|
+
return [
|
|
1044
|
+
region.kind,
|
|
1045
|
+
r.xTwips,
|
|
1046
|
+
r.yTwips,
|
|
1047
|
+
r.widthTwips,
|
|
1048
|
+
r.heightTwips,
|
|
1049
|
+
region.fragmentIds.length,
|
|
1050
|
+
].join(":");
|
|
1051
|
+
});
|
|
1052
|
+
return [
|
|
1053
|
+
"page-frame",
|
|
1054
|
+
input.pageIndex,
|
|
1055
|
+
input.sectionIndex,
|
|
1056
|
+
input.displayPageNumber,
|
|
1057
|
+
input.layout.pageWidth,
|
|
1058
|
+
input.layout.pageHeight,
|
|
1059
|
+
...regionParts,
|
|
1060
|
+
...pageLocalStories.map((story) => story.signature),
|
|
1061
|
+
...divergenceIds,
|
|
1062
|
+
].join("|");
|
|
1063
|
+
}
|
|
1064
|
+
|
|
1065
|
+
function detectFrameDivergences(
|
|
1066
|
+
frameId: string,
|
|
1067
|
+
regions: RuntimePageRegions,
|
|
1068
|
+
): RuntimeLayoutDivergence[] {
|
|
1069
|
+
const candidates = collectRegions(regions).filter((region) => region.rectTwips !== undefined);
|
|
1070
|
+
const divergences: RuntimeLayoutDivergence[] = [];
|
|
1071
|
+
for (let i = 0; i < candidates.length; i += 1) {
|
|
1072
|
+
for (let j = i + 1; j < candidates.length; j += 1) {
|
|
1073
|
+
const a = candidates[i]!;
|
|
1074
|
+
const b = candidates[j]!;
|
|
1075
|
+
if (!shouldDetectCollision(a, b)) continue;
|
|
1076
|
+
if (!rectsOverlap(a.rectTwips!, b.rectTwips!)) continue;
|
|
1077
|
+
const kinds = [a.kind, b.kind].sort();
|
|
1078
|
+
divergences.push({
|
|
1079
|
+
divergenceId: `${frameId}:frame-collision:${kinds.join("-")}`,
|
|
1080
|
+
kind: "frame-collision",
|
|
1081
|
+
source: "runtime",
|
|
1082
|
+
severity: "warning",
|
|
1083
|
+
message: `Resolved page regions overlap: ${kinds.join(" / ")}`,
|
|
1084
|
+
regionKinds: kinds as RuntimeLayoutDivergence["regionKinds"],
|
|
1085
|
+
fragmentIds: [...a.fragmentIds, ...b.fragmentIds],
|
|
1086
|
+
});
|
|
1087
|
+
}
|
|
1088
|
+
}
|
|
1089
|
+
return divergences;
|
|
1090
|
+
}
|
|
1091
|
+
|
|
1092
|
+
function collectRegions(regions: RuntimePageRegions): RuntimePageRegion[] {
|
|
1093
|
+
return [
|
|
1094
|
+
regions.body,
|
|
1095
|
+
...(regions.header ? [regions.header] : []),
|
|
1096
|
+
...(regions.footer ? [regions.footer] : []),
|
|
1097
|
+
...(regions.columns ?? []),
|
|
1098
|
+
...(regions.footnotes ?? []),
|
|
1099
|
+
];
|
|
1100
|
+
}
|
|
1101
|
+
|
|
1102
|
+
function shouldDetectCollision(a: RuntimePageRegion, b: RuntimePageRegion): boolean {
|
|
1103
|
+
if (a.kind === "body" && b.kind === "column") return false;
|
|
1104
|
+
if (a.kind === "column" && b.kind === "body") return false;
|
|
1105
|
+
if (a.kind === "column" && b.kind === "column") return false;
|
|
1106
|
+
return true;
|
|
1107
|
+
}
|
|
1108
|
+
|
|
1109
|
+
function rectsOverlap(a: RuntimeTwipsRect, b: RuntimeTwipsRect): boolean {
|
|
1110
|
+
return (
|
|
1111
|
+
a.xTwips < b.xTwips + b.widthTwips &&
|
|
1112
|
+
a.xTwips + a.widthTwips > b.xTwips &&
|
|
1113
|
+
a.yTwips < b.yTwips + b.heightTwips &&
|
|
1114
|
+
a.yTwips + a.heightTwips > b.yTwips
|
|
1115
|
+
);
|
|
1116
|
+
}
|
|
1117
|
+
|
|
315
1118
|
// ---------------------------------------------------------------------------
|
|
316
1119
|
// Graph queries
|
|
317
1120
|
// ---------------------------------------------------------------------------
|
|
@@ -464,10 +1267,11 @@ export function spliceGraph(
|
|
|
464
1267
|
}));
|
|
465
1268
|
|
|
466
1269
|
const contentPageCount = nextPages.filter((p) => !p.isBlankFiller).length;
|
|
1270
|
+
const normalizedPages = normalizePageLocalStoryFieldsForPages(nextPages);
|
|
467
1271
|
|
|
468
1272
|
return {
|
|
469
1273
|
revision: graphRevision,
|
|
470
|
-
pages:
|
|
1274
|
+
pages: normalizedPages,
|
|
471
1275
|
fragments: mergedFragments,
|
|
472
1276
|
anchors,
|
|
473
1277
|
sections: [...prior.sections],
|
|
@@ -475,6 +1279,77 @@ export function spliceGraph(
|
|
|
475
1279
|
};
|
|
476
1280
|
}
|
|
477
1281
|
|
|
1282
|
+
function normalizePageLocalStoryFieldsForPages(
|
|
1283
|
+
pages: readonly RuntimePageNode[],
|
|
1284
|
+
): RuntimePageNode[] {
|
|
1285
|
+
const pageFieldCounts = buildPageFieldCounts(pages);
|
|
1286
|
+
return pages.map((page) => {
|
|
1287
|
+
const frame = page.frame;
|
|
1288
|
+
if (!frame || frame.pageLocalStories.every((story) => story.resolvedFields.length === 0)) {
|
|
1289
|
+
return page;
|
|
1290
|
+
}
|
|
1291
|
+
|
|
1292
|
+
let changed = false;
|
|
1293
|
+
const pageLocalStories = frame.pageLocalStories.map((story) => {
|
|
1294
|
+
if (story.resolvedFields.length === 0) return story;
|
|
1295
|
+
let storyChanged = false;
|
|
1296
|
+
const resolvedFields = story.resolvedFields.map((field) => {
|
|
1297
|
+
const displayText = resolvePageInstanceFieldDisplayText(
|
|
1298
|
+
field.family,
|
|
1299
|
+
field.displayText,
|
|
1300
|
+
{
|
|
1301
|
+
storyKey: story.storyKey,
|
|
1302
|
+
pageIndex: page.pageIndex,
|
|
1303
|
+
sectionIndex: page.sectionIndex,
|
|
1304
|
+
displayPageNumber: page.stories.displayPageNumber,
|
|
1305
|
+
layout: page.layout,
|
|
1306
|
+
pageFieldCounts,
|
|
1307
|
+
},
|
|
1308
|
+
);
|
|
1309
|
+
if (displayText === field.displayText) return field;
|
|
1310
|
+
storyChanged = true;
|
|
1311
|
+
changed = true;
|
|
1312
|
+
return { ...field, displayText };
|
|
1313
|
+
});
|
|
1314
|
+
if (!storyChanged) return story;
|
|
1315
|
+
const sectionPart =
|
|
1316
|
+
story.sectionIndex === undefined ? "section-unknown" : `section-${story.sectionIndex}`;
|
|
1317
|
+
return {
|
|
1318
|
+
...story,
|
|
1319
|
+
resolvedFields,
|
|
1320
|
+
signature: buildPageLocalStorySignature({
|
|
1321
|
+
kind: story.kind,
|
|
1322
|
+
variant: story.variant,
|
|
1323
|
+
relationshipId: story.relationshipId,
|
|
1324
|
+
sectionPart,
|
|
1325
|
+
measuredFrameHeightTwips: story.measuredFrameHeightTwips,
|
|
1326
|
+
resolvedFields,
|
|
1327
|
+
anchoredObjects: story.anchoredObjects,
|
|
1328
|
+
}),
|
|
1329
|
+
};
|
|
1330
|
+
});
|
|
1331
|
+
|
|
1332
|
+
if (!changed) return page;
|
|
1333
|
+
const divergenceIds = frame.divergenceIds;
|
|
1334
|
+
const nextFrame: RuntimePageFrame = {
|
|
1335
|
+
...frame,
|
|
1336
|
+
pageLocalStories,
|
|
1337
|
+
signature: buildPageFrameSignature(
|
|
1338
|
+
{
|
|
1339
|
+
pageIndex: frame.pageIndex,
|
|
1340
|
+
sectionIndex: frame.sectionIndex,
|
|
1341
|
+
displayPageNumber: frame.displayPageNumber,
|
|
1342
|
+
layout: page.layout,
|
|
1343
|
+
regions: page.regions,
|
|
1344
|
+
},
|
|
1345
|
+
pageLocalStories,
|
|
1346
|
+
divergenceIds,
|
|
1347
|
+
),
|
|
1348
|
+
};
|
|
1349
|
+
return { ...page, frame: nextFrame };
|
|
1350
|
+
});
|
|
1351
|
+
}
|
|
1352
|
+
|
|
478
1353
|
/**
|
|
479
1354
|
* Compare two `RuntimePageNode`s by the fields that matter for
|
|
480
1355
|
* pagination identity — if these all match, the fresh node represents
|