@beyondwork/docx-react-component 1.0.102 → 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/core/commands/formatting-commands.ts +8 -7
- package/src/core/commands/paragraph-layout-commands.ts +11 -10
- package/src/core/commands/section-layout-commands.ts +7 -6
- package/src/core/commands/style-commands.ts +3 -2
- package/src/io/normalize/normalize-text.ts +6 -5
- package/src/io/ooxml/parse-anchor.ts +15 -15
- package/src/io/ooxml/parse-drawing.ts +103 -5
- package/src/io/ooxml/parse-fields.ts +43 -21
- package/src/io/ooxml/parse-font-table.ts +2 -1
- package/src/io/ooxml/parse-footnotes.ts +3 -2
- package/src/io/ooxml/parse-headers-footers.ts +7 -6
- package/src/io/ooxml/parse-main-document.ts +41 -40
- package/src/io/ooxml/parse-numbering.ts +3 -2
- package/src/io/ooxml/parse-object.ts +6 -6
- package/src/io/ooxml/parse-paragraph-formatting.ts +12 -11
- package/src/io/ooxml/parse-picture.ts +16 -16
- package/src/io/ooxml/parse-run-formatting.ts +11 -10
- package/src/io/ooxml/parse-settings.ts +2 -1
- package/src/io/ooxml/parse-shapes.ts +148 -17
- package/src/io/ooxml/parse-styles.ts +16 -16
- package/src/io/ooxml/parse-theme.ts +5 -4
- package/src/model/canonical-document.ts +869 -836
- 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/document-lookup.ts +3 -2
- package/src/runtime/formatting/formatting-context.ts +176 -34
- package/src/runtime/formatting/index.ts +20 -0
- package/src/runtime/formatting/layout-inputs.ts +320 -0
- package/src/runtime/formatting/numbering/geometry.ts +13 -12
- package/src/runtime/formatting/style-cascade.ts +2 -1
- package/src/runtime/formatting/table-style-resolver.ts +8 -7
- 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 +74 -39
- package/src/runtime/workflow/coordinator.ts +57 -11
- package/src/session/import/normalize.ts +2 -1
- package/src/session/import/source-package-evidence.ts +612 -1
- package/src/ui-tailwind/chrome-overlay/tw-page-stack-overlay-layer.tsx +3 -0
|
@@ -59,6 +59,12 @@
|
|
|
59
59
|
export type {
|
|
60
60
|
RuntimePageRegions,
|
|
61
61
|
RuntimePageRegion,
|
|
62
|
+
RuntimeTwipsRect,
|
|
63
|
+
RuntimeResolvedRegions,
|
|
64
|
+
RuntimeExclusionZone,
|
|
65
|
+
RuntimeLayoutDivergenceKind,
|
|
66
|
+
RuntimeLayoutDivergence,
|
|
67
|
+
RuntimePageFrame,
|
|
62
68
|
RuntimeBlockFragment,
|
|
63
69
|
RuntimeLineBox,
|
|
64
70
|
RuntimeNoteAllocation,
|
|
@@ -48,6 +48,17 @@ export interface RuntimePageRegions {
|
|
|
48
48
|
footnotes?: RuntimePageRegion[];
|
|
49
49
|
}
|
|
50
50
|
|
|
51
|
+
export interface RuntimeTwipsRect {
|
|
52
|
+
/** Twips offset from the physical page left edge. */
|
|
53
|
+
xTwips: number;
|
|
54
|
+
/** Twips offset from the physical page top edge. */
|
|
55
|
+
yTwips: number;
|
|
56
|
+
/** Width in twips. */
|
|
57
|
+
widthTwips: number;
|
|
58
|
+
/** Height in twips. */
|
|
59
|
+
heightTwips: number;
|
|
60
|
+
}
|
|
61
|
+
|
|
51
62
|
export interface RuntimePageRegion {
|
|
52
63
|
kind: "body" | "header" | "footer" | "column" | "footnote-area";
|
|
53
64
|
/** Twips offset from page top (header) or similar region-specific origin. */
|
|
@@ -56,10 +67,60 @@ export interface RuntimePageRegion {
|
|
|
56
67
|
widthTwips: number;
|
|
57
68
|
/** Height in twips. */
|
|
58
69
|
heightTwips: number;
|
|
70
|
+
/**
|
|
71
|
+
* PE2 Slice 2 — explicit twips-space frame for this region.
|
|
72
|
+
* This is semantic layout geometry, not rendered pixel geometry.
|
|
73
|
+
*/
|
|
74
|
+
rectTwips?: RuntimeTwipsRect;
|
|
59
75
|
/** IDs of block fragments rendered in this region, in order. */
|
|
60
76
|
fragmentIds: string[];
|
|
61
77
|
}
|
|
62
78
|
|
|
79
|
+
export interface RuntimeResolvedRegions {
|
|
80
|
+
body: RuntimePageRegion;
|
|
81
|
+
header?: RuntimePageRegion;
|
|
82
|
+
footer?: RuntimePageRegion;
|
|
83
|
+
columns?: RuntimePageRegion[];
|
|
84
|
+
footnotes?: RuntimePageRegion[];
|
|
85
|
+
exclusionZones: RuntimeExclusionZone[];
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
export interface RuntimeExclusionZone {
|
|
89
|
+
rectTwips: RuntimeTwipsRect;
|
|
90
|
+
objectId: string;
|
|
91
|
+
wrapMode: string;
|
|
92
|
+
layoutInCell?: boolean;
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
export type RuntimeLayoutDivergenceKind =
|
|
96
|
+
| "frame-collision"
|
|
97
|
+
| "intentional-overflow"
|
|
98
|
+
| "unsupported-wrap"
|
|
99
|
+
| "stale-field-label"
|
|
100
|
+
| "preserve-only-placeholder";
|
|
101
|
+
|
|
102
|
+
export interface RuntimeLayoutDivergence {
|
|
103
|
+
divergenceId: string;
|
|
104
|
+
kind: RuntimeLayoutDivergenceKind;
|
|
105
|
+
source: "runtime";
|
|
106
|
+
severity: "info" | "warning" | "error";
|
|
107
|
+
message: string;
|
|
108
|
+
regionKinds?: RuntimePageRegion["kind"][];
|
|
109
|
+
fragmentIds?: string[];
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
export interface RuntimePageFrame {
|
|
113
|
+
frameId: string;
|
|
114
|
+
pageId: string;
|
|
115
|
+
pageIndex: number;
|
|
116
|
+
sectionIndex: number;
|
|
117
|
+
displayPageNumber: number;
|
|
118
|
+
physicalBoundsTwips: RuntimeTwipsRect;
|
|
119
|
+
regions: RuntimeResolvedRegions;
|
|
120
|
+
divergenceIds: string[];
|
|
121
|
+
signature: string;
|
|
122
|
+
}
|
|
123
|
+
|
|
63
124
|
// ---------------------------------------------------------------------------
|
|
64
125
|
// Fragment types
|
|
65
126
|
// ---------------------------------------------------------------------------
|
|
@@ -16,9 +16,11 @@
|
|
|
16
16
|
|
|
17
17
|
import type {
|
|
18
18
|
RuntimeBlockFragment,
|
|
19
|
+
RuntimeLayoutDivergence,
|
|
19
20
|
RuntimeLineBox,
|
|
20
21
|
RuntimeNoteAllocation,
|
|
21
22
|
RuntimePageAnchor,
|
|
23
|
+
RuntimePageFrame,
|
|
22
24
|
RuntimePageRegions,
|
|
23
25
|
} from "./page-graph-types.ts";
|
|
24
26
|
import type {
|
|
@@ -63,6 +65,14 @@ export interface RuntimePageNode {
|
|
|
63
65
|
stories: ResolvedPageStories;
|
|
64
66
|
/** Sub-regions on the page. */
|
|
65
67
|
regions: RuntimePageRegions;
|
|
68
|
+
/**
|
|
69
|
+
* PE2 Slice 2 — page-level frame record for consumers that need a
|
|
70
|
+
* single immutable twips-space layout payload instead of reconstructing
|
|
71
|
+
* it from `layout` + `regions`.
|
|
72
|
+
*/
|
|
73
|
+
frame?: RuntimePageFrame;
|
|
74
|
+
/** Typed layout limitations or overlaps detected while building this page. */
|
|
75
|
+
divergences?: RuntimeLayoutDivergence[];
|
|
66
76
|
/** Line boxes rendered in the body region. */
|
|
67
77
|
lineBoxes: RuntimeLineBox[];
|
|
68
78
|
/** Footnote allocations reserved at the bottom of the page. */
|
|
@@ -473,11 +473,11 @@ export function createRuntimeCollabSync(
|
|
|
473
473
|
// For a late joiner it may already be populated — the seed ensures the
|
|
474
474
|
// runtime reflects pre-existing shared state at attach time (i.e. if a
|
|
475
475
|
// peer already set `lockedMode`, the new peer starts out locked).
|
|
476
|
-
runtime.setSharedWorkflowState(workflowShared.get());
|
|
476
|
+
runtime.setSharedWorkflowState(workflowShared.get(), { source: "collab" });
|
|
477
477
|
|
|
478
478
|
const workflowUnsub = workflowShared.subscribe((state) => {
|
|
479
479
|
if (readOnly) return; // don't propagate while in read-only (post-mismatch)
|
|
480
|
-
runtime.setSharedWorkflowState(state);
|
|
480
|
+
runtime.setSharedWorkflowState(state, { source: "collab" });
|
|
481
481
|
});
|
|
482
482
|
|
|
483
483
|
const unsubscribeCommandApplied = commandAppliedBridge.subscribe((command, _transaction, context, meta) => {
|
|
@@ -607,7 +607,7 @@ export function createRuntimeCollabSync(
|
|
|
607
607
|
yCheckpoints.unobserve(onCheckpointsChange);
|
|
608
608
|
workflowUnsub();
|
|
609
609
|
workflowShared.destroy();
|
|
610
|
-
runtime.setSharedWorkflowState(null);
|
|
610
|
+
runtime.setSharedWorkflowState(null, { source: "runtime" });
|
|
611
611
|
listeners.clear();
|
|
612
612
|
// R3 — abort the current remote-activity signal so any lingering
|
|
613
613
|
// idle consumers detect the shutdown.
|
|
@@ -576,13 +576,23 @@ function projectLayout(
|
|
|
576
576
|
runtime: DocumentRuntime,
|
|
577
577
|
fallbacks: string[],
|
|
578
578
|
): DebugInspectorLayoutSection {
|
|
579
|
-
const
|
|
579
|
+
const graphPageCount = safeCall(
|
|
580
580
|
runtime,
|
|
581
|
-
(r) => r.
|
|
581
|
+
(r) => r.getLayoutFacet().getPageCount(),
|
|
582
582
|
null,
|
|
583
583
|
fallbacks,
|
|
584
|
-
"page-
|
|
584
|
+
"layout-page-count",
|
|
585
585
|
);
|
|
586
|
+
const pageLayout =
|
|
587
|
+
graphPageCount === null
|
|
588
|
+
? safeCall(
|
|
589
|
+
runtime,
|
|
590
|
+
(r) => r.getPageLayoutSnapshot(),
|
|
591
|
+
null,
|
|
592
|
+
fallbacks,
|
|
593
|
+
"page-layout-summary",
|
|
594
|
+
)
|
|
595
|
+
: null;
|
|
586
596
|
const sections = safeCall(runtime, (r) => r.getSections(), [], fallbacks, "sections");
|
|
587
597
|
const stories = safeCall(
|
|
588
598
|
runtime,
|
|
@@ -592,7 +602,10 @@ function projectLayout(
|
|
|
592
602
|
"document-text-stream",
|
|
593
603
|
);
|
|
594
604
|
return {
|
|
595
|
-
pageCount:
|
|
605
|
+
pageCount:
|
|
606
|
+
graphPageCount ??
|
|
607
|
+
(pageLayout as { pages?: unknown[] } | null)?.pages?.length ??
|
|
608
|
+
null,
|
|
596
609
|
sectionCount: sections.length,
|
|
597
610
|
storyCount: stories.length,
|
|
598
611
|
};
|
|
@@ -82,6 +82,7 @@ import type {
|
|
|
82
82
|
WorkflowCandidateRange,
|
|
83
83
|
WorkflowCandidateRangeOptions,
|
|
84
84
|
WorkflowBlockedCommandReason,
|
|
85
|
+
WorkflowEventOrigin,
|
|
85
86
|
WorkflowMetadataDefinition,
|
|
86
87
|
OverlayKind,
|
|
87
88
|
OverlayVisibilityPolicy,
|
|
@@ -764,7 +765,10 @@ export interface DocumentRuntime {
|
|
|
764
765
|
setWorkflowOverlay(overlay: WorkflowOverlay): void;
|
|
765
766
|
clearWorkflowOverlay(): void;
|
|
766
767
|
getWorkflowOverlay(): WorkflowOverlay | null;
|
|
767
|
-
setSharedWorkflowState(
|
|
768
|
+
setSharedWorkflowState(
|
|
769
|
+
state: SharedWorkflowState | null,
|
|
770
|
+
origin?: WorkflowEventOrigin,
|
|
771
|
+
): void;
|
|
768
772
|
getWorkflowScopeSnapshot(): WorkflowScopeSnapshot | null;
|
|
769
773
|
getInteractionGuardSnapshot(): InteractionGuardSnapshot;
|
|
770
774
|
getWorkflowMarkupSnapshot(): WorkflowMarkupSnapshot;
|
|
@@ -1880,6 +1884,19 @@ export function createDocumentRuntime(
|
|
|
1880
1884
|
nextActiveStory: EditorStoryTarget,
|
|
1881
1885
|
): DocumentNavigationSnapshot {
|
|
1882
1886
|
const activeStoryKey = storyTargetKey(nextActiveStory);
|
|
1887
|
+
const buildSnapshot = (): DocumentNavigationSnapshot =>
|
|
1888
|
+
layoutEngine.getNavigationSnapshot(
|
|
1889
|
+
{
|
|
1890
|
+
document: nextState.document,
|
|
1891
|
+
viewState: {
|
|
1892
|
+
activeStory: nextActiveStory,
|
|
1893
|
+
workspaceMode: viewState.workspaceMode,
|
|
1894
|
+
zoomLevel: viewState.zoomLevel,
|
|
1895
|
+
},
|
|
1896
|
+
},
|
|
1897
|
+
nextState.selection,
|
|
1898
|
+
nextActiveStory,
|
|
1899
|
+
);
|
|
1883
1900
|
if (
|
|
1884
1901
|
cachedNavigation &&
|
|
1885
1902
|
cachedNavigation.revisionToken === nextState.revisionToken &&
|
|
@@ -1889,11 +1906,7 @@ export function createDocumentRuntime(
|
|
|
1889
1906
|
return cachedNavigation.snapshot;
|
|
1890
1907
|
}
|
|
1891
1908
|
|
|
1892
|
-
const snapshot =
|
|
1893
|
-
nextState.document,
|
|
1894
|
-
nextState.selection.head,
|
|
1895
|
-
nextActiveStory,
|
|
1896
|
-
);
|
|
1909
|
+
const snapshot = buildSnapshot();
|
|
1897
1910
|
if (
|
|
1898
1911
|
snapshot.activePageIndex === cachedNavigation.snapshot.activePageIndex &&
|
|
1899
1912
|
snapshot.activeSectionIndex === cachedNavigation.snapshot.activeSectionIndex
|
|
@@ -1915,11 +1928,7 @@ export function createDocumentRuntime(
|
|
|
1915
1928
|
return snapshot;
|
|
1916
1929
|
}
|
|
1917
1930
|
|
|
1918
|
-
const snapshot =
|
|
1919
|
-
nextState.document,
|
|
1920
|
-
nextState.selection.head,
|
|
1921
|
-
nextActiveStory,
|
|
1922
|
-
);
|
|
1931
|
+
const snapshot = buildSnapshot();
|
|
1923
1932
|
recordPerfSample("snapshot.navigation");
|
|
1924
1933
|
incrementInvalidationCounter("runtime.snapshot.navigationMisses");
|
|
1925
1934
|
cachedNavigation = {
|
|
@@ -4560,6 +4569,7 @@ export function createDocumentRuntime(
|
|
|
4560
4569
|
// P5 — TOC entries print Word's display number (honors page-
|
|
4561
4570
|
// number restarts), not the raw 0-based pageIndex+1.
|
|
4562
4571
|
(pageIndex: number) => layoutFacet.getDisplayPageNumber(pageIndex),
|
|
4572
|
+
getCachedDocumentNavigationSnapshot(state, activeStory),
|
|
4563
4573
|
);
|
|
4564
4574
|
if (refreshed.changed) {
|
|
4565
4575
|
this.dispatch({
|
|
@@ -4709,8 +4719,8 @@ export function createDocumentRuntime(
|
|
|
4709
4719
|
getWorkflowOverlay() {
|
|
4710
4720
|
return workflowCoordinator.getWorkflowOverlay();
|
|
4711
4721
|
},
|
|
4712
|
-
setSharedWorkflowState(sharedState) {
|
|
4713
|
-
workflowCoordinator.setSharedWorkflowState(sharedState);
|
|
4722
|
+
setSharedWorkflowState(sharedState, origin) {
|
|
4723
|
+
workflowCoordinator.setSharedWorkflowState(sharedState, origin);
|
|
4714
4724
|
},
|
|
4715
4725
|
getWorkflowScopeSnapshot() {
|
|
4716
4726
|
return workflowCoordinator.getWorkflowScopeSnapshot();
|
|
@@ -5935,6 +5945,7 @@ export function createDocumentRuntime(
|
|
|
5935
5945
|
activeStory,
|
|
5936
5946
|
undefined,
|
|
5937
5947
|
(pageIndex: number) => layoutFacet.getDisplayPageNumber(pageIndex),
|
|
5948
|
+
getCachedDocumentNavigationSnapshot(state, activeStory),
|
|
5938
5949
|
);
|
|
5939
5950
|
perfCounters.increment("toc.autoRefresh.us", Math.round((performance.now() - t) * 1000));
|
|
5940
5951
|
if (!refreshed.changed) {
|
|
@@ -6144,6 +6155,9 @@ export function createDocumentRuntime(
|
|
|
6144
6155
|
case "story_changed":
|
|
6145
6156
|
case "workflow_overlay_changed":
|
|
6146
6157
|
case "workflow_active_work_item_changed":
|
|
6158
|
+
case "workflow_shared_state_changed":
|
|
6159
|
+
case "workflow_visibility_policy_changed":
|
|
6160
|
+
case "workflow_markup_mode_policy_changed":
|
|
6147
6161
|
case "change_authored":
|
|
6148
6162
|
case "change_accepted":
|
|
6149
6163
|
case "change_rejected":
|
|
@@ -7482,6 +7496,7 @@ function refreshDocumentTableOfContents(
|
|
|
7482
7496
|
activeStory: EditorStoryTarget,
|
|
7483
7497
|
options?: TocRefreshOptions,
|
|
7484
7498
|
resolveDisplayPageNumber?: (pageIndex: number) => number | null,
|
|
7499
|
+
navigationSnapshot?: DocumentNavigationSnapshot,
|
|
7485
7500
|
): {
|
|
7486
7501
|
document: CanonicalDocumentEnvelope;
|
|
7487
7502
|
result: TocRefreshResult;
|
|
@@ -7511,7 +7526,8 @@ function refreshDocumentTableOfContents(
|
|
|
7511
7526
|
};
|
|
7512
7527
|
}
|
|
7513
7528
|
|
|
7514
|
-
const navigation =
|
|
7529
|
+
const navigation =
|
|
7530
|
+
navigationSnapshot ?? createDocumentNavigationSnapshot(document, selectionHead, activeStory);
|
|
7515
7531
|
// Build a single O(N) map from paragraph offset → bookmark name so the
|
|
7516
7532
|
// per-heading lookup below is O(1) instead of O(N) per heading.
|
|
7517
7533
|
const bookmarkNameByOffset = new Map<number, string>();
|
|
@@ -25,6 +25,9 @@ export function describeEventImpact(
|
|
|
25
25
|
};
|
|
26
26
|
case "workflow_overlay_changed":
|
|
27
27
|
case "workflow_active_work_item_changed":
|
|
28
|
+
case "workflow_shared_state_changed":
|
|
29
|
+
case "workflow_visibility_policy_changed":
|
|
30
|
+
case "workflow_markup_mode_policy_changed":
|
|
28
31
|
return {
|
|
29
32
|
invalidate: [
|
|
30
33
|
"workflowScope",
|
|
@@ -26,6 +26,7 @@ import type {
|
|
|
26
26
|
TableNode,
|
|
27
27
|
TextMark,
|
|
28
28
|
TextNode,
|
|
29
|
+
Mutable,
|
|
29
30
|
} from "../../model/canonical-document.ts";
|
|
30
31
|
|
|
31
32
|
export interface FoundParagraph {
|
|
@@ -165,7 +166,7 @@ export function canonicalMarksToRunFormatting(
|
|
|
165
166
|
marks: readonly TextMark[] | undefined,
|
|
166
167
|
): CanonicalRunFormatting | undefined {
|
|
167
168
|
if (!marks || marks.length === 0) return undefined;
|
|
168
|
-
const direct: CanonicalRunFormatting = {};
|
|
169
|
+
const direct: Mutable<CanonicalRunFormatting> = {};
|
|
169
170
|
for (const mark of marks) {
|
|
170
171
|
switch (mark.type) {
|
|
171
172
|
case "bold":
|
|
@@ -190,7 +191,7 @@ export function canonicalMarksToRunFormatting(
|
|
|
190
191
|
direct.smallCaps = true;
|
|
191
192
|
break;
|
|
192
193
|
case "fontFamily":
|
|
193
|
-
direct.fontFamily = mark.val;
|
|
194
|
+
(direct as Mutable<typeof direct>).fontFamily = mark.val;
|
|
194
195
|
direct.fontFamilyAscii = mark.val;
|
|
195
196
|
break;
|
|
196
197
|
case "fontSize":
|
|
@@ -44,7 +44,9 @@ import type {
|
|
|
44
44
|
RevisionRecord,
|
|
45
45
|
TableNode,
|
|
46
46
|
TableStyleConditionalRegion,
|
|
47
|
+
Mutable,
|
|
47
48
|
} from "../../model/canonical-document.ts";
|
|
49
|
+
import { collectCanonicalFieldRegionIdentities } from "../../model/canonical-layout-inputs.ts";
|
|
48
50
|
import type { FieldPageGraph, FieldResolver, ResolvedField } from "./field/resolver.ts";
|
|
49
51
|
import { createFieldResolver } from "./field/resolver.ts";
|
|
50
52
|
import { ThemeColorResolver, concretizeThemeColors } from "./theme-color.ts";
|
|
@@ -67,7 +69,22 @@ import type {
|
|
|
67
69
|
EffectiveNumbering,
|
|
68
70
|
EffectiveParagraphFormatting,
|
|
69
71
|
EffectiveRunFormatting,
|
|
72
|
+
RevisionDisplayFlags,
|
|
70
73
|
} from "./formatting-types.ts";
|
|
74
|
+
import {
|
|
75
|
+
buildEffectiveLayoutFormatting,
|
|
76
|
+
buildRevisionLayoutPosture,
|
|
77
|
+
collectFieldLayoutInputs,
|
|
78
|
+
mergeLayoutTabStops,
|
|
79
|
+
toFieldLayoutInput,
|
|
80
|
+
toLayoutTabStops,
|
|
81
|
+
toNumberingLayoutInput,
|
|
82
|
+
type EffectiveLayoutFormatting,
|
|
83
|
+
type FieldLayoutInput,
|
|
84
|
+
type LayoutTabStopInput,
|
|
85
|
+
type NumberingLayoutInput,
|
|
86
|
+
type RevisionLayoutPosture,
|
|
87
|
+
} from "./layout-inputs.ts";
|
|
71
88
|
|
|
72
89
|
/**
|
|
73
90
|
* Direct-text-mark direct formatting. Mirrors (and replaces) the
|
|
@@ -242,6 +259,16 @@ export interface FormattingContext {
|
|
|
242
259
|
options?: { advance?: boolean; emitGeometry?: boolean },
|
|
243
260
|
): NumberingResolution | null;
|
|
244
261
|
|
|
262
|
+
/**
|
|
263
|
+
* PE2 layout-ready numbering input. Normalizes the existing prefix result
|
|
264
|
+
* into marker/text-column/tab-stop fields L04 can measure without
|
|
265
|
+
* reinterpreting numbering semantics.
|
|
266
|
+
*/
|
|
267
|
+
resolveNumberingLayoutInput(
|
|
268
|
+
para: ParagraphNode,
|
|
269
|
+
options?: { advance?: boolean; emitGeometry?: boolean },
|
|
270
|
+
): NumberingLayoutInput | undefined;
|
|
271
|
+
|
|
245
272
|
/**
|
|
246
273
|
* Per-paragraph numbering-marker rPr cascade (ECMA-376 §17.9).
|
|
247
274
|
* Mirrors `resolveNumberingMarkerRunFormatting` with the layer's
|
|
@@ -318,6 +345,29 @@ export interface FormattingContext {
|
|
|
318
345
|
* context has no page-graph (so the resolver was never built). */
|
|
319
346
|
resolveField(entry: FieldRegistryEntry): ResolvedField | undefined;
|
|
320
347
|
|
|
348
|
+
/** Resolve one registered field into the PE2 field-layout input shape. */
|
|
349
|
+
resolveFieldLayoutInput(entry: FieldRegistryEntry): FieldLayoutInput;
|
|
350
|
+
|
|
351
|
+
/** Resolve every field/TOC registry entry into PE2 field-layout inputs. */
|
|
352
|
+
collectFieldLayoutInputs(): readonly FieldLayoutInput[];
|
|
353
|
+
|
|
354
|
+
/** Resolve paragraph tab stops into L04-ready twip/leader inputs. */
|
|
355
|
+
resolveParagraphTabStops(para: ParagraphNode): readonly LayoutTabStopInput[];
|
|
356
|
+
|
|
357
|
+
/**
|
|
358
|
+
* Consolidated PE2 paragraph formatting bundle. This is the L03-owned
|
|
359
|
+
* handoff shape for L04 measurement: paragraph cascade, run formatting,
|
|
360
|
+
* numbering input, paragraph/numbering tabs, compatibility flags, revision
|
|
361
|
+
* posture, and a stable structural hash are resolved in one context call.
|
|
362
|
+
*/
|
|
363
|
+
resolveParagraphLayoutFormatting(input: {
|
|
364
|
+
readonly paragraph: ParagraphNode;
|
|
365
|
+
readonly runs?: readonly EffectiveRunFormatting[];
|
|
366
|
+
readonly revisionDisplays?: readonly RevisionDisplayFlags[];
|
|
367
|
+
readonly revisionMode?: RevisionLayoutPosture["mode"];
|
|
368
|
+
readonly compatFlags?: readonly string[];
|
|
369
|
+
}): EffectiveLayoutFormatting;
|
|
370
|
+
|
|
321
371
|
/** Resolve the effective font family for a run following
|
|
322
372
|
* ECMA-376 §17.3.2.26 precedence + theme-minor fallback. */
|
|
323
373
|
resolveFontFamily(input: RunResolveInput, themeMinorFont?: string): string | undefined;
|
|
@@ -489,6 +539,13 @@ class FormattingContextImpl implements FormattingContext {
|
|
|
489
539
|
return this.numbering.resolveDetailed(effectiveNumbering, para);
|
|
490
540
|
}
|
|
491
541
|
|
|
542
|
+
resolveNumberingLayoutInput(
|
|
543
|
+
para: ParagraphNode,
|
|
544
|
+
options: { advance?: boolean; emitGeometry?: boolean } = {},
|
|
545
|
+
): NumberingLayoutInput | undefined {
|
|
546
|
+
return toNumberingLayoutInput(this.resolveParagraphNumbering(para, options));
|
|
547
|
+
}
|
|
548
|
+
|
|
492
549
|
resolveNumberingMarkerRunFormatting(
|
|
493
550
|
paragraphStyleId: string | undefined,
|
|
494
551
|
levelRunProperties: CanonicalRunFormatting | undefined,
|
|
@@ -599,6 +656,61 @@ class FormattingContextImpl implements FormattingContext {
|
|
|
599
656
|
return this.field.resolve(entry);
|
|
600
657
|
}
|
|
601
658
|
|
|
659
|
+
resolveFieldLayoutInput(entry: FieldRegistryEntry): FieldLayoutInput {
|
|
660
|
+
return toFieldLayoutInput(entry, this.resolveField(entry));
|
|
661
|
+
}
|
|
662
|
+
|
|
663
|
+
collectFieldLayoutInputs(): readonly FieldLayoutInput[] {
|
|
664
|
+
return collectFieldLayoutInputs(this.doc.fieldRegistry, (entry) =>
|
|
665
|
+
this.resolveField(entry),
|
|
666
|
+
collectCanonicalFieldRegionIdentities(this.doc),
|
|
667
|
+
);
|
|
668
|
+
}
|
|
669
|
+
|
|
670
|
+
resolveParagraphTabStops(para: ParagraphNode): readonly LayoutTabStopInput[] {
|
|
671
|
+
return toLayoutTabStops(this.resolveParagraphCascade(para).tabStops, "paragraph");
|
|
672
|
+
}
|
|
673
|
+
|
|
674
|
+
resolveParagraphLayoutFormatting(input: {
|
|
675
|
+
readonly paragraph: ParagraphNode;
|
|
676
|
+
readonly runs?: readonly EffectiveRunFormatting[];
|
|
677
|
+
readonly revisionDisplays?: readonly RevisionDisplayFlags[];
|
|
678
|
+
readonly revisionMode?: RevisionLayoutPosture["mode"];
|
|
679
|
+
readonly compatFlags?: readonly string[];
|
|
680
|
+
}): EffectiveLayoutFormatting {
|
|
681
|
+
const numberingDetail = this.resolveParagraphNumbering(input.paragraph, { advance: true });
|
|
682
|
+
const numbering = toNumberingLayoutInput(numberingDetail);
|
|
683
|
+
const paragraph = buildEffectiveParagraphFormatting(
|
|
684
|
+
this.resolveParagraphCascade(input.paragraph),
|
|
685
|
+
numberingDetail,
|
|
686
|
+
this.theme,
|
|
687
|
+
);
|
|
688
|
+
const runs = input.runs ?? [];
|
|
689
|
+
const revisionDisplays =
|
|
690
|
+
input.revisionDisplays ??
|
|
691
|
+
runs
|
|
692
|
+
.map((run) => run.revisionDisplay)
|
|
693
|
+
.filter((display): display is RevisionDisplayFlags => display !== undefined);
|
|
694
|
+
const revisionMode = input.revisionMode ?? revisionDisplays[0]?.markupMode;
|
|
695
|
+
const revisionPosture = revisionMode
|
|
696
|
+
? buildRevisionLayoutPosture(revisionMode, revisionDisplays)
|
|
697
|
+
: undefined;
|
|
698
|
+
return buildEffectiveLayoutFormatting({
|
|
699
|
+
paragraph,
|
|
700
|
+
runs,
|
|
701
|
+
...(numbering ? { numbering } : {}),
|
|
702
|
+
tabs: mergeLayoutTabStops(
|
|
703
|
+
this.resolveParagraphTabStops(input.paragraph),
|
|
704
|
+
numbering?.associatedTabStops,
|
|
705
|
+
),
|
|
706
|
+
compatFlags: mergeCompatFlags(
|
|
707
|
+
collectParagraphCompatFlags(paragraph),
|
|
708
|
+
input.compatFlags ?? [],
|
|
709
|
+
),
|
|
710
|
+
...(revisionPosture ? { revisionPosture } : {}),
|
|
711
|
+
});
|
|
712
|
+
}
|
|
713
|
+
|
|
602
714
|
resolveFontFamily(
|
|
603
715
|
input: RunResolveInput,
|
|
604
716
|
themeMinorFont?: string,
|
|
@@ -614,17 +726,11 @@ class FormattingContextImpl implements FormattingContext {
|
|
|
614
726
|
|
|
615
727
|
resolveParagraph(para: ParagraphNode): EffectiveParagraphFormatting {
|
|
616
728
|
const cascade = this.resolveParagraphCascade(para);
|
|
617
|
-
|
|
729
|
+
return buildEffectiveParagraphFormatting(
|
|
730
|
+
cascade,
|
|
618
731
|
this.resolveParagraphNumbering(para, { advance: true }),
|
|
732
|
+
this.theme,
|
|
619
733
|
);
|
|
620
|
-
const paragraphMarkRun = cascade.paragraphMarkRunProperties && this.theme
|
|
621
|
-
? (concretizeThemeColors(cascade.paragraphMarkRunProperties, this.theme) as EffectiveRunFormatting)
|
|
622
|
-
: (cascade.paragraphMarkRunProperties as EffectiveRunFormatting | undefined);
|
|
623
|
-
return {
|
|
624
|
-
...cascade,
|
|
625
|
-
...(numbering ? { numbering } : {}),
|
|
626
|
-
...(paragraphMarkRun ? { paragraphMarkRun } : {}),
|
|
627
|
-
};
|
|
628
734
|
}
|
|
629
735
|
|
|
630
736
|
resolveRunWithProvenance(input: {
|
|
@@ -635,7 +741,7 @@ class FormattingContextImpl implements FormattingContext {
|
|
|
635
741
|
// Walk each tier in priority-ascending order, recording which tier
|
|
636
742
|
// last set each field. Highest-priority writer wins — same order
|
|
637
743
|
// as `resolveEffectiveRunFormatting` but with provenance tracked.
|
|
638
|
-
const properties: { [K in keyof CanonicalRunFormatting]?: RunResolvedProperty } = {};
|
|
744
|
+
const properties: { -readonly [K in keyof CanonicalRunFormatting]?: RunResolvedProperty } = {};
|
|
639
745
|
const applyTier = (
|
|
640
746
|
tierRecord: CanonicalRunFormatting | undefined,
|
|
641
747
|
source: RunResolvedProperty["source"],
|
|
@@ -755,25 +861,25 @@ function buildDirectRunFormattingFromProjected(
|
|
|
755
861
|
projected: ProjectedRunMarks | undefined,
|
|
756
862
|
): CanonicalRunFormatting | undefined {
|
|
757
863
|
if (!projected) return undefined;
|
|
758
|
-
const direct: CanonicalRunFormatting = {};
|
|
864
|
+
const direct: Mutable<CanonicalRunFormatting> = {};
|
|
759
865
|
const marks = projected.marks;
|
|
760
866
|
if (marks) {
|
|
761
|
-
if (marks.includes("bold")) direct.bold = true;
|
|
762
|
-
if (marks.includes("italic")) direct.italic = true;
|
|
763
|
-
if (marks.includes("underline")) direct.underline = "single";
|
|
764
|
-
if (marks.includes("strikethrough")) direct.strikethrough = true;
|
|
765
|
-
if (marks.includes("doubleStrikethrough")) direct.doubleStrikethrough = true;
|
|
766
|
-
if (marks.includes("vanish")) direct.vanish = true;
|
|
767
|
-
if (marks.includes("allCaps")) direct.allCaps = true;
|
|
867
|
+
if (marks.includes("bold")) (direct as Mutable<typeof direct>).bold = true;
|
|
868
|
+
if (marks.includes("italic")) (direct as Mutable<typeof direct>).italic = true;
|
|
869
|
+
if (marks.includes("underline")) (direct as Mutable<typeof direct>).underline = "single";
|
|
870
|
+
if (marks.includes("strikethrough")) (direct as Mutable<typeof direct>).strikethrough = true;
|
|
871
|
+
if (marks.includes("doubleStrikethrough")) (direct as Mutable<typeof direct>).doubleStrikethrough = true;
|
|
872
|
+
if (marks.includes("vanish")) (direct as Mutable<typeof direct>).vanish = true;
|
|
873
|
+
if (marks.includes("allCaps")) (direct as Mutable<typeof direct>).allCaps = true;
|
|
768
874
|
if (marks.includes("smallCaps")) direct.smallCaps = true;
|
|
769
875
|
}
|
|
770
876
|
const markAttrs = projected.markAttrs;
|
|
771
877
|
if (markAttrs) {
|
|
772
878
|
if (markAttrs.fontFamily) {
|
|
773
|
-
direct.fontFamily = markAttrs.fontFamily;
|
|
879
|
+
(direct as Mutable<typeof direct>).fontFamily = markAttrs.fontFamily;
|
|
774
880
|
direct.fontFamilyAscii = markAttrs.fontFamily;
|
|
775
881
|
}
|
|
776
|
-
if (typeof markAttrs.fontSize === "number") {
|
|
882
|
+
if (typeof (markAttrs as Mutable<typeof markAttrs>).fontSize === "number") {
|
|
777
883
|
direct.fontSizeHalfPoints = markAttrs.fontSize;
|
|
778
884
|
}
|
|
779
885
|
if (markAttrs.textColor) {
|
|
@@ -789,20 +895,20 @@ function buildDirectRunFormattingFromProjected(
|
|
|
789
895
|
function extractDirectParagraphFormatting(
|
|
790
896
|
para: ParagraphNode,
|
|
791
897
|
): CanonicalParagraphFormatting {
|
|
792
|
-
const direct: CanonicalParagraphFormatting = {};
|
|
793
|
-
if (para.alignment !== undefined) direct.alignment = para.alignment;
|
|
794
|
-
if (para.spacing !== undefined) direct.spacing = para.spacing;
|
|
795
|
-
if (para.contextualSpacing !== undefined) direct.contextualSpacing = para.contextualSpacing;
|
|
796
|
-
if (para.indentation !== undefined) direct.indentation = para.indentation;
|
|
797
|
-
if (para.tabStops !== undefined) direct.tabStops = para.tabStops;
|
|
798
|
-
if (para.keepNext !== undefined) direct.keepNext = para.keepNext;
|
|
799
|
-
if (para.keepLines !== undefined) direct.keepLines = para.keepLines;
|
|
800
|
-
if (para.outlineLevel !== undefined) direct.outlineLevel = para.outlineLevel;
|
|
801
|
-
if (para.pageBreakBefore !== undefined) direct.pageBreakBefore = para.pageBreakBefore;
|
|
802
|
-
if (para.widowControl !== undefined) direct.widowControl = para.widowControl;
|
|
803
|
-
if (para.borders !== undefined) direct.borders = para.borders;
|
|
804
|
-
if (para.shading !== undefined) direct.shading = para.shading;
|
|
805
|
-
if (para.bidi !== undefined) direct.bidi = para.bidi;
|
|
898
|
+
const direct: Mutable<CanonicalParagraphFormatting> = {};
|
|
899
|
+
if (para.alignment !== undefined) (direct as Mutable<typeof direct>).alignment = para.alignment;
|
|
900
|
+
if (para.spacing !== undefined) (direct as Mutable<typeof direct>).spacing = para.spacing;
|
|
901
|
+
if (para.contextualSpacing !== undefined) (direct as Mutable<typeof direct>).contextualSpacing = para.contextualSpacing;
|
|
902
|
+
if (para.indentation !== undefined) (direct as Mutable<typeof direct>).indentation = para.indentation;
|
|
903
|
+
if (para.tabStops !== undefined) (direct as Mutable<typeof direct>).tabStops = para.tabStops;
|
|
904
|
+
if (para.keepNext !== undefined) (direct as Mutable<typeof direct>).keepNext = para.keepNext;
|
|
905
|
+
if (para.keepLines !== undefined) (direct as Mutable<typeof direct>).keepLines = para.keepLines;
|
|
906
|
+
if (para.outlineLevel !== undefined) (direct as Mutable<typeof direct>).outlineLevel = para.outlineLevel;
|
|
907
|
+
if (para.pageBreakBefore !== undefined) (direct as Mutable<typeof direct>).pageBreakBefore = para.pageBreakBefore;
|
|
908
|
+
if (para.widowControl !== undefined) (direct as Mutable<typeof direct>).widowControl = para.widowControl;
|
|
909
|
+
if (para.borders !== undefined) (direct as Mutable<typeof direct>).borders = para.borders;
|
|
910
|
+
if (para.shading !== undefined) (direct as Mutable<typeof direct>).shading = para.shading;
|
|
911
|
+
if (para.bidi !== undefined) (direct as Mutable<typeof direct>).bidi = para.bidi;
|
|
806
912
|
if (para.suppressLineNumbers !== undefined) direct.suppressLineNumbers = para.suppressLineNumbers;
|
|
807
913
|
return direct;
|
|
808
914
|
}
|
|
@@ -869,6 +975,42 @@ function buildEffectiveNumbering(
|
|
|
869
975
|
return result;
|
|
870
976
|
}
|
|
871
977
|
|
|
978
|
+
function buildEffectiveParagraphFormatting(
|
|
979
|
+
cascade: CanonicalParagraphFormatting,
|
|
980
|
+
numberingDetail: NumberingResolution | null,
|
|
981
|
+
theme: ThemeColorResolver | undefined,
|
|
982
|
+
): EffectiveParagraphFormatting {
|
|
983
|
+
const numbering = buildEffectiveNumbering(numberingDetail);
|
|
984
|
+
const paragraphMarkRun = cascade.paragraphMarkRunProperties && theme
|
|
985
|
+
? (concretizeThemeColors(cascade.paragraphMarkRunProperties, theme) as EffectiveRunFormatting)
|
|
986
|
+
: (cascade.paragraphMarkRunProperties as EffectiveRunFormatting | undefined);
|
|
987
|
+
return {
|
|
988
|
+
...cascade,
|
|
989
|
+
...(numbering ? { numbering } : {}),
|
|
990
|
+
...(paragraphMarkRun ? { paragraphMarkRun } : {}),
|
|
991
|
+
};
|
|
992
|
+
}
|
|
993
|
+
|
|
994
|
+
function collectParagraphCompatFlags(
|
|
995
|
+
paragraph: EffectiveParagraphFormatting,
|
|
996
|
+
): readonly string[] {
|
|
997
|
+
const flags: string[] = [];
|
|
998
|
+
if (paragraph.keepNext === true) flags.push("keepNext");
|
|
999
|
+
if (paragraph.keepLines === true) flags.push("keepLines");
|
|
1000
|
+
if (paragraph.pageBreakBefore === true) flags.push("pageBreakBefore");
|
|
1001
|
+
if (paragraph.contextualSpacing === true) flags.push("contextualSpacing");
|
|
1002
|
+
if (paragraph.widowControl === false) flags.push("widowControlDisabled");
|
|
1003
|
+
if (paragraph.bidi === true) flags.push("bidi");
|
|
1004
|
+
if (paragraph.suppressLineNumbers === true) flags.push("suppressLineNumbers");
|
|
1005
|
+
return flags;
|
|
1006
|
+
}
|
|
1007
|
+
|
|
1008
|
+
function mergeCompatFlags(
|
|
1009
|
+
...groups: readonly (readonly string[] | undefined)[]
|
|
1010
|
+
): readonly string[] {
|
|
1011
|
+
return [...new Set(groups.flatMap((group) => group ?? []))].sort();
|
|
1012
|
+
}
|
|
1013
|
+
|
|
872
1014
|
// Type-only re-export of HyperlinkNode so callers can type their walk.
|
|
873
1015
|
export type { HyperlinkNode, InlineNode };
|
|
874
1016
|
|
|
@@ -103,6 +103,26 @@ export { formatPageNumber } from "./field/page-number-format.ts";
|
|
|
103
103
|
|
|
104
104
|
export { rebuildFieldRegistry } from "./field/registry.ts";
|
|
105
105
|
|
|
106
|
+
// ── PE2 layout-ready formatting inputs ───────────────────────────────────
|
|
107
|
+
export {
|
|
108
|
+
buildEffectiveLayoutFormatting,
|
|
109
|
+
buildRevisionLayoutPosture,
|
|
110
|
+
collectFieldLayoutInputs,
|
|
111
|
+
createStructuralHash,
|
|
112
|
+
mergeLayoutTabStops,
|
|
113
|
+
normalizeNumberingMarkerSuffix,
|
|
114
|
+
toMeasuredRevisionLayoutPosture,
|
|
115
|
+
toFieldLayoutInput,
|
|
116
|
+
toLayoutTabStops,
|
|
117
|
+
toNumberingLayoutInput,
|
|
118
|
+
type EffectiveLayoutFormatting,
|
|
119
|
+
type FieldLayoutInput,
|
|
120
|
+
type LayoutMarkerSuffix,
|
|
121
|
+
type LayoutTabStopInput,
|
|
122
|
+
type NumberingLayoutInput,
|
|
123
|
+
type RevisionLayoutPosture,
|
|
124
|
+
} from "./layout-inputs.ts";
|
|
125
|
+
|
|
106
126
|
// ── Debug projector (Slice 3) ─────────────────────────────────────────────
|
|
107
127
|
export {
|
|
108
128
|
buildFormattingDebugEntry,
|