@beyondwork/docx-react-component 1.0.101 → 1.0.103
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/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/export/build-app-properties-xml.ts +24 -0
- package/src/io/normalize/normalize-text.ts +6 -5
- package/src/io/ooxml/docprops.ts +298 -0
- package/src/io/ooxml/parse-anchor.ts +15 -15
- package/src/io/ooxml/parse-drawing.ts +5 -5
- package/src/io/ooxml/parse-fields.ts +16 -15
- 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 +18 -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 +920 -815
- package/src/runtime/formatting/document-lookup.ts +3 -2
- package/src/runtime/formatting/formatting-context.ts +66 -25
- package/src/runtime/formatting/index.ts +18 -0
- package/src/runtime/formatting/layout-inputs.ts +256 -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/surface-projection.ts +31 -36
- package/src/session/export/stateful-export-pipeline.ts +9 -4
- package/src/session/export/stateful-export.ts +22 -6
- package/src/session/import/canonical-assembly.ts +2 -3
- package/src/session/import/loader-types.ts +3 -1
- package/src/session/import/loader.ts +12 -0
- package/src/session/import/normalize.ts +2 -1
- package/src/session/import/source-package-evidence.ts +1016 -0
- package/src/session/shared/session-utils.ts +9 -0
|
@@ -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,6 +44,7 @@ import type {
|
|
|
44
44
|
RevisionRecord,
|
|
45
45
|
TableNode,
|
|
46
46
|
TableStyleConditionalRegion,
|
|
47
|
+
Mutable,
|
|
47
48
|
} from "../../model/canonical-document.ts";
|
|
48
49
|
import type { FieldPageGraph, FieldResolver, ResolvedField } from "./field/resolver.ts";
|
|
49
50
|
import { createFieldResolver } from "./field/resolver.ts";
|
|
@@ -68,6 +69,13 @@ import type {
|
|
|
68
69
|
EffectiveParagraphFormatting,
|
|
69
70
|
EffectiveRunFormatting,
|
|
70
71
|
} from "./formatting-types.ts";
|
|
72
|
+
import {
|
|
73
|
+
collectFieldLayoutInputs,
|
|
74
|
+
toFieldLayoutInput,
|
|
75
|
+
toNumberingLayoutInput,
|
|
76
|
+
type FieldLayoutInput,
|
|
77
|
+
type NumberingLayoutInput,
|
|
78
|
+
} from "./layout-inputs.ts";
|
|
71
79
|
|
|
72
80
|
/**
|
|
73
81
|
* Direct-text-mark direct formatting. Mirrors (and replaces) the
|
|
@@ -242,6 +250,16 @@ export interface FormattingContext {
|
|
|
242
250
|
options?: { advance?: boolean; emitGeometry?: boolean },
|
|
243
251
|
): NumberingResolution | null;
|
|
244
252
|
|
|
253
|
+
/**
|
|
254
|
+
* PE2 layout-ready numbering input. Normalizes the existing prefix result
|
|
255
|
+
* into marker/text-column/tab-stop fields L04 can measure without
|
|
256
|
+
* reinterpreting numbering semantics.
|
|
257
|
+
*/
|
|
258
|
+
resolveNumberingLayoutInput(
|
|
259
|
+
para: ParagraphNode,
|
|
260
|
+
options?: { advance?: boolean; emitGeometry?: boolean },
|
|
261
|
+
): NumberingLayoutInput | undefined;
|
|
262
|
+
|
|
245
263
|
/**
|
|
246
264
|
* Per-paragraph numbering-marker rPr cascade (ECMA-376 §17.9).
|
|
247
265
|
* Mirrors `resolveNumberingMarkerRunFormatting` with the layer's
|
|
@@ -318,6 +336,12 @@ export interface FormattingContext {
|
|
|
318
336
|
* context has no page-graph (so the resolver was never built). */
|
|
319
337
|
resolveField(entry: FieldRegistryEntry): ResolvedField | undefined;
|
|
320
338
|
|
|
339
|
+
/** Resolve one registered field into the PE2 field-layout input shape. */
|
|
340
|
+
resolveFieldLayoutInput(entry: FieldRegistryEntry): FieldLayoutInput;
|
|
341
|
+
|
|
342
|
+
/** Resolve every field/TOC registry entry into PE2 field-layout inputs. */
|
|
343
|
+
collectFieldLayoutInputs(): readonly FieldLayoutInput[];
|
|
344
|
+
|
|
321
345
|
/** Resolve the effective font family for a run following
|
|
322
346
|
* ECMA-376 §17.3.2.26 precedence + theme-minor fallback. */
|
|
323
347
|
resolveFontFamily(input: RunResolveInput, themeMinorFont?: string): string | undefined;
|
|
@@ -489,6 +513,13 @@ class FormattingContextImpl implements FormattingContext {
|
|
|
489
513
|
return this.numbering.resolveDetailed(effectiveNumbering, para);
|
|
490
514
|
}
|
|
491
515
|
|
|
516
|
+
resolveNumberingLayoutInput(
|
|
517
|
+
para: ParagraphNode,
|
|
518
|
+
options: { advance?: boolean; emitGeometry?: boolean } = {},
|
|
519
|
+
): NumberingLayoutInput | undefined {
|
|
520
|
+
return toNumberingLayoutInput(this.resolveParagraphNumbering(para, options));
|
|
521
|
+
}
|
|
522
|
+
|
|
492
523
|
resolveNumberingMarkerRunFormatting(
|
|
493
524
|
paragraphStyleId: string | undefined,
|
|
494
525
|
levelRunProperties: CanonicalRunFormatting | undefined,
|
|
@@ -599,6 +630,16 @@ class FormattingContextImpl implements FormattingContext {
|
|
|
599
630
|
return this.field.resolve(entry);
|
|
600
631
|
}
|
|
601
632
|
|
|
633
|
+
resolveFieldLayoutInput(entry: FieldRegistryEntry): FieldLayoutInput {
|
|
634
|
+
return toFieldLayoutInput(entry, this.resolveField(entry));
|
|
635
|
+
}
|
|
636
|
+
|
|
637
|
+
collectFieldLayoutInputs(): readonly FieldLayoutInput[] {
|
|
638
|
+
return collectFieldLayoutInputs(this.doc.fieldRegistry, (entry) =>
|
|
639
|
+
this.resolveField(entry),
|
|
640
|
+
);
|
|
641
|
+
}
|
|
642
|
+
|
|
602
643
|
resolveFontFamily(
|
|
603
644
|
input: RunResolveInput,
|
|
604
645
|
themeMinorFont?: string,
|
|
@@ -635,7 +676,7 @@ class FormattingContextImpl implements FormattingContext {
|
|
|
635
676
|
// Walk each tier in priority-ascending order, recording which tier
|
|
636
677
|
// last set each field. Highest-priority writer wins — same order
|
|
637
678
|
// as `resolveEffectiveRunFormatting` but with provenance tracked.
|
|
638
|
-
const properties: { [K in keyof CanonicalRunFormatting]?: RunResolvedProperty } = {};
|
|
679
|
+
const properties: { -readonly [K in keyof CanonicalRunFormatting]?: RunResolvedProperty } = {};
|
|
639
680
|
const applyTier = (
|
|
640
681
|
tierRecord: CanonicalRunFormatting | undefined,
|
|
641
682
|
source: RunResolvedProperty["source"],
|
|
@@ -755,25 +796,25 @@ function buildDirectRunFormattingFromProjected(
|
|
|
755
796
|
projected: ProjectedRunMarks | undefined,
|
|
756
797
|
): CanonicalRunFormatting | undefined {
|
|
757
798
|
if (!projected) return undefined;
|
|
758
|
-
const direct: CanonicalRunFormatting = {};
|
|
799
|
+
const direct: Mutable<CanonicalRunFormatting> = {};
|
|
759
800
|
const marks = projected.marks;
|
|
760
801
|
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;
|
|
802
|
+
if (marks.includes("bold")) (direct as Mutable<typeof direct>).bold = true;
|
|
803
|
+
if (marks.includes("italic")) (direct as Mutable<typeof direct>).italic = true;
|
|
804
|
+
if (marks.includes("underline")) (direct as Mutable<typeof direct>).underline = "single";
|
|
805
|
+
if (marks.includes("strikethrough")) (direct as Mutable<typeof direct>).strikethrough = true;
|
|
806
|
+
if (marks.includes("doubleStrikethrough")) (direct as Mutable<typeof direct>).doubleStrikethrough = true;
|
|
807
|
+
if (marks.includes("vanish")) (direct as Mutable<typeof direct>).vanish = true;
|
|
808
|
+
if (marks.includes("allCaps")) (direct as Mutable<typeof direct>).allCaps = true;
|
|
768
809
|
if (marks.includes("smallCaps")) direct.smallCaps = true;
|
|
769
810
|
}
|
|
770
811
|
const markAttrs = projected.markAttrs;
|
|
771
812
|
if (markAttrs) {
|
|
772
813
|
if (markAttrs.fontFamily) {
|
|
773
|
-
direct.fontFamily = markAttrs.fontFamily;
|
|
814
|
+
(direct as Mutable<typeof direct>).fontFamily = markAttrs.fontFamily;
|
|
774
815
|
direct.fontFamilyAscii = markAttrs.fontFamily;
|
|
775
816
|
}
|
|
776
|
-
if (typeof markAttrs.fontSize === "number") {
|
|
817
|
+
if (typeof (markAttrs as Mutable<typeof markAttrs>).fontSize === "number") {
|
|
777
818
|
direct.fontSizeHalfPoints = markAttrs.fontSize;
|
|
778
819
|
}
|
|
779
820
|
if (markAttrs.textColor) {
|
|
@@ -789,20 +830,20 @@ function buildDirectRunFormattingFromProjected(
|
|
|
789
830
|
function extractDirectParagraphFormatting(
|
|
790
831
|
para: ParagraphNode,
|
|
791
832
|
): 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;
|
|
833
|
+
const direct: Mutable<CanonicalParagraphFormatting> = {};
|
|
834
|
+
if (para.alignment !== undefined) (direct as Mutable<typeof direct>).alignment = para.alignment;
|
|
835
|
+
if (para.spacing !== undefined) (direct as Mutable<typeof direct>).spacing = para.spacing;
|
|
836
|
+
if (para.contextualSpacing !== undefined) (direct as Mutable<typeof direct>).contextualSpacing = para.contextualSpacing;
|
|
837
|
+
if (para.indentation !== undefined) (direct as Mutable<typeof direct>).indentation = para.indentation;
|
|
838
|
+
if (para.tabStops !== undefined) (direct as Mutable<typeof direct>).tabStops = para.tabStops;
|
|
839
|
+
if (para.keepNext !== undefined) (direct as Mutable<typeof direct>).keepNext = para.keepNext;
|
|
840
|
+
if (para.keepLines !== undefined) (direct as Mutable<typeof direct>).keepLines = para.keepLines;
|
|
841
|
+
if (para.outlineLevel !== undefined) (direct as Mutable<typeof direct>).outlineLevel = para.outlineLevel;
|
|
842
|
+
if (para.pageBreakBefore !== undefined) (direct as Mutable<typeof direct>).pageBreakBefore = para.pageBreakBefore;
|
|
843
|
+
if (para.widowControl !== undefined) (direct as Mutable<typeof direct>).widowControl = para.widowControl;
|
|
844
|
+
if (para.borders !== undefined) (direct as Mutable<typeof direct>).borders = para.borders;
|
|
845
|
+
if (para.shading !== undefined) (direct as Mutable<typeof direct>).shading = para.shading;
|
|
846
|
+
if (para.bidi !== undefined) (direct as Mutable<typeof direct>).bidi = para.bidi;
|
|
806
847
|
if (para.suppressLineNumbers !== undefined) direct.suppressLineNumbers = para.suppressLineNumbers;
|
|
807
848
|
return direct;
|
|
808
849
|
}
|
|
@@ -103,6 +103,24 @@ 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
|
+
normalizeNumberingMarkerSuffix,
|
|
113
|
+
toFieldLayoutInput,
|
|
114
|
+
toLayoutTabStops,
|
|
115
|
+
toNumberingLayoutInput,
|
|
116
|
+
type EffectiveLayoutFormatting,
|
|
117
|
+
type FieldLayoutInput,
|
|
118
|
+
type LayoutMarkerSuffix,
|
|
119
|
+
type LayoutTabStopInput,
|
|
120
|
+
type NumberingLayoutInput,
|
|
121
|
+
type RevisionLayoutPosture,
|
|
122
|
+
} from "./layout-inputs.ts";
|
|
123
|
+
|
|
106
124
|
// ── Debug projector (Slice 3) ─────────────────────────────────────────────
|
|
107
125
|
export {
|
|
108
126
|
buildFormattingDebugEntry,
|
|
@@ -0,0 +1,256 @@
|
|
|
1
|
+
import type {
|
|
2
|
+
CanonicalParagraphFormatting,
|
|
3
|
+
CanonicalRunFormatting,
|
|
4
|
+
FieldFamily,
|
|
5
|
+
FieldRefreshStatus,
|
|
6
|
+
FieldRegistry,
|
|
7
|
+
FieldRegistryEntry,
|
|
8
|
+
TabStop,
|
|
9
|
+
} from "../../model/canonical-document.ts";
|
|
10
|
+
import type { ResolvedField } from "./field/resolver.ts";
|
|
11
|
+
import type {
|
|
12
|
+
EffectiveParagraphFormatting,
|
|
13
|
+
EffectiveRunFormatting,
|
|
14
|
+
EffectiveTableFormatting,
|
|
15
|
+
RevisionDisplayFlags,
|
|
16
|
+
} from "./formatting-types.ts";
|
|
17
|
+
import type { NumberingPrefixResult } from "./numbering/prefix.ts";
|
|
18
|
+
|
|
19
|
+
export type LayoutMarkerSuffix = "tab" | "space" | "none";
|
|
20
|
+
|
|
21
|
+
export interface LayoutTabStopInput {
|
|
22
|
+
readonly positionTwips: number;
|
|
23
|
+
readonly align: TabStop["align"];
|
|
24
|
+
readonly leader?: TabStop["leader"];
|
|
25
|
+
readonly source: "paragraph" | "numbering" | "toc";
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
export interface NumberingLayoutInput {
|
|
29
|
+
readonly markerText: string | null;
|
|
30
|
+
readonly markerRunFormatting?: CanonicalRunFormatting;
|
|
31
|
+
readonly markerSuffix: LayoutMarkerSuffix;
|
|
32
|
+
readonly markerLaneStartTwips?: number;
|
|
33
|
+
readonly markerLaneWidthTwips?: number;
|
|
34
|
+
readonly textColumnStartTwips?: number;
|
|
35
|
+
readonly hangingTwips?: number;
|
|
36
|
+
readonly associatedTabStops: readonly LayoutTabStopInput[];
|
|
37
|
+
readonly level: number;
|
|
38
|
+
readonly format: string;
|
|
39
|
+
readonly startAt: number;
|
|
40
|
+
readonly isLegalNumbering?: boolean;
|
|
41
|
+
readonly pictureBulletMediaId?: string;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
export interface FieldLayoutInput {
|
|
45
|
+
readonly fieldId: string;
|
|
46
|
+
readonly fieldIndex: number;
|
|
47
|
+
readonly family: FieldFamily;
|
|
48
|
+
readonly instruction: string;
|
|
49
|
+
readonly cachedText: string;
|
|
50
|
+
readonly switches?: FieldRegistryEntry["switches"];
|
|
51
|
+
readonly targetBookmark?: string;
|
|
52
|
+
readonly refreshState: FieldRefreshStatus | "unresolved";
|
|
53
|
+
readonly resolvedText?: string;
|
|
54
|
+
readonly asHyperlink?: boolean;
|
|
55
|
+
readonly tocRegionId?: string;
|
|
56
|
+
readonly tocEntryCount?: number;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
export interface RevisionLayoutPosture {
|
|
60
|
+
readonly mode: "clean" | "simple" | "all" | "original" | "no-markup";
|
|
61
|
+
readonly affectsMeasuredText: boolean;
|
|
62
|
+
readonly hiddenRevisionIds: readonly string[];
|
|
63
|
+
readonly visibleRevisionIds: readonly string[];
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
export interface EffectiveLayoutFormatting {
|
|
67
|
+
readonly paragraph?: EffectiveParagraphFormatting;
|
|
68
|
+
readonly runs: readonly EffectiveRunFormatting[];
|
|
69
|
+
readonly table?: EffectiveTableFormatting;
|
|
70
|
+
readonly cell?: {
|
|
71
|
+
readonly paragraph: CanonicalParagraphFormatting;
|
|
72
|
+
readonly run: CanonicalRunFormatting;
|
|
73
|
+
};
|
|
74
|
+
readonly numbering?: NumberingLayoutInput;
|
|
75
|
+
readonly tabs: readonly LayoutTabStopInput[];
|
|
76
|
+
readonly compatFlags: readonly string[];
|
|
77
|
+
readonly revisionPosture?: RevisionLayoutPosture;
|
|
78
|
+
readonly structuralHash: string;
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
export function normalizeNumberingMarkerSuffix(
|
|
82
|
+
suffix: NumberingPrefixResult["suffix"] | undefined,
|
|
83
|
+
): LayoutMarkerSuffix {
|
|
84
|
+
if (suffix === "tab" || suffix === "space") return suffix;
|
|
85
|
+
return "none";
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
export function toLayoutTabStops(
|
|
89
|
+
tabStops: readonly TabStop[] | undefined,
|
|
90
|
+
source: LayoutTabStopInput["source"],
|
|
91
|
+
): readonly LayoutTabStopInput[] {
|
|
92
|
+
if (!tabStops || tabStops.length === 0) return [];
|
|
93
|
+
return tabStops
|
|
94
|
+
.map((tabStop) => ({
|
|
95
|
+
positionTwips: tabStop.position,
|
|
96
|
+
align: tabStop.align,
|
|
97
|
+
...(tabStop.leader ? { leader: tabStop.leader } : {}),
|
|
98
|
+
source,
|
|
99
|
+
}))
|
|
100
|
+
.sort((a, b) => a.positionTwips - b.positionTwips);
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
export function toNumberingLayoutInput(
|
|
104
|
+
numbering: NumberingPrefixResult | null | undefined,
|
|
105
|
+
): NumberingLayoutInput | undefined {
|
|
106
|
+
if (!numbering) return undefined;
|
|
107
|
+
const markerLane = numbering.geometry.markerLane;
|
|
108
|
+
const textColumn = numbering.geometry.textColumn;
|
|
109
|
+
const hangingTwips =
|
|
110
|
+
textColumn?.hanging ??
|
|
111
|
+
numbering.geometry.indentation?.hanging ??
|
|
112
|
+
(typeof numbering.geometry.indentation?.firstLine === "number" &&
|
|
113
|
+
numbering.geometry.indentation.firstLine < 0
|
|
114
|
+
? Math.abs(numbering.geometry.indentation.firstLine)
|
|
115
|
+
: undefined);
|
|
116
|
+
|
|
117
|
+
return {
|
|
118
|
+
markerText: numbering.text,
|
|
119
|
+
...(numbering.markerRunProperties
|
|
120
|
+
? { markerRunFormatting: numbering.markerRunProperties }
|
|
121
|
+
: {}),
|
|
122
|
+
markerSuffix: normalizeNumberingMarkerSuffix(numbering.suffix),
|
|
123
|
+
...(markerLane ? { markerLaneStartTwips: markerLane.start } : {}),
|
|
124
|
+
...(markerLane ? { markerLaneWidthTwips: markerLane.width } : {}),
|
|
125
|
+
...(textColumn ? { textColumnStartTwips: textColumn.start } : {}),
|
|
126
|
+
...(hangingTwips !== undefined ? { hangingTwips } : {}),
|
|
127
|
+
associatedTabStops: toLayoutTabStops(numbering.geometry.tabStops, "numbering"),
|
|
128
|
+
level: numbering.level,
|
|
129
|
+
format: numbering.format,
|
|
130
|
+
startAt: numbering.startAt,
|
|
131
|
+
...(numbering.isLegalNumbering ? { isLegalNumbering: true } : {}),
|
|
132
|
+
...(numbering.picBulletMediaId
|
|
133
|
+
? { pictureBulletMediaId: numbering.picBulletMediaId }
|
|
134
|
+
: {}),
|
|
135
|
+
};
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
export function toFieldLayoutInput(
|
|
139
|
+
entry: FieldRegistryEntry,
|
|
140
|
+
resolved?: ResolvedField,
|
|
141
|
+
): FieldLayoutInput {
|
|
142
|
+
return {
|
|
143
|
+
fieldId: `field-${entry.fieldIndex}`,
|
|
144
|
+
fieldIndex: entry.fieldIndex,
|
|
145
|
+
family: entry.fieldFamily,
|
|
146
|
+
instruction: entry.instruction,
|
|
147
|
+
cachedText: entry.displayText,
|
|
148
|
+
...(entry.switches ? { switches: entry.switches } : {}),
|
|
149
|
+
...(entry.fieldTarget ? { targetBookmark: entry.fieldTarget } : {}),
|
|
150
|
+
refreshState: resolved?.refreshStatus ?? entry.refreshStatus ?? "unresolved",
|
|
151
|
+
...(resolved?.displayText !== undefined ? { resolvedText: resolved.displayText } : {}),
|
|
152
|
+
...(resolved?.asHyperlink ? { asHyperlink: true } : {}),
|
|
153
|
+
};
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
export function collectFieldLayoutInputs(
|
|
157
|
+
registry: FieldRegistry | undefined,
|
|
158
|
+
resolve?: (entry: FieldRegistryEntry) => ResolvedField | undefined,
|
|
159
|
+
): readonly FieldLayoutInput[] {
|
|
160
|
+
if (!registry) return [];
|
|
161
|
+
const inputs: FieldLayoutInput[] = [];
|
|
162
|
+
const entries = [...registry.supported, ...registry.preserveOnly];
|
|
163
|
+
for (const entry of entries) {
|
|
164
|
+
inputs.push(toFieldLayoutInput(entry, resolve?.(entry)));
|
|
165
|
+
}
|
|
166
|
+
for (const region of registry.tocRegions ?? []) {
|
|
167
|
+
const source = registry.supported.find(
|
|
168
|
+
(entry) => entry.fieldIndex === region.sourceFieldIndex,
|
|
169
|
+
);
|
|
170
|
+
if (!source) continue;
|
|
171
|
+
inputs.push({
|
|
172
|
+
...toFieldLayoutInput(source, resolve?.(source)),
|
|
173
|
+
fieldId: `toc-${region.tocId}`,
|
|
174
|
+
family: "TOC",
|
|
175
|
+
instruction: region.instruction.raw,
|
|
176
|
+
cachedText: region.cachedEntries.map((entry) => entry.displayText).join("\n"),
|
|
177
|
+
refreshState: region.status === "current" ? "current" : "stale",
|
|
178
|
+
tocRegionId: region.tocId,
|
|
179
|
+
tocEntryCount: region.cachedEntries.length + region.generatedEntries.length,
|
|
180
|
+
});
|
|
181
|
+
}
|
|
182
|
+
return inputs;
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
export function buildRevisionLayoutPosture(
|
|
186
|
+
mode: RevisionLayoutPosture["mode"],
|
|
187
|
+
displays: readonly RevisionDisplayFlags[] = [],
|
|
188
|
+
): RevisionLayoutPosture {
|
|
189
|
+
const hiddenRevisionIds = displays
|
|
190
|
+
.filter((display) => display.hidden === true)
|
|
191
|
+
.map((display) => display.revisionId);
|
|
192
|
+
const visibleRevisionIds = displays
|
|
193
|
+
.filter((display) => display.hidden !== true)
|
|
194
|
+
.map((display) => display.revisionId);
|
|
195
|
+
return {
|
|
196
|
+
mode,
|
|
197
|
+
affectsMeasuredText: hiddenRevisionIds.length > 0,
|
|
198
|
+
hiddenRevisionIds,
|
|
199
|
+
visibleRevisionIds,
|
|
200
|
+
};
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
export function buildEffectiveLayoutFormatting(input: {
|
|
204
|
+
readonly paragraph?: EffectiveParagraphFormatting;
|
|
205
|
+
readonly runs?: readonly EffectiveRunFormatting[];
|
|
206
|
+
readonly table?: EffectiveTableFormatting;
|
|
207
|
+
readonly cell?: {
|
|
208
|
+
readonly paragraph: CanonicalParagraphFormatting;
|
|
209
|
+
readonly run: CanonicalRunFormatting;
|
|
210
|
+
};
|
|
211
|
+
readonly numbering?: NumberingLayoutInput;
|
|
212
|
+
readonly tabs?: readonly LayoutTabStopInput[];
|
|
213
|
+
readonly compatFlags?: readonly string[];
|
|
214
|
+
readonly revisionPosture?: RevisionLayoutPosture;
|
|
215
|
+
}): EffectiveLayoutFormatting {
|
|
216
|
+
const withoutHash = {
|
|
217
|
+
...(input.paragraph ? { paragraph: input.paragraph } : {}),
|
|
218
|
+
runs: input.runs ?? [],
|
|
219
|
+
...(input.table ? { table: input.table } : {}),
|
|
220
|
+
...(input.cell ? { cell: input.cell } : {}),
|
|
221
|
+
...(input.numbering ? { numbering: input.numbering } : {}),
|
|
222
|
+
tabs: input.tabs ?? [],
|
|
223
|
+
compatFlags: input.compatFlags ?? [],
|
|
224
|
+
...(input.revisionPosture ? { revisionPosture: input.revisionPosture } : {}),
|
|
225
|
+
};
|
|
226
|
+
return {
|
|
227
|
+
...withoutHash,
|
|
228
|
+
structuralHash: createStructuralHash(withoutHash),
|
|
229
|
+
};
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
export function createStructuralHash(value: unknown): string {
|
|
233
|
+
const stable = stableStringify(value);
|
|
234
|
+
let hash = 0x811c9dc5;
|
|
235
|
+
for (let i = 0; i < stable.length; i += 1) {
|
|
236
|
+
hash ^= stable.charCodeAt(i);
|
|
237
|
+
hash = Math.imul(hash, 0x01000193);
|
|
238
|
+
}
|
|
239
|
+
return `fnv1a32:${(hash >>> 0).toString(16).padStart(8, "0")}`;
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
function stableStringify(value: unknown): string {
|
|
243
|
+
if (value === null || typeof value !== "object") {
|
|
244
|
+
return JSON.stringify(value);
|
|
245
|
+
}
|
|
246
|
+
if (Array.isArray(value)) {
|
|
247
|
+
return `[${value.map((entry) => stableStringify(entry)).join(",")}]`;
|
|
248
|
+
}
|
|
249
|
+
const record = value as Record<string, unknown>;
|
|
250
|
+
const keys = Object.keys(record)
|
|
251
|
+
.filter((key) => record[key] !== undefined)
|
|
252
|
+
.sort();
|
|
253
|
+
return `{${keys
|
|
254
|
+
.map((key) => `${JSON.stringify(key)}:${stableStringify(record[key])}`)
|
|
255
|
+
.join(",")}}`;
|
|
256
|
+
}
|
|
@@ -8,6 +8,7 @@ import type {
|
|
|
8
8
|
ParagraphNode,
|
|
9
9
|
ParagraphSpacing,
|
|
10
10
|
TabStop,
|
|
11
|
+
Mutable,
|
|
11
12
|
} from "../../../model/canonical-document.ts";
|
|
12
13
|
|
|
13
14
|
export const DEFAULT_NUMBERING_START_AT = 1;
|
|
@@ -52,7 +53,7 @@ export interface ResolvedNumberingDefinitionSet {
|
|
|
52
53
|
}
|
|
53
54
|
|
|
54
55
|
export function resolveNumberingDefinitionSet(
|
|
55
|
-
catalog: NumberingCatalog
|
|
56
|
+
catalog: Mutable<NumberingCatalog>,
|
|
56
57
|
numbering: ParagraphNode["numbering"] | undefined,
|
|
57
58
|
paragraph?: Pick<ParagraphNode, "spacing" | "indentation" | "tabStops">,
|
|
58
59
|
): ResolvedNumberingDefinitionSet | null {
|
|
@@ -145,7 +146,7 @@ function mergeLevelDefinition(
|
|
|
145
146
|
};
|
|
146
147
|
}
|
|
147
148
|
|
|
148
|
-
function withDefaultStartAt(level: NumberingLevelDefinition): NumberingLevelDefinition {
|
|
149
|
+
function withDefaultStartAt(level: Mutable<NumberingLevelDefinition>): NumberingLevelDefinition {
|
|
149
150
|
return {
|
|
150
151
|
...level,
|
|
151
152
|
startAt: level.startAt ?? DEFAULT_NUMBERING_START_AT,
|
|
@@ -156,7 +157,7 @@ function withDefaultStartAt(level: NumberingLevelDefinition): NumberingLevelDefi
|
|
|
156
157
|
}
|
|
157
158
|
|
|
158
159
|
function cloneLevelParagraphGeometry(
|
|
159
|
-
geometry: NumberingLevelParagraphGeometry
|
|
160
|
+
geometry: Mutable<NumberingLevelParagraphGeometry>,
|
|
160
161
|
): NumberingLevelParagraphGeometry {
|
|
161
162
|
return {
|
|
162
163
|
...(geometry.justification ? { justification: geometry.justification } : {}),
|
|
@@ -244,7 +245,7 @@ function mergeParagraphSpacing(
|
|
|
244
245
|
return undefined;
|
|
245
246
|
}
|
|
246
247
|
|
|
247
|
-
const spacing: ParagraphSpacing = {
|
|
248
|
+
const spacing: Mutable<ParagraphSpacing> = {
|
|
248
249
|
...(base ?? {}),
|
|
249
250
|
...(override ?? {}),
|
|
250
251
|
};
|
|
@@ -260,24 +261,24 @@ function mergeParagraphIndentation(
|
|
|
260
261
|
return undefined;
|
|
261
262
|
}
|
|
262
263
|
|
|
263
|
-
const out: ParagraphIndentation = {};
|
|
264
|
+
const out: Mutable<ParagraphIndentation> = {};
|
|
264
265
|
|
|
265
266
|
const left = override?.left ?? base?.left;
|
|
266
|
-
if (left !== undefined) out.left = left;
|
|
267
|
+
if (left !== undefined) (out as Mutable<typeof out>).left = left;
|
|
267
268
|
|
|
268
269
|
const right = override?.right ?? base?.right;
|
|
269
|
-
if (right !== undefined) out.right = right;
|
|
270
|
+
if (right !== undefined) (out as Mutable<typeof out>).right = right;
|
|
270
271
|
|
|
271
272
|
// ECMA-376 §17.3.1.12: firstLine and hanging are mutually exclusive on a single
|
|
272
273
|
// w:ind element. When override specifies either, it wins exclusively.
|
|
273
274
|
const overrideTouchesFLorH =
|
|
274
275
|
override?.firstLine !== undefined || override?.hanging !== undefined;
|
|
275
276
|
if (overrideTouchesFLorH) {
|
|
276
|
-
if (override?.hanging !== undefined) out.hanging = override.hanging;
|
|
277
|
-
else if (override?.firstLine !== undefined) out.firstLine = override.firstLine;
|
|
277
|
+
if (override?.hanging !== undefined) (out as Mutable<typeof out>).hanging = override.hanging;
|
|
278
|
+
else if (override?.firstLine !== undefined) (out as Mutable<typeof out>).firstLine = override.firstLine;
|
|
278
279
|
} else {
|
|
279
|
-
if (base?.hanging !== undefined) out.hanging = base.hanging;
|
|
280
|
-
else if (base?.firstLine !== undefined) out.firstLine = base.firstLine;
|
|
280
|
+
if (base?.hanging !== undefined) (out as Mutable<typeof out>).hanging = base.hanging;
|
|
281
|
+
else if (base?.firstLine !== undefined) (out as Mutable<typeof out>).firstLine = base.firstLine;
|
|
281
282
|
}
|
|
282
283
|
|
|
283
284
|
return Object.keys(out).length > 0 ? out : undefined;
|
|
@@ -333,7 +334,7 @@ function deriveTextColumn(
|
|
|
333
334
|
};
|
|
334
335
|
}
|
|
335
336
|
|
|
336
|
-
function resolveHangingWidth(indentation: ParagraphIndentation): number | undefined {
|
|
337
|
+
function resolveHangingWidth(indentation: Mutable<ParagraphIndentation>): number | undefined {
|
|
337
338
|
if (indentation.hanging !== undefined) {
|
|
338
339
|
return indentation.hanging;
|
|
339
340
|
}
|
|
@@ -28,6 +28,7 @@ import type {
|
|
|
28
28
|
StylesCatalog,
|
|
29
29
|
TableStyleConditionalRegion,
|
|
30
30
|
TableStyleDefinition,
|
|
31
|
+
Mutable,
|
|
31
32
|
} from "../../model/canonical-document.ts";
|
|
32
33
|
import {
|
|
33
34
|
resolveCharacterStyleChain,
|
|
@@ -51,7 +52,7 @@ export function mergeParagraph(
|
|
|
51
52
|
over: CanonicalParagraphFormatting | undefined,
|
|
52
53
|
): CanonicalParagraphFormatting | undefined {
|
|
53
54
|
if (!base && !over) return undefined;
|
|
54
|
-
const merged: CanonicalParagraphFormatting = { ...(base ?? {}), ...(over ?? {}) };
|
|
55
|
+
const merged: Mutable<CanonicalParagraphFormatting> = { ...(base ?? {}), ...(over ?? {}) };
|
|
55
56
|
if (base?.spacing || over?.spacing) {
|
|
56
57
|
merged.spacing = { ...(base?.spacing ?? {}), ...(over?.spacing ?? {}) };
|
|
57
58
|
}
|
|
@@ -10,6 +10,7 @@ import type {
|
|
|
10
10
|
TableStyleDefinition,
|
|
11
11
|
TableStyleFormatting,
|
|
12
12
|
TableWidth,
|
|
13
|
+
Mutable,
|
|
13
14
|
} from "../../model/canonical-document.ts";
|
|
14
15
|
|
|
15
16
|
export interface ResolvedTableCellStyle {
|
|
@@ -58,7 +59,7 @@ export interface ResolvedTableStyleResolution {
|
|
|
58
59
|
}>;
|
|
59
60
|
}
|
|
60
61
|
|
|
61
|
-
const DEFAULT_EFFECTIVE_TABLE_LOOK: TableLook = {
|
|
62
|
+
const DEFAULT_EFFECTIVE_TABLE_LOOK: Mutable<TableLook> = {
|
|
62
63
|
val: "04A0",
|
|
63
64
|
firstRow: true,
|
|
64
65
|
lastRow: false,
|
|
@@ -80,7 +81,7 @@ const CONDITIONAL_REGION_ORDER: TableStyleConditionalRegion[] = [
|
|
|
80
81
|
];
|
|
81
82
|
|
|
82
83
|
export function resolveTableStyleResolution(
|
|
83
|
-
table: TableNode
|
|
84
|
+
table: Mutable<TableNode>,
|
|
84
85
|
tableStyles: Record<string, TableStyleDefinition>,
|
|
85
86
|
): ResolvedTableStyleResolution {
|
|
86
87
|
const resolvedStyle = table.styleId ? resolveTableStyleDefinition(table.styleId, tableStyles) : {};
|
|
@@ -266,7 +267,7 @@ function decodeTableLookMask(val: string | undefined): TableLook | undefined {
|
|
|
266
267
|
function getActiveRowRegions(
|
|
267
268
|
rowIndex: number,
|
|
268
269
|
rowCount: number,
|
|
269
|
-
effectiveTblLook: TableLook
|
|
270
|
+
effectiveTblLook: Mutable<TableLook>,
|
|
270
271
|
): TableStyleConditionalRegion[] {
|
|
271
272
|
const active = new Set<TableStyleConditionalRegion>();
|
|
272
273
|
|
|
@@ -294,7 +295,7 @@ function getActiveCellRegions(
|
|
|
294
295
|
startColumn: number,
|
|
295
296
|
endColumn: number,
|
|
296
297
|
columnCount: number,
|
|
297
|
-
effectiveTblLook: TableLook
|
|
298
|
+
effectiveTblLook: Mutable<TableLook>,
|
|
298
299
|
): TableStyleConditionalRegion[] {
|
|
299
300
|
const active = new Set<TableStyleConditionalRegion>(rowRegions);
|
|
300
301
|
|
|
@@ -343,7 +344,7 @@ function mergeWholeStyleFormatting(
|
|
|
343
344
|
return undefined;
|
|
344
345
|
}
|
|
345
346
|
|
|
346
|
-
const merged: TableStyleFormatting = {};
|
|
347
|
+
const merged: Mutable<TableStyleFormatting> = {};
|
|
347
348
|
const table = mergeTableFormatting(base?.table, override?.table);
|
|
348
349
|
const row = mergeRowFormatting(base?.row, override?.row);
|
|
349
350
|
const cell = mergeCellFormatting(base?.cell, override?.cell);
|
|
@@ -429,14 +430,14 @@ function mergeBorderMap<T extends TableBorders | TableCellBorders>(
|
|
|
429
430
|
}
|
|
430
431
|
|
|
431
432
|
const sides = ["top", "left", "bottom", "right", "insideH", "insideV"] as const;
|
|
432
|
-
const merged = {} as T
|
|
433
|
+
const merged = {} as Mutable<T>;
|
|
433
434
|
for (const side of sides) {
|
|
434
435
|
const spec = mergePlainObject(base?.[side], override?.[side]) as BorderSpec | undefined;
|
|
435
436
|
if (spec) {
|
|
436
437
|
merged[side] = spec as T[typeof side];
|
|
437
438
|
}
|
|
438
439
|
}
|
|
439
|
-
return Object.keys(merged).length > 0 ? merged : undefined;
|
|
440
|
+
return Object.keys(merged).length > 0 ? (merged as T) : undefined;
|
|
440
441
|
}
|
|
441
442
|
|
|
442
443
|
function mergePlainObject<T extends object>(base: T | undefined, override: T | undefined): T | undefined {
|