@beyondwork/docx-react-component 1.0.103 → 1.0.104
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +1 -1
- package/src/api/public-types.ts +63 -1
- package/src/api/v3/_runtime-handle.ts +2 -0
- package/src/api/v3/ai/outline.ts +2 -7
- package/src/api/v3/runtime/geometry.ts +79 -0
- package/src/io/ooxml/parse-drawing.ts +99 -1
- package/src/io/ooxml/parse-fields.ts +27 -6
- package/src/io/ooxml/parse-shapes.ts +130 -0
- package/src/model/canonical-document.ts +34 -3
- package/src/model/canonical-layout-inputs.ts +979 -0
- package/src/model/layout/index.ts +6 -0
- package/src/model/layout/page-graph-types.ts +61 -0
- package/src/model/layout/runtime-page-graph-types.ts +10 -0
- package/src/runtime/collab/runtime-collab-sync.ts +3 -3
- package/src/runtime/debug/build-debug-inspector-snapshot.ts +17 -4
- package/src/runtime/document-runtime.ts +30 -14
- package/src/runtime/event-refresh-hints.ts +3 -0
- package/src/runtime/formatting/formatting-context.ts +110 -9
- package/src/runtime/formatting/index.ts +2 -0
- package/src/runtime/formatting/layout-inputs.ts +67 -3
- package/src/runtime/geometry/caret-geometry.ts +82 -10
- package/src/runtime/geometry/geometry-facet.ts +36 -0
- package/src/runtime/geometry/geometry-index.ts +891 -0
- package/src/runtime/geometry/geometry-types.ts +221 -1
- package/src/runtime/geometry/index.ts +26 -0
- package/src/runtime/geometry/inert-geometry-facet.ts +3 -0
- package/src/runtime/geometry/replacement-envelope.ts +41 -2
- package/src/runtime/layout/layout-engine-version.ts +16 -1
- package/src/runtime/layout/page-graph.ts +191 -1
- package/src/runtime/prerender/graph-canonicalize.ts +30 -0
- package/src/runtime/surface-projection.ts +43 -3
- package/src/runtime/workflow/coordinator.ts +57 -11
- package/src/ui-tailwind/chrome-overlay/tw-page-stack-overlay-layer.tsx +3 -0
|
@@ -0,0 +1,979 @@
|
|
|
1
|
+
import type {
|
|
2
|
+
AnchorGeometry,
|
|
3
|
+
BlockNode,
|
|
4
|
+
CanonicalDocument,
|
|
5
|
+
DrawingFrameNode,
|
|
6
|
+
FieldFamily,
|
|
7
|
+
FieldRefreshStatus,
|
|
8
|
+
HeaderFooterReference,
|
|
9
|
+
HeaderFooterVariant,
|
|
10
|
+
ImageNode,
|
|
11
|
+
InlineNode,
|
|
12
|
+
SectionProperties,
|
|
13
|
+
TableCellNode,
|
|
14
|
+
TableNode,
|
|
15
|
+
TableRowNode,
|
|
16
|
+
TocRegion,
|
|
17
|
+
} from "./canonical-document.ts";
|
|
18
|
+
|
|
19
|
+
export const MAIN_STORY_KEY = "main";
|
|
20
|
+
|
|
21
|
+
export type CanonicalStoryKind =
|
|
22
|
+
| "main"
|
|
23
|
+
| "header"
|
|
24
|
+
| "footer"
|
|
25
|
+
| "footnote"
|
|
26
|
+
| "endnote";
|
|
27
|
+
|
|
28
|
+
export interface CanonicalStoryIdentity {
|
|
29
|
+
readonly storyKey: string;
|
|
30
|
+
readonly kind: CanonicalStoryKind;
|
|
31
|
+
readonly blockCount: number;
|
|
32
|
+
readonly sectionIndex?: number;
|
|
33
|
+
readonly boundSectionIndexes?: readonly number[];
|
|
34
|
+
readonly explicitSectionIndexes?: readonly number[];
|
|
35
|
+
readonly inheritedSectionIndexes?: readonly number[];
|
|
36
|
+
readonly variant?: HeaderFooterVariant;
|
|
37
|
+
readonly relationshipId?: string;
|
|
38
|
+
readonly partPath?: string;
|
|
39
|
+
readonly noteId?: string;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
export type CanonicalFieldRegionKind = "field" | "toc-region";
|
|
43
|
+
|
|
44
|
+
export interface CanonicalFieldRegionIdentity {
|
|
45
|
+
readonly regionId: string;
|
|
46
|
+
readonly kind: CanonicalFieldRegionKind;
|
|
47
|
+
readonly storyKey: string;
|
|
48
|
+
readonly fieldIndex: number;
|
|
49
|
+
readonly fieldFamily: FieldFamily;
|
|
50
|
+
readonly refreshStatus: FieldRefreshStatus;
|
|
51
|
+
readonly paragraphIndex: number;
|
|
52
|
+
readonly tocId?: string;
|
|
53
|
+
readonly sourcePath?: string;
|
|
54
|
+
readonly parentKind?: TocRegion["parentKind"];
|
|
55
|
+
readonly resultRange?: TocRegion["resultRange"];
|
|
56
|
+
readonly status?: TocRegion["status"];
|
|
57
|
+
readonly cachedEntryCount?: number;
|
|
58
|
+
readonly generatedEntryCount?: number;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
export interface CanonicalLayoutInputs {
|
|
62
|
+
readonly stories: readonly CanonicalStoryIdentity[];
|
|
63
|
+
readonly fieldRegions: readonly CanonicalFieldRegionIdentity[];
|
|
64
|
+
readonly tables: readonly CanonicalTableLayoutInput[];
|
|
65
|
+
readonly anchors: readonly CanonicalAnchorLayoutInput[];
|
|
66
|
+
readonly scopes: readonly CanonicalScopeLayoutInput[];
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
export interface CanonicalTableLayoutInput {
|
|
70
|
+
readonly tableKey: string;
|
|
71
|
+
readonly storyKey: string;
|
|
72
|
+
readonly blockPath: string;
|
|
73
|
+
readonly rowCount: number;
|
|
74
|
+
readonly columnCount: number;
|
|
75
|
+
readonly gridColumns: readonly number[];
|
|
76
|
+
readonly width?: TableNode["width"];
|
|
77
|
+
readonly alignment?: TableNode["alignment"];
|
|
78
|
+
readonly borders?: TableNode["borders"];
|
|
79
|
+
readonly cellMargins?: TableNode["cellMargins"];
|
|
80
|
+
readonly tblLook?: TableNode["tblLook"];
|
|
81
|
+
readonly indent?: TableNode["indent"];
|
|
82
|
+
readonly layoutMode?: TableNode["layoutMode"];
|
|
83
|
+
readonly cellSpacing?: TableNode["cellSpacing"];
|
|
84
|
+
readonly bidiVisual?: boolean;
|
|
85
|
+
readonly floating?: TableNode["floating"];
|
|
86
|
+
readonly rows: readonly CanonicalTableRowLayoutInput[];
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
export interface CanonicalTableRowLayoutInput {
|
|
90
|
+
readonly rowKey: string;
|
|
91
|
+
readonly rowIndex: number;
|
|
92
|
+
readonly cellCount: number;
|
|
93
|
+
readonly gridBefore?: number;
|
|
94
|
+
readonly widthBefore?: TableRowNode["widthBefore"];
|
|
95
|
+
readonly gridAfter?: number;
|
|
96
|
+
readonly widthAfter?: TableRowNode["widthAfter"];
|
|
97
|
+
readonly height?: number;
|
|
98
|
+
readonly heightRule?: TableRowNode["heightRule"];
|
|
99
|
+
readonly isHeader?: boolean;
|
|
100
|
+
readonly cantSplit?: boolean;
|
|
101
|
+
readonly horizontalAlignment?: TableRowNode["horizontalAlignment"];
|
|
102
|
+
readonly cnfStyle?: string;
|
|
103
|
+
readonly cells: readonly CanonicalTableCellLayoutInput[];
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
export interface CanonicalTableCellLayoutInput {
|
|
107
|
+
readonly cellKey: string;
|
|
108
|
+
readonly rowIndex: number;
|
|
109
|
+
readonly cellIndex: number;
|
|
110
|
+
readonly gridColumnStart: number;
|
|
111
|
+
readonly gridSpan: number;
|
|
112
|
+
readonly verticalMerge?: TableCellNode["verticalMerge"];
|
|
113
|
+
readonly width?: TableCellNode["width"];
|
|
114
|
+
readonly borders?: TableCellNode["borders"];
|
|
115
|
+
readonly shading?: TableCellNode["shading"];
|
|
116
|
+
readonly verticalAlign?: TableCellNode["verticalAlign"];
|
|
117
|
+
readonly textDirection?: TableCellNode["textDirection"];
|
|
118
|
+
readonly noWrap?: boolean;
|
|
119
|
+
readonly fitText?: boolean;
|
|
120
|
+
readonly margins?: TableCellNode["margins"];
|
|
121
|
+
readonly cnfStyle?: string;
|
|
122
|
+
readonly blockCount: number;
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
export type CanonicalAnchorObjectKind =
|
|
126
|
+
| "legacy-image"
|
|
127
|
+
| "picture"
|
|
128
|
+
| "shape"
|
|
129
|
+
| "textbox"
|
|
130
|
+
| "chart"
|
|
131
|
+
| "smartart"
|
|
132
|
+
| "opaque";
|
|
133
|
+
|
|
134
|
+
export type CanonicalAnchorEditPosture =
|
|
135
|
+
| "editable"
|
|
136
|
+
| "read-only-preview"
|
|
137
|
+
| "preserve-only";
|
|
138
|
+
|
|
139
|
+
export interface CanonicalAnchorLayoutInput {
|
|
140
|
+
readonly objectKey: string;
|
|
141
|
+
readonly storyKey: string;
|
|
142
|
+
readonly blockPath: string;
|
|
143
|
+
readonly inlinePath: string;
|
|
144
|
+
readonly objectKind: CanonicalAnchorObjectKind;
|
|
145
|
+
readonly editPosture: CanonicalAnchorEditPosture;
|
|
146
|
+
readonly display?: "inline" | "floating";
|
|
147
|
+
readonly extent?: AnchorGeometry["extent"];
|
|
148
|
+
readonly wrapMode?: AnchorGeometry["wrapMode"];
|
|
149
|
+
readonly wrapPolygonPointCount?: number;
|
|
150
|
+
readonly positionH?: AnchorGeometry["positionH"];
|
|
151
|
+
readonly positionV?: AnchorGeometry["positionV"];
|
|
152
|
+
readonly distMargins?: AnchorGeometry["distMargins"];
|
|
153
|
+
readonly relativeHeight?: number;
|
|
154
|
+
readonly zOrder?: "behind-document" | "in-front-of-text";
|
|
155
|
+
readonly layoutInCell?: boolean;
|
|
156
|
+
readonly allowOverlap?: boolean;
|
|
157
|
+
readonly simplePos?: boolean;
|
|
158
|
+
readonly docPrId?: string;
|
|
159
|
+
readonly docPrName?: string;
|
|
160
|
+
readonly docPrDescription?: string;
|
|
161
|
+
readonly hidden?: boolean;
|
|
162
|
+
readonly frameLocks?: AnchorGeometry["frameLocks"];
|
|
163
|
+
readonly mediaId?: string;
|
|
164
|
+
readonly packagePartName?: string;
|
|
165
|
+
readonly relationshipId?: string;
|
|
166
|
+
readonly isLinked?: boolean;
|
|
167
|
+
readonly hasTextBoxBlocks?: boolean;
|
|
168
|
+
readonly textBoxBlockCount?: number;
|
|
169
|
+
readonly rawXmlPreserved?: boolean;
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
export type CanonicalScopeLayoutStatus = "paired" | "start-only" | "end-only";
|
|
173
|
+
|
|
174
|
+
export interface CanonicalScopeMarkerReference {
|
|
175
|
+
readonly markerKey: string;
|
|
176
|
+
readonly blockPath: string;
|
|
177
|
+
readonly inlinePath: string;
|
|
178
|
+
readonly markerIndex: number;
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
export interface CanonicalScopeLayoutInput {
|
|
182
|
+
readonly scopeKey: string;
|
|
183
|
+
readonly scopeId: string;
|
|
184
|
+
readonly storyKey: string;
|
|
185
|
+
readonly status: CanonicalScopeLayoutStatus;
|
|
186
|
+
readonly source: "canonical-marker";
|
|
187
|
+
readonly start?: CanonicalScopeMarkerReference;
|
|
188
|
+
readonly end?: CanonicalScopeMarkerReference;
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
export interface CanonicalStoryBlockContext {
|
|
192
|
+
readonly storyKey: string;
|
|
193
|
+
readonly blocks: readonly BlockNode[];
|
|
194
|
+
readonly basePath: string;
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
export function createHeaderFooterStoryKey(input: {
|
|
198
|
+
kind: "header" | "footer";
|
|
199
|
+
relationshipId: string;
|
|
200
|
+
variant: HeaderFooterVariant;
|
|
201
|
+
sectionIndex?: number;
|
|
202
|
+
}): string {
|
|
203
|
+
const section = input.sectionIndex === undefined ? "unscoped" : String(input.sectionIndex);
|
|
204
|
+
return `${input.kind}:${section}:${input.variant}:${input.relationshipId}`;
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
export function createNoteStoryKey(
|
|
208
|
+
kind: "footnote" | "endnote",
|
|
209
|
+
noteId: string,
|
|
210
|
+
): string {
|
|
211
|
+
return `${kind}:${noteId}`;
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
export function collectCanonicalLayoutInputs(
|
|
215
|
+
doc: CanonicalDocument,
|
|
216
|
+
): CanonicalLayoutInputs {
|
|
217
|
+
const blockContexts = collectStoryBlockContexts(doc);
|
|
218
|
+
return {
|
|
219
|
+
stories: collectCanonicalStoryIdentities(doc),
|
|
220
|
+
fieldRegions: collectCanonicalFieldRegionIdentities(doc),
|
|
221
|
+
tables: collectCanonicalTableLayoutInputs(blockContexts),
|
|
222
|
+
anchors: collectCanonicalAnchorLayoutInputs(doc, blockContexts),
|
|
223
|
+
scopes: collectCanonicalScopeLayoutInputs(blockContexts),
|
|
224
|
+
};
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
export function collectCanonicalStoryIdentities(
|
|
228
|
+
doc: CanonicalDocument,
|
|
229
|
+
): CanonicalStoryIdentity[] {
|
|
230
|
+
const stories: CanonicalStoryIdentity[] = [
|
|
231
|
+
{
|
|
232
|
+
storyKey: MAIN_STORY_KEY,
|
|
233
|
+
kind: "main",
|
|
234
|
+
blockCount: doc.content.children.length,
|
|
235
|
+
},
|
|
236
|
+
];
|
|
237
|
+
|
|
238
|
+
const sectionCount = countSections(doc);
|
|
239
|
+
for (const header of doc.subParts?.headers ?? []) {
|
|
240
|
+
const bindings = collectHeaderFooterBindings(
|
|
241
|
+
doc,
|
|
242
|
+
"header",
|
|
243
|
+
header.relationshipId,
|
|
244
|
+
header.variant,
|
|
245
|
+
header.sectionIndex,
|
|
246
|
+
sectionCount,
|
|
247
|
+
);
|
|
248
|
+
stories.push({
|
|
249
|
+
storyKey: createHeaderFooterStoryKey({
|
|
250
|
+
kind: "header",
|
|
251
|
+
relationshipId: header.relationshipId,
|
|
252
|
+
variant: header.variant,
|
|
253
|
+
...(header.sectionIndex !== undefined
|
|
254
|
+
? { sectionIndex: header.sectionIndex }
|
|
255
|
+
: {}),
|
|
256
|
+
}),
|
|
257
|
+
kind: "header",
|
|
258
|
+
blockCount: header.blocks.length,
|
|
259
|
+
variant: header.variant,
|
|
260
|
+
relationshipId: header.relationshipId,
|
|
261
|
+
partPath: header.partPath,
|
|
262
|
+
...(header.sectionIndex !== undefined ? { sectionIndex: header.sectionIndex } : {}),
|
|
263
|
+
boundSectionIndexes: bindings.boundSectionIndexes,
|
|
264
|
+
explicitSectionIndexes: bindings.explicitSectionIndexes,
|
|
265
|
+
inheritedSectionIndexes: bindings.inheritedSectionIndexes,
|
|
266
|
+
});
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
for (const footer of doc.subParts?.footers ?? []) {
|
|
270
|
+
const bindings = collectHeaderFooterBindings(
|
|
271
|
+
doc,
|
|
272
|
+
"footer",
|
|
273
|
+
footer.relationshipId,
|
|
274
|
+
footer.variant,
|
|
275
|
+
footer.sectionIndex,
|
|
276
|
+
sectionCount,
|
|
277
|
+
);
|
|
278
|
+
stories.push({
|
|
279
|
+
storyKey: createHeaderFooterStoryKey({
|
|
280
|
+
kind: "footer",
|
|
281
|
+
relationshipId: footer.relationshipId,
|
|
282
|
+
variant: footer.variant,
|
|
283
|
+
...(footer.sectionIndex !== undefined
|
|
284
|
+
? { sectionIndex: footer.sectionIndex }
|
|
285
|
+
: {}),
|
|
286
|
+
}),
|
|
287
|
+
kind: "footer",
|
|
288
|
+
blockCount: footer.blocks.length,
|
|
289
|
+
variant: footer.variant,
|
|
290
|
+
relationshipId: footer.relationshipId,
|
|
291
|
+
partPath: footer.partPath,
|
|
292
|
+
...(footer.sectionIndex !== undefined ? { sectionIndex: footer.sectionIndex } : {}),
|
|
293
|
+
boundSectionIndexes: bindings.boundSectionIndexes,
|
|
294
|
+
explicitSectionIndexes: bindings.explicitSectionIndexes,
|
|
295
|
+
inheritedSectionIndexes: bindings.inheritedSectionIndexes,
|
|
296
|
+
});
|
|
297
|
+
}
|
|
298
|
+
|
|
299
|
+
const notes = doc.subParts?.footnoteCollection;
|
|
300
|
+
if (notes) {
|
|
301
|
+
for (const note of Object.values(notes.footnotes)) {
|
|
302
|
+
stories.push({
|
|
303
|
+
storyKey: createNoteStoryKey("footnote", note.noteId),
|
|
304
|
+
kind: "footnote",
|
|
305
|
+
noteId: note.noteId,
|
|
306
|
+
blockCount: note.blocks.length,
|
|
307
|
+
});
|
|
308
|
+
}
|
|
309
|
+
for (const note of Object.values(notes.endnotes)) {
|
|
310
|
+
stories.push({
|
|
311
|
+
storyKey: createNoteStoryKey("endnote", note.noteId),
|
|
312
|
+
kind: "endnote",
|
|
313
|
+
noteId: note.noteId,
|
|
314
|
+
blockCount: note.blocks.length,
|
|
315
|
+
});
|
|
316
|
+
}
|
|
317
|
+
}
|
|
318
|
+
|
|
319
|
+
return stories;
|
|
320
|
+
}
|
|
321
|
+
|
|
322
|
+
export function collectCanonicalFieldRegionIdentities(
|
|
323
|
+
doc: CanonicalDocument,
|
|
324
|
+
): CanonicalFieldRegionIdentity[] {
|
|
325
|
+
const registry = doc.fieldRegistry;
|
|
326
|
+
if (!registry) return [];
|
|
327
|
+
|
|
328
|
+
const byFieldIndex = new Map(
|
|
329
|
+
[...registry.supported, ...registry.preserveOnly].map((entry) => [
|
|
330
|
+
entry.fieldIndex,
|
|
331
|
+
entry,
|
|
332
|
+
]),
|
|
333
|
+
);
|
|
334
|
+
const regions: CanonicalFieldRegionIdentity[] = [];
|
|
335
|
+
|
|
336
|
+
for (const entry of byFieldIndex.values()) {
|
|
337
|
+
regions.push({
|
|
338
|
+
regionId: `field:${entry.fieldIndex}`,
|
|
339
|
+
kind: "field",
|
|
340
|
+
storyKey: entry.storyKey ?? MAIN_STORY_KEY,
|
|
341
|
+
fieldIndex: entry.fieldIndex,
|
|
342
|
+
fieldFamily: entry.fieldFamily,
|
|
343
|
+
refreshStatus: entry.refreshStatus,
|
|
344
|
+
paragraphIndex: entry.paragraphIndex,
|
|
345
|
+
});
|
|
346
|
+
}
|
|
347
|
+
|
|
348
|
+
for (const toc of registry.tocRegions ?? []) {
|
|
349
|
+
const field = byFieldIndex.get(toc.sourceFieldIndex);
|
|
350
|
+
regions.push({
|
|
351
|
+
regionId: `toc:${toc.tocId}`,
|
|
352
|
+
kind: "toc-region",
|
|
353
|
+
storyKey: field?.storyKey ?? MAIN_STORY_KEY,
|
|
354
|
+
fieldIndex: toc.sourceFieldIndex,
|
|
355
|
+
fieldFamily: field?.fieldFamily ?? "TOC",
|
|
356
|
+
refreshStatus: field?.refreshStatus ?? "stale",
|
|
357
|
+
paragraphIndex: toc.resultRange.fromParagraphIndex,
|
|
358
|
+
tocId: toc.tocId,
|
|
359
|
+
sourcePath: toc.sourcePath,
|
|
360
|
+
parentKind: toc.parentKind,
|
|
361
|
+
resultRange: toc.resultRange,
|
|
362
|
+
status: toc.status,
|
|
363
|
+
cachedEntryCount: toc.cachedEntries.length,
|
|
364
|
+
generatedEntryCount: toc.generatedEntries.length,
|
|
365
|
+
});
|
|
366
|
+
}
|
|
367
|
+
|
|
368
|
+
return regions;
|
|
369
|
+
}
|
|
370
|
+
|
|
371
|
+
export function collectCanonicalTableLayoutInputs(
|
|
372
|
+
contexts: readonly CanonicalStoryBlockContext[],
|
|
373
|
+
): CanonicalTableLayoutInput[] {
|
|
374
|
+
const tables: CanonicalTableLayoutInput[] = [];
|
|
375
|
+
for (const context of contexts) {
|
|
376
|
+
walkBlocks(context.blocks, context.storyKey, context.basePath, {
|
|
377
|
+
table(table, blockPath) {
|
|
378
|
+
tables.push(projectTableLayoutInput(table, context.storyKey, blockPath));
|
|
379
|
+
},
|
|
380
|
+
});
|
|
381
|
+
}
|
|
382
|
+
return tables;
|
|
383
|
+
}
|
|
384
|
+
|
|
385
|
+
export function collectCanonicalAnchorLayoutInputs(
|
|
386
|
+
doc: CanonicalDocument,
|
|
387
|
+
contexts: readonly CanonicalStoryBlockContext[] = collectStoryBlockContexts(doc),
|
|
388
|
+
): CanonicalAnchorLayoutInput[] {
|
|
389
|
+
const anchors: CanonicalAnchorLayoutInput[] = [];
|
|
390
|
+
for (const context of contexts) {
|
|
391
|
+
walkBlocks(context.blocks, context.storyKey, context.basePath, {
|
|
392
|
+
inline(inline, blockPath, inlinePath) {
|
|
393
|
+
const anchor = projectAnchorLayoutInput(
|
|
394
|
+
doc,
|
|
395
|
+
inline,
|
|
396
|
+
context.storyKey,
|
|
397
|
+
blockPath,
|
|
398
|
+
inlinePath,
|
|
399
|
+
);
|
|
400
|
+
if (anchor) anchors.push(anchor);
|
|
401
|
+
},
|
|
402
|
+
});
|
|
403
|
+
}
|
|
404
|
+
return anchors;
|
|
405
|
+
}
|
|
406
|
+
|
|
407
|
+
export function collectCanonicalScopeLayoutInputs(
|
|
408
|
+
contexts: readonly CanonicalStoryBlockContext[],
|
|
409
|
+
): CanonicalScopeLayoutInput[] {
|
|
410
|
+
const scopes: CanonicalScopeLayoutInput[] = [];
|
|
411
|
+
for (const context of contexts) {
|
|
412
|
+
scopes.push(...collectScopeLayoutInputsForStory(context));
|
|
413
|
+
}
|
|
414
|
+
return scopes.sort((left, right) => left.scopeKey.localeCompare(right.scopeKey));
|
|
415
|
+
}
|
|
416
|
+
|
|
417
|
+
export function collectStoryBlockContexts(
|
|
418
|
+
doc: CanonicalDocument,
|
|
419
|
+
): CanonicalStoryBlockContext[] {
|
|
420
|
+
const contexts: CanonicalStoryBlockContext[] = [
|
|
421
|
+
{ storyKey: MAIN_STORY_KEY, blocks: doc.content.children, basePath: "main" },
|
|
422
|
+
];
|
|
423
|
+
|
|
424
|
+
for (const header of doc.subParts?.headers ?? []) {
|
|
425
|
+
contexts.push({
|
|
426
|
+
storyKey: createHeaderFooterStoryKey({
|
|
427
|
+
kind: "header",
|
|
428
|
+
relationshipId: header.relationshipId,
|
|
429
|
+
variant: header.variant,
|
|
430
|
+
...(header.sectionIndex !== undefined
|
|
431
|
+
? { sectionIndex: header.sectionIndex }
|
|
432
|
+
: {}),
|
|
433
|
+
}),
|
|
434
|
+
blocks: header.blocks,
|
|
435
|
+
basePath: `header:${header.partPath}`,
|
|
436
|
+
});
|
|
437
|
+
}
|
|
438
|
+
|
|
439
|
+
for (const footer of doc.subParts?.footers ?? []) {
|
|
440
|
+
contexts.push({
|
|
441
|
+
storyKey: createHeaderFooterStoryKey({
|
|
442
|
+
kind: "footer",
|
|
443
|
+
relationshipId: footer.relationshipId,
|
|
444
|
+
variant: footer.variant,
|
|
445
|
+
...(footer.sectionIndex !== undefined
|
|
446
|
+
? { sectionIndex: footer.sectionIndex }
|
|
447
|
+
: {}),
|
|
448
|
+
}),
|
|
449
|
+
blocks: footer.blocks,
|
|
450
|
+
basePath: `footer:${footer.partPath}`,
|
|
451
|
+
});
|
|
452
|
+
}
|
|
453
|
+
|
|
454
|
+
const notes = doc.subParts?.footnoteCollection;
|
|
455
|
+
if (notes) {
|
|
456
|
+
for (const note of Object.values(notes.footnotes)) {
|
|
457
|
+
contexts.push({
|
|
458
|
+
storyKey: createNoteStoryKey("footnote", note.noteId),
|
|
459
|
+
blocks: note.blocks,
|
|
460
|
+
basePath: `footnote:${note.noteId}`,
|
|
461
|
+
});
|
|
462
|
+
}
|
|
463
|
+
for (const note of Object.values(notes.endnotes)) {
|
|
464
|
+
contexts.push({
|
|
465
|
+
storyKey: createNoteStoryKey("endnote", note.noteId),
|
|
466
|
+
blocks: note.blocks,
|
|
467
|
+
basePath: `endnote:${note.noteId}`,
|
|
468
|
+
});
|
|
469
|
+
}
|
|
470
|
+
}
|
|
471
|
+
|
|
472
|
+
return contexts;
|
|
473
|
+
}
|
|
474
|
+
|
|
475
|
+
interface BlockWalkVisitor {
|
|
476
|
+
readonly table?: (table: TableNode, blockPath: string) => void;
|
|
477
|
+
readonly inline?: (
|
|
478
|
+
inline: InlineNode,
|
|
479
|
+
blockPath: string,
|
|
480
|
+
inlinePath: string,
|
|
481
|
+
) => void;
|
|
482
|
+
}
|
|
483
|
+
|
|
484
|
+
function walkBlocks(
|
|
485
|
+
blocks: readonly BlockNode[],
|
|
486
|
+
storyKey: string,
|
|
487
|
+
basePath: string,
|
|
488
|
+
visitor: BlockWalkVisitor,
|
|
489
|
+
): void {
|
|
490
|
+
for (let blockIndex = 0; blockIndex < blocks.length; blockIndex += 1) {
|
|
491
|
+
const block = blocks[blockIndex];
|
|
492
|
+
if (!block) continue;
|
|
493
|
+
const blockPath = `${basePath}/block[${blockIndex}]`;
|
|
494
|
+
|
|
495
|
+
if (block.type === "table") {
|
|
496
|
+
visitor.table?.(block, blockPath);
|
|
497
|
+
for (let rowIndex = 0; rowIndex < block.rows.length; rowIndex += 1) {
|
|
498
|
+
const row = block.rows[rowIndex];
|
|
499
|
+
if (!row) continue;
|
|
500
|
+
for (let cellIndex = 0; cellIndex < row.cells.length; cellIndex += 1) {
|
|
501
|
+
const cell = row.cells[cellIndex];
|
|
502
|
+
if (!cell) continue;
|
|
503
|
+
walkBlocks(
|
|
504
|
+
cell.children,
|
|
505
|
+
storyKey,
|
|
506
|
+
`${blockPath}/row[${rowIndex}]/cell[${cellIndex}]`,
|
|
507
|
+
visitor,
|
|
508
|
+
);
|
|
509
|
+
}
|
|
510
|
+
}
|
|
511
|
+
continue;
|
|
512
|
+
}
|
|
513
|
+
|
|
514
|
+
if (block.type === "paragraph") {
|
|
515
|
+
walkInlines(block.children, storyKey, blockPath, `${blockPath}/inline`, visitor);
|
|
516
|
+
continue;
|
|
517
|
+
}
|
|
518
|
+
|
|
519
|
+
if (block.type === "sdt" || block.type === "custom_xml") {
|
|
520
|
+
walkBlocks(block.children, storyKey, blockPath, visitor);
|
|
521
|
+
}
|
|
522
|
+
}
|
|
523
|
+
}
|
|
524
|
+
|
|
525
|
+
function walkInlines(
|
|
526
|
+
inlines: readonly InlineNode[],
|
|
527
|
+
storyKey: string,
|
|
528
|
+
blockPath: string,
|
|
529
|
+
basePath: string,
|
|
530
|
+
visitor: BlockWalkVisitor,
|
|
531
|
+
): void {
|
|
532
|
+
for (let inlineIndex = 0; inlineIndex < inlines.length; inlineIndex += 1) {
|
|
533
|
+
const inline = inlines[inlineIndex];
|
|
534
|
+
if (!inline) continue;
|
|
535
|
+
const inlinePath = `${basePath}[${inlineIndex}]`;
|
|
536
|
+
visitor.inline?.(inline, blockPath, inlinePath);
|
|
537
|
+
|
|
538
|
+
if (inline.type === "hyperlink") {
|
|
539
|
+
walkInlines(inline.children, storyKey, blockPath, `${inlinePath}/child`, visitor);
|
|
540
|
+
continue;
|
|
541
|
+
}
|
|
542
|
+
|
|
543
|
+
if (inline.type === "shape" && inline.txbxBlocks) {
|
|
544
|
+
walkBlocks(inline.txbxBlocks, storyKey, `${inlinePath}/txbx`, visitor);
|
|
545
|
+
continue;
|
|
546
|
+
}
|
|
547
|
+
|
|
548
|
+
if (
|
|
549
|
+
inline.type === "drawing_frame" &&
|
|
550
|
+
inline.content.type === "shape" &&
|
|
551
|
+
inline.content.txbxBlocks
|
|
552
|
+
) {
|
|
553
|
+
walkBlocks(inline.content.txbxBlocks, storyKey, `${inlinePath}/txbx`, visitor);
|
|
554
|
+
}
|
|
555
|
+
}
|
|
556
|
+
}
|
|
557
|
+
|
|
558
|
+
function projectTableLayoutInput(
|
|
559
|
+
table: TableNode,
|
|
560
|
+
storyKey: string,
|
|
561
|
+
blockPath: string,
|
|
562
|
+
): CanonicalTableLayoutInput {
|
|
563
|
+
const tableKey = `${storyKey}:${blockPath}`;
|
|
564
|
+
const rows = table.rows.map((row, rowIndex) =>
|
|
565
|
+
projectTableRowLayoutInput(row, tableKey, rowIndex),
|
|
566
|
+
);
|
|
567
|
+
return {
|
|
568
|
+
tableKey,
|
|
569
|
+
storyKey,
|
|
570
|
+
blockPath,
|
|
571
|
+
rowCount: table.rows.length,
|
|
572
|
+
columnCount: table.gridColumns.length,
|
|
573
|
+
gridColumns: [...table.gridColumns],
|
|
574
|
+
...(table.width !== undefined ? { width: table.width } : {}),
|
|
575
|
+
...(table.alignment !== undefined ? { alignment: table.alignment } : {}),
|
|
576
|
+
...(table.borders !== undefined ? { borders: table.borders } : {}),
|
|
577
|
+
...(table.cellMargins !== undefined ? { cellMargins: table.cellMargins } : {}),
|
|
578
|
+
...(table.tblLook !== undefined ? { tblLook: table.tblLook } : {}),
|
|
579
|
+
...(table.indent !== undefined ? { indent: table.indent } : {}),
|
|
580
|
+
...(table.layoutMode !== undefined ? { layoutMode: table.layoutMode } : {}),
|
|
581
|
+
...(table.cellSpacing !== undefined ? { cellSpacing: table.cellSpacing } : {}),
|
|
582
|
+
...(table.bidiVisual !== undefined ? { bidiVisual: table.bidiVisual } : {}),
|
|
583
|
+
...(table.floating !== undefined ? { floating: table.floating } : {}),
|
|
584
|
+
rows,
|
|
585
|
+
};
|
|
586
|
+
}
|
|
587
|
+
|
|
588
|
+
function projectTableRowLayoutInput(
|
|
589
|
+
row: TableRowNode,
|
|
590
|
+
tableKey: string,
|
|
591
|
+
rowIndex: number,
|
|
592
|
+
): CanonicalTableRowLayoutInput {
|
|
593
|
+
const rowKey = `${tableKey}:row[${rowIndex}]`;
|
|
594
|
+
let gridColumn = row.gridBefore ?? 0;
|
|
595
|
+
const cells = row.cells.map((cell, cellIndex) => {
|
|
596
|
+
const input = projectTableCellLayoutInput(cell, rowKey, rowIndex, cellIndex, gridColumn);
|
|
597
|
+
gridColumn += input.gridSpan;
|
|
598
|
+
return input;
|
|
599
|
+
});
|
|
600
|
+
return {
|
|
601
|
+
rowKey,
|
|
602
|
+
rowIndex,
|
|
603
|
+
cellCount: row.cells.length,
|
|
604
|
+
...(row.gridBefore !== undefined ? { gridBefore: row.gridBefore } : {}),
|
|
605
|
+
...(row.widthBefore !== undefined ? { widthBefore: row.widthBefore } : {}),
|
|
606
|
+
...(row.gridAfter !== undefined ? { gridAfter: row.gridAfter } : {}),
|
|
607
|
+
...(row.widthAfter !== undefined ? { widthAfter: row.widthAfter } : {}),
|
|
608
|
+
...(row.height !== undefined ? { height: row.height } : {}),
|
|
609
|
+
...(row.heightRule !== undefined ? { heightRule: row.heightRule } : {}),
|
|
610
|
+
...(row.isHeader !== undefined ? { isHeader: row.isHeader } : {}),
|
|
611
|
+
...(row.cantSplit !== undefined ? { cantSplit: row.cantSplit } : {}),
|
|
612
|
+
...(row.horizontalAlignment !== undefined
|
|
613
|
+
? { horizontalAlignment: row.horizontalAlignment }
|
|
614
|
+
: {}),
|
|
615
|
+
...(row.cnfStyle !== undefined ? { cnfStyle: row.cnfStyle } : {}),
|
|
616
|
+
cells,
|
|
617
|
+
};
|
|
618
|
+
}
|
|
619
|
+
|
|
620
|
+
function projectTableCellLayoutInput(
|
|
621
|
+
cell: TableCellNode,
|
|
622
|
+
rowKey: string,
|
|
623
|
+
rowIndex: number,
|
|
624
|
+
cellIndex: number,
|
|
625
|
+
gridColumnStart: number,
|
|
626
|
+
): CanonicalTableCellLayoutInput {
|
|
627
|
+
const gridSpan = cell.gridSpan ?? 1;
|
|
628
|
+
return {
|
|
629
|
+
cellKey: `${rowKey}:cell[${cellIndex}]`,
|
|
630
|
+
rowIndex,
|
|
631
|
+
cellIndex,
|
|
632
|
+
gridColumnStart,
|
|
633
|
+
gridSpan,
|
|
634
|
+
...(cell.verticalMerge !== undefined ? { verticalMerge: cell.verticalMerge } : {}),
|
|
635
|
+
...(cell.width !== undefined ? { width: cell.width } : {}),
|
|
636
|
+
...(cell.borders !== undefined ? { borders: cell.borders } : {}),
|
|
637
|
+
...(cell.shading !== undefined ? { shading: cell.shading } : {}),
|
|
638
|
+
...(cell.verticalAlign !== undefined ? { verticalAlign: cell.verticalAlign } : {}),
|
|
639
|
+
...(cell.textDirection !== undefined ? { textDirection: cell.textDirection } : {}),
|
|
640
|
+
...(cell.noWrap !== undefined ? { noWrap: cell.noWrap } : {}),
|
|
641
|
+
...(cell.fitText !== undefined ? { fitText: cell.fitText } : {}),
|
|
642
|
+
...(cell.margins !== undefined ? { margins: cell.margins } : {}),
|
|
643
|
+
...(cell.cnfStyle !== undefined ? { cnfStyle: cell.cnfStyle } : {}),
|
|
644
|
+
blockCount: cell.children.length,
|
|
645
|
+
};
|
|
646
|
+
}
|
|
647
|
+
|
|
648
|
+
function projectAnchorLayoutInput(
|
|
649
|
+
doc: CanonicalDocument,
|
|
650
|
+
inline: InlineNode,
|
|
651
|
+
storyKey: string,
|
|
652
|
+
blockPath: string,
|
|
653
|
+
inlinePath: string,
|
|
654
|
+
): CanonicalAnchorLayoutInput | null {
|
|
655
|
+
if (inline.type === "drawing_frame") {
|
|
656
|
+
return projectDrawingFrameAnchor(inline, storyKey, blockPath, inlinePath);
|
|
657
|
+
}
|
|
658
|
+
|
|
659
|
+
if (inline.type === "image") {
|
|
660
|
+
return projectLegacyImageAnchor(doc, inline, storyKey, blockPath, inlinePath);
|
|
661
|
+
}
|
|
662
|
+
|
|
663
|
+
return null;
|
|
664
|
+
}
|
|
665
|
+
|
|
666
|
+
function projectDrawingFrameAnchor(
|
|
667
|
+
node: DrawingFrameNode,
|
|
668
|
+
storyKey: string,
|
|
669
|
+
blockPath: string,
|
|
670
|
+
inlinePath: string,
|
|
671
|
+
): CanonicalAnchorLayoutInput {
|
|
672
|
+
const anchor = node.anchor;
|
|
673
|
+
const content = node.content;
|
|
674
|
+
const shapeBlocks = content.type === "shape" ? content.txbxBlocks : undefined;
|
|
675
|
+
const objectKind = drawingContentKind(content);
|
|
676
|
+
return {
|
|
677
|
+
objectKey: `${storyKey}:${inlinePath}`,
|
|
678
|
+
storyKey,
|
|
679
|
+
blockPath,
|
|
680
|
+
inlinePath,
|
|
681
|
+
objectKind,
|
|
682
|
+
editPosture: drawingEditPosture(node),
|
|
683
|
+
display: anchor.display,
|
|
684
|
+
extent: anchor.extent,
|
|
685
|
+
wrapMode: anchor.wrapMode,
|
|
686
|
+
...(anchor.wrapPolygon !== undefined
|
|
687
|
+
? { wrapPolygonPointCount: anchor.wrapPolygon.length }
|
|
688
|
+
: {}),
|
|
689
|
+
...(anchor.positionH !== undefined ? { positionH: anchor.positionH } : {}),
|
|
690
|
+
...(anchor.positionV !== undefined ? { positionV: anchor.positionV } : {}),
|
|
691
|
+
...(anchor.distMargins !== undefined ? { distMargins: anchor.distMargins } : {}),
|
|
692
|
+
...(anchor.relativeHeight !== undefined ? { relativeHeight: anchor.relativeHeight } : {}),
|
|
693
|
+
...(anchor.behindDoc !== undefined
|
|
694
|
+
? { zOrder: anchor.behindDoc ? "behind-document" : "in-front-of-text" }
|
|
695
|
+
: {}),
|
|
696
|
+
...(anchor.layoutInCell !== undefined ? { layoutInCell: anchor.layoutInCell } : {}),
|
|
697
|
+
...(anchor.allowOverlap !== undefined ? { allowOverlap: anchor.allowOverlap } : {}),
|
|
698
|
+
...(anchor.simplePos !== undefined ? { simplePos: anchor.simplePos } : {}),
|
|
699
|
+
...(anchor.docPr?.id !== undefined ? { docPrId: anchor.docPr.id } : {}),
|
|
700
|
+
...(anchor.docPr?.name !== undefined ? { docPrName: anchor.docPr.name } : {}),
|
|
701
|
+
...(anchor.docPr?.descr !== undefined ? { docPrDescription: anchor.docPr.descr } : {}),
|
|
702
|
+
...(anchor.docPr?.hidden !== undefined ? { hidden: anchor.docPr.hidden } : {}),
|
|
703
|
+
...(anchor.frameLocks !== undefined ? { frameLocks: anchor.frameLocks } : {}),
|
|
704
|
+
...(content.type === "picture" && content.mediaId !== undefined
|
|
705
|
+
? { mediaId: content.mediaId }
|
|
706
|
+
: {}),
|
|
707
|
+
...(content.type === "picture" && content.packagePartName !== undefined
|
|
708
|
+
? { packagePartName: content.packagePartName }
|
|
709
|
+
: {}),
|
|
710
|
+
...(content.type === "picture" ? { relationshipId: content.blipRef } : {}),
|
|
711
|
+
...(content.type === "picture" && content.isLinked !== undefined
|
|
712
|
+
? { isLinked: content.isLinked }
|
|
713
|
+
: {}),
|
|
714
|
+
...(shapeBlocks !== undefined ? { hasTextBoxBlocks: true } : {}),
|
|
715
|
+
...(shapeBlocks !== undefined ? { textBoxBlockCount: shapeBlocks.length } : {}),
|
|
716
|
+
rawXmlPreserved: content.type !== "picture" || content.rawXml !== undefined,
|
|
717
|
+
};
|
|
718
|
+
}
|
|
719
|
+
|
|
720
|
+
function projectLegacyImageAnchor(
|
|
721
|
+
doc: CanonicalDocument,
|
|
722
|
+
node: ImageNode,
|
|
723
|
+
storyKey: string,
|
|
724
|
+
blockPath: string,
|
|
725
|
+
inlinePath: string,
|
|
726
|
+
): CanonicalAnchorLayoutInput {
|
|
727
|
+
const media = doc.media.items[node.mediaId];
|
|
728
|
+
return {
|
|
729
|
+
objectKey: `${storyKey}:${inlinePath}`,
|
|
730
|
+
storyKey,
|
|
731
|
+
blockPath,
|
|
732
|
+
inlinePath,
|
|
733
|
+
objectKind: "legacy-image",
|
|
734
|
+
editPosture: "editable",
|
|
735
|
+
...(node.display !== undefined ? { display: node.display } : {}),
|
|
736
|
+
...(media?.widthEmu !== undefined && media.heightEmu !== undefined
|
|
737
|
+
? { extent: { widthEmu: media.widthEmu, heightEmu: media.heightEmu } }
|
|
738
|
+
: {}),
|
|
739
|
+
...(node.floating?.wrap !== undefined ? { wrapMode: node.floating.wrap } : {}),
|
|
740
|
+
...(node.floating?.horizontalPosition !== undefined
|
|
741
|
+
? { positionH: normalizeFloatingAxis(node.floating.horizontalPosition) }
|
|
742
|
+
: {}),
|
|
743
|
+
...(node.floating?.verticalPosition !== undefined
|
|
744
|
+
? { positionV: normalizeFloatingAxis(node.floating.verticalPosition) }
|
|
745
|
+
: {}),
|
|
746
|
+
...(node.floating?.behindDoc !== undefined
|
|
747
|
+
? { zOrder: node.floating.behindDoc ? "behind-document" : "in-front-of-text" }
|
|
748
|
+
: {}),
|
|
749
|
+
...(node.floating?.layoutInCell !== undefined
|
|
750
|
+
? { layoutInCell: node.floating.layoutInCell }
|
|
751
|
+
: {}),
|
|
752
|
+
...(node.floating?.allowOverlap !== undefined
|
|
753
|
+
? { allowOverlap: node.floating.allowOverlap }
|
|
754
|
+
: {}),
|
|
755
|
+
mediaId: node.mediaId,
|
|
756
|
+
...(media?.packagePartName !== undefined ? { packagePartName: media.packagePartName } : {}),
|
|
757
|
+
...(media?.relationshipId !== undefined ? { relationshipId: media.relationshipId } : {}),
|
|
758
|
+
...(node.placementXml !== undefined ? { rawXmlPreserved: true } : {}),
|
|
759
|
+
};
|
|
760
|
+
}
|
|
761
|
+
|
|
762
|
+
function normalizeFloatingAxis(
|
|
763
|
+
axis: NonNullable<ImageNode["floating"]>["horizontalPosition"],
|
|
764
|
+
): AnchorGeometry["positionH"] {
|
|
765
|
+
return {
|
|
766
|
+
relativeFrom: axis?.relativeFrom ?? "",
|
|
767
|
+
...(axis?.align !== undefined ? { align: axis.align } : {}),
|
|
768
|
+
...(axis?.offset !== undefined ? { offset: axis.offset } : {}),
|
|
769
|
+
};
|
|
770
|
+
}
|
|
771
|
+
|
|
772
|
+
function drawingContentKind(
|
|
773
|
+
content: DrawingFrameNode["content"],
|
|
774
|
+
): CanonicalAnchorObjectKind {
|
|
775
|
+
if (content.type === "picture") return "picture";
|
|
776
|
+
if (content.type === "shape") {
|
|
777
|
+
return content.isTextBox || content.txbxBlocks || content.txbxContentXml
|
|
778
|
+
? "textbox"
|
|
779
|
+
: "shape";
|
|
780
|
+
}
|
|
781
|
+
if (content.type === "chart_preview") return "chart";
|
|
782
|
+
if (content.type === "smartart_preview") return "smartart";
|
|
783
|
+
return "opaque";
|
|
784
|
+
}
|
|
785
|
+
|
|
786
|
+
function drawingEditPosture(
|
|
787
|
+
node: DrawingFrameNode,
|
|
788
|
+
): CanonicalAnchorEditPosture {
|
|
789
|
+
if (node.content.type === "picture" && !node.content.isLinked) return "editable";
|
|
790
|
+
if (node.content.type === "opaque") return "preserve-only";
|
|
791
|
+
return "read-only-preview";
|
|
792
|
+
}
|
|
793
|
+
|
|
794
|
+
interface ScopeMarkerOccurrence {
|
|
795
|
+
readonly scopeId: string;
|
|
796
|
+
readonly markerType: "start" | "end";
|
|
797
|
+
readonly markerIndex: number;
|
|
798
|
+
readonly blockPath: string;
|
|
799
|
+
readonly inlinePath: string;
|
|
800
|
+
}
|
|
801
|
+
|
|
802
|
+
function collectScopeLayoutInputsForStory(
|
|
803
|
+
context: CanonicalStoryBlockContext,
|
|
804
|
+
): CanonicalScopeLayoutInput[] {
|
|
805
|
+
const occurrences: ScopeMarkerOccurrence[] = [];
|
|
806
|
+
walkBlocks(context.blocks, context.storyKey, context.basePath, {
|
|
807
|
+
inline(inline, blockPath, inlinePath) {
|
|
808
|
+
if (inline.type !== "scope_marker_start" && inline.type !== "scope_marker_end") {
|
|
809
|
+
return;
|
|
810
|
+
}
|
|
811
|
+
occurrences.push({
|
|
812
|
+
scopeId: inline.scopeId,
|
|
813
|
+
markerType: inline.type === "scope_marker_start" ? "start" : "end",
|
|
814
|
+
markerIndex: occurrences.length,
|
|
815
|
+
blockPath,
|
|
816
|
+
inlinePath,
|
|
817
|
+
});
|
|
818
|
+
},
|
|
819
|
+
});
|
|
820
|
+
|
|
821
|
+
const open = new Map<string, ScopeMarkerOccurrence[]>();
|
|
822
|
+
const scopes: CanonicalScopeLayoutInput[] = [];
|
|
823
|
+
|
|
824
|
+
for (const marker of occurrences) {
|
|
825
|
+
if (marker.markerType === "start") {
|
|
826
|
+
const stack = open.get(marker.scopeId) ?? [];
|
|
827
|
+
stack.push(marker);
|
|
828
|
+
open.set(marker.scopeId, stack);
|
|
829
|
+
continue;
|
|
830
|
+
}
|
|
831
|
+
|
|
832
|
+
const stack = open.get(marker.scopeId);
|
|
833
|
+
const opener = stack?.pop();
|
|
834
|
+
if (stack && stack.length === 0) {
|
|
835
|
+
open.delete(marker.scopeId);
|
|
836
|
+
}
|
|
837
|
+
|
|
838
|
+
if (opener) {
|
|
839
|
+
scopes.push(createScopeLayoutInput(context.storyKey, "paired", opener, marker));
|
|
840
|
+
continue;
|
|
841
|
+
}
|
|
842
|
+
|
|
843
|
+
scopes.push(createScopeLayoutInput(context.storyKey, "end-only", undefined, marker));
|
|
844
|
+
}
|
|
845
|
+
|
|
846
|
+
for (const stack of open.values()) {
|
|
847
|
+
for (const opener of stack) {
|
|
848
|
+
scopes.push(createScopeLayoutInput(context.storyKey, "start-only", opener, undefined));
|
|
849
|
+
}
|
|
850
|
+
}
|
|
851
|
+
|
|
852
|
+
return scopes.sort(
|
|
853
|
+
(left, right) =>
|
|
854
|
+
(left.start?.markerIndex ?? left.end?.markerIndex ?? Number.MAX_SAFE_INTEGER) -
|
|
855
|
+
(right.start?.markerIndex ?? right.end?.markerIndex ?? Number.MAX_SAFE_INTEGER) ||
|
|
856
|
+
left.scopeId.localeCompare(right.scopeId),
|
|
857
|
+
);
|
|
858
|
+
}
|
|
859
|
+
|
|
860
|
+
function createScopeLayoutInput(
|
|
861
|
+
storyKey: string,
|
|
862
|
+
status: CanonicalScopeLayoutStatus,
|
|
863
|
+
start: ScopeMarkerOccurrence | undefined,
|
|
864
|
+
end: ScopeMarkerOccurrence | undefined,
|
|
865
|
+
): CanonicalScopeLayoutInput {
|
|
866
|
+
const scopeId = start?.scopeId ?? end?.scopeId ?? "";
|
|
867
|
+
const keyIndex = start?.markerIndex ?? end?.markerIndex ?? 0;
|
|
868
|
+
return {
|
|
869
|
+
scopeKey: `${storyKey}:scope:${scopeId}:${keyIndex}`,
|
|
870
|
+
scopeId,
|
|
871
|
+
storyKey,
|
|
872
|
+
status,
|
|
873
|
+
source: "canonical-marker",
|
|
874
|
+
...(start !== undefined ? { start: createScopeMarkerReference(storyKey, start) } : {}),
|
|
875
|
+
...(end !== undefined ? { end: createScopeMarkerReference(storyKey, end) } : {}),
|
|
876
|
+
};
|
|
877
|
+
}
|
|
878
|
+
|
|
879
|
+
function createScopeMarkerReference(
|
|
880
|
+
storyKey: string,
|
|
881
|
+
marker: ScopeMarkerOccurrence,
|
|
882
|
+
): CanonicalScopeMarkerReference {
|
|
883
|
+
return {
|
|
884
|
+
markerKey: `${storyKey}:scope-marker:${marker.scopeId}:${marker.markerIndex}`,
|
|
885
|
+
blockPath: marker.blockPath,
|
|
886
|
+
inlinePath: marker.inlinePath,
|
|
887
|
+
markerIndex: marker.markerIndex,
|
|
888
|
+
};
|
|
889
|
+
}
|
|
890
|
+
|
|
891
|
+
function countSections(doc: CanonicalDocument): number {
|
|
892
|
+
let explicitBreaks = 0;
|
|
893
|
+
for (const block of doc.content.children) {
|
|
894
|
+
if (block.type === "section_break") explicitBreaks += 1;
|
|
895
|
+
}
|
|
896
|
+
return explicitBreaks + 1;
|
|
897
|
+
}
|
|
898
|
+
|
|
899
|
+
interface HeaderFooterBindings {
|
|
900
|
+
readonly boundSectionIndexes: readonly number[];
|
|
901
|
+
readonly explicitSectionIndexes: readonly number[];
|
|
902
|
+
readonly inheritedSectionIndexes: readonly number[];
|
|
903
|
+
}
|
|
904
|
+
|
|
905
|
+
function collectHeaderFooterBindings(
|
|
906
|
+
doc: CanonicalDocument,
|
|
907
|
+
kind: "header" | "footer",
|
|
908
|
+
relationshipId: string,
|
|
909
|
+
variant: HeaderFooterVariant,
|
|
910
|
+
fallbackSectionIndex: number | undefined,
|
|
911
|
+
sectionCount: number,
|
|
912
|
+
): HeaderFooterBindings {
|
|
913
|
+
const bound = new Set<number>();
|
|
914
|
+
const explicit = new Set<number>();
|
|
915
|
+
const inherited = new Set<number>();
|
|
916
|
+
const effectiveByVariant = new Map<HeaderFooterVariant, string>();
|
|
917
|
+
const sectionProperties = collectSectionProperties(doc);
|
|
918
|
+
|
|
919
|
+
for (let sectionIndex = 0; sectionIndex < sectionCount; sectionIndex += 1) {
|
|
920
|
+
const properties = sectionProperties[sectionIndex];
|
|
921
|
+
const refs = getHeaderFooterReferences(properties, kind);
|
|
922
|
+
const hasRequestedVariant = refs.some((ref) => ref.variant === variant);
|
|
923
|
+
if (!hasRequestedVariant && effectiveByVariant.get(variant) === relationshipId) {
|
|
924
|
+
inherited.add(sectionIndex);
|
|
925
|
+
bound.add(sectionIndex);
|
|
926
|
+
}
|
|
927
|
+
|
|
928
|
+
if (
|
|
929
|
+
refs.some(
|
|
930
|
+
(ref) =>
|
|
931
|
+
ref.relationshipId === relationshipId && ref.variant === variant,
|
|
932
|
+
)
|
|
933
|
+
) {
|
|
934
|
+
explicit.add(sectionIndex);
|
|
935
|
+
bound.add(sectionIndex);
|
|
936
|
+
}
|
|
937
|
+
|
|
938
|
+
for (const ref of refs) {
|
|
939
|
+
effectiveByVariant.set(ref.variant, ref.relationshipId);
|
|
940
|
+
}
|
|
941
|
+
}
|
|
942
|
+
|
|
943
|
+
if (fallbackSectionIndex !== undefined) {
|
|
944
|
+
bound.add(fallbackSectionIndex);
|
|
945
|
+
} else if (bound.size === 0) {
|
|
946
|
+
for (let sectionIndex = 0; sectionIndex < sectionCount; sectionIndex += 1) {
|
|
947
|
+
bound.add(sectionIndex);
|
|
948
|
+
}
|
|
949
|
+
}
|
|
950
|
+
|
|
951
|
+
return {
|
|
952
|
+
boundSectionIndexes: sortIndexes(bound),
|
|
953
|
+
explicitSectionIndexes: sortIndexes(explicit),
|
|
954
|
+
inheritedSectionIndexes: sortIndexes(inherited),
|
|
955
|
+
};
|
|
956
|
+
}
|
|
957
|
+
|
|
958
|
+
function collectSectionProperties(
|
|
959
|
+
doc: CanonicalDocument,
|
|
960
|
+
): Array<SectionProperties | undefined> {
|
|
961
|
+
const sectionProperties = doc.content.children
|
|
962
|
+
.filter((block) => block.type === "section_break")
|
|
963
|
+
.map((block) => block.sectionProperties);
|
|
964
|
+
sectionProperties.push(doc.subParts?.finalSectionProperties);
|
|
965
|
+
return sectionProperties;
|
|
966
|
+
}
|
|
967
|
+
|
|
968
|
+
function getHeaderFooterReferences(
|
|
969
|
+
properties: SectionProperties | undefined,
|
|
970
|
+
kind: "header" | "footer",
|
|
971
|
+
): readonly HeaderFooterReference[] {
|
|
972
|
+
return (kind === "header"
|
|
973
|
+
? properties?.headerReferences
|
|
974
|
+
: properties?.footerReferences) ?? [];
|
|
975
|
+
}
|
|
976
|
+
|
|
977
|
+
function sortIndexes(indexes: ReadonlySet<number>): readonly number[] {
|
|
978
|
+
return [...indexes].sort((left, right) => left - right);
|
|
979
|
+
}
|