@beyondwork/docx-react-component 1.0.86 → 1.0.87
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 +49 -0
- package/src/api/v3/ui/chrome-composition.ts +2 -11
- package/src/api/v3/ui/chrome.ts +6 -8
- package/src/index.ts +5 -0
- package/src/io/export/serialize-main-document.ts +215 -6
- package/src/io/ooxml/parse-drawing.ts +15 -1
- package/src/io/ooxml/parse-fields.ts +410 -12
- package/src/model/canonical-document.ts +177 -2
- package/src/model/layout/page-layout-snapshot.ts +2 -0
- package/src/model/layout/runtime-page-graph-types.ts +6 -0
- package/src/preservation/store.ts +4 -5
- package/src/runtime/document-outline.ts +80 -0
- package/src/runtime/document-runtime.ts +338 -13
- package/src/runtime/formatting/field/page-number-format.ts +49 -0
- package/src/runtime/formatting/field/resolver.ts +61 -40
- package/src/runtime/layout/layout-engine-instance.ts +18 -1
- package/src/runtime/layout/layout-engine-version.ts +19 -1
- package/src/runtime/layout/measurement-backend-canvas.ts +21 -9
- package/src/runtime/layout/measurement-backend-empirical.ts +18 -4
- package/src/runtime/layout/page-graph.ts +13 -2
- package/src/runtime/layout/paginated-layout-engine.ts +440 -117
- package/src/runtime/layout/project-block-fragments.ts +87 -4
- package/src/runtime/layout/resolve-page-fields.ts +8 -5
- package/src/runtime/layout/table-row-split.ts +97 -23
- package/src/runtime/surface-projection.ts +227 -27
- package/src/shell/session-bootstrap.ts +6 -1
- package/src/ui/WordReviewEditor.tsx +8 -0
- package/src/ui/editor-surface-controller.tsx +1 -0
- package/src/ui/headless/revision-decoration-model.ts +11 -13
- package/src/ui-tailwind/chrome/responsive-chrome.ts +2 -2
- package/src/ui-tailwind/chrome/tw-alert-banner.tsx +27 -0
- package/src/ui-tailwind/editor-surface/fast-text-edit-lane.ts +57 -6
- package/src/ui-tailwind/editor-surface/page-slice-util.ts +17 -0
- package/src/ui-tailwind/editor-surface/perf-probe.ts +5 -0
- package/src/ui-tailwind/editor-surface/pm-contextual-ui.ts +34 -0
- package/src/ui-tailwind/editor-surface/pm-decorations.ts +146 -20
- package/src/ui-tailwind/editor-surface/pm-position-map.ts +8 -2
- package/src/ui-tailwind/editor-surface/tw-prosemirror-surface.tsx +41 -44
- package/src/ui-tailwind/page-stack/use-visible-block-range.ts +1 -1
- package/src/ui-tailwind/review/tw-revision-sidebar.tsx +32 -3
- package/src/ui-tailwind/review-workspace/use-review-rail-state.ts +1 -1
- package/src/ui-tailwind/tw-review-workspace.tsx +18 -0
|
@@ -120,6 +120,12 @@ export interface ParagraphLineSlice {
|
|
|
120
120
|
pageIndex: number;
|
|
121
121
|
/** Inclusive-exclusive line range rendered by this slice. */
|
|
122
122
|
lineRange: { from: number; to: number; totalLines: number };
|
|
123
|
+
/** Height consumed by this visible slice, when measured by pagination. */
|
|
124
|
+
heightTwips?: number;
|
|
125
|
+
/** Resolved line height used for this paragraph slice. */
|
|
126
|
+
lineHeightTwips?: number;
|
|
127
|
+
/** Available inline width for this slice. */
|
|
128
|
+
widthTwips?: number;
|
|
123
129
|
}
|
|
124
130
|
|
|
125
131
|
/**
|
|
@@ -137,6 +143,10 @@ export interface TableRowSlice {
|
|
|
137
143
|
pageIndex: number;
|
|
138
144
|
/** Inclusive-exclusive row range rendered by this slice. */
|
|
139
145
|
rowRange: { from: number; to: number; totalRows: number };
|
|
146
|
+
/** Height consumed by this row slice, including repeated headers. */
|
|
147
|
+
heightTwips?: number;
|
|
148
|
+
/** Column occupied by this slice when the table flows across columns. */
|
|
149
|
+
columnIndex?: number;
|
|
140
150
|
}
|
|
141
151
|
|
|
142
152
|
/**
|
|
@@ -154,6 +164,17 @@ export interface BlockSplits {
|
|
|
154
164
|
tablesByBlockId: Map<string, TableRowSlice[]>;
|
|
155
165
|
}
|
|
156
166
|
|
|
167
|
+
export interface FragmentMeasurement {
|
|
168
|
+
/** Height consumed by this block fragment on the page. */
|
|
169
|
+
heightTwips: number;
|
|
170
|
+
/** Paragraph line count for this fragment, when known. */
|
|
171
|
+
lineCount?: number;
|
|
172
|
+
/** Resolved paragraph line height for generated body line boxes. */
|
|
173
|
+
lineHeightTwips?: number;
|
|
174
|
+
/** Available inline width used during measurement. */
|
|
175
|
+
widthTwips?: number;
|
|
176
|
+
}
|
|
177
|
+
|
|
157
178
|
export interface PageStackResultWithSplits {
|
|
158
179
|
pages: DocumentPageSnapshot[];
|
|
159
180
|
splits: BlockSplits;
|
|
@@ -178,6 +199,13 @@ export interface PageStackResultWithSplits {
|
|
|
178
199
|
* `RuntimePageRegions.columns[i].fragmentIds`.
|
|
179
200
|
*/
|
|
180
201
|
columnByBlockIdByPageIndex?: ReadonlyMap<number, ReadonlyMap<string, number>>;
|
|
202
|
+
/**
|
|
203
|
+
* Measured whole-fragment geometry keyed by global page index and blockId.
|
|
204
|
+
* Split paragraph/table slices carry their own height on the slice object;
|
|
205
|
+
* this map covers unsplit/whole fragments and keeps projection from
|
|
206
|
+
* recomputing geometry with heuristic span math.
|
|
207
|
+
*/
|
|
208
|
+
fragmentMeasurementsByPageIndex?: ReadonlyMap<number, ReadonlyMap<string, FragmentMeasurement>>;
|
|
181
209
|
}
|
|
182
210
|
|
|
183
211
|
// ---------------------------------------------------------------------------
|
|
@@ -234,6 +262,7 @@ export function buildPageStackWithSplits(
|
|
|
234
262
|
: undefined;
|
|
235
263
|
const pages: DocumentPageSnapshot[] = [];
|
|
236
264
|
const splitsByBlock = new Map<string, ParagraphLineSlice[]>();
|
|
265
|
+
const tableSplitsByBlock = new Map<string, TableRowSlice[]>();
|
|
237
266
|
// P8.1b — aggregate note allocations and fragments across all sections,
|
|
238
267
|
// keyed by global page index.
|
|
239
268
|
const globalNoteAllocationsByPageIndex = new Map<number, RuntimeNoteAllocation[]>();
|
|
@@ -245,6 +274,7 @@ export function buildPageStackWithSplits(
|
|
|
245
274
|
// sections, keyed by global page index. Populated only for blocks that
|
|
246
275
|
// paginated under a multi-column layout; absent entries mean single-column.
|
|
247
276
|
const globalColumnByBlockIdByPageIndex = new Map<number, Map<string, number>>();
|
|
277
|
+
const globalFragmentMeasurementsByPageIndex = new Map<number, Map<string, FragmentMeasurement>>();
|
|
248
278
|
// Refactor/04 post-Slice-4 — end-column per global page, used by the
|
|
249
279
|
// next section's `nextColumn` handling to decide whether to seed
|
|
250
280
|
// section N at `prevEnd + 1` or start fresh on a new page.
|
|
@@ -407,10 +437,27 @@ export function buildPageStackWithSplits(
|
|
|
407
437
|
existing.push({
|
|
408
438
|
pageIndex: globalPageIdx,
|
|
409
439
|
lineRange: localSlice.lineRange,
|
|
440
|
+
...(localSlice.heightTwips !== undefined ? { heightTwips: localSlice.heightTwips } : {}),
|
|
441
|
+
...(localSlice.lineHeightTwips !== undefined ? { lineHeightTwips: localSlice.lineHeightTwips } : {}),
|
|
442
|
+
...(localSlice.widthTwips !== undefined ? { widthTwips: localSlice.widthTwips } : {}),
|
|
410
443
|
});
|
|
411
444
|
}
|
|
412
445
|
if (existing.length > 0) splitsByBlock.set(blockId, existing);
|
|
413
446
|
}
|
|
447
|
+
for (const [blockId, localSlices] of paginatedResult.splits.tablesByBlockId) {
|
|
448
|
+
const existing = tableSplitsByBlock.get(blockId) ?? [];
|
|
449
|
+
for (const localSlice of localSlices) {
|
|
450
|
+
const globalPageIdx = pageInSectionToGlobal.get(localSlice.pageInSection);
|
|
451
|
+
if (globalPageIdx === undefined) continue;
|
|
452
|
+
existing.push({
|
|
453
|
+
pageIndex: globalPageIdx,
|
|
454
|
+
rowRange: localSlice.rowRange,
|
|
455
|
+
...(localSlice.heightTwips !== undefined ? { heightTwips: localSlice.heightTwips } : {}),
|
|
456
|
+
...(localSlice.columnIndex !== undefined ? { columnIndex: localSlice.columnIndex } : {}),
|
|
457
|
+
});
|
|
458
|
+
}
|
|
459
|
+
if (existing.length > 0) tableSplitsByBlock.set(blockId, existing);
|
|
460
|
+
}
|
|
414
461
|
|
|
415
462
|
// P8.1b — resolve per-section note allocations + fragments to global
|
|
416
463
|
// page index and merge into the global maps.
|
|
@@ -451,6 +498,31 @@ export function buildPageStackWithSplits(
|
|
|
451
498
|
}
|
|
452
499
|
endColumnByGlobalPageIndex.set(globalPageIdx, maxCol);
|
|
453
500
|
}
|
|
501
|
+
for (const [pageInSec, sectionMeasurements] of paginatedResult.fragmentMeasurementsByPageInSection) {
|
|
502
|
+
const globalPageIdx = pageInSectionToGlobal.get(pageInSec);
|
|
503
|
+
if (globalPageIdx === undefined) continue;
|
|
504
|
+
let forPage = globalFragmentMeasurementsByPageIndex.get(globalPageIdx);
|
|
505
|
+
if (!forPage) {
|
|
506
|
+
forPage = new Map<string, FragmentMeasurement>();
|
|
507
|
+
globalFragmentMeasurementsByPageIndex.set(globalPageIdx, forPage);
|
|
508
|
+
}
|
|
509
|
+
for (const [blockId, measurement] of sectionMeasurements) {
|
|
510
|
+
const existing = forPage.get(blockId);
|
|
511
|
+
if (!existing) {
|
|
512
|
+
forPage.set(blockId, measurement);
|
|
513
|
+
continue;
|
|
514
|
+
}
|
|
515
|
+
forPage.set(blockId, {
|
|
516
|
+
heightTwips: existing.heightTwips + measurement.heightTwips,
|
|
517
|
+
lineCount:
|
|
518
|
+
existing.lineCount !== undefined || measurement.lineCount !== undefined
|
|
519
|
+
? (existing.lineCount ?? 0) + (measurement.lineCount ?? 0)
|
|
520
|
+
: undefined,
|
|
521
|
+
lineHeightTwips: measurement.lineHeightTwips ?? existing.lineHeightTwips,
|
|
522
|
+
widthTwips: measurement.widthTwips ?? existing.widthTwips,
|
|
523
|
+
});
|
|
524
|
+
}
|
|
525
|
+
}
|
|
454
526
|
}
|
|
455
527
|
|
|
456
528
|
// Guarantee at least one page
|
|
@@ -470,7 +542,7 @@ export function buildPageStackWithSplits(
|
|
|
470
542
|
}
|
|
471
543
|
|
|
472
544
|
applyWidowControlPass(pages, mainSurface);
|
|
473
|
-
|
|
545
|
+
applyChapterPageNumbering(pages, mainSurface);
|
|
474
546
|
return {
|
|
475
547
|
pages,
|
|
476
548
|
splits: { byBlockId: splitsByBlock, tablesByBlockId: tableSplitsByBlock },
|
|
@@ -483,9 +555,133 @@ export function buildPageStackWithSplits(
|
|
|
483
555
|
columnByBlockIdByPageIndex: globalColumnByBlockIdByPageIndex.size > 0
|
|
484
556
|
? globalColumnByBlockIdByPageIndex
|
|
485
557
|
: undefined,
|
|
558
|
+
fragmentMeasurementsByPageIndex: globalFragmentMeasurementsByPageIndex.size > 0
|
|
559
|
+
? globalFragmentMeasurementsByPageIndex
|
|
560
|
+
: undefined,
|
|
486
561
|
};
|
|
487
562
|
}
|
|
488
563
|
|
|
564
|
+
function applyChapterPageNumbering(
|
|
565
|
+
pages: DocumentPageSnapshot[],
|
|
566
|
+
mainSurface: EditorSurfaceSnapshot,
|
|
567
|
+
): void {
|
|
568
|
+
const pagesNeedingChapterNumber = pages.filter(
|
|
569
|
+
(page) => page.layout.pageNumbering?.chapterStyle,
|
|
570
|
+
);
|
|
571
|
+
if (pagesNeedingChapterNumber.length === 0) {
|
|
572
|
+
return;
|
|
573
|
+
}
|
|
574
|
+
|
|
575
|
+
for (const page of pagesNeedingChapterNumber) {
|
|
576
|
+
const pageNumbering = page.layout.pageNumbering;
|
|
577
|
+
if (!pageNumbering?.chapterStyle) {
|
|
578
|
+
continue;
|
|
579
|
+
}
|
|
580
|
+
const chapterNumber = resolveChapterNumberForPage(
|
|
581
|
+
mainSurface.blocks,
|
|
582
|
+
page,
|
|
583
|
+
pageNumbering.chapterStyle,
|
|
584
|
+
);
|
|
585
|
+
if (!chapterNumber) {
|
|
586
|
+
continue;
|
|
587
|
+
}
|
|
588
|
+
page.layout = {
|
|
589
|
+
...page.layout,
|
|
590
|
+
pageNumbering: {
|
|
591
|
+
...pageNumbering,
|
|
592
|
+
chapterNumber,
|
|
593
|
+
},
|
|
594
|
+
};
|
|
595
|
+
}
|
|
596
|
+
}
|
|
597
|
+
|
|
598
|
+
function resolveChapterNumberForPage(
|
|
599
|
+
blocks: readonly SurfaceBlockSnapshot[],
|
|
600
|
+
page: DocumentPageSnapshot,
|
|
601
|
+
chapterStyle: string,
|
|
602
|
+
): string | undefined {
|
|
603
|
+
let current: string | undefined;
|
|
604
|
+
let firstOnPage: string | undefined;
|
|
605
|
+
for (const block of blocks) {
|
|
606
|
+
if (block.kind !== "paragraph") {
|
|
607
|
+
continue;
|
|
608
|
+
}
|
|
609
|
+
if (block.from >= page.endOffset) {
|
|
610
|
+
break;
|
|
611
|
+
}
|
|
612
|
+
if (!paragraphMatchesChapterStyle(block, chapterStyle)) {
|
|
613
|
+
continue;
|
|
614
|
+
}
|
|
615
|
+
const chapterNumber = normalizeChapterNumberMarker(
|
|
616
|
+
block.numberingPrefix ?? block.resolvedNumbering?.text,
|
|
617
|
+
);
|
|
618
|
+
if (!chapterNumber) {
|
|
619
|
+
continue;
|
|
620
|
+
}
|
|
621
|
+
if (block.from >= page.startOffset && firstOnPage === undefined) {
|
|
622
|
+
firstOnPage = chapterNumber;
|
|
623
|
+
}
|
|
624
|
+
if (block.from <= page.startOffset) {
|
|
625
|
+
current = chapterNumber;
|
|
626
|
+
}
|
|
627
|
+
}
|
|
628
|
+
return current ?? firstOnPage;
|
|
629
|
+
}
|
|
630
|
+
|
|
631
|
+
function paragraphMatchesChapterStyle(
|
|
632
|
+
block: Extract<SurfaceBlockSnapshot, { kind: "paragraph" }>,
|
|
633
|
+
chapterStyle: string,
|
|
634
|
+
): boolean {
|
|
635
|
+
const normalizedChapterStyle = normalizeStyleToken(chapterStyle);
|
|
636
|
+
if (!normalizedChapterStyle) {
|
|
637
|
+
return false;
|
|
638
|
+
}
|
|
639
|
+
const normalizedStyleId = normalizeStyleToken(block.styleId);
|
|
640
|
+
if (normalizedStyleId && normalizedStyleId === normalizedChapterStyle) {
|
|
641
|
+
return true;
|
|
642
|
+
}
|
|
643
|
+
|
|
644
|
+
const chapterLevel = chapterStyleToHeadingLevel(chapterStyle);
|
|
645
|
+
if (chapterLevel === undefined) {
|
|
646
|
+
return false;
|
|
647
|
+
}
|
|
648
|
+
if (block.outlineLevel === chapterLevel - 1) {
|
|
649
|
+
return true;
|
|
650
|
+
}
|
|
651
|
+
const headingLevel = styleIdToHeadingLevel(block.styleId);
|
|
652
|
+
return headingLevel === chapterLevel;
|
|
653
|
+
}
|
|
654
|
+
|
|
655
|
+
function chapterStyleToHeadingLevel(value: string): number | undefined {
|
|
656
|
+
const trimmed = value.trim();
|
|
657
|
+
if (!trimmed) {
|
|
658
|
+
return undefined;
|
|
659
|
+
}
|
|
660
|
+
if (/^[1-9]$/u.test(trimmed)) {
|
|
661
|
+
return Number(trimmed);
|
|
662
|
+
}
|
|
663
|
+
return styleIdToHeadingLevel(trimmed);
|
|
664
|
+
}
|
|
665
|
+
|
|
666
|
+
function styleIdToHeadingLevel(value: string | undefined): number | undefined {
|
|
667
|
+
const normalized = normalizeStyleToken(value);
|
|
668
|
+
const match = /^heading([1-9])$/u.exec(normalized);
|
|
669
|
+
return match ? Number(match[1]) : undefined;
|
|
670
|
+
}
|
|
671
|
+
|
|
672
|
+
function normalizeStyleToken(value: string | undefined): string {
|
|
673
|
+
return (value ?? "").trim().replace(/[\s_-]+/gu, "").toLowerCase();
|
|
674
|
+
}
|
|
675
|
+
|
|
676
|
+
function normalizeChapterNumberMarker(value: string | undefined): string | undefined {
|
|
677
|
+
const trimmed = value?.trim();
|
|
678
|
+
if (!trimmed) {
|
|
679
|
+
return undefined;
|
|
680
|
+
}
|
|
681
|
+
const normalized = trimmed.replace(/[\s.):-]+$/u, "");
|
|
682
|
+
return normalized.length > 0 ? normalized : trimmed;
|
|
683
|
+
}
|
|
684
|
+
|
|
489
685
|
/**
|
|
490
686
|
* Resumable variant of `buildPageStack` — returns page snapshots starting at
|
|
491
687
|
* `resumeAt.startPageIndex`, suitable for splicing into a prior page graph.
|
|
@@ -582,6 +778,14 @@ export function buildPageStackFromWithSplits(
|
|
|
582
778
|
const tail = slices.filter((s) => s.pageIndex >= dirtyPageNumberOffset);
|
|
583
779
|
if (tail.length > 0) tailTableSplits.set(blockId, tail);
|
|
584
780
|
}
|
|
781
|
+
const tailFragmentMeasurements =
|
|
782
|
+
full.fragmentMeasurementsByPageIndex && full.fragmentMeasurementsByPageIndex.size > 0
|
|
783
|
+
? new Map(
|
|
784
|
+
Array.from(full.fragmentMeasurementsByPageIndex.entries()).filter(
|
|
785
|
+
([pi]) => pi >= dirtyPageNumberOffset,
|
|
786
|
+
),
|
|
787
|
+
)
|
|
788
|
+
: undefined;
|
|
585
789
|
return {
|
|
586
790
|
pages: tailPages,
|
|
587
791
|
splits: { byBlockId: tailSplits, tablesByBlockId: tailTableSplits },
|
|
@@ -594,6 +798,9 @@ export function buildPageStackFromWithSplits(
|
|
|
594
798
|
),
|
|
595
799
|
}
|
|
596
800
|
: {}),
|
|
801
|
+
...(tailFragmentMeasurements !== undefined
|
|
802
|
+
? { fragmentMeasurementsByPageIndex: tailFragmentMeasurements }
|
|
803
|
+
: {}),
|
|
597
804
|
};
|
|
598
805
|
}
|
|
599
806
|
|
|
@@ -616,6 +823,7 @@ export function buildPageStackFromWithSplits(
|
|
|
616
823
|
dirtySurface,
|
|
617
824
|
measurementProvider,
|
|
618
825
|
);
|
|
826
|
+
applyChapterPageNumbering(tailResult.pages, mainSurface);
|
|
619
827
|
|
|
620
828
|
// Shift global page indices on all returned pages and splits so they align
|
|
621
829
|
// with the global page graph (the caller's spliceGraph prepends the head).
|
|
@@ -666,6 +874,15 @@ export function buildPageStackFromWithSplits(
|
|
|
666
874
|
]),
|
|
667
875
|
)
|
|
668
876
|
: undefined;
|
|
877
|
+
const shiftedFragmentMeasurements =
|
|
878
|
+
tailResult.fragmentMeasurementsByPageIndex && tailResult.fragmentMeasurementsByPageIndex.size > 0
|
|
879
|
+
? new Map<number, ReadonlyMap<string, FragmentMeasurement>>(
|
|
880
|
+
Array.from(tailResult.fragmentMeasurementsByPageIndex.entries()).map(([pi, measurements]) => [
|
|
881
|
+
pi + dirtyPageNumberOffset,
|
|
882
|
+
measurements,
|
|
883
|
+
]),
|
|
884
|
+
)
|
|
885
|
+
: undefined;
|
|
669
886
|
|
|
670
887
|
return {
|
|
671
888
|
pages: shiftedPages,
|
|
@@ -679,88 +896,12 @@ export function buildPageStackFromWithSplits(
|
|
|
679
896
|
...(shiftedColumnByBlockId !== undefined
|
|
680
897
|
? { columnByBlockIdByPageIndex: shiftedColumnByBlockId }
|
|
681
898
|
: {}),
|
|
899
|
+
...(shiftedFragmentMeasurements !== undefined
|
|
900
|
+
? { fragmentMeasurementsByPageIndex: shiftedFragmentMeasurements }
|
|
901
|
+
: {}),
|
|
682
902
|
};
|
|
683
903
|
}
|
|
684
904
|
|
|
685
|
-
// ---------------------------------------------------------------------------
|
|
686
|
-
// R3: table row-slice collection
|
|
687
|
-
// ---------------------------------------------------------------------------
|
|
688
|
-
|
|
689
|
-
/**
|
|
690
|
-
* Compute `TableRowSlice[]` entries for table blocks that span multiple pages.
|
|
691
|
-
*
|
|
692
|
-
* Tables are currently placed atomically by `paginateSectionBlocks` — the
|
|
693
|
-
* engine never splits a table mid-row. As a consequence the offset range of
|
|
694
|
-
* every table block falls inside exactly one page's `[startOffset, endOffset)`,
|
|
695
|
-
* and this function returns an empty map.
|
|
696
|
-
*
|
|
697
|
-
* The function is wired so that when row-level table pagination lands (the
|
|
698
|
-
* engine emits `pushPage` mid-table, making `pages[]` contain a page whose
|
|
699
|
-
* `startOffset` lands between two rows of a table), the same walk
|
|
700
|
-
* automatically groups rows by page and emits `TableRowSlice` entries
|
|
701
|
-
* without any further schema change.
|
|
702
|
-
*
|
|
703
|
-
* Row offsets are derived from `row.cells[0].content[0].from` — the first
|
|
704
|
-
* cell's first inner block. Rows whose first cell has no paragraph fall
|
|
705
|
-
* back to the table's own `from` so they group with the table's origin page.
|
|
706
|
-
*/
|
|
707
|
-
function collectTableRowSlices(
|
|
708
|
-
blocks: readonly SurfaceBlockSnapshot[],
|
|
709
|
-
pages: readonly DocumentPageSnapshot[],
|
|
710
|
-
): Map<string, TableRowSlice[]> {
|
|
711
|
-
const result = new Map<string, TableRowSlice[]>();
|
|
712
|
-
if (pages.length === 0) return result;
|
|
713
|
-
|
|
714
|
-
const findPageIndex = (offset: number): number | null => {
|
|
715
|
-
for (const page of pages) {
|
|
716
|
-
if (offset >= page.startOffset && offset < page.endOffset) {
|
|
717
|
-
return page.pageIndex;
|
|
718
|
-
}
|
|
719
|
-
}
|
|
720
|
-
const last = pages[pages.length - 1];
|
|
721
|
-
if (last && offset >= last.startOffset && offset <= last.endOffset) {
|
|
722
|
-
return last.pageIndex;
|
|
723
|
-
}
|
|
724
|
-
return null;
|
|
725
|
-
};
|
|
726
|
-
|
|
727
|
-
for (const block of blocks) {
|
|
728
|
-
if (block.kind !== "table") continue;
|
|
729
|
-
const totalRows = block.rows.length;
|
|
730
|
-
if (totalRows === 0) continue;
|
|
731
|
-
|
|
732
|
-
// Walk rows and assign each to the page containing its start offset.
|
|
733
|
-
const perPageRange = new Map<number, { from: number; to: number }>();
|
|
734
|
-
for (let rowIndex = 0; rowIndex < totalRows; rowIndex += 1) {
|
|
735
|
-
const row = block.rows[rowIndex]!;
|
|
736
|
-
const rowStart = row.cells[0]?.content[0]?.from ?? block.from;
|
|
737
|
-
const pageIndex = findPageIndex(rowStart);
|
|
738
|
-
if (pageIndex === null) continue;
|
|
739
|
-
const existing = perPageRange.get(pageIndex);
|
|
740
|
-
if (existing) {
|
|
741
|
-
existing.to = rowIndex + 1;
|
|
742
|
-
} else {
|
|
743
|
-
perPageRange.set(pageIndex, { from: rowIndex, to: rowIndex + 1 });
|
|
744
|
-
}
|
|
745
|
-
}
|
|
746
|
-
|
|
747
|
-
// Single-page tables produce no slices.
|
|
748
|
-
if (perPageRange.size <= 1) continue;
|
|
749
|
-
|
|
750
|
-
const slices: TableRowSlice[] = [];
|
|
751
|
-
for (const [pageIndex, range] of perPageRange) {
|
|
752
|
-
slices.push({
|
|
753
|
-
pageIndex,
|
|
754
|
-
rowRange: { from: range.from, to: range.to, totalRows },
|
|
755
|
-
});
|
|
756
|
-
}
|
|
757
|
-
slices.sort((a, b) => a.pageIndex - b.pageIndex);
|
|
758
|
-
result.set(block.blockId, slices);
|
|
759
|
-
}
|
|
760
|
-
|
|
761
|
-
return result;
|
|
762
|
-
}
|
|
763
|
-
|
|
764
905
|
// ---------------------------------------------------------------------------
|
|
765
906
|
// Widow control pass
|
|
766
907
|
// ---------------------------------------------------------------------------
|
|
@@ -1273,6 +1414,10 @@ function measureParagraphLineCount(
|
|
|
1273
1414
|
currentLineCapacity = subsequentLineCapacity;
|
|
1274
1415
|
break;
|
|
1275
1416
|
case "image":
|
|
1417
|
+
// Keep the heuristic paginator calibrated to the historical CCEP
|
|
1418
|
+
// page-count locks. Intrinsic object extent is preserved by the
|
|
1419
|
+
// measurement backends; true object placement belongs to the frame
|
|
1420
|
+
// layout path, not paragraph line-count inflation.
|
|
1276
1421
|
lineCount += Math.max(1, Math.round(segment.display === "floating" ? 2 : 1));
|
|
1277
1422
|
currentLineChars = 0;
|
|
1278
1423
|
currentLineCapacity = subsequentLineCapacity;
|
|
@@ -1351,11 +1496,24 @@ function collectSectionBlocks(
|
|
|
1351
1496
|
interface SectionLocalSlice {
|
|
1352
1497
|
pageInSection: number;
|
|
1353
1498
|
lineRange: { from: number; to: number; totalLines: number };
|
|
1499
|
+
heightTwips?: number;
|
|
1500
|
+
lineHeightTwips?: number;
|
|
1501
|
+
widthTwips?: number;
|
|
1502
|
+
}
|
|
1503
|
+
|
|
1504
|
+
interface SectionLocalTableSlice {
|
|
1505
|
+
pageInSection: number;
|
|
1506
|
+
rowRange: { from: number; to: number; totalRows: number };
|
|
1507
|
+
heightTwips?: number;
|
|
1508
|
+
columnIndex?: number;
|
|
1354
1509
|
}
|
|
1355
1510
|
|
|
1356
1511
|
interface SectionPaginationResult {
|
|
1357
1512
|
pages: Omit<DocumentPageSnapshot, "pageIndex">[];
|
|
1358
|
-
splits: {
|
|
1513
|
+
splits: {
|
|
1514
|
+
byBlockId: Map<string, SectionLocalSlice[]>;
|
|
1515
|
+
tablesByBlockId: Map<string, SectionLocalTableSlice[]>;
|
|
1516
|
+
};
|
|
1359
1517
|
/** P8.1b — per-page note allocations keyed by pageInSection index. */
|
|
1360
1518
|
noteAllocationsByPageInSection: Map<number, RuntimeNoteAllocation[]>;
|
|
1361
1519
|
/** P8.1b — per-page note body fragments keyed by pageInSection index. */
|
|
@@ -1366,6 +1524,7 @@ interface SectionPaginationResult {
|
|
|
1366
1524
|
* single-column sections; absent for note/footnote blocks.
|
|
1367
1525
|
*/
|
|
1368
1526
|
columnByBlockIdByPageInSection: Map<number, Map<string, number>>;
|
|
1527
|
+
fragmentMeasurementsByPageInSection: Map<number, Map<string, FragmentMeasurement>>;
|
|
1369
1528
|
}
|
|
1370
1529
|
|
|
1371
1530
|
/**
|
|
@@ -1431,15 +1590,18 @@ export function paginateSectionBlocksWithSplits(
|
|
|
1431
1590
|
layout,
|
|
1432
1591
|
},
|
|
1433
1592
|
],
|
|
1434
|
-
splits: { byBlockId: new Map()
|
|
1593
|
+
splits: { byBlockId: new Map(), tablesByBlockId: new Map() },
|
|
1435
1594
|
noteAllocationsByPageInSection: new Map(),
|
|
1436
1595
|
noteFragmentsByPageInSection: new Map(),
|
|
1437
1596
|
columnByBlockIdByPageInSection: new Map(),
|
|
1597
|
+
fragmentMeasurementsByPageInSection: new Map(),
|
|
1438
1598
|
};
|
|
1439
1599
|
}
|
|
1440
1600
|
|
|
1441
1601
|
const pages: Omit<DocumentPageSnapshot, "pageIndex">[] = [];
|
|
1442
1602
|
const splitsByBlock = new Map<string, SectionLocalSlice[]>();
|
|
1603
|
+
const tableSplitsByBlock = new Map<string, SectionLocalTableSlice[]>();
|
|
1604
|
+
const fragmentMeasurementsByPageInSection = new Map<number, Map<string, FragmentMeasurement>>();
|
|
1443
1605
|
const usableHeight = getUsablePageHeight(layout);
|
|
1444
1606
|
const columnMetrics = getUsableColumnMetrics(layout);
|
|
1445
1607
|
const maxColumns = Math.max(1, columnMetrics.length);
|
|
@@ -1476,6 +1638,38 @@ export function paginateSectionBlocksWithSplits(
|
|
|
1476
1638
|
forPage.set(blockId, columnIndex);
|
|
1477
1639
|
}
|
|
1478
1640
|
};
|
|
1641
|
+
const recordFragmentMeasurement = (
|
|
1642
|
+
blockId: string,
|
|
1643
|
+
measurement: FragmentMeasurement,
|
|
1644
|
+
): void => {
|
|
1645
|
+
let forPage = fragmentMeasurementsByPageInSection.get(pageInSection);
|
|
1646
|
+
if (!forPage) {
|
|
1647
|
+
forPage = new Map<string, FragmentMeasurement>();
|
|
1648
|
+
fragmentMeasurementsByPageInSection.set(pageInSection, forPage);
|
|
1649
|
+
}
|
|
1650
|
+
const existing = forPage.get(blockId);
|
|
1651
|
+
if (existing) {
|
|
1652
|
+
forPage.set(blockId, {
|
|
1653
|
+
heightTwips: existing.heightTwips + measurement.heightTwips,
|
|
1654
|
+
lineCount:
|
|
1655
|
+
existing.lineCount !== undefined || measurement.lineCount !== undefined
|
|
1656
|
+
? (existing.lineCount ?? 0) + (measurement.lineCount ?? 0)
|
|
1657
|
+
: undefined,
|
|
1658
|
+
lineHeightTwips: measurement.lineHeightTwips ?? existing.lineHeightTwips,
|
|
1659
|
+
widthTwips: measurement.widthTwips ?? existing.widthTwips,
|
|
1660
|
+
});
|
|
1661
|
+
return;
|
|
1662
|
+
}
|
|
1663
|
+
forPage.set(blockId, measurement);
|
|
1664
|
+
};
|
|
1665
|
+
const appendTableSlice = (
|
|
1666
|
+
blockId: string,
|
|
1667
|
+
slice: SectionLocalTableSlice,
|
|
1668
|
+
): void => {
|
|
1669
|
+
const list = tableSplitsByBlock.get(blockId) ?? [];
|
|
1670
|
+
list.push(slice);
|
|
1671
|
+
tableSplitsByBlock.set(blockId, list);
|
|
1672
|
+
};
|
|
1479
1673
|
// P6.c: per-table progress when a table is being split row-by-row
|
|
1480
1674
|
// across pages. Map<blockId, nextRowIndexToPlace>. Cleared once a
|
|
1481
1675
|
// table is fully placed.
|
|
@@ -1501,6 +1695,30 @@ export function paginateSectionBlocksWithSplits(
|
|
|
1501
1695
|
if (delta === 0) return baseHeight;
|
|
1502
1696
|
return Math.max(MIN_BLOCK_HEIGHT_TWIPS, baseHeight - delta);
|
|
1503
1697
|
};
|
|
1698
|
+
const measureParagraphFragment = (
|
|
1699
|
+
block: Extract<SurfaceBlockSnapshot, { kind: "paragraph" }>,
|
|
1700
|
+
formatting: import("./resolved-formatting-state.ts").ResolvedParagraphFormatting | null,
|
|
1701
|
+
columnWidth: number,
|
|
1702
|
+
heightTwips: number,
|
|
1703
|
+
lineRange?: { from: number; to: number; totalLines: number },
|
|
1704
|
+
): FragmentMeasurement => {
|
|
1705
|
+
if (!formatting) return { heightTwips, widthTwips: columnWidth };
|
|
1706
|
+
const lineCount = lineRange
|
|
1707
|
+
? Math.max(0, lineRange.to - lineRange.from)
|
|
1708
|
+
: (
|
|
1709
|
+
cache?.getLineCount(block, columnWidth) ??
|
|
1710
|
+
measureParagraphLineCount(block, formatting, columnWidth, measurementProvider)
|
|
1711
|
+
);
|
|
1712
|
+
if (!lineRange && cache?.getLineCount(block, columnWidth) === undefined) {
|
|
1713
|
+
cache?.setLineCount(block, columnWidth, lineCount);
|
|
1714
|
+
}
|
|
1715
|
+
return {
|
|
1716
|
+
heightTwips,
|
|
1717
|
+
lineCount: Math.max(1, lineCount),
|
|
1718
|
+
lineHeightTwips: formatting.lineHeight,
|
|
1719
|
+
widthTwips: columnWidth,
|
|
1720
|
+
};
|
|
1721
|
+
};
|
|
1504
1722
|
|
|
1505
1723
|
// P8.1b — per-page note tracking.
|
|
1506
1724
|
// `pendingNoteKeys` parallels `reservedNotes` but is only snapshotted on
|
|
@@ -1700,6 +1918,8 @@ export function paginateSectionBlocksWithSplits(
|
|
|
1700
1918
|
block,
|
|
1701
1919
|
columnWidth,
|
|
1702
1920
|
measurementProvider,
|
|
1921
|
+
defaultTabInterval,
|
|
1922
|
+
themeFonts,
|
|
1703
1923
|
});
|
|
1704
1924
|
const { cantSplitFlags, isHeaderFlags } = extractRowFlags(block);
|
|
1705
1925
|
const repeatedHeaderHeightTwips = computeRepeatedHeaderHeight(
|
|
@@ -1720,10 +1940,29 @@ export function paginateSectionBlocksWithSplits(
|
|
|
1720
1940
|
const firstChild = row?.cells[0]?.content[0];
|
|
1721
1941
|
return firstChild?.from ?? block.from;
|
|
1722
1942
|
};
|
|
1943
|
+
const sliceHeight = (fromRow: number, toRow: number): number => {
|
|
1944
|
+
let height = fromRow > 0 ? repeatedHeaderHeightTwips : 0;
|
|
1945
|
+
for (let r = fromRow; r < toRow; r += 1) {
|
|
1946
|
+
height += rowHeights[r] ?? 0;
|
|
1947
|
+
}
|
|
1948
|
+
return height;
|
|
1949
|
+
};
|
|
1723
1950
|
|
|
1724
1951
|
// Case 1: remainder fits — place and break.
|
|
1725
1952
|
if (remainderHeight <= remainingForTable) {
|
|
1726
1953
|
recordColumnPlacement(block.blockId);
|
|
1954
|
+
if (startRow > 0) {
|
|
1955
|
+
appendTableSlice(block.blockId, {
|
|
1956
|
+
pageInSection,
|
|
1957
|
+
rowRange: { from: startRow, to: rowHeights.length, totalRows: rowHeights.length },
|
|
1958
|
+
heightTwips: remainderHeight,
|
|
1959
|
+
...(isMultiColumn ? { columnIndex } : {}),
|
|
1960
|
+
});
|
|
1961
|
+
}
|
|
1962
|
+
recordFragmentMeasurement(block.blockId, {
|
|
1963
|
+
heightTwips: startRow > 0 ? remainderHeight : baseHeight,
|
|
1964
|
+
widthTwips: columnWidth,
|
|
1965
|
+
});
|
|
1727
1966
|
columnHeight += startRow > 0 ? remainderHeight : baseHeight;
|
|
1728
1967
|
if (startRow > 0) tableProgress.delete(block.blockId);
|
|
1729
1968
|
if (index === blocks.length - 1) pushPage(section.end);
|
|
@@ -1740,16 +1979,83 @@ export function paginateSectionBlocksWithSplits(
|
|
|
1740
1979
|
startRow,
|
|
1741
1980
|
});
|
|
1742
1981
|
if (decision.rowsOnCurrentPage > 0) {
|
|
1982
|
+
const splitHeight = sliceHeight(startRow, decision.splitRowIndex);
|
|
1743
1983
|
recordColumnPlacement(block.blockId);
|
|
1984
|
+
appendTableSlice(block.blockId, {
|
|
1985
|
+
pageInSection,
|
|
1986
|
+
rowRange: {
|
|
1987
|
+
from: startRow,
|
|
1988
|
+
to: decision.splitRowIndex,
|
|
1989
|
+
totalRows: rowHeights.length,
|
|
1990
|
+
},
|
|
1991
|
+
heightTwips: splitHeight,
|
|
1992
|
+
...(isMultiColumn ? { columnIndex } : {}),
|
|
1993
|
+
});
|
|
1994
|
+
recordFragmentMeasurement(block.blockId, {
|
|
1995
|
+
heightTwips: splitHeight,
|
|
1996
|
+
widthTwips: columnWidth,
|
|
1997
|
+
});
|
|
1998
|
+
columnHeight += splitHeight;
|
|
1744
1999
|
tableProgress.set(block.blockId, decision.splitRowIndex);
|
|
2000
|
+
if (columnIndex < maxColumns - 1) {
|
|
2001
|
+
columnIndex += 1;
|
|
2002
|
+
columnHeight = 0;
|
|
2003
|
+
continue;
|
|
2004
|
+
}
|
|
1745
2005
|
pushPage(rowOffset(decision.splitRowIndex));
|
|
1746
2006
|
continue;
|
|
1747
2007
|
}
|
|
1748
2008
|
|
|
2009
|
+
// Degraded row-boundary placement for an oversized continuation row.
|
|
2010
|
+
// If no row fits on a fresh continuation page, pushing at startRow's
|
|
2011
|
+
// offset would re-open the same page forever. Keep the row intact,
|
|
2012
|
+
// mark it as visually overflowing this page/column, and advance to the
|
|
2013
|
+
// next row boundary.
|
|
2014
|
+
if (startRow > 0 && columnHeight === 0) {
|
|
2015
|
+
const forcedEndRow = Math.min(rowHeights.length, startRow + 1);
|
|
2016
|
+
const forcedHeight = sliceHeight(startRow, forcedEndRow);
|
|
2017
|
+
recordColumnPlacement(block.blockId);
|
|
2018
|
+
appendTableSlice(block.blockId, {
|
|
2019
|
+
pageInSection,
|
|
2020
|
+
rowRange: {
|
|
2021
|
+
from: startRow,
|
|
2022
|
+
to: forcedEndRow,
|
|
2023
|
+
totalRows: rowHeights.length,
|
|
2024
|
+
},
|
|
2025
|
+
heightTwips: forcedHeight,
|
|
2026
|
+
...(isMultiColumn ? { columnIndex } : {}),
|
|
2027
|
+
});
|
|
2028
|
+
recordFragmentMeasurement(block.blockId, {
|
|
2029
|
+
heightTwips: forcedHeight,
|
|
2030
|
+
widthTwips: columnWidth,
|
|
2031
|
+
});
|
|
2032
|
+
columnHeight += forcedHeight;
|
|
2033
|
+
if (forcedEndRow >= rowHeights.length) {
|
|
2034
|
+
tableProgress.delete(block.blockId);
|
|
2035
|
+
if (index === blocks.length - 1) pushPage(section.end);
|
|
2036
|
+
break;
|
|
2037
|
+
}
|
|
2038
|
+
tableProgress.set(block.blockId, forcedEndRow);
|
|
2039
|
+
if (columnIndex < maxColumns - 1) {
|
|
2040
|
+
columnIndex += 1;
|
|
2041
|
+
columnHeight = 0;
|
|
2042
|
+
continue;
|
|
2043
|
+
}
|
|
2044
|
+
const nextRowOffset = rowOffset(forcedEndRow);
|
|
2045
|
+
const fallbackOffset = Math.min(section.end, pageStart + 1);
|
|
2046
|
+
pushPage(nextRowOffset > pageStart ? nextRowOffset : fallbackOffset);
|
|
2047
|
+
continue;
|
|
2048
|
+
}
|
|
2049
|
+
|
|
1749
2050
|
// Case 3: can't split here. If there's content above (or we're
|
|
1750
2051
|
// resuming), push everything from the resume point to the next
|
|
1751
2052
|
// page so the next iteration starts fresh.
|
|
1752
2053
|
if (columnHeight > 0 || startRow > 0) {
|
|
2054
|
+
if (columnIndex < maxColumns - 1) {
|
|
2055
|
+
columnIndex += 1;
|
|
2056
|
+
columnHeight = 0;
|
|
2057
|
+
continue;
|
|
2058
|
+
}
|
|
1753
2059
|
pushPage(startRow > 0 ? rowOffset(startRow) : block.from);
|
|
1754
2060
|
continue;
|
|
1755
2061
|
}
|
|
@@ -1758,23 +2064,22 @@ export function paginateSectionBlocksWithSplits(
|
|
|
1758
2064
|
// it doesn't fit on, AND the first row alone exceeds page
|
|
1759
2065
|
// height). Preserve pre-P6.c semantics so offsets stay clean.
|
|
1760
2066
|
recordColumnPlacement(block.blockId);
|
|
2067
|
+
recordFragmentMeasurement(block.blockId, {
|
|
2068
|
+
heightTwips: baseHeight,
|
|
2069
|
+
widthTwips: columnWidth,
|
|
2070
|
+
});
|
|
1761
2071
|
columnHeight += baseHeight;
|
|
1762
2072
|
if (index === blocks.length - 1) pushPage(section.end);
|
|
1763
2073
|
break;
|
|
1764
2074
|
}
|
|
1765
2075
|
|
|
1766
|
-
// Overflow check —
|
|
2076
|
+
// Overflow check — block doesn't fit on current page/column.
|
|
1767
2077
|
if (projectedHeight > usableHeight && pageStart < block.from) {
|
|
1768
2078
|
if (columnIndex < maxColumns - 1) {
|
|
1769
|
-
// Advance to next column without a page break
|
|
2079
|
+
// Advance to next column without a page break. Footnotes are
|
|
2080
|
+
// page-scoped, so keep pending note state alive until pushPage().
|
|
1770
2081
|
columnIndex += 1;
|
|
1771
2082
|
columnHeight = 0;
|
|
1772
|
-
reservedNoteHeight = 0;
|
|
1773
|
-
reservedNotes.clear();
|
|
1774
|
-
// P8.1b: clear pending note state WITHOUT snapshotting.
|
|
1775
|
-
pendingNoteKeys.clear();
|
|
1776
|
-
pendingNoteBlockFroms.clear();
|
|
1777
|
-
pendingNoteColumnWidths.clear();
|
|
1778
2083
|
continue;
|
|
1779
2084
|
}
|
|
1780
2085
|
|
|
@@ -1822,24 +2127,42 @@ export function paginateSectionBlocksWithSplits(
|
|
|
1822
2127
|
if (splitRule) {
|
|
1823
2128
|
const bleedUpPageInSection = pageInSection;
|
|
1824
2129
|
const anchorPageInSection = pageInSection + 1;
|
|
2130
|
+
const currentLineRange = {
|
|
2131
|
+
from: 0,
|
|
2132
|
+
to: splitRule.linesOnCurrent,
|
|
2133
|
+
totalLines,
|
|
2134
|
+
};
|
|
2135
|
+
const nextLineRange = {
|
|
2136
|
+
from: splitRule.linesOnCurrent,
|
|
2137
|
+
to: totalLines,
|
|
2138
|
+
totalLines,
|
|
2139
|
+
};
|
|
1825
2140
|
splitsByBlock.set(block.blockId, [
|
|
1826
2141
|
{
|
|
1827
2142
|
pageInSection: bleedUpPageInSection,
|
|
1828
|
-
lineRange:
|
|
1829
|
-
|
|
1830
|
-
|
|
1831
|
-
|
|
1832
|
-
},
|
|
2143
|
+
lineRange: currentLineRange,
|
|
2144
|
+
heightTwips: splitRule.linesOnCurrent * formatting.lineHeight,
|
|
2145
|
+
lineHeightTwips: formatting.lineHeight,
|
|
2146
|
+
widthTwips: columnWidth,
|
|
1833
2147
|
},
|
|
1834
2148
|
{
|
|
1835
2149
|
pageInSection: anchorPageInSection,
|
|
1836
|
-
lineRange:
|
|
1837
|
-
|
|
1838
|
-
|
|
1839
|
-
|
|
1840
|
-
},
|
|
2150
|
+
lineRange: nextLineRange,
|
|
2151
|
+
heightTwips: Math.max(0, totalLines - splitRule.linesOnCurrent) * formatting.lineHeight,
|
|
2152
|
+
lineHeightTwips: formatting.lineHeight,
|
|
2153
|
+
widthTwips: columnWidth,
|
|
1841
2154
|
},
|
|
1842
2155
|
]);
|
|
2156
|
+
recordFragmentMeasurement(
|
|
2157
|
+
block.blockId,
|
|
2158
|
+
measureParagraphFragment(
|
|
2159
|
+
block,
|
|
2160
|
+
formatting,
|
|
2161
|
+
columnWidth,
|
|
2162
|
+
splitRule.linesOnCurrent * formatting.lineHeight,
|
|
2163
|
+
currentLineRange,
|
|
2164
|
+
),
|
|
2165
|
+
);
|
|
1843
2166
|
}
|
|
1844
2167
|
}
|
|
1845
2168
|
|
|
@@ -1852,15 +2175,10 @@ export function paginateSectionBlocksWithSplits(
|
|
|
1852
2175
|
// span the full page if it's truly larger than a page).
|
|
1853
2176
|
if (keepLinesActive && columnHeight > 0 && baseHeight > usableHeight - columnHeight && pageStart < block.from) {
|
|
1854
2177
|
if (columnIndex < maxColumns - 1) {
|
|
1855
|
-
// Column advance without page break
|
|
2178
|
+
// Column advance without page break. Keep page-scoped footnote
|
|
2179
|
+
// reservations/pending allocations until the page closes.
|
|
1856
2180
|
columnIndex += 1;
|
|
1857
2181
|
columnHeight = 0;
|
|
1858
|
-
reservedNoteHeight = 0;
|
|
1859
|
-
reservedNotes.clear();
|
|
1860
|
-
// P8.1b: clear pending note state WITHOUT snapshotting.
|
|
1861
|
-
pendingNoteKeys.clear();
|
|
1862
|
-
pendingNoteBlockFroms.clear();
|
|
1863
|
-
pendingNoteColumnWidths.clear();
|
|
1864
2182
|
continue;
|
|
1865
2183
|
}
|
|
1866
2184
|
pushPage(block.from);
|
|
@@ -1877,6 +2195,17 @@ export function paginateSectionBlocksWithSplits(
|
|
|
1877
2195
|
// branch below advances `columnIndex`; the block itself lives in the
|
|
1878
2196
|
// PRE-advance column.
|
|
1879
2197
|
recordColumnPlacement(block.blockId);
|
|
2198
|
+
if (block.kind === "paragraph") {
|
|
2199
|
+
recordFragmentMeasurement(
|
|
2200
|
+
block.blockId,
|
|
2201
|
+
measureParagraphFragment(block, formatting, columnWidth, baseHeight),
|
|
2202
|
+
);
|
|
2203
|
+
} else {
|
|
2204
|
+
recordFragmentMeasurement(block.blockId, {
|
|
2205
|
+
heightTwips: baseHeight,
|
|
2206
|
+
widthTwips: columnWidth,
|
|
2207
|
+
});
|
|
2208
|
+
}
|
|
1880
2209
|
columnHeight += baseHeight;
|
|
1881
2210
|
reservedNoteHeight += effectiveNoteHeight;
|
|
1882
2211
|
currentPageNoteIds(block).forEach((noteKey) => {
|
|
@@ -1904,17 +2233,10 @@ export function paginateSectionBlocksWithSplits(
|
|
|
1904
2233
|
if (hasColumnBreak(block)) {
|
|
1905
2234
|
if (columnIndex < maxColumns - 1) {
|
|
1906
2235
|
// Column break within a multi-column layout: advance to next column.
|
|
1907
|
-
//
|
|
1908
|
-
//
|
|
1909
|
-
// appeared before the column break don't get double-counted.
|
|
2236
|
+
// Do not snapshot note allocations here; they remain attached to
|
|
2237
|
+
// the physical page and are emitted when pushPage() closes it.
|
|
1910
2238
|
columnIndex += 1;
|
|
1911
2239
|
columnHeight = 0;
|
|
1912
|
-
reservedNoteHeight = 0;
|
|
1913
|
-
reservedNotes.clear();
|
|
1914
|
-
// P8.1b: clear pending note state WITHOUT snapshotting.
|
|
1915
|
-
pendingNoteKeys.clear();
|
|
1916
|
-
pendingNoteBlockFroms.clear();
|
|
1917
|
-
pendingNoteColumnWidths.clear();
|
|
1918
2240
|
} else {
|
|
1919
2241
|
pushPage(nextBoundary);
|
|
1920
2242
|
}
|
|
@@ -1941,10 +2263,11 @@ export function paginateSectionBlocksWithSplits(
|
|
|
1941
2263
|
layout,
|
|
1942
2264
|
},
|
|
1943
2265
|
],
|
|
1944
|
-
splits: { byBlockId: splitsByBlock },
|
|
2266
|
+
splits: { byBlockId: splitsByBlock, tablesByBlockId: tableSplitsByBlock },
|
|
1945
2267
|
noteAllocationsByPageInSection,
|
|
1946
2268
|
noteFragmentsByPageInSection,
|
|
1947
2269
|
columnByBlockIdByPageInSection,
|
|
2270
|
+
fragmentMeasurementsByPageInSection,
|
|
1948
2271
|
};
|
|
1949
2272
|
}
|
|
1950
2273
|
|