@stll/folio-core 0.37.5 → 0.38.0

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.
Files changed (121) hide show
  1. package/dist/ai-edits/apply.js +275 -88
  2. package/dist/ai-edits/clean-text.js +12 -1
  3. package/dist/ai-edits/headless.d.ts +110 -0
  4. package/dist/ai-edits/headless.js +569 -40
  5. package/dist/ai-edits/snapshot.d.ts +24 -1
  6. package/dist/ai-edits/snapshot.js +206 -9
  7. package/dist/ai-edits/table-geometry.d.ts +7 -7
  8. package/dist/ai-edits/table-geometry.js +43 -27
  9. package/dist/ai-edits/table-template.d.ts +2 -1
  10. package/dist/ai-edits/table-template.js +9 -3
  11. package/dist/ai-edits/types.d.ts +37 -15
  12. package/dist/compare/__fixtures__/body-sequence.d.ts +4 -0
  13. package/dist/compare/__fixtures__/body-sequence.js +5 -3
  14. package/dist/compare/compare.d.ts +26 -2
  15. package/dist/compare/compare.js +444 -16
  16. package/dist/compare/content-alignment.js +3 -2
  17. package/dist/compare/content-types.d.ts +34 -2
  18. package/dist/compare/content.d.ts +13 -10
  19. package/dist/compare/content.js +69 -23
  20. package/dist/compare/formatting.d.ts +4 -22
  21. package/dist/compare/formatting.js +8 -112
  22. package/dist/compare/inline-atom-resources.d.ts +6 -0
  23. package/dist/compare/inline-atom-resources.js +26 -0
  24. package/dist/compare/inline-atoms.d.ts +35 -0
  25. package/dist/compare/inline-atoms.js +383 -0
  26. package/dist/compare/inline-provenance.d.ts +36 -0
  27. package/dist/compare/inline-provenance.js +334 -0
  28. package/dist/compare/plan.d.ts +4 -1
  29. package/dist/compare/plan.js +32 -4
  30. package/dist/compare/scenario.d.ts +4 -6
  31. package/dist/compare/section-boundary-properties.d.ts +63 -0
  32. package/dist/compare/section-boundary-properties.js +235 -0
  33. package/dist/compare/style-resources.d.ts +30 -0
  34. package/dist/compare/style-resources.js +404 -0
  35. package/dist/compare/types.d.ts +50 -11
  36. package/dist/compare/types.js +3 -1
  37. package/dist/compare/verification.d.ts +3 -5
  38. package/dist/compare/verification.js +8 -39
  39. package/dist/compat/eigenpal.d.ts +4 -4
  40. package/dist/compat/eigenpal.js +3 -3
  41. package/dist/document-operations.d.ts +5 -3
  42. package/dist/document-operations.js +112 -5
  43. package/dist/docx/blockContentParser.js +14 -2
  44. package/dist/docx/drawingIdNormalization.js +43 -2
  45. package/dist/docx/drawingRelationships.d.ts +10 -0
  46. package/dist/docx/drawingRelationships.js +28 -0
  47. package/dist/docx/fieldParser.js +1 -1
  48. package/dist/docx/hyperlinkParser.js +3 -5
  49. package/dist/docx/imageRawXml.d.ts +8 -0
  50. package/dist/docx/imageRawXml.js +13 -0
  51. package/dist/docx/newImage.d.ts +2 -2
  52. package/dist/docx/newImage.js +3 -3
  53. package/dist/docx/numberingParser.js +30 -27
  54. package/dist/docx/paragraphParser.js +11 -4
  55. package/dist/docx/removeHeaderFooterParts.d.ts +14 -0
  56. package/dist/docx/removeHeaderFooterParts.js +87 -0
  57. package/dist/docx/rezip.d.ts +3 -1
  58. package/dist/docx/rezip.js +143 -41
  59. package/dist/docx/runParser.js +16 -5
  60. package/dist/docx/sectionParser.js +3 -0
  61. package/dist/docx/sectionReferenceHistory.d.ts +17 -0
  62. package/dist/docx/sectionReferenceHistory.js +59 -0
  63. package/dist/docx/selectiveSave.js +11 -31
  64. package/dist/docx/selectiveXmlPatch.d.ts +8 -1
  65. package/dist/docx/selectiveXmlPatch.js +62 -2
  66. package/dist/docx/serializer/paragraphSerializer.js +2 -0
  67. package/dist/docx/serializer/partNamespaces.d.ts +1 -1
  68. package/dist/docx/serializer/partNamespaces.js +4 -0
  69. package/dist/docx/serializer/runSerializer.js +2 -1
  70. package/dist/docx/serializer/sectionPropertiesSerializer.js +5 -2
  71. package/dist/docx/serializer/textFormattingSerializer.d.ts +1 -1
  72. package/dist/docx/serializer/textFormattingSerializer.js +3 -1
  73. package/dist/docx/tableParser.js +21 -11
  74. package/dist/docx/xmlParser.d.ts +10 -1
  75. package/dist/docx/xmlParser.js +23 -1
  76. package/dist/index.d.ts +4 -4
  77. package/dist/index.js +3 -3
  78. package/dist/internal/compare/inline-presentation.d.ts +20 -0
  79. package/dist/internal/compare/inline-presentation.js +222 -0
  80. package/dist/internal/paragraphFormattingSerialization.d.ts +3 -1
  81. package/dist/internal/paragraphFormattingSerialization.js +8 -1
  82. package/dist/internal/sectionReferenceResolution.d.ts +21 -0
  83. package/dist/internal/sectionReferenceResolution.js +72 -0
  84. package/dist/layout-bridge/convert/toFlowBlocks.js +1 -0
  85. package/dist/markdown/renderRuns.js +1 -0
  86. package/dist/markdown/renderTable.js +1 -0
  87. package/dist/prosemirror/attrs/index.js +20 -1
  88. package/dist/prosemirror/commands/comments.d.ts +11 -1
  89. package/dist/prosemirror/commands/comments.js +68 -6
  90. package/dist/prosemirror/commands/propertyChangeScope.d.ts +9 -3
  91. package/dist/prosemirror/commands/propertyChangeScope.js +4 -10
  92. package/dist/prosemirror/conversion/fromProseDoc.js +16 -8
  93. package/dist/prosemirror/conversion/toProseDoc.js +16 -57
  94. package/dist/prosemirror/extensions/core/ParagraphExtension.js +2 -0
  95. package/dist/prosemirror/extensions/marks/RunFormattingOverrideExtension.js +3 -0
  96. package/dist/prosemirror/imageCommit.js +7 -9
  97. package/dist/prosemirror/paragraphIndentation.d.ts +21 -0
  98. package/dist/prosemirror/paragraphIndentation.js +72 -0
  99. package/dist/prosemirror/plugins/documentNumbering.d.ts +3 -1
  100. package/dist/prosemirror/plugins/documentNumbering.js +7 -1
  101. package/dist/prosemirror/plugins/documentStyles.d.ts +3 -1
  102. package/dist/prosemirror/plugins/documentStyles.js +7 -1
  103. package/dist/prosemirror/rebaseParagraphRunFormatting.d.ts +3 -1
  104. package/dist/prosemirror/rebaseParagraphRunFormatting.js +2 -1
  105. package/dist/prosemirror/runFormattingInlineCarriers.d.ts +15 -2
  106. package/dist/prosemirror/runFormattingInlineCarriers.js +33 -3
  107. package/dist/prosemirror/runStyleFormatting.d.ts +15 -2
  108. package/dist/prosemirror/runStyleFormatting.js +76 -2
  109. package/dist/prosemirror/schema/marks.d.ts +3 -1
  110. package/dist/prosemirror/schema/marks.js +2 -0
  111. package/dist/prosemirror/schema/nodes.d.ts +3 -0
  112. package/dist/prosemirror/tableCellRevisionVisibility.d.ts +6 -0
  113. package/dist/prosemirror/tableCellRevisionVisibility.js +14 -0
  114. package/dist/prosemirror/trackedRunInlineAtoms.d.ts +1 -1
  115. package/dist/prosemirror/trackedRunInlineAtoms.js +1 -1
  116. package/dist/redline.js +15 -2
  117. package/dist/server.d.ts +2 -2
  118. package/dist/server.js +2 -2
  119. package/dist/version-comparison.d.ts +2 -0
  120. package/dist/version-comparison.js +7 -2
  121. package/package.json +2 -2
