@stll/folio-core 0.37.5 → 0.39.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.
- package/dist/ai-edits/apply.js +275 -88
- package/dist/ai-edits/clean-text.js +12 -1
- package/dist/ai-edits/headless.d.ts +110 -0
- package/dist/ai-edits/headless.js +569 -40
- package/dist/ai-edits/snapshot.d.ts +24 -1
- package/dist/ai-edits/snapshot.js +206 -9
- package/dist/ai-edits/table-geometry.d.ts +7 -7
- package/dist/ai-edits/table-geometry.js +43 -27
- package/dist/ai-edits/table-template.d.ts +2 -1
- package/dist/ai-edits/table-template.js +9 -3
- package/dist/ai-edits/types.d.ts +37 -15
- package/dist/compare/__fixtures__/body-sequence.d.ts +4 -0
- package/dist/compare/__fixtures__/body-sequence.js +5 -3
- package/dist/compare/compare.d.ts +26 -2
- package/dist/compare/compare.js +444 -16
- package/dist/compare/content-alignment.js +3 -2
- package/dist/compare/content-types.d.ts +34 -2
- package/dist/compare/content.d.ts +13 -10
- package/dist/compare/content.js +69 -23
- package/dist/compare/formatting.d.ts +4 -22
- package/dist/compare/formatting.js +8 -112
- package/dist/compare/inline-atom-resources.d.ts +6 -0
- package/dist/compare/inline-atom-resources.js +26 -0
- package/dist/compare/inline-atoms.d.ts +35 -0
- package/dist/compare/inline-atoms.js +383 -0
- package/dist/compare/inline-provenance.d.ts +36 -0
- package/dist/compare/inline-provenance.js +334 -0
- package/dist/compare/plan.d.ts +4 -1
- package/dist/compare/plan.js +32 -4
- package/dist/compare/scenario.d.ts +4 -6
- package/dist/compare/section-boundary-properties.d.ts +63 -0
- package/dist/compare/section-boundary-properties.js +235 -0
- package/dist/compare/style-resources.d.ts +30 -0
- package/dist/compare/style-resources.js +404 -0
- package/dist/compare/types.d.ts +50 -11
- package/dist/compare/types.js +3 -1
- package/dist/compare/verification.d.ts +3 -5
- package/dist/compare/verification.js +8 -39
- package/dist/compat/eigenpal.d.ts +4 -4
- package/dist/compat/eigenpal.js +3 -3
- package/dist/controller/layoutPipeline.js +4 -0
- package/dist/display-list/build/buildContext.d.ts +3 -1
- package/dist/display-list/build/buildDisplayList.d.ts +4 -1
- package/dist/display-list/build/buildDisplayList.js +38 -4
- package/dist/display-list/build/commentAnnotations.d.ts +10 -0
- package/dist/display-list/build/commentAnnotations.js +17 -0
- package/dist/display-list/build/paragraphPrimitives.js +39 -1
- package/dist/display-list/primitives.d.ts +1 -1
- package/dist/display-list/types.d.ts +16 -1
- package/dist/document-operations.d.ts +5 -3
- package/dist/document-operations.js +112 -5
- package/dist/docx/blockContentParser.js +14 -2
- package/dist/docx/diagramPreview.d.ts +7 -0
- package/dist/docx/diagramPreview.js +191 -0
- package/dist/docx/drawingIdNormalization.js +43 -2
- package/dist/docx/drawingRelationships.d.ts +10 -0
- package/dist/docx/drawingRelationships.js +28 -0
- package/dist/docx/fieldParser.js +1 -1
- package/dist/docx/hyperlinkParser.js +3 -5
- package/dist/docx/imageRawXml.d.ts +8 -0
- package/dist/docx/imageRawXml.js +13 -0
- package/dist/docx/newImage.d.ts +2 -2
- package/dist/docx/newImage.js +3 -3
- package/dist/docx/numberingParser.js +30 -27
- package/dist/docx/paragraphParser.js +11 -4
- package/dist/docx/paragraphTextBoxEnrichment.js +2 -1
- package/dist/docx/parser.js +13 -0
- package/dist/docx/removeHeaderFooterParts.d.ts +14 -0
- package/dist/docx/removeHeaderFooterParts.js +87 -0
- package/dist/docx/rezip.d.ts +3 -1
- package/dist/docx/rezip.js +143 -41
- package/dist/docx/runParser.js +24 -5
- package/dist/docx/sectionParser.js +3 -0
- package/dist/docx/sectionReferenceHistory.d.ts +17 -0
- package/dist/docx/sectionReferenceHistory.js +59 -0
- package/dist/docx/selectiveSave.js +11 -31
- package/dist/docx/selectiveXmlPatch.d.ts +8 -1
- package/dist/docx/selectiveXmlPatch.js +62 -2
- package/dist/docx/serializer/paragraphSerializer.js +2 -0
- package/dist/docx/serializer/partNamespaces.d.ts +1 -1
- package/dist/docx/serializer/partNamespaces.js +4 -0
- package/dist/docx/serializer/runSerializer.js +5 -2
- package/dist/docx/serializer/sectionPropertiesSerializer.js +5 -2
- package/dist/docx/serializer/textFormattingSerializer.d.ts +1 -1
- package/dist/docx/serializer/textFormattingSerializer.js +3 -1
- package/dist/docx/tableParser.js +21 -11
- package/dist/docx/textBoxParser.js +25 -5
- package/dist/docx/xmlParser.d.ts +10 -1
- package/dist/docx/xmlParser.js +23 -1
- package/dist/export-pdf.js +2 -0
- package/dist/headless-layout.js +1 -0
- package/dist/index.d.ts +4 -4
- package/dist/index.js +3 -3
- package/dist/internal/compare/inline-presentation.d.ts +20 -0
- package/dist/internal/compare/inline-presentation.js +222 -0
- package/dist/internal/paragraphFormattingSerialization.d.ts +3 -1
- package/dist/internal/paragraphFormattingSerialization.js +8 -1
- package/dist/internal/sectionReferenceResolution.d.ts +21 -0
- package/dist/internal/sectionReferenceResolution.js +72 -0
- package/dist/layout-bridge/convert/toFlowBlocks.js +1 -0
- package/dist/layout-engine/index.js +5 -5
- package/dist/layout-engine/sectionVerticalAlignment.d.ts +7 -0
- package/dist/layout-engine/sectionVerticalAlignment.js +52 -0
- package/dist/layout-engine/types.d.ts +3 -1
- package/dist/markdown/renderRuns.js +1 -0
- package/dist/markdown/renderTable.js +1 -0
- package/dist/pdf/writePdf.js +37 -2
- package/dist/prosemirror/attrs/index.js +55 -1
- package/dist/prosemirror/commands/comments.d.ts +11 -1
- package/dist/prosemirror/commands/comments.js +68 -6
- package/dist/prosemirror/commands/propertyChangeScope.d.ts +9 -3
- package/dist/prosemirror/commands/propertyChangeScope.js +4 -10
- package/dist/prosemirror/conversion/fromProseDoc.js +17 -8
- package/dist/prosemirror/conversion/toProseDoc.js +18 -57
- package/dist/prosemirror/extensions/core/ParagraphExtension.js +2 -0
- package/dist/prosemirror/extensions/marks/RunFormattingOverrideExtension.js +3 -0
- package/dist/prosemirror/extensions/nodes/TextBoxExtension.d.ts +2 -1
- package/dist/prosemirror/extensions/nodes/TextBoxExtension.js +39 -1
- package/dist/prosemirror/imageCommit.js +7 -9
- package/dist/prosemirror/paragraphIndentation.d.ts +21 -0
- package/dist/prosemirror/paragraphIndentation.js +72 -0
- package/dist/prosemirror/plugins/documentNumbering.d.ts +3 -1
- package/dist/prosemirror/plugins/documentNumbering.js +7 -1
- package/dist/prosemirror/plugins/documentStyles.d.ts +3 -1
- package/dist/prosemirror/plugins/documentStyles.js +7 -1
- package/dist/prosemirror/rebaseParagraphRunFormatting.d.ts +3 -1
- package/dist/prosemirror/rebaseParagraphRunFormatting.js +2 -1
- package/dist/prosemirror/runFormattingInlineCarriers.d.ts +15 -2
- package/dist/prosemirror/runFormattingInlineCarriers.js +33 -3
- package/dist/prosemirror/runStyleFormatting.d.ts +15 -2
- package/dist/prosemirror/runStyleFormatting.js +76 -2
- package/dist/prosemirror/schema/marks.d.ts +3 -1
- package/dist/prosemirror/schema/marks.js +2 -0
- package/dist/prosemirror/schema/nodes.d.ts +5 -0
- package/dist/prosemirror/tableCellRevisionVisibility.d.ts +6 -0
- package/dist/prosemirror/tableCellRevisionVisibility.js +14 -0
- package/dist/prosemirror/trackedRunInlineAtoms.d.ts +1 -1
- package/dist/prosemirror/trackedRunInlineAtoms.js +1 -1
- package/dist/redline.js +15 -2
- package/dist/server.d.ts +2 -2
- package/dist/server.js +2 -2
- package/dist/version-comparison.d.ts +2 -0
- package/dist/version-comparison.js +7 -2
- package/package.json +2 -2
|
@@ -0,0 +1,222 @@
|
|
|
1
|
+
import { panic } from "better-result";
|
|
2
|
+
//#region src/internal/compare/inline-presentation.ts
|
|
3
|
+
/**
|
|
4
|
+
* Canonical interpretation of the inline presentation carried by comparison
|
|
5
|
+
* snapshots.
|
|
6
|
+
*
|
|
7
|
+
* Effective and explicitly authored values are separate facts. Every caller
|
|
8
|
+
* compares them through the descriptor grammar below, so adding a supported
|
|
9
|
+
* property cannot update redline planning while leaving verification behind.
|
|
10
|
+
*/
|
|
11
|
+
const NO_PRESENTATION_DIFFERENCE = Symbol("no-inline-presentation-difference");
|
|
12
|
+
const HEX_COLOR = /^#?(?:[0-9A-Fa-f]{3}|[0-9A-Fa-f]{6}|[0-9A-Fa-f]{8})$/u;
|
|
13
|
+
const normalizeInlinePresentationColor = (color) => HEX_COLOR.test(color) ? color.replace(/^#/u, "").toUpperCase() : color;
|
|
14
|
+
/**
|
|
15
|
+
* The one grammar for every modeled run-presentation property. The
|
|
16
|
+
* `SamePropertySet` constraint also rejects a field added to `FolioContentRun`
|
|
17
|
+
* without a corresponding authored-formatting representation, or vice versa.
|
|
18
|
+
*/
|
|
19
|
+
const INLINE_PRESENTATION_GRAMMAR = {
|
|
20
|
+
boolean: [
|
|
21
|
+
"bold",
|
|
22
|
+
"italic",
|
|
23
|
+
"underline",
|
|
24
|
+
"strike"
|
|
25
|
+
],
|
|
26
|
+
string: ["fontFamily"],
|
|
27
|
+
number: ["fontSizePt"],
|
|
28
|
+
color: ["color"]
|
|
29
|
+
};
|
|
30
|
+
const INLINE_PRESENTATION_HOT_PATH_GRAMMAR = INLINE_PRESENTATION_GRAMMAR;
|
|
31
|
+
/** Derived from the descriptor grammar; tests use it to prove full coverage. */
|
|
32
|
+
const CANONICAL_INLINE_PRESENTATION_PROPERTIES = Object.freeze([
|
|
33
|
+
...INLINE_PRESENTATION_GRAMMAR.boolean,
|
|
34
|
+
...INLINE_PRESENTATION_GRAMMAR.string,
|
|
35
|
+
...INLINE_PRESENTATION_GRAMMAR.number,
|
|
36
|
+
...INLINE_PRESENTATION_GRAMMAR.color
|
|
37
|
+
]);
|
|
38
|
+
const canonicalColor = (value) => value === null || value === void 0 ? value : normalizeInlinePresentationColor(value);
|
|
39
|
+
const writeCanonicalInlinePresentationDifferences = (base, revised, patch) => {
|
|
40
|
+
let changed = false;
|
|
41
|
+
const baseDirect = base?.directFormatting ?? {};
|
|
42
|
+
const revisedDirect = revised?.directFormatting ?? {};
|
|
43
|
+
for (const property of INLINE_PRESENTATION_HOT_PATH_GRAMMAR.boolean) {
|
|
44
|
+
const baseAuthored = baseDirect[property];
|
|
45
|
+
const revisedAuthored = revisedDirect[property];
|
|
46
|
+
const baseEffective = base?.[property] === true;
|
|
47
|
+
const revisedEffective = revised?.[property] === true;
|
|
48
|
+
let value;
|
|
49
|
+
if (baseAuthored !== revisedAuthored) value = revisedAuthored ?? null;
|
|
50
|
+
else if (baseEffective === revisedEffective) value = NO_PRESENTATION_DIFFERENCE;
|
|
51
|
+
else value = revisedEffective;
|
|
52
|
+
if (value !== NO_PRESENTATION_DIFFERENCE) {
|
|
53
|
+
changed = true;
|
|
54
|
+
if (patch !== null) patch[property] = value;
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
const stringProperty = INLINE_PRESENTATION_HOT_PATH_GRAMMAR.string[0];
|
|
58
|
+
let stringValue;
|
|
59
|
+
if (baseDirect[stringProperty] !== revisedDirect[stringProperty]) stringValue = revisedDirect[stringProperty] ?? null;
|
|
60
|
+
else if (base?.[stringProperty] === revised?.[stringProperty]) stringValue = NO_PRESENTATION_DIFFERENCE;
|
|
61
|
+
else stringValue = revised?.[stringProperty] ?? null;
|
|
62
|
+
if (stringValue !== NO_PRESENTATION_DIFFERENCE) {
|
|
63
|
+
changed = true;
|
|
64
|
+
if (patch !== null) patch[stringProperty] = stringValue;
|
|
65
|
+
}
|
|
66
|
+
const numberProperty = INLINE_PRESENTATION_HOT_PATH_GRAMMAR.number[0];
|
|
67
|
+
let numberValue;
|
|
68
|
+
if (baseDirect[numberProperty] !== revisedDirect[numberProperty]) numberValue = revisedDirect[numberProperty] ?? null;
|
|
69
|
+
else if (base?.[numberProperty] === revised?.[numberProperty]) numberValue = NO_PRESENTATION_DIFFERENCE;
|
|
70
|
+
else numberValue = revised?.[numberProperty] ?? null;
|
|
71
|
+
if (numberValue !== NO_PRESENTATION_DIFFERENCE) {
|
|
72
|
+
changed = true;
|
|
73
|
+
if (patch !== null) patch[numberProperty] = numberValue;
|
|
74
|
+
}
|
|
75
|
+
const colorProperty = INLINE_PRESENTATION_HOT_PATH_GRAMMAR.color[0];
|
|
76
|
+
const baseAuthoredColor = canonicalColor(baseDirect[colorProperty]);
|
|
77
|
+
const revisedAuthoredColor = canonicalColor(revisedDirect[colorProperty]);
|
|
78
|
+
const baseEffectiveColor = canonicalColor(base?.[colorProperty]);
|
|
79
|
+
const revisedEffectiveColor = canonicalColor(revised?.[colorProperty]);
|
|
80
|
+
let color;
|
|
81
|
+
if (baseAuthoredColor !== revisedAuthoredColor) color = revisedAuthoredColor ?? null;
|
|
82
|
+
else if (baseEffectiveColor === revisedEffectiveColor) color = NO_PRESENTATION_DIFFERENCE;
|
|
83
|
+
else color = revisedEffectiveColor ?? null;
|
|
84
|
+
if (color !== NO_PRESENTATION_DIFFERENCE) {
|
|
85
|
+
changed = true;
|
|
86
|
+
if (patch !== null) patch[colorProperty] = color;
|
|
87
|
+
}
|
|
88
|
+
return changed;
|
|
89
|
+
};
|
|
90
|
+
const canonicalInlinePresentationsEqual = (base, revised) => !writeCanonicalInlinePresentationDifferences(base, revised, null);
|
|
91
|
+
const changedCanonicalInlinePresentation = (base, revised) => {
|
|
92
|
+
const patch = {};
|
|
93
|
+
return writeCanonicalInlinePresentationDifferences(base, revised, patch) ? patch : null;
|
|
94
|
+
};
|
|
95
|
+
const sameCanonicalInlinePresentationPatch = (left, right) => {
|
|
96
|
+
for (const property of INLINE_PRESENTATION_GRAMMAR.boolean) if (left[property] !== right[property]) return false;
|
|
97
|
+
for (const property of INLINE_PRESENTATION_GRAMMAR.string) if (left[property] !== right[property]) return false;
|
|
98
|
+
for (const property of INLINE_PRESENTATION_GRAMMAR.number) if (left[property] !== right[property]) return false;
|
|
99
|
+
for (const property of INLINE_PRESENTATION_GRAMMAR.color) if (left[property] !== right[property]) return false;
|
|
100
|
+
return true;
|
|
101
|
+
};
|
|
102
|
+
/**
|
|
103
|
+
* Walk the shared UTF-16 coordinate space once. Run boundaries may differ;
|
|
104
|
+
* malformed run streams never yield a trusted projection.
|
|
105
|
+
*/
|
|
106
|
+
const visitAlignedRunRanges = (options) => {
|
|
107
|
+
const { baseBlock, revisedBlock } = options;
|
|
108
|
+
if (baseBlock.text !== revisedBlock.text) return "unalignable";
|
|
109
|
+
const baseRuns = baseBlock.previewRuns ?? [{ text: baseBlock.text }];
|
|
110
|
+
const revisedRuns = revisedBlock.previewRuns ?? [{ text: revisedBlock.text }];
|
|
111
|
+
let baseRunIndex = 0;
|
|
112
|
+
let revisedRunIndex = 0;
|
|
113
|
+
let baseRunOffset = 0;
|
|
114
|
+
let revisedRunOffset = 0;
|
|
115
|
+
let baseBlockOffset = 0;
|
|
116
|
+
let revisedBlockOffset = 0;
|
|
117
|
+
let exceededBudget = false;
|
|
118
|
+
while (true) {
|
|
119
|
+
let baseRunText = baseRuns[baseRunIndex]?.text;
|
|
120
|
+
while (baseRunText !== void 0 && baseRunOffset === baseRunText.length) {
|
|
121
|
+
baseRunIndex++;
|
|
122
|
+
baseRunOffset = 0;
|
|
123
|
+
baseRunText = baseRuns[baseRunIndex]?.text;
|
|
124
|
+
}
|
|
125
|
+
let revisedRunText = revisedRuns[revisedRunIndex]?.text;
|
|
126
|
+
while (revisedRunText !== void 0 && revisedRunOffset === revisedRunText.length) {
|
|
127
|
+
revisedRunIndex++;
|
|
128
|
+
revisedRunOffset = 0;
|
|
129
|
+
revisedRunText = revisedRuns[revisedRunIndex]?.text;
|
|
130
|
+
}
|
|
131
|
+
if (baseRunIndex === baseRuns.length || revisedRunIndex === revisedRuns.length) {
|
|
132
|
+
if (!(baseRunIndex === baseRuns.length && revisedRunIndex === revisedRuns.length && baseBlockOffset === baseBlock.text.length && revisedBlockOffset === revisedBlock.text.length)) return "unalignable";
|
|
133
|
+
if (exceededBudget) return "budget-exceeded";
|
|
134
|
+
return "complete";
|
|
135
|
+
}
|
|
136
|
+
if (baseRunText === void 0 || revisedRunText === void 0) return "unalignable";
|
|
137
|
+
if (baseRunOffset === 0 && !baseBlock.text.startsWith(baseRunText, baseBlockOffset) || revisedRunOffset === 0 && !revisedBlock.text.startsWith(revisedRunText, revisedBlockOffset)) return "unalignable";
|
|
138
|
+
const length = Math.min(baseRunText.length - baseRunOffset, revisedRunText.length - revisedRunOffset);
|
|
139
|
+
const baseRun = baseRuns[baseRunIndex];
|
|
140
|
+
const revisedRun = revisedRuns[revisedRunIndex];
|
|
141
|
+
if (options.type === "equivalence") {
|
|
142
|
+
if (!canonicalInlinePresentationsEqual(baseRun, revisedRun)) return "stopped";
|
|
143
|
+
} else if (!exceededBudget) {
|
|
144
|
+
const formatting = changedCanonicalInlinePresentation(baseRun, revisedRun);
|
|
145
|
+
if (formatting !== null) {
|
|
146
|
+
const previous = options.segments.at(-1);
|
|
147
|
+
if (previous && previous.endOffset === baseBlockOffset && sameCanonicalInlinePresentationPatch(previous.formatting, formatting)) previous.endOffset += length;
|
|
148
|
+
else if (options.segments.length >= options.maxSegments) exceededBudget = true;
|
|
149
|
+
else options.segments.push({
|
|
150
|
+
startOffset: baseBlockOffset,
|
|
151
|
+
endOffset: baseBlockOffset + length,
|
|
152
|
+
formatting
|
|
153
|
+
});
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
baseRunOffset += length;
|
|
157
|
+
revisedRunOffset += length;
|
|
158
|
+
baseBlockOffset += length;
|
|
159
|
+
revisedBlockOffset += length;
|
|
160
|
+
}
|
|
161
|
+
};
|
|
162
|
+
const hasAlignedRunStream = (block) => {
|
|
163
|
+
if (block.previewRuns === void 0) return true;
|
|
164
|
+
let offset = 0;
|
|
165
|
+
for (const run of block.previewRuns) {
|
|
166
|
+
if (!block.text.startsWith(run.text, offset)) return false;
|
|
167
|
+
offset += run.text.length;
|
|
168
|
+
}
|
|
169
|
+
return offset === block.text.length;
|
|
170
|
+
};
|
|
171
|
+
/**
|
|
172
|
+
* Collect coalesced canonical presentation differences in character order.
|
|
173
|
+
* Invalid run streams take precedence over the segment budget, so malformed
|
|
174
|
+
* content cannot change meaning as the caller's remaining budget changes.
|
|
175
|
+
*/
|
|
176
|
+
const canonicalInlinePresentationSegments = ({ baseBlock, revisedBlock, maxSegments }) => {
|
|
177
|
+
const segments = [];
|
|
178
|
+
const result = visitAlignedRunRanges({
|
|
179
|
+
type: "segments",
|
|
180
|
+
baseBlock,
|
|
181
|
+
revisedBlock,
|
|
182
|
+
maxSegments,
|
|
183
|
+
segments
|
|
184
|
+
});
|
|
185
|
+
switch (result) {
|
|
186
|
+
case "complete": return {
|
|
187
|
+
status: "compared",
|
|
188
|
+
segments
|
|
189
|
+
};
|
|
190
|
+
case "budget-exceeded": return {
|
|
191
|
+
status: "budget-exceeded",
|
|
192
|
+
maximum: maxSegments
|
|
193
|
+
};
|
|
194
|
+
case "unalignable": {
|
|
195
|
+
const baseAligned = hasAlignedRunStream(baseBlock);
|
|
196
|
+
if (baseAligned === hasAlignedRunStream(revisedBlock)) return {
|
|
197
|
+
status: "unalignable",
|
|
198
|
+
side: "both"
|
|
199
|
+
};
|
|
200
|
+
if (baseAligned) return {
|
|
201
|
+
status: "unalignable",
|
|
202
|
+
side: "revised"
|
|
203
|
+
};
|
|
204
|
+
return {
|
|
205
|
+
status: "unalignable",
|
|
206
|
+
side: "base"
|
|
207
|
+
};
|
|
208
|
+
}
|
|
209
|
+
case "stopped": return panic("A segment traversal cannot stop at a presentation difference");
|
|
210
|
+
default: return result;
|
|
211
|
+
}
|
|
212
|
+
};
|
|
213
|
+
/** Effective and authored presentation equality, independent of run splits. */
|
|
214
|
+
const sameCanonicalInlinePresentation = (baseBlock, revisedBlock) => {
|
|
215
|
+
return visitAlignedRunRanges({
|
|
216
|
+
type: "equivalence",
|
|
217
|
+
baseBlock,
|
|
218
|
+
revisedBlock
|
|
219
|
+
}) === "complete";
|
|
220
|
+
};
|
|
221
|
+
//#endregion
|
|
222
|
+
export { CANONICAL_INLINE_PRESENTATION_PROPERTIES, canonicalInlinePresentationSegments, sameCanonicalInlinePresentation };
|
|
@@ -6,6 +6,8 @@ type ClassifiedParagraphFormattingField = "alignment" | "bidi" | "kinsoku" | "ov
|
|
|
6
6
|
type ExhaustiveParagraphFormatting = ExhaustiveFields<document_d_exports.ParagraphFormatting, ClassifiedParagraphFormattingField>;
|
|
7
7
|
/** Compare numbering references by their emitted id and effective level. */
|
|
8
8
|
declare const paragraphNumberingReferencesEqual: (left: ParagraphNumberingReference, right: ParagraphNumberingReference) => boolean;
|
|
9
|
+
/** Compare the authored `w:numPr` attributes, including an absent `w:ilvl`. */
|
|
10
|
+
declare const sameAuthoredParagraphNumberingReference: (left: ParagraphNumberingReference, right: ParagraphNumberingReference) => boolean;
|
|
9
11
|
/** Whether resolved numbering still belongs to the paragraph's style tier. */
|
|
10
12
|
declare const isStyleSourcedParagraphNumbering: (numPr: ParagraphNumberingReference, numPrFromStyle: ParagraphNumberingReference) => boolean;
|
|
11
13
|
/** Exact fallback-emission instructions for the modeled part of `w:pPr`. */
|
|
@@ -20,4 +22,4 @@ type ModeledParagraphFormattingEmission = Readonly<{
|
|
|
20
22
|
*/
|
|
21
23
|
declare const modelParagraphFormattingEmission: (input: ExhaustiveParagraphFormatting | undefined) => ModeledParagraphFormattingEmission;
|
|
22
24
|
//#endregion
|
|
23
|
-
export { ModeledParagraphFormattingEmission, isStyleSourcedParagraphNumbering, modelParagraphFormattingEmission, paragraphNumberingReferencesEqual };
|
|
25
|
+
export { ModeledParagraphFormattingEmission, isStyleSourcedParagraphNumbering, modelParagraphFormattingEmission, paragraphNumberingReferencesEqual, sameAuthoredParagraphNumberingReference };
|
|
@@ -17,6 +17,13 @@ const paragraphNumberingReferencesEqual = (left, right) => {
|
|
|
17
17
|
if (modeledLeft === null || modeledRight === null) return modeledLeft === null && modeledRight === null;
|
|
18
18
|
return modeledLeft.numId === modeledRight.numId && (modeledLeft.ilvl ?? 0) === (modeledRight.ilvl ?? 0);
|
|
19
19
|
};
|
|
20
|
+
/** Compare the authored `w:numPr` attributes, including an absent `w:ilvl`. */
|
|
21
|
+
const sameAuthoredParagraphNumberingReference = (left, right) => {
|
|
22
|
+
const modeledLeft = modelParagraphNumberingReference(left);
|
|
23
|
+
const modeledRight = modelParagraphNumberingReference(right);
|
|
24
|
+
if (modeledLeft === null || modeledRight === null) return modeledLeft === null && modeledRight === null;
|
|
25
|
+
return modeledLeft.numId === modeledRight.numId && modeledLeft.ilvl === modeledRight.ilvl;
|
|
26
|
+
};
|
|
20
27
|
/** Whether resolved numbering still belongs to the paragraph's style tier. */
|
|
21
28
|
const isStyleSourcedParagraphNumbering = (numPr, numPrFromStyle) => numPr != null && numPrFromStyle != null && paragraphNumberingReferencesEqual(numPr, numPrFromStyle);
|
|
22
29
|
const serializeToggle = (name, value) => {
|
|
@@ -154,4 +161,4 @@ const modelParagraphFormattingEmission = (input) => {
|
|
|
154
161
|
return emission;
|
|
155
162
|
};
|
|
156
163
|
//#endregion
|
|
157
|
-
export { isStyleSourcedParagraphNumbering, modelParagraphFormattingEmission, paragraphNumberingReferencesEqual };
|
|
164
|
+
export { isStyleSourcedParagraphNumbering, modelParagraphFormattingEmission, paragraphNumberingReferencesEqual, sameAuthoredParagraphNumberingReference };
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import { document_d_exports } from "../types/document.js";
|
|
2
|
+
import { RemovedSectionReference } from "./sectionEndpointResolution.js";
|
|
3
|
+
import { Node } from "prosemirror-model";
|
|
4
|
+
//#region src/internal/sectionReferenceResolution.d.ts
|
|
5
|
+
type SectionReferenceInventory = {
|
|
6
|
+
references: readonly RemovedSectionReference[];
|
|
7
|
+
revisionRelationships: ReadonlySet<string>;
|
|
8
|
+
};
|
|
9
|
+
declare const captureSectionReferenceInventory: (document: Node, finalProperties: document_d_exports.SectionProperties | undefined) => SectionReferenceInventory;
|
|
10
|
+
declare const resolvedSectionReferenceLosses: ({ before, after }: {
|
|
11
|
+
before: SectionReferenceInventory;
|
|
12
|
+
after: SectionReferenceInventory;
|
|
13
|
+
}) => RemovedSectionReference[];
|
|
14
|
+
declare const withSectionReferenceResolution: <T>({ document, removedReferences, repack }: {
|
|
15
|
+
document: document_d_exports.Document;
|
|
16
|
+
removedReferences: readonly RemovedSectionReference[];
|
|
17
|
+
repack: () => Promise<T>;
|
|
18
|
+
}) => Promise<T>;
|
|
19
|
+
declare const consumeSectionReferenceResolution: (document: document_d_exports.Document) => readonly RemovedSectionReference[];
|
|
20
|
+
//#endregion
|
|
21
|
+
export { SectionReferenceInventory, captureSectionReferenceInventory, consumeSectionReferenceResolution, resolvedSectionReferenceLosses, withSectionReferenceResolution };
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
import { expectParagraphAttrs } from "../prosemirror/attrs/index.js";
|
|
2
|
+
import { panic } from "better-result";
|
|
3
|
+
//#region src/internal/sectionReferenceResolution.ts
|
|
4
|
+
const relationshipKey = ({ part, relationshipId }) => `${part}:${relationshipId}`;
|
|
5
|
+
const referenceKey = (reference) => `${relationshipKey(reference)}:${reference.type}`;
|
|
6
|
+
const captureSectionReferenceInventory = (document, finalProperties) => {
|
|
7
|
+
const references = [];
|
|
8
|
+
const revisionRelationships = /* @__PURE__ */ new Set();
|
|
9
|
+
const collect = (properties) => {
|
|
10
|
+
if (!properties) return;
|
|
11
|
+
const start = references.length;
|
|
12
|
+
const append = (selection) => {
|
|
13
|
+
for (const { type, rId } of selection.headerReferences ?? []) references.push({
|
|
14
|
+
part: "header",
|
|
15
|
+
type,
|
|
16
|
+
relationshipId: rId
|
|
17
|
+
});
|
|
18
|
+
for (const { type, rId } of selection.footerReferences ?? []) references.push({
|
|
19
|
+
part: "footer",
|
|
20
|
+
type,
|
|
21
|
+
relationshipId: rId
|
|
22
|
+
});
|
|
23
|
+
};
|
|
24
|
+
append(properties);
|
|
25
|
+
const history = properties.propertyChanges?.filter(({ previousReferences }) => previousReferences !== void 0) ?? [];
|
|
26
|
+
for (const change of history) if (change.previousReferences) append(change.previousReferences);
|
|
27
|
+
if (history.length > 0) for (const reference of references.slice(start)) revisionRelationships.add(relationshipKey(reference));
|
|
28
|
+
};
|
|
29
|
+
document.descendants((node) => {
|
|
30
|
+
if (node.type.name === "paragraph") collect(expectParagraphAttrs(node)._sectionProperties);
|
|
31
|
+
});
|
|
32
|
+
collect(finalProperties);
|
|
33
|
+
return {
|
|
34
|
+
references,
|
|
35
|
+
revisionRelationships
|
|
36
|
+
};
|
|
37
|
+
};
|
|
38
|
+
const resolvedSectionReferenceLosses = ({ before, after }) => {
|
|
39
|
+
const remaining = /* @__PURE__ */ new Map();
|
|
40
|
+
for (const reference of after.references) {
|
|
41
|
+
const key = referenceKey(reference);
|
|
42
|
+
remaining.set(key, (remaining.get(key) ?? 0) + 1);
|
|
43
|
+
}
|
|
44
|
+
const removed = [];
|
|
45
|
+
for (const reference of before.references) {
|
|
46
|
+
if (!before.revisionRelationships.has(relationshipKey(reference))) continue;
|
|
47
|
+
const key = referenceKey(reference);
|
|
48
|
+
const count = remaining.get(key) ?? 0;
|
|
49
|
+
if (count > 0) remaining.set(key, count - 1);
|
|
50
|
+
else removed.push(reference);
|
|
51
|
+
}
|
|
52
|
+
return removed;
|
|
53
|
+
};
|
|
54
|
+
const activeResolutions = /* @__PURE__ */ new WeakMap();
|
|
55
|
+
const withSectionReferenceResolution = async ({ document, removedReferences, repack }) => {
|
|
56
|
+
if (activeResolutions.has(document)) panic("A section reference repack is already active");
|
|
57
|
+
activeResolutions.set(document, removedReferences.map((reference) => ({ ...reference })));
|
|
58
|
+
try {
|
|
59
|
+
const result = await repack();
|
|
60
|
+
if (activeResolutions.has(document)) panic("The section reference repack did not consume its resolution");
|
|
61
|
+
return result;
|
|
62
|
+
} finally {
|
|
63
|
+
activeResolutions.delete(document);
|
|
64
|
+
}
|
|
65
|
+
};
|
|
66
|
+
const consumeSectionReferenceResolution = (document) => {
|
|
67
|
+
const references = activeResolutions.get(document) ?? [];
|
|
68
|
+
activeResolutions.delete(document);
|
|
69
|
+
return references;
|
|
70
|
+
};
|
|
71
|
+
//#endregion
|
|
72
|
+
export { captureSectionReferenceInventory, consumeSectionReferenceResolution, resolvedSectionReferenceLosses, withSectionReferenceResolution };
|
|
@@ -393,6 +393,7 @@ const RUN_FORMATTING_OVERRIDE_HANDLER_ENTRIES = Object.entries({
|
|
|
393
393
|
fontSizeCs(formatting, value) {
|
|
394
394
|
if (typeof value === "number") formatting.complexScriptFontSize = value / 2;
|
|
395
395
|
},
|
|
396
|
+
noProof() {},
|
|
396
397
|
hidden(formatting, value) {
|
|
397
398
|
if (typeof value === "boolean") formatting.hidden = value;
|
|
398
399
|
},
|
|
@@ -11,6 +11,7 @@ import { collapseParagraphSpacing, getParagraphSpacingAfter, getParagraphSpacing
|
|
|
11
11
|
import { assertExhaustiveFlowBlock, findPageIndexContainingPmPos } from "./pmPageIndex.js";
|
|
12
12
|
import { INITIAL_RENDERED_BREAK_STATE, PAGE_ADVANCE, reconcileAfterBlock, reconcileBreakBeforeBlock, recordReflowBoundary } from "./renderedBreakReconciliation.js";
|
|
13
13
|
import { applyPendingToActive, createInitialSectionState, getEffectiveColumns, getEffectiveMargins, getEffectivePageSize, normalizeSectionBreakType, scheduleSectionBreak } from "./section-breaks.js";
|
|
14
|
+
import { applySectionVerticalAlignment } from "./sectionVerticalAlignment.js";
|
|
14
15
|
import { buildTableRowBreakInfo, getRowContinuationSkip, snapRowBreak } from "./tableRowBreak.js";
|
|
15
16
|
import { bandFragmentX, bandTopContentY, isPageFrameRelativeAnchor } from "./textBoxFlow.js";
|
|
16
17
|
import { DEFAULT_TEXTBOX_MARGINS, DEFAULT_TEXTBOX_WIDTH, FOOTNOTE_ENTRY_MARGIN_BOTTOM, FOOTNOTE_FALLBACK_LINE_HEIGHT, FOOTNOTE_SEPARATOR_HEIGHT, floatingTextBoxReservesBand, floatingTextBoxWrapsText, getTableRowLeadingWidth, isFloatingImageRun, isFloatingTextBoxBlock, isTextWrappingFloatingImageRun, resolveTableCellPadding, tableColumnsArePinned } from "./types.js";
|
|
@@ -145,16 +146,15 @@ function applyContextualSpacing(blocks) {
|
|
|
145
146
|
* reservations as page floors so every column participates in the same footnote geometry.
|
|
146
147
|
*/
|
|
147
148
|
function layoutDocument(blocks, measures, options) {
|
|
148
|
-
const
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
initialLayout: layout,
|
|
149
|
+
const initialLayout = layoutDocumentPass(blocks, measures, options);
|
|
150
|
+
return applySectionVerticalAlignment(options.footnoteHeightById ? reflowFootnoteColumns({
|
|
151
|
+
initialLayout,
|
|
152
152
|
...options.footnoteReservedHeights ? { initialReserveFloors: options.footnoteReservedHeights } : {},
|
|
153
153
|
runLayout: (reserveFloors) => layoutDocumentPass(blocks, measures, {
|
|
154
154
|
...options,
|
|
155
155
|
footnoteReservedHeights: reserveFloors
|
|
156
156
|
})
|
|
157
|
-
});
|
|
157
|
+
}) : initialLayout, options.sectionVerticalAlignments);
|
|
158
158
|
}
|
|
159
159
|
function layoutDocumentPass(blocks, measures, options) {
|
|
160
160
|
if (blocks.length !== measures.length) panic(`layoutDocument: expected one measure per block (blocks=${blocks.length}, measures=${measures.length})`);
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import { Layout } from "./types.js";
|
|
2
|
+
import { VerticalAlign } from "@stll/docx-core/model";
|
|
3
|
+
//#region src/layout-engine/sectionVerticalAlignment.d.ts
|
|
4
|
+
/** Align each page's body inside the authored section content frame. */
|
|
5
|
+
declare const applySectionVerticalAlignment: (layout: Layout, alignments: readonly (VerticalAlign | undefined)[] | undefined) => Layout;
|
|
6
|
+
//#endregion
|
|
7
|
+
export { applySectionVerticalAlignment };
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
//#region src/layout-engine/sectionVerticalAlignment.ts
|
|
2
|
+
const isFlowFragment = (fragment) => {
|
|
3
|
+
switch (fragment.kind) {
|
|
4
|
+
case "paragraph": return true;
|
|
5
|
+
case "table": return !fragment.isFloating;
|
|
6
|
+
case "image": return !fragment.isAnchored;
|
|
7
|
+
case "textBox": return !fragment.isPositioned;
|
|
8
|
+
default: return false;
|
|
9
|
+
}
|
|
10
|
+
};
|
|
11
|
+
const alignedOffset = (alignment, availableHeight) => {
|
|
12
|
+
switch (alignment) {
|
|
13
|
+
case "center": return availableHeight / 2;
|
|
14
|
+
case "bottom": return availableHeight;
|
|
15
|
+
case "top":
|
|
16
|
+
case "both": return 0;
|
|
17
|
+
default: return 0;
|
|
18
|
+
}
|
|
19
|
+
};
|
|
20
|
+
const alignPage = (page, alignment) => {
|
|
21
|
+
if (alignment !== "center" && alignment !== "bottom") return page;
|
|
22
|
+
const flow = page.fragments.filter(isFlowFragment);
|
|
23
|
+
if (flow.length === 0) return page;
|
|
24
|
+
const contentBottom = page.size.h - page.margins.bottom - (page.footnoteReservedHeight ?? 0);
|
|
25
|
+
const occupiedBottom = Math.max(...flow.map((fragment) => fragment.y + fragment.height));
|
|
26
|
+
const offset = alignedOffset(alignment, Math.max(0, contentBottom - occupiedBottom));
|
|
27
|
+
if (offset === 0) return page;
|
|
28
|
+
const flowSet = new Set(flow);
|
|
29
|
+
return {
|
|
30
|
+
...page,
|
|
31
|
+
fragments: page.fragments.map((fragment) => flowSet.has(fragment) ? {
|
|
32
|
+
...fragment,
|
|
33
|
+
y: fragment.y + offset
|
|
34
|
+
} : fragment)
|
|
35
|
+
};
|
|
36
|
+
};
|
|
37
|
+
/** Align each page's body inside the authored section content frame. */
|
|
38
|
+
const applySectionVerticalAlignment = (layout, alignments) => {
|
|
39
|
+
if (alignments === void 0) return layout;
|
|
40
|
+
let changed = false;
|
|
41
|
+
const pages = layout.pages.map((page) => {
|
|
42
|
+
const aligned = alignPage(page, alignments[page.sectionIndex ?? 0]);
|
|
43
|
+
changed ||= aligned !== page;
|
|
44
|
+
return aligned;
|
|
45
|
+
});
|
|
46
|
+
return changed ? {
|
|
47
|
+
...layout,
|
|
48
|
+
pages
|
|
49
|
+
} : layout;
|
|
50
|
+
};
|
|
51
|
+
//#endregion
|
|
52
|
+
export { applySectionVerticalAlignment };
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { OutlineStyleAttr } from "../types/documentEnumValues.js";
|
|
2
|
-
import { ImagePosition, ImageWrap, NumberFormat, SdtProperties, SdtType, ShapeTextBody, TableCellFormatting, TableWidthType } from "@stll/docx-core/model";
|
|
2
|
+
import { ImagePosition, ImageWrap, NumberFormat, SdtProperties, SdtType, ShapeTextBody, TableCellFormatting, TableWidthType, VerticalAlign } from "@stll/docx-core/model";
|
|
3
3
|
declare namespace types_d_exports {
|
|
4
4
|
export { BlockId, BorderStyle, CellBorderSpec, CellBorders, ColumnBreakBlock, ColumnBreakMeasure, ColumnLayout, DEFAULT_TEXTBOX_MARGINS, DEFAULT_TEXTBOX_WIDTH, DocumentPosition, FOOTNOTE_ENTRY_MARGIN_BOTTOM, FOOTNOTE_FALLBACK_LINE_HEIGHT, FOOTNOTE_SEPARATOR_HEIGHT, FieldRun, FloatingTablePosition, FlowBlock, FootnoteContent, Fragment, FragmentBase, HeaderFooterContent, HeaderFooterContentHeights, HeaderFooterLayout, HitTestResult, HyperlinkInfo, ImageBlock, ImageFragment, ImageMeasure, ImageRun, ImageRunPosition, Layout, LayoutOptions, LineBreakRun, ListMarkerFormatting, ListNumPr, MathRun, Measure, MeasuredLine, Page, PageBreakBlock, PageBreakMeasure, PageHeaderFooterRefs, PageMargins, ParagraphAttrs, ParagraphBlock, ParagraphBorders, ParagraphFragment, ParagraphIndent, ParagraphMeasure, ParagraphSpacing, RenderedPageBreakRun, Run, RunFormatting, SdtGroup, SectionBreakBlock, SectionBreakMeasure, SectionPageNumbering, TabAlignment, TabRun, TabStop, TableBlock, TableCell, TableCellMeasure, TableFragment, TableMeasure, TableRow, TableRowMeasure, TextBoxBlock, TextBoxFlowAttrs, TextBoxFragment, TextBoxMeasure, TextRun, floatingTextBoxReservesBand, floatingTextBoxWrapsText, getTableRowLeadingWidth, isFloatingImageRun, isFloatingTextBoxBlock, isTextWrappingFloatingImageRun, resolveTableCellPadding, tableColumnsArePinned };
|
|
5
5
|
}
|
|
@@ -1252,6 +1252,8 @@ type LayoutOptions = {
|
|
|
1252
1252
|
finalColumns?: ColumnLayout;
|
|
1253
1253
|
/** Body-level final section page-number policy. */
|
|
1254
1254
|
finalPageNumbering?: SectionPageNumbering;
|
|
1255
|
+
/** Vertical body alignment for each section, indexed by section ordinal. */
|
|
1256
|
+
sectionVerticalAlignments?: readonly (VerticalAlign | undefined)[];
|
|
1255
1257
|
/** Column configuration. */
|
|
1256
1258
|
columns?: ColumnLayout;
|
|
1257
1259
|
/** Gap between rendered pages (for UI). */
|
|
@@ -148,6 +148,7 @@ function renderTrackedWrapper(ctx, pkg, wrapper, paraId) {
|
|
|
148
148
|
const renderChild = (child) => {
|
|
149
149
|
if (child.type === "run") return renderRun(ctx, pkg, child, paraId);
|
|
150
150
|
if (child.type === "hyperlink") return renderHyperlink(ctx, pkg, child, paraId);
|
|
151
|
+
if (child.type === "mathEquation") return child.plainText ? escapeInline(child.plainText) : "";
|
|
151
152
|
return "";
|
|
152
153
|
};
|
|
153
154
|
if (ctx.opts.trackedChanges === "clean") {
|
|
@@ -160,6 +160,7 @@ function renderHtmlChildren(ctx, pkg, children, paraId) {
|
|
|
160
160
|
return children.map((child) => {
|
|
161
161
|
if (child.type === "run") return renderHtmlRun(ctx, pkg, child, paraId);
|
|
162
162
|
if (child.type === "hyperlink") return renderHtmlHyperlink(ctx, pkg, child, paraId);
|
|
163
|
+
if (child.type === "mathEquation") return child.plainText ? escapeHtml(child.plainText) : "";
|
|
163
164
|
return "";
|
|
164
165
|
}).join("");
|
|
165
166
|
}
|
package/dist/pdf/writePdf.js
CHANGED
|
@@ -116,7 +116,7 @@ const buildDestination = ({ pageRefs, pages, pageIndex, yPx }) => {
|
|
|
116
116
|
pdfNull
|
|
117
117
|
]));
|
|
118
118
|
};
|
|
119
|
-
const
|
|
119
|
+
const buildLinkAnnotation = ({ document, link, page, pages, pageRefs }) => {
|
|
120
120
|
const topLeft = displayPointToPdf(page.heightPx, link.rect.xPx, link.rect.yPx);
|
|
121
121
|
const bottomRight = displayPointToPdf(page.heightPx, link.rect.xPx + link.rect.widthPx, link.rect.yPx + link.rect.heightPx);
|
|
122
122
|
let action;
|
|
@@ -160,6 +160,26 @@ const buildAnnotation = ({ document, link, page, pages, pageRefs }) => {
|
|
|
160
160
|
["Dest", destination]
|
|
161
161
|
])));
|
|
162
162
|
};
|
|
163
|
+
const buildCommentAnnotation = ({ document, comment, page }) => {
|
|
164
|
+
const rect = comment.rects.at(-1);
|
|
165
|
+
if (rect === void 0) return Result.err(new WritePdfError({ message: `comment ${String(comment.commentId)} has no anchor rectangle` }));
|
|
166
|
+
const topLeft = displayPointToPdf(page.heightPx, rect.xPx, rect.yPx);
|
|
167
|
+
const bottomRight = displayPointToPdf(page.heightPx, rect.xPx + rect.widthPx, rect.yPx + rect.heightPx);
|
|
168
|
+
return Result.ok(document.add(pdfDict([
|
|
169
|
+
["Type", pdfName("Annot")],
|
|
170
|
+
["Subtype", pdfName("Text")],
|
|
171
|
+
["Rect", pdfNumberArray([
|
|
172
|
+
Math.min(topLeft.x, bottomRight.x),
|
|
173
|
+
Math.min(topLeft.y, bottomRight.y),
|
|
174
|
+
Math.max(topLeft.x, bottomRight.x),
|
|
175
|
+
Math.max(topLeft.y, bottomRight.y)
|
|
176
|
+
])],
|
|
177
|
+
["Contents", pdfTextString(comment.contents)],
|
|
178
|
+
["T", pdfTextString(comment.author)],
|
|
179
|
+
["Name", pdfName("Comment")],
|
|
180
|
+
["NM", pdfAsciiString(`folio-comment-${String(comment.commentId)}`)]
|
|
181
|
+
])));
|
|
182
|
+
};
|
|
163
183
|
const buildOutlineTree = (entries, document) => {
|
|
164
184
|
const roots = [];
|
|
165
185
|
const ancestors = [];
|
|
@@ -255,6 +275,11 @@ const writePdf = async (list, options) => {
|
|
|
255
275
|
}
|
|
256
276
|
const outlineRoots = buildOutlineTree(list.outline, document);
|
|
257
277
|
const outlineRef = outlineRoots.length === 0 ? void 0 : document.allocate();
|
|
278
|
+
const commentLocations = /* @__PURE__ */ new Map();
|
|
279
|
+
for (const [pageIndex, page] of list.pages.entries()) for (const comment of page.comments) commentLocations.set(comment.commentId, {
|
|
280
|
+
pageIndex,
|
|
281
|
+
comment
|
|
282
|
+
});
|
|
258
283
|
for (const [pageIndex, page] of list.pages.entries()) {
|
|
259
284
|
const pageRef = pageRefs[pageIndex];
|
|
260
285
|
if (pageRef === void 0) return Result.err(new WritePdfError({ message: `page ${String(pageIndex)} lost its object` }));
|
|
@@ -266,7 +291,7 @@ const writePdf = async (list, options) => {
|
|
|
266
291
|
const contentRef = document.add(pdfFlateStream([], new TextEncoder().encode(renderContentStream(parts, resources))));
|
|
267
292
|
const annotationRefs = [];
|
|
268
293
|
for (const link of page.links) {
|
|
269
|
-
const annotation =
|
|
294
|
+
const annotation = buildLinkAnnotation({
|
|
270
295
|
document,
|
|
271
296
|
link,
|
|
272
297
|
page,
|
|
@@ -276,6 +301,16 @@ const writePdf = async (list, options) => {
|
|
|
276
301
|
if (annotation.isErr()) return Result.err(annotation.error);
|
|
277
302
|
annotationRefs.push(annotation.value);
|
|
278
303
|
}
|
|
304
|
+
for (const { pageIndex: commentPageIndex, comment } of commentLocations.values()) {
|
|
305
|
+
if (commentPageIndex !== pageIndex) continue;
|
|
306
|
+
const annotation = buildCommentAnnotation({
|
|
307
|
+
document,
|
|
308
|
+
comment,
|
|
309
|
+
page
|
|
310
|
+
});
|
|
311
|
+
if (annotation.isErr()) return Result.err(annotation.error);
|
|
312
|
+
annotationRefs.push(annotation.value);
|
|
313
|
+
}
|
|
279
314
|
document.define(pageRef, pdfDict([
|
|
280
315
|
["Type", pdfName("Page")],
|
|
281
316
|
["Parent", pagesRef],
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { EMPHASIS_MARK_VALUES, FIELD_TYPE_VALUES, FONT_HINT_VALUES, FONT_THEME_VALUES, HIGHLIGHT_COLOR_VALUES, IMAGE_HORIZONTAL_ALIGNMENT_VALUES, IMAGE_HORIZONTAL_RELATIVE_TO_VALUES, IMAGE_VERTICAL_ALIGNMENT_VALUES, IMAGE_VERTICAL_RELATIVE_TO_VALUES, IMAGE_WRAP_TEXT_VALUES, IMAGE_WRAP_TYPE_VALUES, LINE_SPACING_RULE_VALUES, NUMBER_FORMAT_VALUES, OUTLINE_STYLE_ATTR_VALUES, PARAGRAPH_ALIGNMENT_VALUES, POSITIONAL_TAB_ALIGNMENT_VALUES, POSITIONAL_TAB_LEADER_VALUES, POSITIONAL_TAB_RELATIVE_TO_VALUES, SDT_LOCK_VALUES, SDT_TYPE_VALUES, SHADING_PATTERN_VALUES, TABLE_CELL_TEXT_DIRECTION_VALUES, TABLE_CELL_VERTICAL_ALIGNMENT_VALUES, TABLE_JUSTIFICATION_VALUES, TABLE_ROW_HEIGHT_RULE_VALUES, TABLE_WIDTH_TYPE_VALUES, TAB_LEADER_VALUES, TAB_STOP_ALIGNMENT_VALUES, TEXT_EFFECT_VALUES, THEME_COLOR_SLOT_VALUES, UNDERLINE_STYLE_VALUES } from "../../types/documentEnumValues.js";
|
|
2
|
+
import { canonicalJson } from "../../utils/canonicalJson.js";
|
|
2
3
|
import { normalizeHorizontalScalePercent } from "../../utils/horizontalScale.js";
|
|
3
4
|
import { isParagraphDirection } from "../paragraphDirection.js";
|
|
4
5
|
import { COMPLEX_SCRIPT_RUN_PROPERTY_KEYS, RUN_FORMATTING_BOOLEAN_PROPERTIES, RUN_FORMATTING_VALUE_PROPERTIES, TRACKED_CHANGE_PROVENANCE_VALUES } from "../schema/marks.js";
|
|
@@ -241,6 +242,7 @@ const readParagraphAttrs = (node) => {
|
|
|
241
242
|
optionalRecord(attrs, "spacingFromDocDefaults", "paragraph.attrs.spacingFromDocDefaults", issues);
|
|
242
243
|
optionalRecord(attrs, "spacingFromImplicitDefaultStyle", "paragraph.attrs.spacingFromImplicitDefaultStyle", issues);
|
|
243
244
|
optionalTextFormatting(attrs, "defaultTextFormatting", "paragraph.attrs.defaultTextFormatting", issues);
|
|
245
|
+
optionalTextFormatting(attrs, "_tableRunFormatting", "paragraph.attrs._tableRunFormatting", issues);
|
|
244
246
|
optionalRecord(attrs, "numPr", "paragraph.attrs.numPr", issues);
|
|
245
247
|
validateNumPr(attrs["numPr"], issues);
|
|
246
248
|
optionalBookmarkArray(attrs["bookmarks"], issues);
|
|
@@ -517,6 +519,7 @@ const readTextBoxAttrs = (node) => {
|
|
|
517
519
|
optionalNumber(attrs, "width", "textBox.attrs.width", issues);
|
|
518
520
|
optionalNumber(attrs, "height", "textBox.attrs.height", issues);
|
|
519
521
|
optionalOneOf(attrs, "autoFit", "textBox.attrs.autoFit", issues, TEXT_BOX_AUTO_FIT_VALUES);
|
|
522
|
+
optionalWordArt(attrs, "wordArt", "textBox.attrs.wordArt", issues);
|
|
520
523
|
optionalOneOf(attrs, "textWrap", "textBox.attrs.textWrap", issues, TEXT_BOX_TEXT_WRAP_VALUES);
|
|
521
524
|
optionalString(attrs, "textBoxId", "textBox.attrs.textBoxId", issues);
|
|
522
525
|
optionalString(attrs, "fillColor", "textBox.attrs.fillColor", issues);
|
|
@@ -814,7 +817,24 @@ const mergeNodeAttrs = (node, readAttrs, label, patch) => {
|
|
|
814
817
|
for (const [key, value] of Object.entries(mergedAttrs)) if (value !== void 0) nextAttrs[key] = value;
|
|
815
818
|
return nextAttrs;
|
|
816
819
|
};
|
|
817
|
-
const
|
|
820
|
+
const IMAGE_RESOURCE_ATTRS = /* @__PURE__ */ new Set([
|
|
821
|
+
"rId",
|
|
822
|
+
"_docxRawXml",
|
|
823
|
+
"_docxRawXmlMode",
|
|
824
|
+
"_docxObjectPreview"
|
|
825
|
+
]);
|
|
826
|
+
const imageAttrValuesEqual = (left, right) => {
|
|
827
|
+
if (left === right) return true;
|
|
828
|
+
if ((left === void 0 || left === null) && (right === void 0 || right === null)) return true;
|
|
829
|
+
return canonicalJson(left) === canonicalJson(right);
|
|
830
|
+
};
|
|
831
|
+
const mergeImageAttrs = (node, patch) => {
|
|
832
|
+
const current = expectImageAttrs(node);
|
|
833
|
+
const merged = mergeNodeAttrs(node, readImageAttrs, "image attrs", patch);
|
|
834
|
+
if (!Object.entries(patch).some(([key, value]) => !IMAGE_RESOURCE_ATTRS.has(key) && !imageAttrValuesEqual(Reflect.get(current, key), value)) || current._docxRawXml === void 0 || current._docxRawXmlMode === DRAWING_RAW_XML_MODES.PRESERVE_ONLY) return merged;
|
|
835
|
+
const { _docxRawXml: _discardedRawXml, ...editableAttrs } = merged;
|
|
836
|
+
return editableAttrs;
|
|
837
|
+
};
|
|
818
838
|
const mergeParagraphAttrs = (node, patch) => mergeNodeAttrs(node, readParagraphAttrs, "paragraph attrs", patch);
|
|
819
839
|
const mergeTableAttrs = (node, patch) => mergeNodeAttrs(node, readTableAttrs, "table attrs", patch);
|
|
820
840
|
const mergeTableRowAttrs = (node, patch) => mergeNodeAttrs(node, readTableRowAttrs, "table row attrs", patch);
|
|
@@ -1528,6 +1548,40 @@ const optionalNestedRecord = (attrs, key, path, issues, validate) => {
|
|
|
1528
1548
|
}
|
|
1529
1549
|
validate(value, path, issues);
|
|
1530
1550
|
};
|
|
1551
|
+
const optionalWordArt = (attrs, key, path, issues) => {
|
|
1552
|
+
const value = attrs[key];
|
|
1553
|
+
if (value === void 0 || value === null) return;
|
|
1554
|
+
if (!isRecord(value)) {
|
|
1555
|
+
issues.push({
|
|
1556
|
+
path,
|
|
1557
|
+
message: "Expected an object."
|
|
1558
|
+
});
|
|
1559
|
+
return;
|
|
1560
|
+
}
|
|
1561
|
+
optionalString(value, "preset", `${path}.preset`, issues);
|
|
1562
|
+
optionalBoolean(value, "fromWordArt", `${path}.fromWordArt`, issues);
|
|
1563
|
+
const adjustments = value["adjustments"];
|
|
1564
|
+
if (adjustments === void 0 || adjustments === null) return;
|
|
1565
|
+
if (!Array.isArray(adjustments)) {
|
|
1566
|
+
issues.push({
|
|
1567
|
+
path: `${path}.adjustments`,
|
|
1568
|
+
message: "Expected an array."
|
|
1569
|
+
});
|
|
1570
|
+
return;
|
|
1571
|
+
}
|
|
1572
|
+
for (const [index, adjustment] of adjustments.entries()) {
|
|
1573
|
+
const adjustmentPath = `${path}.adjustments[${index}]`;
|
|
1574
|
+
if (!isRecord(adjustment)) {
|
|
1575
|
+
issues.push({
|
|
1576
|
+
path: adjustmentPath,
|
|
1577
|
+
message: "Expected an object."
|
|
1578
|
+
});
|
|
1579
|
+
continue;
|
|
1580
|
+
}
|
|
1581
|
+
optionalString(adjustment, "name", `${adjustmentPath}.name`, issues);
|
|
1582
|
+
optionalString(adjustment, "formula", `${adjustmentPath}.formula`, issues);
|
|
1583
|
+
}
|
|
1584
|
+
};
|
|
1531
1585
|
const PARAGRAPH_FORMATTING_BOOLEAN_KEYS = [
|
|
1532
1586
|
"bidi",
|
|
1533
1587
|
"kinsoku",
|