@beyondwork/docx-react-component 1.0.109 → 1.0.111
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +1 -1
- package/src/api/public-types.ts +3 -0
- package/src/model/layout/page-graph-types.ts +33 -0
- package/src/model/layout/runtime-page-graph-types.ts +25 -0
- package/src/runtime/document-runtime.ts +46 -0
- package/src/runtime/geometry/adjacent-geometry-intake.ts +820 -15
- package/src/runtime/geometry/caret-geometry.ts +219 -7
- package/src/runtime/geometry/geometry-index.ts +52 -12
- package/src/runtime/geometry/object-handles.ts +42 -1
- package/src/runtime/layout/index.ts +3 -0
- package/src/runtime/layout/inert-layout-facet.ts +13 -0
- package/src/runtime/layout/layout-engine-instance.ts +233 -4
- package/src/runtime/layout/layout-engine-version.ts +47 -2
- package/src/runtime/layout/layout-facet-types.ts +3 -0
- package/src/runtime/layout/page-graph.ts +88 -7
- package/src/runtime/layout/paginated-layout-engine.ts +34 -0
- package/src/runtime/layout/project-block-fragments.ts +144 -1
- package/src/runtime/layout/public-facet.ts +228 -9
- package/src/runtime/layout/resolve-page-previews.ts +46 -8
- package/src/runtime/scopes/adjacent-geometry-evidence.ts +456 -0
- package/src/runtime/scopes/compile-scope-bundle.ts +8 -0
- package/src/runtime/scopes/evidence.ts +16 -0
- package/src/runtime/scopes/index.ts +13 -0
- package/src/runtime/scopes/semantic-scope-types.ts +67 -0
- package/src/ui-tailwind/chrome-overlay/tw-table-split-row-carry-overlay.tsx +62 -0
- package/src/ui-tailwind/debug/layer11-consumer-readiness.ts +104 -0
- package/src/ui-tailwind/editor-surface/pm-page-break-decorations.ts +50 -5
- package/src/ui-tailwind/editor-surface/tw-prosemirror-surface.tsx +27 -0
- package/src/ui-tailwind/page-stack/tw-page-chrome-entry.tsx +62 -0
- package/src/ui-tailwind/page-stack/tw-page-stack-chrome-layer.tsx +1 -0
- package/src/README.md +0 -85
- package/src/api/README.md +0 -26
- package/src/api/v3/README.md +0 -91
- package/src/component-inventory.md +0 -99
- package/src/core/README.md +0 -10
- package/src/core/commands/README.md +0 -3
- package/src/core/schema/README.md +0 -3
- package/src/core/selection/README.md +0 -3
- package/src/core/state/README.md +0 -3
- package/src/io/README.md +0 -10
- package/src/io/export/README.md +0 -3
- package/src/io/normalize/README.md +0 -3
- package/src/io/ooxml/README.md +0 -3
- package/src/io/opc/README.md +0 -3
- package/src/model/README.md +0 -3
- package/src/preservation/README.md +0 -3
- package/src/review/README.md +0 -16
- package/src/review/store/README.md +0 -3
- package/src/runtime/README.md +0 -3
- package/src/ui/README.md +0 -30
- package/src/ui/comments/README.md +0 -3
- package/src/ui/compatibility/README.md +0 -3
- package/src/ui/editor-surface/README.md +0 -3
- package/src/ui/review/README.md +0 -3
- package/src/ui/status/README.md +0 -3
- package/src/ui/theme/README.md +0 -3
- package/src/ui/toolbar/README.md +0 -3
- package/src/ui-tailwind/debug/README.md +0 -22
- package/src/validation/README.md +0 -3
|
@@ -209,6 +209,14 @@ export interface PageStackResultWithSplits {
|
|
|
209
209
|
fragmentMeasurementsByPageIndex?: ReadonlyMap<number, ReadonlyMap<string, FragmentMeasurement>>;
|
|
210
210
|
}
|
|
211
211
|
|
|
212
|
+
export interface PageStackPaginationOptions {
|
|
213
|
+
/**
|
|
214
|
+
* Stop after this many global pages have been emitted. Used by L04 lazy
|
|
215
|
+
* pagination so viewport reads can avoid walking the whole document tail.
|
|
216
|
+
*/
|
|
217
|
+
maxPageCount?: number;
|
|
218
|
+
}
|
|
219
|
+
|
|
212
220
|
// ---------------------------------------------------------------------------
|
|
213
221
|
// Facade
|
|
214
222
|
// ---------------------------------------------------------------------------
|
|
@@ -248,6 +256,7 @@ export function buildPageStackWithSplits(
|
|
|
248
256
|
sections: ResolvedDocumentSection[],
|
|
249
257
|
mainSurface: EditorSurfaceSnapshot,
|
|
250
258
|
measurementProvider?: LayoutMeasurementProvider,
|
|
259
|
+
options?: PageStackPaginationOptions,
|
|
251
260
|
): PageStackResultWithSplits {
|
|
252
261
|
const defaultTabInterval = document.subParts?.settings?.defaultTabStop ?? 720;
|
|
253
262
|
// Theme-font fallback for L04 dominant-font resolution. Threaded into
|
|
@@ -288,8 +297,15 @@ export function buildPageStackWithSplits(
|
|
|
288
297
|
// reuses heights. The WeakMap frees memory automatically when the block
|
|
289
298
|
// snapshots go out of scope at the end of the call.
|
|
290
299
|
const cache = createMeasurementCache();
|
|
300
|
+
const maxPageCount =
|
|
301
|
+
options?.maxPageCount !== undefined
|
|
302
|
+
? Math.max(1, Math.floor(options.maxPageCount))
|
|
303
|
+
: undefined;
|
|
304
|
+
const hasReachedMaxPageCount = (): boolean =>
|
|
305
|
+
maxPageCount !== undefined && globalPageIndex >= maxPageCount;
|
|
291
306
|
|
|
292
307
|
for (let sectionIdx = 0; sectionIdx < sections.length; sectionIdx++) {
|
|
308
|
+
if (hasReachedMaxPageCount()) break;
|
|
293
309
|
const section = sections[sectionIdx]!;
|
|
294
310
|
const layout = buildPageLayoutSnapshot(
|
|
295
311
|
section.index,
|
|
@@ -347,6 +363,7 @@ export function buildPageStackWithSplits(
|
|
|
347
363
|
|
|
348
364
|
if (breakType === "evenPage" && globalPageIndex > 0) {
|
|
349
365
|
if (nextDisplayPage % 2 !== 0) {
|
|
366
|
+
if (hasReachedMaxPageCount()) break;
|
|
350
367
|
const prevLayout = pages[pages.length - 1]?.layout ?? layout;
|
|
351
368
|
pages.push({
|
|
352
369
|
pageIndex: globalPageIndex,
|
|
@@ -360,6 +377,7 @@ export function buildPageStackWithSplits(
|
|
|
360
377
|
}
|
|
361
378
|
} else if (breakType === "oddPage" && globalPageIndex > 0) {
|
|
362
379
|
if (nextDisplayPage % 2 === 0) {
|
|
380
|
+
if (hasReachedMaxPageCount()) break;
|
|
363
381
|
const prevLayout = pages[pages.length - 1]?.layout ?? layout;
|
|
364
382
|
pages.push({
|
|
365
383
|
pageIndex: globalPageIndex,
|
|
@@ -383,6 +401,9 @@ export function buildPageStackWithSplits(
|
|
|
383
401
|
defaultTabInterval,
|
|
384
402
|
nextColumnSeed,
|
|
385
403
|
themeFonts,
|
|
404
|
+
maxPageCount !== undefined
|
|
405
|
+
? { maxPageCount: Math.max(1, maxPageCount - globalPageIndex) }
|
|
406
|
+
: undefined,
|
|
386
407
|
);
|
|
387
408
|
const paginated = paginatedResult.pages;
|
|
388
409
|
|
|
@@ -421,6 +442,7 @@ export function buildPageStackWithSplits(
|
|
|
421
442
|
}
|
|
422
443
|
continue;
|
|
423
444
|
}
|
|
445
|
+
if (hasReachedMaxPageCount()) break;
|
|
424
446
|
pageInSectionToGlobal.set(page.pageInSection, globalPageIndex);
|
|
425
447
|
pages.push({
|
|
426
448
|
...page,
|
|
@@ -1612,6 +1634,7 @@ export function paginateSectionBlocksWithSplits(
|
|
|
1612
1634
|
* `subParts.resolvedTheme` — threaded from `buildPageStackWithSplits`.
|
|
1613
1635
|
*/
|
|
1614
1636
|
themeFonts?: LayoutThemeFonts,
|
|
1637
|
+
options?: PageStackPaginationOptions,
|
|
1615
1638
|
): SectionPaginationResult {
|
|
1616
1639
|
if (blocks.length === 0) {
|
|
1617
1640
|
return {
|
|
@@ -1633,6 +1656,11 @@ export function paginateSectionBlocksWithSplits(
|
|
|
1633
1656
|
}
|
|
1634
1657
|
|
|
1635
1658
|
const pages: Omit<DocumentPageSnapshot, "pageIndex">[] = [];
|
|
1659
|
+
const maxPageCount =
|
|
1660
|
+
options?.maxPageCount !== undefined
|
|
1661
|
+
? Math.max(1, Math.floor(options.maxPageCount))
|
|
1662
|
+
: undefined;
|
|
1663
|
+
let reachedPageLimit = false;
|
|
1636
1664
|
const splitsByBlock = new Map<string, SectionLocalSlice[]>();
|
|
1637
1665
|
const tableSplitsByBlock = new Map<string, SectionLocalTableSlice[]>();
|
|
1638
1666
|
const fragmentMeasurementsByPageInSection = new Map<number, Map<string, FragmentMeasurement>>();
|
|
@@ -1850,6 +1878,7 @@ export function paginateSectionBlocksWithSplits(
|
|
|
1850
1878
|
};
|
|
1851
1879
|
|
|
1852
1880
|
const pushPage = (endOffset: number): void => {
|
|
1881
|
+
if (reachedPageLimit) return;
|
|
1853
1882
|
const boundedEnd = Math.max(pageStart, Math.min(endOffset, section.end));
|
|
1854
1883
|
if (boundedEnd === pageStart && pages.length > 0) {
|
|
1855
1884
|
return;
|
|
@@ -1877,12 +1906,17 @@ export function paginateSectionBlocksWithSplits(
|
|
|
1877
1906
|
pendingNoteKeys.clear();
|
|
1878
1907
|
pendingNoteBlockFroms.clear();
|
|
1879
1908
|
pendingNoteColumnWidths.clear();
|
|
1909
|
+
if (maxPageCount !== undefined && pages.length >= maxPageCount) {
|
|
1910
|
+
reachedPageLimit = true;
|
|
1911
|
+
}
|
|
1880
1912
|
};
|
|
1881
1913
|
|
|
1882
1914
|
for (let index = 0; index < blocks.length; index += 1) {
|
|
1915
|
+
if (reachedPageLimit) break;
|
|
1883
1916
|
const block = blocks[index]!;
|
|
1884
1917
|
const nextBoundary = blocks[index + 1]?.from ?? section.end;
|
|
1885
1918
|
while (true) {
|
|
1919
|
+
if (reachedPageLimit) break;
|
|
1886
1920
|
const columnWidth =
|
|
1887
1921
|
columnMetrics[Math.min(columnIndex, columnMetrics.length - 1)]?.width ??
|
|
1888
1922
|
getUsableColumnWidth(layout);
|
|
@@ -23,6 +23,7 @@ import type {
|
|
|
23
23
|
DocumentPageSnapshot,
|
|
24
24
|
EditorSurfaceSnapshot,
|
|
25
25
|
SurfaceBlockSnapshot,
|
|
26
|
+
SurfaceInlineSegment,
|
|
26
27
|
} from "../../api/public-types";
|
|
27
28
|
import type {
|
|
28
29
|
CanonicalFieldRegionIdentity,
|
|
@@ -31,7 +32,9 @@ import type { RuntimeBlockFragment } from "./page-graph.ts";
|
|
|
31
32
|
import type {
|
|
32
33
|
RuntimeFieldRegionLayoutFacts,
|
|
33
34
|
RuntimeLineBox,
|
|
35
|
+
RuntimeLineRunAnchor,
|
|
34
36
|
RuntimeNumberingLayoutFacts,
|
|
37
|
+
RuntimeTwipsRect,
|
|
35
38
|
} from "../../model/layout/page-graph-types.ts";
|
|
36
39
|
import type {
|
|
37
40
|
BlockSplits,
|
|
@@ -185,8 +188,12 @@ export function projectLineBoxesForPageFragments(
|
|
|
185
188
|
ReadonlyArray<Omit<RuntimeBlockFragment, "pageId">>
|
|
186
189
|
>,
|
|
187
190
|
fragmentMeasurementsByPageIndex?: ReadonlyMap<number, ReadonlyMap<string, FragmentMeasurement>>,
|
|
191
|
+
surface?: EditorSurfaceSnapshot,
|
|
188
192
|
): Map<number, RuntimeLineBox[]> {
|
|
189
193
|
const byPage = new Map<number, RuntimeLineBox[]>();
|
|
194
|
+
const blocksById = surface
|
|
195
|
+
? new Map(surface.blocks.map((block) => [block.blockId, block] as const))
|
|
196
|
+
: new Map<string, SurfaceBlockSnapshot>();
|
|
190
197
|
for (const page of pages) {
|
|
191
198
|
const fragments = [...(fragmentsByPageIndex.get(page.pageIndex) ?? [])]
|
|
192
199
|
.filter((fragment) => fragment.regionKind === "body")
|
|
@@ -224,12 +231,33 @@ export function projectLineBoxesForPageFragments(
|
|
|
224
231
|
page.layout.gutter,
|
|
225
232
|
);
|
|
226
233
|
for (let lineIndex = 0; lineIndex < lineCount; lineIndex += 1) {
|
|
234
|
+
const lineTopTwips = cursorTwips + lineIndex * lineHeight;
|
|
235
|
+
const rectTwips = lineRectForFragment(page, fragment, lineTopTwips, widthTwips, lineHeight);
|
|
236
|
+
const baselinePageYTwips = rectTwips.yTwips + Math.round(lineHeight * 0.8);
|
|
237
|
+
const block = blocksById.get(fragment.blockId);
|
|
238
|
+
const direction = block?.kind === "paragraph" && block.bidi === true ? "rtl" : "ltr";
|
|
227
239
|
lines.push({
|
|
228
240
|
fragmentId: fragment.fragmentId,
|
|
229
241
|
lineIndex,
|
|
230
|
-
baselineTwips:
|
|
242
|
+
baselineTwips: lineTopTwips,
|
|
243
|
+
baselinePageYTwips,
|
|
231
244
|
heightTwips: lineHeight,
|
|
232
245
|
widthTwips,
|
|
246
|
+
rectTwips,
|
|
247
|
+
direction,
|
|
248
|
+
...(block?.kind === "paragraph"
|
|
249
|
+
? {
|
|
250
|
+
runAnchors: buildRunAnchorsForLine({
|
|
251
|
+
block,
|
|
252
|
+
fragment,
|
|
253
|
+
lineIndex,
|
|
254
|
+
lineCount,
|
|
255
|
+
rectTwips,
|
|
256
|
+
baselinePageYTwips,
|
|
257
|
+
direction,
|
|
258
|
+
}),
|
|
259
|
+
}
|
|
260
|
+
: {}),
|
|
233
261
|
});
|
|
234
262
|
}
|
|
235
263
|
cursorTwips += Math.max(0, fragment.heightTwips);
|
|
@@ -241,6 +269,121 @@ export function projectLineBoxesForPageFragments(
|
|
|
241
269
|
return byPage;
|
|
242
270
|
}
|
|
243
271
|
|
|
272
|
+
function lineRectForFragment(
|
|
273
|
+
page: DocumentPageSnapshot,
|
|
274
|
+
fragment: Omit<RuntimeBlockFragment, "pageId">,
|
|
275
|
+
lineTopTwips: number,
|
|
276
|
+
widthTwips: number,
|
|
277
|
+
heightTwips: number,
|
|
278
|
+
): RuntimeTwipsRect {
|
|
279
|
+
const bodyX = page.layout.marginLeft + page.layout.gutter;
|
|
280
|
+
const bodyY = page.layout.marginTop;
|
|
281
|
+
const columnIndex = Math.max(0, fragment.columnIndex ?? 0);
|
|
282
|
+
const columnOffset = columnIndex > 0 ? columnIndex * Math.max(0, widthTwips) : 0;
|
|
283
|
+
return {
|
|
284
|
+
xTwips: bodyX + columnOffset,
|
|
285
|
+
yTwips: bodyY + lineTopTwips,
|
|
286
|
+
widthTwips: Math.max(0, widthTwips),
|
|
287
|
+
heightTwips: Math.max(0, heightTwips),
|
|
288
|
+
};
|
|
289
|
+
}
|
|
290
|
+
|
|
291
|
+
function buildRunAnchorsForLine(input: {
|
|
292
|
+
block: Extract<SurfaceBlockSnapshot, { kind: "paragraph" }>;
|
|
293
|
+
fragment: Omit<RuntimeBlockFragment, "pageId">;
|
|
294
|
+
lineIndex: number;
|
|
295
|
+
lineCount: number;
|
|
296
|
+
rectTwips: RuntimeTwipsRect;
|
|
297
|
+
baselinePageYTwips: number;
|
|
298
|
+
direction: "ltr" | "rtl";
|
|
299
|
+
}): RuntimeLineRunAnchor[] {
|
|
300
|
+
const textSegments = input.block.segments.filter(
|
|
301
|
+
(segment): segment is Extract<SurfaceInlineSegment, { kind: "text" }> =>
|
|
302
|
+
segment.kind === "text" && segment.text.length > 0,
|
|
303
|
+
);
|
|
304
|
+
if (textSegments.length === 0) return [];
|
|
305
|
+
|
|
306
|
+
const totalChars = Math.max(
|
|
307
|
+
1,
|
|
308
|
+
textSegments.reduce((sum, segment) => sum + Array.from(segment.text).length, 0),
|
|
309
|
+
);
|
|
310
|
+
const totalLineCount =
|
|
311
|
+
input.fragment.kind === "paragraph-slice" && input.fragment.paragraphLineRange
|
|
312
|
+
? Math.max(1, input.fragment.paragraphLineRange.totalLines)
|
|
313
|
+
: Math.max(1, input.lineCount);
|
|
314
|
+
const globalLineIndex =
|
|
315
|
+
(input.fragment.kind === "paragraph-slice" && input.fragment.paragraphLineRange
|
|
316
|
+
? input.fragment.paragraphLineRange.from
|
|
317
|
+
: 0) + input.lineIndex;
|
|
318
|
+
const charsPerLine = Math.max(1, Math.ceil(totalChars / totalLineCount));
|
|
319
|
+
const lineCharStart = globalLineIndex * charsPerLine;
|
|
320
|
+
const lineCharEnd = Math.min(totalChars, lineCharStart + charsPerLine);
|
|
321
|
+
if (lineCharEnd <= lineCharStart) return [];
|
|
322
|
+
|
|
323
|
+
const charWidth = Math.max(1, input.rectTwips.widthTwips / charsPerLine);
|
|
324
|
+
const anchors: RuntimeLineRunAnchor[] = [];
|
|
325
|
+
let charCursor = 0;
|
|
326
|
+
for (const segment of textSegments) {
|
|
327
|
+
const segmentLength = Array.from(segment.text).length;
|
|
328
|
+
const segmentStart = charCursor;
|
|
329
|
+
const segmentEnd = charCursor + segmentLength;
|
|
330
|
+
charCursor = segmentEnd;
|
|
331
|
+
|
|
332
|
+
const overlapStart = Math.max(segmentStart, lineCharStart);
|
|
333
|
+
const overlapEnd = Math.min(segmentEnd, lineCharEnd);
|
|
334
|
+
if (overlapEnd <= overlapStart) continue;
|
|
335
|
+
|
|
336
|
+
const startInLine = overlapStart - lineCharStart;
|
|
337
|
+
const glyphCount = Math.max(1, overlapEnd - overlapStart);
|
|
338
|
+
const runWidthTwips = Math.max(1, Math.round(glyphCount * charWidth));
|
|
339
|
+
const startOffsetTwips = Math.round(startInLine * charWidth);
|
|
340
|
+
const xTwips =
|
|
341
|
+
input.direction === "rtl"
|
|
342
|
+
? input.rectTwips.xTwips + input.rectTwips.widthTwips - startOffsetTwips - runWidthTwips
|
|
343
|
+
: input.rectTwips.xTwips + startOffsetTwips;
|
|
344
|
+
const glyphWidthTwips = Math.max(1, Math.round(charWidth));
|
|
345
|
+
const runRectTwips: RuntimeTwipsRect = {
|
|
346
|
+
xTwips,
|
|
347
|
+
yTwips: input.rectTwips.yTwips,
|
|
348
|
+
widthTwips: runWidthTwips,
|
|
349
|
+
heightTwips: input.rectTwips.heightTwips,
|
|
350
|
+
};
|
|
351
|
+
const firstGlyphRectTwips: RuntimeTwipsRect =
|
|
352
|
+
input.direction === "rtl"
|
|
353
|
+
? {
|
|
354
|
+
...runRectTwips,
|
|
355
|
+
xTwips: xTwips + Math.max(0, runWidthTwips - glyphWidthTwips),
|
|
356
|
+
widthTwips: glyphWidthTwips,
|
|
357
|
+
}
|
|
358
|
+
: { ...runRectTwips, widthTwips: glyphWidthTwips };
|
|
359
|
+
const lastGlyphRectTwips: RuntimeTwipsRect =
|
|
360
|
+
input.direction === "rtl"
|
|
361
|
+
? { ...runRectTwips, widthTwips: glyphWidthTwips }
|
|
362
|
+
: {
|
|
363
|
+
...runRectTwips,
|
|
364
|
+
xTwips: xTwips + Math.max(0, runWidthTwips - glyphWidthTwips),
|
|
365
|
+
widthTwips: glyphWidthTwips,
|
|
366
|
+
};
|
|
367
|
+
const runId = `${input.block.blockId}:${segment.segmentId}`;
|
|
368
|
+
anchors.push({
|
|
369
|
+
anchorId: `${input.fragment.fragmentId}:line-${input.lineIndex}:${segment.segmentId}`,
|
|
370
|
+
runId,
|
|
371
|
+
segmentId: segment.segmentId,
|
|
372
|
+
blockId: input.block.blockId,
|
|
373
|
+
fragmentId: input.fragment.fragmentId,
|
|
374
|
+
lineIndex: input.lineIndex,
|
|
375
|
+
direction: input.direction,
|
|
376
|
+
baselinePageYTwips: input.baselinePageYTwips,
|
|
377
|
+
lineRectTwips: { ...input.rectTwips },
|
|
378
|
+
firstGlyphRectTwips,
|
|
379
|
+
lastGlyphRectTwips,
|
|
380
|
+
runRectTwips,
|
|
381
|
+
precision: "layout-estimate",
|
|
382
|
+
});
|
|
383
|
+
}
|
|
384
|
+
return anchors;
|
|
385
|
+
}
|
|
386
|
+
|
|
244
387
|
/**
|
|
245
388
|
* Emit one fragment per slice for a paragraph that pagination split across
|
|
246
389
|
* pages. The source `block` offset range stays intact on every slice; the
|
|
@@ -34,6 +34,7 @@ import type {
|
|
|
34
34
|
RuntimePageNode,
|
|
35
35
|
RuntimePageRegion,
|
|
36
36
|
RuntimePageRegions,
|
|
37
|
+
RuntimeLineRunAnchor,
|
|
37
38
|
RuntimeStoryAnchoredObject,
|
|
38
39
|
RuntimeTwipsRect,
|
|
39
40
|
} from "./page-graph.ts";
|
|
@@ -189,6 +190,7 @@ export interface PublicStoryAnchoredObject {
|
|
|
189
190
|
widthTwips: number;
|
|
190
191
|
heightTwips: number;
|
|
191
192
|
};
|
|
193
|
+
anchorRectTwips?: PublicTwipsRect;
|
|
192
194
|
relationshipIds?: readonly string[];
|
|
193
195
|
mediaIds?: readonly string[];
|
|
194
196
|
preserveOnly: boolean;
|
|
@@ -204,6 +206,8 @@ export interface PublicPageNode {
|
|
|
204
206
|
endOffset: number;
|
|
205
207
|
/** Whether this page is a blank filler (e.g. from evenPage/oddPage). */
|
|
206
208
|
isBlankFiller: boolean;
|
|
209
|
+
/** Whether L04 has measured this page or it is a lazy-pagination placeholder. */
|
|
210
|
+
materialization: NonNullable<RuntimePageNode["materialization"]>;
|
|
207
211
|
/** Resolved display page number (1-based, honors section restarts). */
|
|
208
212
|
displayPageNumber: number;
|
|
209
213
|
/** Whether this is treated as the first page of its section (title page). */
|
|
@@ -323,8 +327,28 @@ export interface PublicLineBox {
|
|
|
323
327
|
fragmentId: string;
|
|
324
328
|
lineIndex: number;
|
|
325
329
|
baselineTwips: number;
|
|
330
|
+
baselinePageYTwips?: number;
|
|
326
331
|
heightTwips: number;
|
|
327
332
|
widthTwips: number;
|
|
333
|
+
rectTwips?: PublicTwipsRect;
|
|
334
|
+
direction?: "ltr" | "rtl";
|
|
335
|
+
runAnchors?: readonly PublicLineRunAnchor[];
|
|
336
|
+
}
|
|
337
|
+
|
|
338
|
+
export interface PublicLineRunAnchor {
|
|
339
|
+
anchorId: string;
|
|
340
|
+
runId: string;
|
|
341
|
+
segmentId: string;
|
|
342
|
+
blockId: string;
|
|
343
|
+
fragmentId: string;
|
|
344
|
+
lineIndex: number;
|
|
345
|
+
direction: "ltr" | "rtl";
|
|
346
|
+
baselinePageYTwips: number;
|
|
347
|
+
lineRectTwips: PublicTwipsRect;
|
|
348
|
+
firstGlyphRectTwips: PublicTwipsRect;
|
|
349
|
+
lastGlyphRectTwips: PublicTwipsRect;
|
|
350
|
+
runRectTwips: PublicTwipsRect;
|
|
351
|
+
precision: RuntimeLineRunAnchor["precision"];
|
|
328
352
|
}
|
|
329
353
|
|
|
330
354
|
export interface PublicNoteAllocation {
|
|
@@ -348,6 +372,36 @@ export interface PublicPageSpan {
|
|
|
348
372
|
pageCount: number;
|
|
349
373
|
}
|
|
350
374
|
|
|
375
|
+
export interface PublicPagePaginationTelemetry {
|
|
376
|
+
pageId: string;
|
|
377
|
+
pageIndex: number;
|
|
378
|
+
startOffset: number;
|
|
379
|
+
endOffset: number;
|
|
380
|
+
isBlankFiller: boolean;
|
|
381
|
+
materialization: NonNullable<RuntimePageNode["materialization"]>;
|
|
382
|
+
bodyFragmentCount: number;
|
|
383
|
+
/**
|
|
384
|
+
* Unique body block ids referenced by this page. Split blocks count once
|
|
385
|
+
* on each page they occupy, which makes this the useful page-window
|
|
386
|
+
* estimator for block-index based viewport callers.
|
|
387
|
+
*/
|
|
388
|
+
bodyBlockReferenceCount: number;
|
|
389
|
+
lineBoxCount: number;
|
|
390
|
+
noteAllocationCount: number;
|
|
391
|
+
}
|
|
392
|
+
|
|
393
|
+
export interface PublicPaginationTelemetry {
|
|
394
|
+
revision: number;
|
|
395
|
+
pageCount: number;
|
|
396
|
+
materializedPageCount: number;
|
|
397
|
+
unpaginatedPageCount: number;
|
|
398
|
+
bodyFragmentCount: number;
|
|
399
|
+
bodyBlockReferenceCount: number;
|
|
400
|
+
averageBodyFragmentsPerMaterializedPage: number;
|
|
401
|
+
averageBodyBlockReferencesPerMaterializedPage: number;
|
|
402
|
+
pages: readonly PublicPagePaginationTelemetry[];
|
|
403
|
+
}
|
|
404
|
+
|
|
351
405
|
export interface PublicSectionNode {
|
|
352
406
|
sectionIndex: number;
|
|
353
407
|
startOffset: number;
|
|
@@ -556,6 +610,13 @@ export interface WordReviewEditorLayoutFacet {
|
|
|
556
610
|
*/
|
|
557
611
|
getStoryRegionsOnPage(pageIndex: number): readonly PublicPageRegion[];
|
|
558
612
|
getFragmentsForPage(pageIndex: number): PublicBlockFragment[];
|
|
613
|
+
/**
|
|
614
|
+
* Per-page materialization and body block/fragment counts derived from the
|
|
615
|
+
* current page graph. Consumers that only have block-index viewport ranges
|
|
616
|
+
* can use this to estimate page windows from the actual document instead
|
|
617
|
+
* of hardcoded blocks-per-page constants.
|
|
618
|
+
*/
|
|
619
|
+
getPaginationTelemetry(): PublicPaginationTelemetry;
|
|
559
620
|
|
|
560
621
|
/**
|
|
561
622
|
* P8 — Returns per-region block snapshots for a page. Body resolves the
|
|
@@ -1229,6 +1290,10 @@ export function createLayoutFacet(
|
|
|
1229
1290
|
.map((f) => toPublicBlockFragment(f, graph));
|
|
1230
1291
|
},
|
|
1231
1292
|
|
|
1293
|
+
getPaginationTelemetry() {
|
|
1294
|
+
return toPublicPaginationTelemetry(currentGraph());
|
|
1295
|
+
},
|
|
1296
|
+
|
|
1232
1297
|
getResolvedFormatting(blockId) {
|
|
1233
1298
|
const state = currentFormatting();
|
|
1234
1299
|
const formatting = state.paragraphs.get(blockId);
|
|
@@ -1446,6 +1511,7 @@ function toPublicPageNode(
|
|
|
1446
1511
|
startOffset: node.startOffset,
|
|
1447
1512
|
endOffset: node.endOffset,
|
|
1448
1513
|
isBlankFiller: node.isBlankFiller,
|
|
1514
|
+
materialization: node.materialization ?? "paginated",
|
|
1449
1515
|
displayPageNumber: node.stories.displayPageNumber,
|
|
1450
1516
|
isFirstPage: node.stories.isFirstPage,
|
|
1451
1517
|
isEvenPage: node.stories.isEvenPage,
|
|
@@ -1554,6 +1620,9 @@ function toPublicStoryAnchoredObject(
|
|
|
1554
1620
|
},
|
|
1555
1621
|
}
|
|
1556
1622
|
: {}),
|
|
1623
|
+
...(object.anchorRectTwips
|
|
1624
|
+
? { anchorRectTwips: toPublicTwipsRect(object.anchorRectTwips) }
|
|
1625
|
+
: {}),
|
|
1557
1626
|
...(object.relationshipIds ? { relationshipIds: [...object.relationshipIds] } : {}),
|
|
1558
1627
|
...(object.mediaIds ? { mediaIds: [...object.mediaIds] } : {}),
|
|
1559
1628
|
preserveOnly: object.preserveOnly,
|
|
@@ -1643,6 +1712,76 @@ function toPublicBlockFragment(
|
|
|
1643
1712
|
};
|
|
1644
1713
|
}
|
|
1645
1714
|
|
|
1715
|
+
function bodyFragmentsForPage(
|
|
1716
|
+
node: RuntimePageNode,
|
|
1717
|
+
graph: RuntimePageGraph,
|
|
1718
|
+
): RuntimeBlockFragment[] {
|
|
1719
|
+
const bodyFragmentIds = new Set(node.regions.body.fragmentIds);
|
|
1720
|
+
return graph.fragments.filter((fragment) =>
|
|
1721
|
+
bodyFragmentIds.has(fragment.fragmentId),
|
|
1722
|
+
);
|
|
1723
|
+
}
|
|
1724
|
+
|
|
1725
|
+
function toPublicPagePaginationTelemetry(
|
|
1726
|
+
node: RuntimePageNode,
|
|
1727
|
+
graph: RuntimePageGraph,
|
|
1728
|
+
): PublicPagePaginationTelemetry {
|
|
1729
|
+
const bodyFragments = bodyFragmentsForPage(node, graph);
|
|
1730
|
+
const bodyBlockIds = new Set(bodyFragments.map((fragment) => fragment.blockId));
|
|
1731
|
+
return {
|
|
1732
|
+
pageId: node.pageId,
|
|
1733
|
+
pageIndex: node.pageIndex,
|
|
1734
|
+
startOffset: node.startOffset,
|
|
1735
|
+
endOffset: node.endOffset,
|
|
1736
|
+
isBlankFiller: node.isBlankFiller,
|
|
1737
|
+
materialization: node.materialization ?? "paginated",
|
|
1738
|
+
bodyFragmentCount: bodyFragments.length,
|
|
1739
|
+
bodyBlockReferenceCount: bodyBlockIds.size,
|
|
1740
|
+
lineBoxCount: node.lineBoxes.length,
|
|
1741
|
+
noteAllocationCount: node.noteAllocations.length,
|
|
1742
|
+
};
|
|
1743
|
+
}
|
|
1744
|
+
|
|
1745
|
+
function average(count: number, divisor: number): number {
|
|
1746
|
+
return divisor > 0 ? count / divisor : 0;
|
|
1747
|
+
}
|
|
1748
|
+
|
|
1749
|
+
function toPublicPaginationTelemetry(
|
|
1750
|
+
graph: RuntimePageGraph,
|
|
1751
|
+
): PublicPaginationTelemetry {
|
|
1752
|
+
const pages = graph.pages.map((node) =>
|
|
1753
|
+
toPublicPagePaginationTelemetry(node, graph),
|
|
1754
|
+
);
|
|
1755
|
+
const materializedPages = pages.filter(
|
|
1756
|
+
(page) => page.materialization === "paginated",
|
|
1757
|
+
);
|
|
1758
|
+
const bodyFragmentCount = materializedPages.reduce(
|
|
1759
|
+
(total, page) => total + page.bodyFragmentCount,
|
|
1760
|
+
0,
|
|
1761
|
+
);
|
|
1762
|
+
const bodyBlockReferenceCount = materializedPages.reduce(
|
|
1763
|
+
(total, page) => total + page.bodyBlockReferenceCount,
|
|
1764
|
+
0,
|
|
1765
|
+
);
|
|
1766
|
+
return {
|
|
1767
|
+
revision: graph.revision,
|
|
1768
|
+
pageCount: pages.length,
|
|
1769
|
+
materializedPageCount: materializedPages.length,
|
|
1770
|
+
unpaginatedPageCount: pages.length - materializedPages.length,
|
|
1771
|
+
bodyFragmentCount,
|
|
1772
|
+
bodyBlockReferenceCount,
|
|
1773
|
+
averageBodyFragmentsPerMaterializedPage: average(
|
|
1774
|
+
bodyFragmentCount,
|
|
1775
|
+
materializedPages.length,
|
|
1776
|
+
),
|
|
1777
|
+
averageBodyBlockReferencesPerMaterializedPage: average(
|
|
1778
|
+
bodyBlockReferenceCount,
|
|
1779
|
+
materializedPages.length,
|
|
1780
|
+
),
|
|
1781
|
+
pages,
|
|
1782
|
+
};
|
|
1783
|
+
}
|
|
1784
|
+
|
|
1646
1785
|
function cloneContinuationCursor(
|
|
1647
1786
|
cursor: RuntimeLayoutContinuationCursor,
|
|
1648
1787
|
): RuntimeLayoutContinuationCursor {
|
|
@@ -1721,8 +1860,32 @@ function toPublicLineBox(box: RuntimeLineBox): PublicLineBox {
|
|
|
1721
1860
|
fragmentId: box.fragmentId,
|
|
1722
1861
|
lineIndex: box.lineIndex,
|
|
1723
1862
|
baselineTwips: box.baselineTwips,
|
|
1863
|
+
...(box.baselinePageYTwips !== undefined
|
|
1864
|
+
? { baselinePageYTwips: box.baselinePageYTwips }
|
|
1865
|
+
: {}),
|
|
1724
1866
|
heightTwips: box.heightTwips,
|
|
1725
1867
|
widthTwips: box.widthTwips,
|
|
1868
|
+
...(box.rectTwips ? { rectTwips: toPublicTwipsRect(box.rectTwips) } : {}),
|
|
1869
|
+
...(box.direction ? { direction: box.direction } : {}),
|
|
1870
|
+
...(box.runAnchors ? { runAnchors: box.runAnchors.map(toPublicLineRunAnchor) } : {}),
|
|
1871
|
+
};
|
|
1872
|
+
}
|
|
1873
|
+
|
|
1874
|
+
function toPublicLineRunAnchor(anchor: RuntimeLineRunAnchor): PublicLineRunAnchor {
|
|
1875
|
+
return {
|
|
1876
|
+
anchorId: anchor.anchorId,
|
|
1877
|
+
runId: anchor.runId,
|
|
1878
|
+
segmentId: anchor.segmentId,
|
|
1879
|
+
blockId: anchor.blockId,
|
|
1880
|
+
fragmentId: anchor.fragmentId,
|
|
1881
|
+
lineIndex: anchor.lineIndex,
|
|
1882
|
+
direction: anchor.direction,
|
|
1883
|
+
baselinePageYTwips: anchor.baselinePageYTwips,
|
|
1884
|
+
lineRectTwips: toPublicTwipsRect(anchor.lineRectTwips),
|
|
1885
|
+
firstGlyphRectTwips: toPublicTwipsRect(anchor.firstGlyphRectTwips),
|
|
1886
|
+
lastGlyphRectTwips: toPublicTwipsRect(anchor.lastGlyphRectTwips),
|
|
1887
|
+
runRectTwips: toPublicTwipsRect(anchor.runRectTwips),
|
|
1888
|
+
precision: anchor.precision,
|
|
1726
1889
|
};
|
|
1727
1890
|
}
|
|
1728
1891
|
|
|
@@ -2178,6 +2341,10 @@ function resolveHeaderFooterRegionBlocks(
|
|
|
2178
2341
|
blockSnapshot,
|
|
2179
2342
|
node,
|
|
2180
2343
|
graph,
|
|
2344
|
+
{
|
|
2345
|
+
ledger: findPageLocalStoryFieldLedger(node, regionKind, storyTarget),
|
|
2346
|
+
ordinal: 0,
|
|
2347
|
+
},
|
|
2181
2348
|
);
|
|
2182
2349
|
return {
|
|
2183
2350
|
blockId: blockSnapshot.blockId,
|
|
@@ -2201,6 +2368,33 @@ const PAGE_INSTANCE_FIELD_FAMILIES = new Set([
|
|
|
2201
2368
|
"SECTIONPAGES",
|
|
2202
2369
|
]);
|
|
2203
2370
|
|
|
2371
|
+
type PageLocalFieldLedger = readonly {
|
|
2372
|
+
readonly family: string;
|
|
2373
|
+
readonly displayText: string;
|
|
2374
|
+
}[];
|
|
2375
|
+
|
|
2376
|
+
interface PageScopedFieldState {
|
|
2377
|
+
readonly ledger: PageLocalFieldLedger | undefined;
|
|
2378
|
+
ordinal: number;
|
|
2379
|
+
}
|
|
2380
|
+
|
|
2381
|
+
function findPageLocalStoryFieldLedger(
|
|
2382
|
+
page: RuntimePageNode,
|
|
2383
|
+
regionKind: "header" | "footer",
|
|
2384
|
+
storyTarget: EditorStoryTarget,
|
|
2385
|
+
): PageLocalFieldLedger | undefined {
|
|
2386
|
+
if (storyTarget.kind !== regionKind) return undefined;
|
|
2387
|
+
const pageLocalStory = page.frame?.pageLocalStories.find(
|
|
2388
|
+
(story) =>
|
|
2389
|
+
story.kind === storyTarget.kind &&
|
|
2390
|
+
story.relationshipId === storyTarget.relationshipId &&
|
|
2391
|
+
story.variant === storyTarget.variant &&
|
|
2392
|
+
(storyTarget.sectionIndex === undefined ||
|
|
2393
|
+
story.sectionIndex === storyTarget.sectionIndex),
|
|
2394
|
+
);
|
|
2395
|
+
return pageLocalStory?.resolvedFields;
|
|
2396
|
+
}
|
|
2397
|
+
|
|
2204
2398
|
function resolvePageScopedFieldsInBlocks(
|
|
2205
2399
|
blocks: readonly SurfaceBlockSnapshot[],
|
|
2206
2400
|
page: RuntimePageNode,
|
|
@@ -2208,7 +2402,10 @@ function resolvePageScopedFieldsInBlocks(
|
|
|
2208
2402
|
): readonly SurfaceBlockSnapshot[] {
|
|
2209
2403
|
let changed = false;
|
|
2210
2404
|
const resolvedBlocks = blocks.map((block) => {
|
|
2211
|
-
const resolved = resolvePageInstanceFieldsInBlock(block, page, graph
|
|
2405
|
+
const resolved = resolvePageInstanceFieldsInBlock(block, page, graph, {
|
|
2406
|
+
ledger: undefined,
|
|
2407
|
+
ordinal: 0,
|
|
2408
|
+
});
|
|
2212
2409
|
if (resolved !== block) changed = true;
|
|
2213
2410
|
return resolved;
|
|
2214
2411
|
});
|
|
@@ -2219,10 +2416,16 @@ function resolvePageInstanceFieldsInBlock(
|
|
|
2219
2416
|
block: SurfaceBlockSnapshot,
|
|
2220
2417
|
page: RuntimePageNode,
|
|
2221
2418
|
graph: RuntimePageGraph,
|
|
2419
|
+
fieldState: PageScopedFieldState,
|
|
2222
2420
|
): SurfaceBlockSnapshot {
|
|
2223
2421
|
switch (block.kind) {
|
|
2224
2422
|
case "paragraph": {
|
|
2225
|
-
const segments = resolvePageInstanceFieldsInSegments(
|
|
2423
|
+
const segments = resolvePageInstanceFieldsInSegments(
|
|
2424
|
+
block.segments,
|
|
2425
|
+
page,
|
|
2426
|
+
graph,
|
|
2427
|
+
fieldState,
|
|
2428
|
+
);
|
|
2226
2429
|
return segments === block.segments ? block : { ...block, segments };
|
|
2227
2430
|
}
|
|
2228
2431
|
case "table": {
|
|
@@ -2232,7 +2435,12 @@ function resolvePageInstanceFieldsInBlock(
|
|
|
2232
2435
|
const cells = row.cells.map((cell) => {
|
|
2233
2436
|
let cellChanged = false;
|
|
2234
2437
|
const content = cell.content.map((child) => {
|
|
2235
|
-
const resolved = resolvePageInstanceFieldsInBlock(
|
|
2438
|
+
const resolved = resolvePageInstanceFieldsInBlock(
|
|
2439
|
+
child,
|
|
2440
|
+
page,
|
|
2441
|
+
graph,
|
|
2442
|
+
fieldState,
|
|
2443
|
+
);
|
|
2236
2444
|
if (resolved !== child) cellChanged = true;
|
|
2237
2445
|
return resolved;
|
|
2238
2446
|
});
|
|
@@ -2249,7 +2457,12 @@ function resolvePageInstanceFieldsInBlock(
|
|
|
2249
2457
|
case "sdt_block": {
|
|
2250
2458
|
let changed = false;
|
|
2251
2459
|
const children = block.children.map((child) => {
|
|
2252
|
-
const resolved = resolvePageInstanceFieldsInBlock(
|
|
2460
|
+
const resolved = resolvePageInstanceFieldsInBlock(
|
|
2461
|
+
child,
|
|
2462
|
+
page,
|
|
2463
|
+
graph,
|
|
2464
|
+
fieldState,
|
|
2465
|
+
);
|
|
2253
2466
|
if (resolved !== child) changed = true;
|
|
2254
2467
|
return resolved;
|
|
2255
2468
|
});
|
|
@@ -2264,17 +2477,23 @@ function resolvePageInstanceFieldsInSegments(
|
|
|
2264
2477
|
segments: SurfaceInlineSegment[],
|
|
2265
2478
|
page: RuntimePageNode,
|
|
2266
2479
|
graph: RuntimePageGraph,
|
|
2480
|
+
fieldState: PageScopedFieldState,
|
|
2267
2481
|
): SurfaceInlineSegment[] {
|
|
2268
2482
|
let changed = false;
|
|
2269
2483
|
const resolvedSegments = segments.map((segment) => {
|
|
2270
2484
|
if (segment.kind !== "field_ref" || !PAGE_INSTANCE_FIELD_FAMILIES.has(segment.fieldFamily)) {
|
|
2271
2485
|
return segment;
|
|
2272
2486
|
}
|
|
2273
|
-
const
|
|
2274
|
-
|
|
2275
|
-
|
|
2276
|
-
|
|
2277
|
-
|
|
2487
|
+
const ledgerField = fieldState.ledger?.[fieldState.ordinal];
|
|
2488
|
+
fieldState.ordinal += 1;
|
|
2489
|
+
const displayText =
|
|
2490
|
+
ledgerField?.family === segment.fieldFamily
|
|
2491
|
+
? ledgerField.displayText
|
|
2492
|
+
: resolvePageFieldDisplayText(
|
|
2493
|
+
segment.fieldFamily,
|
|
2494
|
+
segment.displayText ?? segment.label,
|
|
2495
|
+
{ page, graph },
|
|
2496
|
+
);
|
|
2278
2497
|
if (segment.displayText === displayText && segment.refreshStatus === "current") {
|
|
2279
2498
|
return segment;
|
|
2280
2499
|
}
|