@@ -0,0 +1,235 @@
1
+ import { sectionReferenceHistory } from "../docx/sectionReferenceHistory.js";
2
+ import { expectParagraphAttrs } from "../prosemirror/attrs/index.js";
3
+ import { resolveAllChangesInHeadlessStateWithMapping } from "../prosemirror/commands/comments.js";
4
+ import { canonicalJson } from "../utils/canonicalJson.js";
5
+ //#region src/compare/section-boundary-properties.ts
6
+ const withoutChangeHistory = (properties) => {
7
+ const { propertyChanges: _propertyChanges, ...result } = properties;
8
+ return result;
9
+ };
10
+ const boundaryChildrenOf = (document) => {
11
+ const children = [];
12
+ let hasChangeHistory = false;
13
+ document.forEach((node, position) => {
14
+ if (node.type.name !== "paragraph") {
15
+ children.push({
16
+ type: "other",
17
+ nodeType: node.type.name
18
+ });
19
+ return;
20
+ }
21
+ const sectionProperties = expectParagraphAttrs(node)._sectionProperties;
22
+ if (sectionProperties?.propertyChanges !== void 0) {
23
+ hasChangeHistory = true;
24
+ return;
25
+ }
26
+ children.push({
27
+ type: "paragraph",
28
+ paragraph: {
29
+ position,
30
+ text: node.textContent,
31
+ sectionProperties
32
+ }
33
+ });
34
+ });
35
+ return hasChangeHistory ? null : children;
36
+ };
37
+ /**
38
+ * Compare section properties attached to retained top-level paragraph endpoints.
39
+ * A section endpoint cannot be added to or removed from a retained paragraph
40
+ * with `sectPrChange`: its previous payload is still a section endpoint.
41
+ */
42
+ const compareSectionBoundaryProperties = ({ current, target, mapTargetProperties }) => {
43
+ const currentChildren = boundaryChildrenOf(current);
44
+ const targetChildren = boundaryChildrenOf(target);
45
+ if (!currentChildren || !targetChildren) return {
46
+ status: "unalignable",
47
+ detail: "section boundary revision history is unsupported"
48
+ };
49
+ if (currentChildren.length !== targetChildren.length) return {
50
+ status: "unalignable",
51
+ detail: "section boundary topology differs"
52
+ };
53
+ const changes = [];
54
+ for (const [index, currentChild] of currentChildren.entries()) {
55
+ const targetChild = targetChildren[index];
56
+ if (!targetChild || currentChild.type !== targetChild.type) return {
57
+ status: "unalignable",
58
+ detail: "section boundary topology differs"
59
+ };
60
+ if (currentChild.type === "other" && targetChild.type === "other") {
61
+ if (currentChild.nodeType !== targetChild.nodeType) return {
62
+ status: "unalignable",
63
+ detail: "section boundary topology differs"
64
+ };
65
+ continue;
66
+ }
67
+ if (currentChild.type !== "paragraph" || targetChild.type !== "paragraph") return {
68
+ status: "unalignable",
69
+ detail: "section boundary topology differs"
70
+ };
71
+ const currentParagraph = currentChild.paragraph;
72
+ const targetParagraph = targetChild.paragraph;
73
+ if (currentParagraph.text !== targetParagraph.text) return {
74
+ status: "unalignable",
75
+ detail: "retained paragraph text differs"
76
+ };
77
+ const currentProperties = currentParagraph.sectionProperties;
78
+ const targetProperties = targetParagraph.sectionProperties;
79
+ if (currentProperties === void 0 || targetProperties === void 0) {
80
+ if (currentProperties !== targetProperties) return {
81
+ status: "unalignable",
82
+ detail: "section endpoint presence differs"
83
+ };
84
+ continue;
85
+ }
86
+ const currentWithoutHistory = withoutChangeHistory(currentProperties);
87
+ const mappedTarget = mapTargetProperties ? mapTargetProperties(targetProperties) : targetProperties;
88
+ if (mappedTarget === null) return {
89
+ status: "unalignable",
90
+ detail: "section references cannot be mapped"
91
+ };
92
+ const targetWithoutHistory = withoutChangeHistory(mappedTarget);
93
+ if (canonicalJson(currentWithoutHistory) === canonicalJson(targetWithoutHistory)) continue;
94
+ changes.push({
95
+ position: currentParagraph.position,
96
+ current: currentWithoutHistory,
97
+ target: targetWithoutHistory
98
+ });
99
+ }
100
+ return {
101
+ status: "matched",
102
+ changes
103
+ };
104
+ };
105
+ const boundaryParagraphEntriesOf = (document) => {
106
+ const children = boundaryChildrenOf(document);
107
+ if (!children) return null;
108
+ return children.flatMap((child) => child.type === "paragraph" ? [child.paragraph] : []);
109
+ };
110
+ /** Stage section deltas after document operations, against the accepted projection. */
111
+ const stageSectionBoundaryProperties = ({ state, target, originalRevisionIdSeed, revisionStamp, author, maxRanges, mapTargetProperties }) => {
112
+ if (!Number.isSafeInteger(maxRanges) || maxRanges < 0) return { status: "budget-exceeded" };
113
+ const reviewedState = resolveAllChangesInHeadlessStateWithMapping(state, "accept");
114
+ const reviewed = boundaryParagraphEntriesOf(reviewedState.state.doc);
115
+ const targets = boundaryParagraphEntriesOf(target);
116
+ if (!reviewed || !targets) return {
117
+ status: "unalignable",
118
+ detail: "section boundary revision history is unsupported"
119
+ };
120
+ if (reviewed.length !== targets.length) return {
121
+ status: "unalignable",
122
+ detail: "retained paragraph count differs"
123
+ };
124
+ const updates = [];
125
+ const inverse = reviewedState.mapping.invert();
126
+ for (const [index, reviewedParagraph] of reviewed.entries()) {
127
+ const targetParagraph = targets[index];
128
+ if (!targetParagraph || reviewedParagraph.text !== targetParagraph.text) return {
129
+ status: "unalignable",
130
+ detail: `retained paragraph text differs at ${index}`
131
+ };
132
+ if (targetParagraph.sectionProperties === void 0) {
133
+ if (reviewedParagraph.sectionProperties !== void 0) return {
134
+ status: "unalignable",
135
+ detail: "section endpoint presence differs"
136
+ };
137
+ continue;
138
+ }
139
+ const targetProperties = withoutChangeHistory(targetParagraph.sectionProperties);
140
+ if (reviewedParagraph.sectionProperties !== void 0 && canonicalJson(withoutChangeHistory(reviewedParagraph.sectionProperties)) === canonicalJson(targetProperties)) continue;
141
+ const mapped = inverse.mapResult(reviewedParagraph.position, 1);
142
+ if (mapped.deleted) return {
143
+ status: "unalignable",
144
+ detail: "boundary has no source position"
145
+ };
146
+ const current = state.doc.nodeAt(mapped.pos);
147
+ if (!current || current.type.name !== "paragraph") return {
148
+ status: "unalignable",
149
+ detail: "mapped boundary is not a paragraph"
150
+ };
151
+ const attrs = expectParagraphAttrs(current);
152
+ const currentProperties = attrs._sectionProperties;
153
+ const marker = attrs.pPrMark;
154
+ if (marker?.kind === "ins" && marker.info.id >= originalRevisionIdSeed) {
155
+ if (currentProperties !== void 0) return {
156
+ status: "unalignable",
157
+ detail: "inserted endpoint already has section properties"
158
+ };
159
+ const staged = mapTargetProperties({
160
+ kind: "inserted",
161
+ current: void 0,
162
+ target: targetProperties
163
+ });
164
+ if (!staged || staged.kind !== "inserted") return {
165
+ status: "unalignable",
166
+ detail: "inserted section references cannot be mapped"
167
+ };
168
+ updates.push({
169
+ position: mapped.pos,
170
+ target: staged.target
171
+ });
172
+ continue;
173
+ }
174
+ if (currentProperties === void 0 || currentProperties.propertyChanges !== void 0) return {
175
+ status: "unalignable",
176
+ detail: "retained endpoint cannot carry a section change"
177
+ };
178
+ const staged = mapTargetProperties({
179
+ kind: "retained",
180
+ current: withoutChangeHistory(currentProperties),
181
+ target: targetProperties
182
+ });
183
+ if (!staged || staged.kind !== "retained") return {
184
+ status: "unalignable",
185
+ detail: "retained section references cannot be mapped"
186
+ };
187
+ updates.push({
188
+ position: mapped.pos,
189
+ previous: staged.previous,
190
+ target: staged.target
191
+ });
192
+ }
193
+ if (updates.length > maxRanges) return { status: "budget-exceeded" };
194
+ let nextRevisionId = revisionStamp.idSeed;
195
+ let transaction = state.tr;
196
+ for (const update of updates.toSorted((left, right) => right.position - left.position)) {
197
+ const position = transaction.mapping.map(update.position, 1);
198
+ const node = transaction.doc.nodeAt(position);
199
+ if (!node || node.type.name !== "paragraph") return {
200
+ status: "unalignable",
201
+ detail: "boundary moved while staging"
202
+ };
203
+ const previousReferences = update.previous === void 0 ? void 0 : sectionReferenceHistory({
204
+ previous: update.previous,
205
+ target: update.target
206
+ });
207
+ const propertyChanges = update.previous === void 0 ? void 0 : [{
208
+ type: "sectionPropertyChange",
209
+ info: {
210
+ id: nextRevisionId++,
211
+ author,
212
+ date: revisionStamp.date
213
+ },
214
+ previousProperties: update.previous,
215
+ ...previousReferences !== void 0 && { previousReferences },
216
+ currentProperties: update.target
217
+ }];
218
+ transaction = transaction.setNodeMarkup(position, void 0, {
219
+ ...node.attrs,
220
+ _sectionProperties: {
221
+ ...update.target,
222
+ ...propertyChanges === void 0 ? {} : { propertyChanges }
223
+ },
224
+ sectionBreakType: update.target.sectionStart ?? null
225
+ });
226
+ }
227
+ return {
228
+ status: "matched",
229
+ transaction,
230
+ nextRevisionId,
231
+ rangeCount: updates.length
232
+ };
233
+ };
234
+ //#endregion
235
+ export { compareSectionBoundaryProperties, stageSectionBoundaryProperties };
@@ -0,0 +1,30 @@
1
+ import { document_d_exports } from "../types/document.js";
2
+ //#region src/compare/style-resources.d.ts
3
+ type ImportReferencedStyleDefinitionsOptions = {
4
+ sourceStyles: document_d_exports.StyleDefinitions | undefined;
5
+ destinationStyles: document_d_exports.StyleDefinitions | undefined;
6
+ sourceTheme: document_d_exports.Theme | undefined;
7
+ destinationTheme: document_d_exports.Theme | undefined;
8
+ referencedStyleIds: readonly string[];
9
+ reservedStyleIds?: readonly string[];
10
+ materializeDefaultParagraphStyle?: boolean;
11
+ };
12
+ type ImportReferencedStyleDefinitionsResult = {
13
+ status: "unchanged" | "imported";
14
+ styles: document_d_exports.StyleDefinitions | undefined;
15
+ styleIdMap: ReadonlyMap<string, string>;
16
+ importedStyleIds: readonly string[];
17
+ defaultParagraphStyleId?: string;
18
+ } | {
19
+ status: "unalignable";
20
+ detail: string;
21
+ };
22
+ /**
23
+ * Add the target style definitions required by a comparison without changing
24
+ * existing package resources. A referenced style is only portable when both
25
+ * packages have the same document defaults and theme: those resources take
26
+ * part in OOXML style resolution even when a style definition itself is new.
27
+ */
28
+ declare const importReferencedStyleDefinitions: ({ sourceStyles, destinationStyles, sourceTheme, destinationTheme, referencedStyleIds, reservedStyleIds, materializeDefaultParagraphStyle }: ImportReferencedStyleDefinitionsOptions) => ImportReferencedStyleDefinitionsResult;
29
+ //#endregion
30
+ export { ImportReferencedStyleDefinitionsOptions, ImportReferencedStyleDefinitionsResult, importReferencedStyleDefinitions };
@@ -0,0 +1,404 @@
1
+ import { createStyleResolver } from "../prosemirror/styles/styleResolver.js";
2
+ import { canonicalJson } from "../utils/canonicalJson.js";
3
+ import { resolveColorToHex } from "../utils/colorResolver.js";
4
+ import { resolveThemeFont } from "../utils/fontResolver.js";
5
+ //#region src/compare/style-resources.ts
6
+ const styleDependencies = ({ basedOn, link, next }) => [
7
+ basedOn,
8
+ link,
9
+ next
10
+ ].filter((styleId) => styleId !== void 0);
11
+ const themesMatch = (sourceTheme, destinationTheme) => canonicalJson(sourceTheme) === canonicalJson(destinationTheme);
12
+ const hasThemeReference = (value) => {
13
+ if (Array.isArray(value)) return value.some(hasThemeReference);
14
+ if (typeof value !== "object" || value === null) return false;
15
+ return Object.entries(value).some(([key, nested]) => key.toLowerCase().includes("theme") || hasThemeReference(nested));
16
+ };
17
+ const materializeThemeColor = (color, theme) => {
18
+ if (!color || !color.themeColor) return color;
19
+ const rgb = resolveColorToHex(color, theme);
20
+ if (!rgb) return void 0;
21
+ const { themeColor: _themeColor, themeTint: _themeTint, themeShade: _themeShade, ...plain } = color;
22
+ return {
23
+ ...plain,
24
+ rgb
25
+ };
26
+ };
27
+ const languageForThemeFontSlot = ({ formatting, slot }) => {
28
+ if (slot === "eastAsia") return formatting.language?.eastAsia ?? formatting.language?.val;
29
+ if (slot === "cs") return formatting.language?.bidi ?? formatting.language?.val;
30
+ return formatting.language?.val;
31
+ };
32
+ const scriptForLanguage = (language) => {
33
+ if (!language || typeof Intl.Locale !== "function") return void 0;
34
+ try {
35
+ return new Intl.Locale(language).maximize().script;
36
+ } catch {
37
+ return;
38
+ }
39
+ };
40
+ const resolveThemeFontForFormatting = ({ formatting, slot, theme, themeRef }) => {
41
+ const script = scriptForLanguage(languageForThemeFontSlot({
42
+ formatting,
43
+ slot
44
+ }));
45
+ const themeFont = themeRef.toLowerCase().startsWith("major") ? theme?.fontScheme?.majorFont : theme?.fontScheme?.minorFont;
46
+ return (script ? themeFont?.fonts?.[script] : void 0) || resolveThemeFont(themeRef, theme?.fontScheme);
47
+ };
48
+ const materializeThemeTextFormatting = (formatting, theme, materializeThemeReferences) => {
49
+ if (!formatting) return {
50
+ status: "ok",
51
+ formatting
52
+ };
53
+ if (!materializeThemeReferences) return {
54
+ status: "ok",
55
+ formatting
56
+ };
57
+ if (hasThemeReference(formatting.shading)) return { status: "unalignable" };
58
+ const color = materializeThemeColor(formatting.color, theme);
59
+ if (formatting.color?.themeColor && !color) return { status: "unalignable" };
60
+ const underlineColor = materializeThemeColor(formatting.underline?.color, theme);
61
+ if (formatting.underline?.color?.themeColor && !underlineColor) return { status: "unalignable" };
62
+ const { asciiTheme: _asciiTheme, hAnsiTheme: _hAnsiTheme, eastAsiaTheme: _eastAsiaTheme, csTheme: _csTheme, ...fontFamily } = formatting.fontFamily ?? {};
63
+ const themeFonts = formatting.fontFamily;
64
+ const resolvedAscii = themeFonts?.asciiTheme ? resolveThemeFontForFormatting({
65
+ formatting,
66
+ slot: "ascii",
67
+ theme,
68
+ themeRef: themeFonts.asciiTheme
69
+ }) : null;
70
+ const resolvedHAnsi = themeFonts?.hAnsiTheme ? resolveThemeFontForFormatting({
71
+ formatting,
72
+ slot: "hAnsi",
73
+ theme,
74
+ themeRef: themeFonts.hAnsiTheme
75
+ }) : null;
76
+ const resolvedEastAsia = themeFonts?.eastAsiaTheme ? resolveThemeFontForFormatting({
77
+ formatting,
78
+ slot: "eastAsia",
79
+ theme,
80
+ themeRef: themeFonts.eastAsiaTheme
81
+ }) : null;
82
+ const resolvedCs = themeFonts?.csTheme ? resolveThemeFontForFormatting({
83
+ formatting,
84
+ slot: "cs",
85
+ theme,
86
+ themeRef: themeFonts.csTheme
87
+ }) : null;
88
+ if (themeFonts?.asciiTheme && !resolvedAscii || themeFonts?.hAnsiTheme && !resolvedHAnsi || themeFonts?.eastAsiaTheme && !resolvedEastAsia || themeFonts?.csTheme && !resolvedCs) return { status: "unalignable" };
89
+ return {
90
+ status: "ok",
91
+ formatting: {
92
+ ...formatting,
93
+ ...formatting.color && color && { color },
94
+ ...formatting.underline && { underline: {
95
+ ...formatting.underline,
96
+ ...underlineColor && { color: underlineColor }
97
+ } },
98
+ ...formatting.fontFamily && { fontFamily: {
99
+ ...fontFamily,
100
+ ...resolvedAscii && { ascii: resolvedAscii },
101
+ ...resolvedHAnsi && { hAnsi: resolvedHAnsi },
102
+ ...resolvedEastAsia && { eastAsia: resolvedEastAsia },
103
+ ...resolvedCs && { cs: resolvedCs }
104
+ } }
105
+ }
106
+ };
107
+ };
108
+ const materializeEffectiveStyle = ({ style, styles, theme, materializeThemeReferences }) => {
109
+ if (style.type === "numbering") return { status: "unalignable" };
110
+ const resolver = createStyleResolver(styles);
111
+ const resolved = style.type === "character" ? {
112
+ paragraphFormatting: void 0,
113
+ runFormatting: resolver.resolveRunStyle(style.styleId)
114
+ } : resolver.resolveParagraphStyle(style.styleId);
115
+ if (materializeThemeReferences && hasThemeReference(resolved.paragraphFormatting)) return { status: "unalignable" };
116
+ const runFormatting = materializeThemeTextFormatting(resolved.runFormatting, theme, materializeThemeReferences);
117
+ if (runFormatting.status === "unalignable") return runFormatting;
118
+ const conditionalStyles = [];
119
+ for (const conditional of style.tblStylePr ?? []) {
120
+ if (materializeThemeReferences && hasThemeReference(conditional.pPr)) return { status: "unalignable" };
121
+ const conditionalRunFormatting = materializeThemeTextFormatting(conditional.rPr, theme, materializeThemeReferences);
122
+ if (conditionalRunFormatting.status === "unalignable") return { status: "unalignable" };
123
+ conditionalStyles.push({
124
+ ...conditional,
125
+ ...conditionalRunFormatting.formatting && { rPr: conditionalRunFormatting.formatting }
126
+ });
127
+ }
128
+ const { basedOn: _basedOn, ...independent } = style;
129
+ return {
130
+ status: "ok",
131
+ style: {
132
+ ...independent,
133
+ ...(style.type === "paragraph" || style.type === "table") && resolved.paragraphFormatting && { pPr: resolved.paragraphFormatting },
134
+ ...runFormatting.formatting && { rPr: runFormatting.formatting },
135
+ ...style.tblStylePr && { tblStylePr: conditionalStyles }
136
+ }
137
+ };
138
+ };
139
+ const materializeEffectiveDefaultParagraphStyle = ({ styles, destinationStyles, theme, styleId, materializeThemeReferences }) => {
140
+ const resolved = createStyleResolver(styles).resolveParagraphStyle(void 0);
141
+ const destinationResolved = createStyleResolver(destinationStyles).resolveParagraphStyle(void 0);
142
+ if (materializeThemeReferences && hasThemeReference(resolved.paragraphFormatting)) return { status: "unalignable" };
143
+ const runFormatting = materializeThemeTextFormatting(resolved.runFormatting, theme, materializeThemeReferences);
144
+ if (runFormatting.status === "unalignable") return runFormatting;
145
+ const sourceParagraphFormatting = resolved.paragraphFormatting;
146
+ const destinationParagraphFormatting = destinationResolved.paragraphFormatting;
147
+ const materializedSpacing = {
148
+ ...sourceParagraphFormatting?.spaceBefore === void 0 && destinationParagraphFormatting?.spaceBefore !== void 0 && { spaceBefore: 0 },
149
+ ...sourceParagraphFormatting?.spaceAfter === void 0 && destinationParagraphFormatting?.spaceAfter !== void 0 && { spaceAfter: 0 },
150
+ ...sourceParagraphFormatting?.lineSpacing === void 0 && destinationParagraphFormatting?.lineSpacing !== void 0 && { lineSpacing: 240 },
151
+ ...sourceParagraphFormatting?.lineSpacingRule === void 0 && destinationParagraphFormatting?.lineSpacingRule !== void 0 && { lineSpacingRule: "auto" },
152
+ ...sourceParagraphFormatting?.beforeAutospacing === void 0 && destinationParagraphFormatting?.beforeAutospacing !== void 0 && { beforeAutospacing: false },
153
+ ...sourceParagraphFormatting?.afterAutospacing === void 0 && destinationParagraphFormatting?.afterAutospacing !== void 0 && { afterAutospacing: false }
154
+ };
155
+ const paragraphFormatting = sourceParagraphFormatting || Object.keys(materializedSpacing).length > 0 ? {
156
+ ...sourceParagraphFormatting,
157
+ ...materializedSpacing
158
+ } : void 0;
159
+ return {
160
+ status: "ok",
161
+ style: {
162
+ styleId,
163
+ type: "paragraph",
164
+ ...paragraphFormatting && { pPr: paragraphFormatting },
165
+ ...runFormatting.formatting && { rPr: runFormatting.formatting }
166
+ }
167
+ };
168
+ };
169
+ const effectiveDefaultParagraphFormattingMatches = ({ sourceStyles, destinationStyles, sourceTheme, destinationTheme, materializeThemeReferences }) => {
170
+ const sourceResolved = createStyleResolver(sourceStyles).resolveParagraphStyle(void 0);
171
+ const destinationResolved = createStyleResolver(destinationStyles).resolveParagraphStyle(void 0);
172
+ const sourceRunFormatting = materializeThemeTextFormatting(sourceResolved.runFormatting, sourceTheme, materializeThemeReferences);
173
+ const destinationRunFormatting = materializeThemeTextFormatting(destinationResolved.runFormatting, destinationTheme, materializeThemeReferences);
174
+ if (sourceRunFormatting.status === "unalignable" || destinationRunFormatting.status === "unalignable" || materializeThemeReferences && hasThemeReference(sourceResolved.paragraphFormatting) || materializeThemeReferences && hasThemeReference(destinationResolved.paragraphFormatting)) return false;
175
+ return canonicalJson({
176
+ pPr: sourceResolved.paragraphFormatting,
177
+ rPr: sourceRunFormatting.formatting
178
+ }) === canonicalJson({
179
+ pPr: destinationResolved.paragraphFormatting,
180
+ rPr: destinationRunFormatting.formatting
181
+ });
182
+ };
183
+ const nextAvailableStyleAlias = ({ taken, sequence }) => {
184
+ let candidate = `FolioImportedStyle${sequence}`;
185
+ let suffix = sequence;
186
+ while (taken.has(candidate)) {
187
+ suffix += 1;
188
+ candidate = `FolioImportedStyle${suffix}`;
189
+ }
190
+ return candidate;
191
+ };
192
+ const remapImportedStyle = ({ style, styleIdMap, isolatedDefaultStyleIds }) => {
193
+ const { default: _default, ...withoutDefault } = style;
194
+ return {
195
+ ...isolatedDefaultStyleIds.has(style.styleId) ? withoutDefault : style,
196
+ styleId: styleIdMap.get(style.styleId) ?? style.styleId,
197
+ ...style.basedOn !== void 0 && { basedOn: styleIdMap.get(style.basedOn) ?? style.basedOn },
198
+ ...style.link !== void 0 && { link: styleIdMap.get(style.link) ?? style.link },
199
+ ...style.next !== void 0 && { next: styleIdMap.get(style.next) ?? style.next }
200
+ };
201
+ };
202
+ /**
203
+ * Add the target style definitions required by a comparison without changing
204
+ * existing package resources. A referenced style is only portable when both
205
+ * packages have the same document defaults and theme: those resources take
206
+ * part in OOXML style resolution even when a style definition itself is new.
207
+ */
208
+ const importReferencedStyleDefinitions = ({ sourceStyles, destinationStyles, sourceTheme, destinationTheme, referencedStyleIds, reservedStyleIds = [], materializeDefaultParagraphStyle = false }) => {
209
+ const requested = [...new Set(referencedStyleIds)].toSorted();
210
+ if (requested.length === 0 && !materializeDefaultParagraphStyle) return {
211
+ status: "unchanged",
212
+ styles: destinationStyles,
213
+ styleIdMap: /* @__PURE__ */ new Map(),
214
+ importedStyleIds: []
215
+ };
216
+ if (!sourceStyles) return {
217
+ status: "unalignable",
218
+ detail: "the target has no style definitions"
219
+ };
220
+ if (!destinationStyles) return {
221
+ status: "unalignable",
222
+ detail: "the base has no style definitions"
223
+ };
224
+ const sourceById = new Map(sourceStyles.styles.map((style) => [style.styleId, style]));
225
+ const destinationById = new Map(destinationStyles.styles.map((style) => [style.styleId, style]));
226
+ const reserved = new Set(reservedStyleIds);
227
+ const required = /* @__PURE__ */ new Set();
228
+ const pending = [...requested];
229
+ while (pending.length > 0) {
230
+ const styleId = pending.pop();
231
+ if (!styleId || required.has(styleId)) continue;
232
+ const source = sourceById.get(styleId);
233
+ if (!source) return {
234
+ status: "unalignable",
235
+ detail: "a referenced target style is missing"
236
+ };
237
+ required.add(styleId);
238
+ pending.push(...styleDependencies(source));
239
+ }
240
+ const imported = [...required].toSorted();
241
+ const taken = /* @__PURE__ */ new Set([
242
+ ...sourceById.keys(),
243
+ ...destinationById.keys(),
244
+ ...reserved
245
+ ]);
246
+ const styleIdMap = /* @__PURE__ */ new Map();
247
+ const isolatedDefaultStyleIds = /* @__PURE__ */ new Set();
248
+ let aliasSequence = 1;
249
+ for (const styleId of imported) {
250
+ const source = sourceById.get(styleId);
251
+ const destination = destinationById.get(styleId);
252
+ if (!source) return {
253
+ status: "unalignable",
254
+ detail: "a referenced target style is missing"
255
+ };
256
+ if (destination !== void 0 && canonicalJson(source) !== canonicalJson(destination) || !destination && (reserved.has(styleId) || source.default)) {
257
+ const alias = nextAvailableStyleAlias({
258
+ taken,
259
+ sequence: aliasSequence
260
+ });
261
+ aliasSequence += 1;
262
+ taken.add(alias);
263
+ styleIdMap.set(styleId, alias);
264
+ if (source.default) isolatedDefaultStyleIds.add(styleId);
265
+ continue;
266
+ }
267
+ styleIdMap.set(styleId, styleId);
268
+ }
269
+ const contextMatches = canonicalJson(sourceStyles.docDefaults) === canonicalJson(destinationStyles.docDefaults) && themesMatch(sourceTheme, destinationTheme);
270
+ const materializeThemeReferences = !themesMatch(sourceTheme, destinationTheme);
271
+ const additions = sourceStyles.styles.filter(({ styleId }) => required.has(styleId) && (!destinationById.has(styleId) || styleIdMap.get(styleId) !== styleId));
272
+ if (contextMatches) {
273
+ if (additions.length === 0) return {
274
+ status: "unchanged",
275
+ styles: destinationStyles,
276
+ styleIdMap,
277
+ importedStyleIds: []
278
+ };
279
+ return {
280
+ status: "imported",
281
+ styles: {
282
+ ...destinationStyles,
283
+ styles: [...destinationStyles.styles, ...additions.map((style) => remapImportedStyle({
284
+ style,
285
+ styleIdMap,
286
+ isolatedDefaultStyleIds
287
+ }))]
288
+ },
289
+ styleIdMap,
290
+ importedStyleIds: additions.map(({ styleId }) => styleId)
291
+ };
292
+ }
293
+ const materializedStyles = [];
294
+ for (const style of additions) {
295
+ const result = materializeEffectiveStyle({
296
+ style,
297
+ styles: sourceStyles,
298
+ theme: sourceTheme,
299
+ materializeThemeReferences
300
+ });
301
+ if (result.status === "unalignable") return {
302
+ status: "unalignable",
303
+ detail: "a referenced style has formatting that cannot be materialized"
304
+ };
305
+ materializedStyles.push(remapImportedStyle({
306
+ style: result.style,
307
+ styleIdMap,
308
+ isolatedDefaultStyleIds
309
+ }));
310
+ }
311
+ let defaultParagraphStyleId;
312
+ let materializedDefaultParagraphStyle;
313
+ if (materializeDefaultParagraphStyle && !effectiveDefaultParagraphFormattingMatches({
314
+ sourceStyles,
315
+ destinationStyles,
316
+ sourceTheme,
317
+ destinationTheme,
318
+ materializeThemeReferences
319
+ })) {
320
+ defaultParagraphStyleId = nextAvailableStyleAlias({
321
+ taken,
322
+ sequence: aliasSequence
323
+ });
324
+ const result = materializeEffectiveDefaultParagraphStyle({
325
+ styles: sourceStyles,
326
+ destinationStyles,
327
+ theme: sourceTheme,
328
+ styleId: defaultParagraphStyleId,
329
+ materializeThemeReferences
330
+ });
331
+ if (result.status === "unalignable") return {
332
+ status: "unalignable",
333
+ detail: "default paragraph formatting cannot be isolated from the base formatting context"
334
+ };
335
+ materializedDefaultParagraphStyle = result.style;
336
+ materializedStyles.push(result.style);
337
+ }
338
+ if (materializedStyles.length === 0) return {
339
+ status: "unchanged",
340
+ styles: destinationStyles,
341
+ styleIdMap,
342
+ importedStyleIds: []
343
+ };
344
+ const candidateStyles = {
345
+ ...destinationStyles,
346
+ styles: [...destinationStyles.styles, ...materializedStyles]
347
+ };
348
+ const sourceResolver = createStyleResolver(sourceStyles);
349
+ const candidateResolver = createStyleResolver(candidateStyles);
350
+ for (const style of additions) {
351
+ const candidateStyleId = styleIdMap.get(style.styleId) ?? style.styleId;
352
+ const sourceResolved = style.type === "character" ? {
353
+ paragraphFormatting: void 0,
354
+ runFormatting: sourceResolver.resolveRunStyle(style.styleId)
355
+ } : sourceResolver.resolveParagraphStyle(style.styleId);
356
+ const candidateResolved = style.type === "character" ? {
357
+ paragraphFormatting: void 0,
358
+ runFormatting: candidateResolver.resolveRunStyle(candidateStyleId)
359
+ } : candidateResolver.resolveParagraphStyle(candidateStyleId);
360
+ const sourceRunFormatting = materializeThemeTextFormatting(sourceResolved.runFormatting, sourceTheme, materializeThemeReferences);
361
+ const candidateRunFormatting = materializeThemeTextFormatting(candidateResolved.runFormatting, destinationTheme, materializeThemeReferences);
362
+ if (sourceRunFormatting.status === "unalignable" || candidateRunFormatting.status === "unalignable" || materializeThemeReferences && hasThemeReference(sourceResolved.paragraphFormatting) || materializeThemeReferences && hasThemeReference(candidateResolved.paragraphFormatting) || canonicalJson({
363
+ pPr: sourceResolved.paragraphFormatting,
364
+ rPr: sourceRunFormatting.formatting
365
+ }) !== canonicalJson({
366
+ pPr: candidateResolved.paragraphFormatting,
367
+ rPr: candidateRunFormatting.formatting
368
+ })) return {
369
+ status: "unalignable",
370
+ detail: "a referenced style differs after materializing its formatting"
371
+ };
372
+ }
373
+ if (defaultParagraphStyleId !== void 0) {
374
+ const sourceResolved = {
375
+ paragraphFormatting: materializedDefaultParagraphStyle?.pPr,
376
+ runFormatting: materializedDefaultParagraphStyle?.rPr
377
+ };
378
+ const candidateResolved = candidateResolver.resolveParagraphStyle(defaultParagraphStyleId);
379
+ const sourceRunFormatting = {
380
+ status: "ok",
381
+ formatting: sourceResolved.runFormatting
382
+ };
383
+ const candidateRunFormatting = materializeThemeTextFormatting(candidateResolved.runFormatting, destinationTheme, materializeThemeReferences);
384
+ if (candidateRunFormatting.status === "unalignable" || materializeThemeReferences && hasThemeReference(candidateResolved.paragraphFormatting) || canonicalJson({
385
+ pPr: sourceResolved.paragraphFormatting,
386
+ rPr: sourceRunFormatting.formatting
387
+ }) !== canonicalJson({
388
+ pPr: candidateResolved.paragraphFormatting,
389
+ rPr: candidateRunFormatting.formatting
390
+ })) return {
391
+ status: "unalignable",
392
+ detail: "default paragraph formatting cannot be isolated from the base formatting context"
393
+ };
394
+ }
395
+ return {
396
+ status: "imported",
397
+ styles: candidateStyles,
398
+ styleIdMap,
399
+ importedStyleIds: materializedStyles.map(({ styleId }) => styleId),
400
+ ...defaultParagraphStyleId !== void 0 && { defaultParagraphStyleId }
401
+ };
402
+ };
403
+ //#endregion
404
+ export { importReferencedStyleDefinitions };