@stll/folio-core 0.33.2 → 0.35.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 +137 -23
- package/dist/ai-edits/snapshot.d.ts +3 -1
- package/dist/ai-edits/snapshot.js +44 -4
- package/dist/ai-edits/types.d.ts +6 -6
- package/dist/compare/compare.js +15 -3
- package/dist/compare/formatting.d.ts +1 -1
- package/dist/compare/formatting.js +38 -9
- package/dist/compare/verification.js +18 -1
- package/dist/controller/headerFooterEditorManager.js +11 -9
- package/dist/controller/layoutPipeline.js +24 -3
- package/dist/display-list/build/watermarkPrimitives.js +15 -3
- package/dist/document-operations.js +30 -3
- package/dist/docx/headerFooterParser.js +8 -14
- package/dist/docx/paragraphParser.js +45 -0
- package/dist/docx/serializer/headerFooterSerializer.js +21 -2
- package/dist/docx/serializer/paragraphSerializer.d.ts +1 -1
- package/dist/docx/serializer/paragraphSerializer.js +19 -9
- package/dist/docx/settingsParser.js +3 -0
- package/dist/docx/watermarkParser.d.ts +2 -4
- package/dist/docx/watermarkParser.js +4 -6
- package/dist/headless-layout.js +14 -2
- package/dist/layout-bridge/convert/footnoteLayout.d.ts +2 -2
- package/dist/layout-bridge/convert/footnoteLayout.js +23 -10
- package/dist/layout-bridge/convert/headerFooterLayout.js +13 -2
- package/dist/layout-bridge/convert/toFlowBlocks.js +96 -13
- package/dist/layout-engine/index.js +11 -4
- package/dist/layout-engine/justifiedLineFit.d.ts +4 -4
- package/dist/layout-engine/justifiedLineFit.js +4 -4
- package/dist/layout-engine/measure/lineBreakProvider.js +1 -0
- package/dist/layout-engine/measure/measureBlocks.js +1 -1
- package/dist/layout-engine/measure/measureParagraph.js +29 -29
- package/dist/layout-painter/renderPage.js +6 -1
- package/dist/layout-painter/renderParagraph.js +18 -8
- package/dist/layout-painter/renderWatermark.js +11 -4
- package/dist/prosemirror/attrs/index.js +11 -0
- package/dist/prosemirror/commands/comments.js +22 -2
- package/dist/prosemirror/conversion/fromProseDoc.js +32 -5
- package/dist/prosemirror/conversion/toProseDoc.js +49 -11
- package/dist/prosemirror/extensions/core/DocExtension.js +7 -1
- package/dist/prosemirror/extensions/core/ParagraphExtension.js +1 -0
- package/dist/prosemirror/extensions/marks/RunFormattingOverrideExtension.js +1 -0
- package/dist/prosemirror/plugins/templateDirectives.d.ts +14 -7
- package/dist/prosemirror/plugins/templateDirectives.js +26 -19
- package/dist/prosemirror/plugins/templateSlashMenu.js +2 -2
- package/dist/prosemirror/schema/marks.d.ts +2 -8
- package/dist/utils/fontResolver.js +51 -0
- package/dist/utils/formatToStyle.js +41 -1
- package/dist/watermark/index.js +7 -0
- package/package.json +3 -3
package/dist/ai-edits/apply.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { expectParagraphAttrs, expectRunPropertyChangeMarkAttrs } from "../prosemirror/attrs/index.js";
|
|
1
|
+
import { expectParagraphAttrs, expectRunFormattingOverrideMarkAttrs, expectRunPropertyChangeMarkAttrs } from "../prosemirror/attrs/index.js";
|
|
2
2
|
import { PPR_CHANGE_SCOPED_ATTR_KEYS } from "../prosemirror/commands/propertyChangeScope.js";
|
|
3
3
|
import { addedBreakCarrierBefore, finalParagraphsOf, paragraphEndsItsContainer } from "../prosemirror/containerFinalParagraph.js";
|
|
4
4
|
import { marksToTextFormatting } from "../prosemirror/conversion/fromProseDoc.js";
|
|
@@ -154,24 +154,132 @@ const REPLACEMENT_BACKGROUND_CLEAR_FORMATTING = {
|
|
|
154
154
|
highlight: false,
|
|
155
155
|
runShading: false
|
|
156
156
|
};
|
|
157
|
-
const
|
|
158
|
-
|
|
159
|
-
|
|
157
|
+
const INLINE_FORMATTING_MARK_NAMES = {
|
|
158
|
+
bold: "bold",
|
|
159
|
+
italic: "italic",
|
|
160
|
+
underline: "underline",
|
|
161
|
+
strike: "strike",
|
|
162
|
+
fontFamily: "fontFamily",
|
|
163
|
+
fontSizePt: "fontSize",
|
|
164
|
+
color: "textColor",
|
|
165
|
+
highlight: "highlight",
|
|
166
|
+
runShading: "runShading"
|
|
167
|
+
};
|
|
168
|
+
const DIRECT_FONT_PROPERTIES = [
|
|
169
|
+
"fontFamily",
|
|
170
|
+
"fontSize",
|
|
171
|
+
"color"
|
|
172
|
+
];
|
|
173
|
+
const formattingMarkName = (property) => {
|
|
174
|
+
if (!Object.hasOwn(INLINE_FORMATTING_MARK_NAMES, property)) return null;
|
|
175
|
+
const name = Reflect.get(INLINE_FORMATTING_MARK_NAMES, property);
|
|
176
|
+
return typeof name === "string" ? name : null;
|
|
177
|
+
};
|
|
178
|
+
const formattingMarkAttrs = (property, value) => {
|
|
179
|
+
switch (property) {
|
|
180
|
+
case "underline": return { style: "single" };
|
|
181
|
+
case "fontFamily": return typeof value === "string" ? {
|
|
182
|
+
ascii: value,
|
|
183
|
+
hAnsi: value
|
|
184
|
+
} : null;
|
|
185
|
+
case "fontSizePt": return typeof value === "number" ? { size: value * 2 } : null;
|
|
186
|
+
case "color": return typeof value === "string" ? { rgb: value.replace(/^#/u, "").toUpperCase() } : null;
|
|
187
|
+
default: return {};
|
|
188
|
+
}
|
|
189
|
+
};
|
|
190
|
+
const applyInlineFormatting = ({ tr, schema, from, to, formatting, includedProperties }) => {
|
|
191
|
+
for (const [property, value] of Object.entries(formatting)) {
|
|
192
|
+
if (includedProperties && !includedProperties.includes(property)) continue;
|
|
193
|
+
const markName = formattingMarkName(property);
|
|
194
|
+
const markType = markName ? schema.marks[markName] : void 0;
|
|
160
195
|
if (!markType) continue;
|
|
161
|
-
if (
|
|
162
|
-
|
|
196
|
+
if (value !== false && value !== null) {
|
|
197
|
+
const attrs = formattingMarkAttrs(property, value);
|
|
198
|
+
if (attrs) tr.addMark(from, to, markType.create(attrs));
|
|
163
199
|
continue;
|
|
164
200
|
}
|
|
165
201
|
tr.removeMark(from, to, markType);
|
|
166
202
|
}
|
|
203
|
+
applyDirectFontProvenance({
|
|
204
|
+
tr,
|
|
205
|
+
schema,
|
|
206
|
+
from,
|
|
207
|
+
to,
|
|
208
|
+
formatting,
|
|
209
|
+
...includedProperties ? { includedProperties } : {}
|
|
210
|
+
});
|
|
167
211
|
return tr;
|
|
168
212
|
};
|
|
169
|
-
const
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
213
|
+
const applyDirectFontProvenance = ({ tr, schema, from, to, formatting, includedProperties }) => {
|
|
214
|
+
if ("highlight" in formatting) return;
|
|
215
|
+
const changed = [
|
|
216
|
+
[
|
|
217
|
+
"fontFamily",
|
|
218
|
+
"fontFamily",
|
|
219
|
+
formatting.fontFamily
|
|
220
|
+
],
|
|
221
|
+
[
|
|
222
|
+
"fontSize",
|
|
223
|
+
"fontSizePt",
|
|
224
|
+
formatting.fontSizePt
|
|
225
|
+
],
|
|
226
|
+
[
|
|
227
|
+
"color",
|
|
228
|
+
"color",
|
|
229
|
+
formatting.color
|
|
230
|
+
]
|
|
231
|
+
].filter(([, inputProperty, value]) => value !== void 0 && (!includedProperties || includedProperties.includes(inputProperty)));
|
|
232
|
+
const markType = schema.marks["runFormattingOverride"];
|
|
233
|
+
if (!markType || changed.length === 0) return;
|
|
234
|
+
tr.doc.nodesBetween(from, to, (node, pos) => {
|
|
235
|
+
if (!node.isText) return;
|
|
236
|
+
const segmentFrom = Math.max(from, pos);
|
|
237
|
+
const segmentTo = Math.min(to, pos + node.nodeSize);
|
|
238
|
+
const existing = node.marks.find((mark) => mark.type === markType);
|
|
239
|
+
const attrs = existing ? expectRunFormattingOverrideMarkAttrs(existing) : {};
|
|
240
|
+
const directFontProperties = new Set(attrs.directFontProperties);
|
|
241
|
+
for (const [property, , value] of changed) if (value === null) directFontProperties.delete(property);
|
|
242
|
+
else directFontProperties.add(property);
|
|
243
|
+
tr.removeMark(segmentFrom, segmentTo, markType);
|
|
244
|
+
const nextAttrs = {
|
|
245
|
+
...attrs,
|
|
246
|
+
directFontProperties: DIRECT_FONT_PROPERTIES.filter((property) => directFontProperties.has(property))
|
|
247
|
+
};
|
|
248
|
+
if (Object.entries(nextAttrs).some(([key, value]) => key === "directFontProperties" ? Array.isArray(value) && value.length > 0 : value !== null && value !== void 0)) tr.addMark(segmentFrom, segmentTo, markType.create(nextAttrs));
|
|
249
|
+
});
|
|
250
|
+
};
|
|
251
|
+
const directFontPropertyForFormattingProperty = (property) => {
|
|
252
|
+
switch (property) {
|
|
253
|
+
case "fontFamily": return "fontFamily";
|
|
254
|
+
case "fontSizePt": return "fontSize";
|
|
255
|
+
case "color": return "color";
|
|
256
|
+
default: return null;
|
|
257
|
+
}
|
|
258
|
+
};
|
|
259
|
+
const formattingPropertyWouldChange = (marks, property, value) => {
|
|
260
|
+
const markName = formattingMarkName(property);
|
|
261
|
+
const mark = marks.find((candidate) => candidate.type.name === markName);
|
|
262
|
+
const directFontProperty = directFontPropertyForFormattingProperty(property);
|
|
263
|
+
if (directFontProperty) {
|
|
264
|
+
const overrideMark = marks.find((candidate) => candidate.type.name === "runFormattingOverride");
|
|
265
|
+
const isDirect = overrideMark ? (expectRunFormattingOverrideMarkAttrs(overrideMark).directFontProperties ?? []).includes(directFontProperty) : false;
|
|
266
|
+
if (value === false || value === null) return isDirect;
|
|
267
|
+
if (!isDirect) return true;
|
|
268
|
+
} else if (value === false || value === null) return mark !== void 0;
|
|
269
|
+
if (property === "underline") return mark?.attrs["style"] !== "single";
|
|
270
|
+
if (property === "fontFamily") return mark?.attrs["ascii"] !== value || mark?.attrs["hAnsi"] !== value;
|
|
271
|
+
if (property === "fontSizePt") return Number(mark?.attrs["size"]) !== Number(value) * 2;
|
|
272
|
+
if (property === "color") {
|
|
273
|
+
const current = mark?.attrs["rgb"];
|
|
274
|
+
return (typeof current === "string" ? current.replace(/^#/u, "").toUpperCase() : null) !== String(value).replace(/^#/u, "").toUpperCase();
|
|
275
|
+
}
|
|
173
276
|
return mark === void 0;
|
|
174
|
-
}
|
|
277
|
+
};
|
|
278
|
+
const formattingChangesForMarks = (marks, formatting) => {
|
|
279
|
+
const changes = [];
|
|
280
|
+
for (const [property, value] of Object.entries(formatting)) if (formattingPropertyWouldChange(marks, property, value)) changes.push(property);
|
|
281
|
+
return changes;
|
|
282
|
+
};
|
|
175
283
|
const clearReplacementBackground = ({ tr, schema, from, to, mode, revisionId, author, date, initials, suggestionId = null }) => {
|
|
176
284
|
if (![schema.marks["highlight"], schema.marks["runShading"]].some((markType) => markType !== void 0 && tr.doc.rangeHasMark(from, to, markType))) return tr;
|
|
177
285
|
if (mode === "direct") return applyInlineFormatting({
|
|
@@ -200,7 +308,9 @@ const applyTrackedInlineFormatting = ({ tr, schema, doc, from, to, formatting, r
|
|
|
200
308
|
if (!propertyChangeType) return tr;
|
|
201
309
|
const segments = [];
|
|
202
310
|
doc.nodesBetween(from, to, (node, pos) => {
|
|
203
|
-
if (!node.isText
|
|
311
|
+
if (!node.isText) return;
|
|
312
|
+
const includedProperties = formattingChangesForMarks(node.marks, formatting);
|
|
313
|
+
if (includedProperties.length === 0) return;
|
|
204
314
|
const segmentFrom = Math.max(from, pos);
|
|
205
315
|
const segmentTo = Math.min(to, pos + node.nodeSize);
|
|
206
316
|
const previousFormatting = marksToTextFormatting(node.marks);
|
|
@@ -219,6 +329,7 @@ const applyTrackedInlineFormatting = ({ tr, schema, doc, from, to, formatting, r
|
|
|
219
329
|
segments.push({
|
|
220
330
|
from: segmentFrom,
|
|
221
331
|
to: segmentTo,
|
|
332
|
+
includedProperties,
|
|
222
333
|
changes: [...existingChanges, change]
|
|
223
334
|
});
|
|
224
335
|
});
|
|
@@ -227,17 +338,20 @@ const applyTrackedInlineFormatting = ({ tr, schema, doc, from, to, formatting, r
|
|
|
227
338
|
provenance: "suggested",
|
|
228
339
|
suggestionId
|
|
229
340
|
};
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
341
|
+
for (const segment of segments) {
|
|
342
|
+
applyInlineFormatting({
|
|
343
|
+
tr,
|
|
344
|
+
schema,
|
|
345
|
+
from: segment.from,
|
|
346
|
+
to: segment.to,
|
|
347
|
+
formatting,
|
|
348
|
+
includedProperties: segment.includedProperties
|
|
349
|
+
});
|
|
350
|
+
tr.addMark(segment.from, segment.to, propertyChangeType.create({
|
|
351
|
+
changes: segment.changes,
|
|
352
|
+
...suggestionAttrs
|
|
353
|
+
}));
|
|
354
|
+
}
|
|
241
355
|
return tr;
|
|
242
356
|
};
|
|
243
357
|
/**
|
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
import { FolioAIBlock, FolioAIEditSnapshot, FolioAITextRangeHandle } from "./types.js";
|
|
2
2
|
import { Node } from "prosemirror-model";
|
|
3
3
|
//#region src/ai-edits/snapshot.d.ts
|
|
4
|
+
/** @internal Numbering references collected during the snapshot's document walk. */
|
|
5
|
+
declare const numberingReferenceKeysOf: (snapshot: FolioAIEditSnapshot) => readonly string[];
|
|
4
6
|
declare const normalizeFolioAIBlockText: (text: string) => string;
|
|
5
7
|
/**
|
|
6
8
|
* Whether a block carries text a reader would see.
|
|
@@ -70,4 +72,4 @@ type FolioStoryTable = {
|
|
|
70
72
|
declare const folioStoryTables: (doc: Node) => FolioStoryTable[];
|
|
71
73
|
declare const createFolioAIEditSnapshot: (doc: Node) => FolioAIEditSnapshot;
|
|
72
74
|
//#endregion
|
|
73
|
-
export { FolioStoryTable, createFolioAIEditSnapshot, createFolioAITextRangeHandle, folioStoryTables, hashFolioAIBlockText, isFolioAIContentBlock, isHiddenTableRow, normalizeFolioAIBlockText, trailingBodyBlockId };
|
|
75
|
+
export { FolioStoryTable, createFolioAIEditSnapshot, createFolioAITextRangeHandle, folioStoryTables, hashFolioAIBlockText, isFolioAIContentBlock, isHiddenTableRow, normalizeFolioAIBlockText, numberingReferenceKeysOf, trailingBodyBlockId };
|
|
@@ -1,7 +1,12 @@
|
|
|
1
|
+
import { expectRunFormattingOverrideMarkAttrs } from "../prosemirror/attrs/index.js";
|
|
1
2
|
import { deriveBlankBlockId, deriveBlockId } from "../types/block-id.js";
|
|
2
3
|
import { buildCleanBlockText } from "./clean-text.js";
|
|
4
|
+
import { panic } from "better-result";
|
|
3
5
|
import { TableMap } from "prosemirror-tables";
|
|
4
6
|
//#region src/ai-edits/snapshot.ts
|
|
7
|
+
const numberingReferenceKeysBySnapshot = /* @__PURE__ */ new WeakMap();
|
|
8
|
+
/** @internal Numbering references collected during the snapshot's document walk. */
|
|
9
|
+
const numberingReferenceKeysOf = (snapshot) => numberingReferenceKeysBySnapshot.get(snapshot) ?? panic("A numbering census was requested for a snapshot that did not record one");
|
|
5
10
|
const normalizeFolioAIBlockText = (text) => text.replace(/\s+/gu, " ").trim();
|
|
6
11
|
/**
|
|
7
12
|
* Whether a block carries text a reader would see.
|
|
@@ -125,6 +130,7 @@ const createFolioAIEditSnapshot = (doc) => {
|
|
|
125
130
|
const draftBlocks = [];
|
|
126
131
|
const hashCounts = /* @__PURE__ */ new Map();
|
|
127
132
|
const usedBlockIds = /* @__PURE__ */ new Set();
|
|
133
|
+
const numberingReferenceKeys = /* @__PURE__ */ new Set();
|
|
128
134
|
const tableIndexByStart = new Map(folioStoryTables(doc).map(({ start, index }) => [start, index]));
|
|
129
135
|
const path = [];
|
|
130
136
|
let blockIndex = 0;
|
|
@@ -170,6 +176,8 @@ const createFolioAIEditSnapshot = (doc) => {
|
|
|
170
176
|
const displayLabel = getDisplayLabel(node);
|
|
171
177
|
const styleId = getStyleId(node);
|
|
172
178
|
const listLevel = getListLevel(node);
|
|
179
|
+
const numberingReferenceKey = getNumberingReferenceKey(node);
|
|
180
|
+
if (numberingReferenceKey) numberingReferenceKeys.add(numberingReferenceKey);
|
|
173
181
|
const previewRuns = getPreviewRuns(node);
|
|
174
182
|
const table = getTableLocation({
|
|
175
183
|
path,
|
|
@@ -208,10 +216,12 @@ const createFolioAIEditSnapshot = (doc) => {
|
|
|
208
216
|
hashOccurrenceCount: hashCounts.get(draft.anchor.textHash) ?? 0
|
|
209
217
|
};
|
|
210
218
|
}
|
|
211
|
-
|
|
219
|
+
const snapshot = {
|
|
212
220
|
blocks,
|
|
213
221
|
anchors
|
|
214
222
|
};
|
|
223
|
+
numberingReferenceKeysBySnapshot.set(snapshot, [...numberingReferenceKeys]);
|
|
224
|
+
return snapshot;
|
|
215
225
|
};
|
|
216
226
|
const getBlockKind = (node, headingLevel) => {
|
|
217
227
|
const listMarker = node.attrs["listMarker"];
|
|
@@ -240,6 +250,15 @@ const getListLevel = (node) => {
|
|
|
240
250
|
const { ilvl } = numPr;
|
|
241
251
|
return typeof ilvl === "number" && Number.isInteger(ilvl) && ilvl >= 0 ? ilvl : void 0;
|
|
242
252
|
};
|
|
253
|
+
const getNumberingReferenceKey = (node) => {
|
|
254
|
+
const numPr = node.attrs["numPr"];
|
|
255
|
+
if (typeof numPr !== "object" || numPr === null || !("numId" in numPr)) return null;
|
|
256
|
+
const { numId } = numPr;
|
|
257
|
+
if (typeof numId !== "number" || !Number.isInteger(numId) || numId <= 0) return null;
|
|
258
|
+
const level = "ilvl" in numPr ? numPr.ilvl : void 0;
|
|
259
|
+
if (level !== void 0 && (typeof level !== "number" || !Number.isInteger(level) || level < 0)) return null;
|
|
260
|
+
return `${String(numId)}:${String(level ?? 0)}`;
|
|
261
|
+
};
|
|
243
262
|
const getStyleId = (node) => {
|
|
244
263
|
const styleId = node.attrs["styleId"];
|
|
245
264
|
return typeof styleId === "string" && styleId.length > 0 ? styleId : void 0;
|
|
@@ -252,14 +271,16 @@ const getPreviewRuns = (node) => {
|
|
|
252
271
|
if (!child.isText || child.text === void 0) return true;
|
|
253
272
|
if (child.marks.some((mark) => mark.type.name === DELETION_MARK)) return false;
|
|
254
273
|
const style = getPreviewRunStyle(child.marks, defaultStyle);
|
|
274
|
+
const directFormatting = getDirectPreviewRunStyle(child.marks, defaultStyle);
|
|
255
275
|
const previous = runs.at(-1);
|
|
256
|
-
if (previous && samePreviewRunStyle(previous, style)) {
|
|
276
|
+
if (previous && samePreviewRunStyle(previous, style) && sameDirectFormatting(previous.directFormatting, directFormatting)) {
|
|
257
277
|
previous.text += child.text;
|
|
258
278
|
return false;
|
|
259
279
|
}
|
|
260
280
|
runs.push({
|
|
261
281
|
text: child.text,
|
|
262
|
-
...style
|
|
282
|
+
...style,
|
|
283
|
+
...!isEmptyPreviewRunStyle(directFormatting) && { directFormatting }
|
|
263
284
|
});
|
|
264
285
|
return false;
|
|
265
286
|
});
|
|
@@ -310,6 +331,23 @@ const getPreviewRunStyle = (marks, defaultStyle) => {
|
|
|
310
331
|
}
|
|
311
332
|
return style;
|
|
312
333
|
};
|
|
334
|
+
const getDirectPreviewRunStyle = (marks, inheritedStyle) => {
|
|
335
|
+
const markedStyle = getPreviewRunStyle(marks, {});
|
|
336
|
+
const directStyle = {};
|
|
337
|
+
const overrideMark = marks.find(({ type }) => type.name === "runFormattingOverride");
|
|
338
|
+
const hasFormattingProvenance = overrideMark !== void 0 || marks.some(({ type }) => type.name === "characterStyle");
|
|
339
|
+
const directFontProperties = overrideMark ? expectRunFormattingOverrideMarkAttrs(overrideMark).directFontProperties : void 0;
|
|
340
|
+
for (const property of [
|
|
341
|
+
"bold",
|
|
342
|
+
"italic",
|
|
343
|
+
"underline",
|
|
344
|
+
"strike"
|
|
345
|
+
]) if (Boolean(markedStyle[property]) !== Boolean(inheritedStyle[property])) directStyle[property] = Boolean(markedStyle[property]);
|
|
346
|
+
if (markedStyle.fontFamily !== void 0 && (hasFormattingProvenance ? directFontProperties?.includes("fontFamily") : markedStyle.fontFamily !== inheritedStyle.fontFamily)) directStyle.fontFamily = markedStyle.fontFamily;
|
|
347
|
+
if (markedStyle.fontSizePt !== void 0 && (hasFormattingProvenance ? directFontProperties?.includes("fontSize") : markedStyle.fontSizePt !== inheritedStyle.fontSizePt)) directStyle.fontSizePt = markedStyle.fontSizePt;
|
|
348
|
+
if (markedStyle.color !== void 0 && (hasFormattingProvenance ? directFontProperties?.includes("color") : markedStyle.color !== inheritedStyle.color)) directStyle.color = markedStyle.color;
|
|
349
|
+
return directStyle;
|
|
350
|
+
};
|
|
313
351
|
const getBooleanTextFormatting = (formatting) => ({
|
|
314
352
|
...Reflect.get(formatting, "bold") === true && { bold: true },
|
|
315
353
|
...Reflect.get(formatting, "italic") === true && { italic: true },
|
|
@@ -352,6 +390,8 @@ const getColorFromAttrs = (attrs) => {
|
|
|
352
390
|
return `#${rgb}`;
|
|
353
391
|
};
|
|
354
392
|
const samePreviewRunStyle = (run, style) => run.bold === style.bold && run.italic === style.italic && run.underline === style.underline && run.strike === style.strike && run.fontFamily === style.fontFamily && run.fontSizePt === style.fontSizePt && run.color === style.color;
|
|
393
|
+
const isEmptyPreviewRunStyle = ({ bold, italic, underline, strike, fontFamily, fontSizePt, color }) => bold === void 0 && italic === void 0 && underline === void 0 && strike === void 0 && fontFamily === void 0 && fontSizePt === void 0 && color === void 0;
|
|
394
|
+
const sameDirectFormatting = (left, right) => left === void 0 && isEmptyPreviewRunStyle(right) || left !== void 0 && left.bold === right.bold && left.italic === right.italic && left.underline === right.underline && left.strike === right.strike && left.fontFamily === right.fontFamily && left.fontSizePt === right.fontSizePt && left.color === right.color;
|
|
355
395
|
const isUnstyledPreviewRun = ({ bold, italic, underline, strike, fontFamily, fontSizePt, color }) => bold === void 0 && italic === void 0 && underline === void 0 && strike === void 0 && fontFamily === void 0 && fontSizePt === void 0 && color === void 0;
|
|
356
396
|
//#endregion
|
|
357
|
-
export { createFolioAIEditSnapshot, createFolioAITextRangeHandle, folioStoryTables, hashFolioAIBlockText, isFolioAIContentBlock, isHiddenTableRow, normalizeFolioAIBlockText, trailingBodyBlockId };
|
|
397
|
+
export { createFolioAIEditSnapshot, createFolioAITextRangeHandle, folioStoryTables, hashFolioAIBlockText, isFolioAIContentBlock, isHiddenTableRow, normalizeFolioAIBlockText, numberingReferenceKeysOf, trailingBodyBlockId };
|
package/dist/ai-edits/types.d.ts
CHANGED
|
@@ -1,5 +1,10 @@
|
|
|
1
1
|
//#region src/ai-edits/types.d.ts
|
|
2
2
|
type FolioAIBlockKind = "heading" | "listItem" | "paragraph";
|
|
3
|
+
type FolioAIInlineFormatting = Partial<Record<"bold" | "italic" | "underline" | "strike", boolean>> & {
|
|
4
|
+
fontFamily?: string | null;
|
|
5
|
+
fontSizePt?: number | null;
|
|
6
|
+
color?: string | null;
|
|
7
|
+
};
|
|
3
8
|
type FolioAIBlockPreviewRun = {
|
|
4
9
|
text: string;
|
|
5
10
|
bold?: boolean;
|
|
@@ -9,6 +14,7 @@ type FolioAIBlockPreviewRun = {
|
|
|
9
14
|
fontFamily?: string;
|
|
10
15
|
fontSizePt?: number;
|
|
11
16
|
color?: string;
|
|
17
|
+
directFormatting?: FolioAIInlineFormatting;
|
|
12
18
|
};
|
|
13
19
|
/**
|
|
14
20
|
* Where a block sits inside its innermost enclosing table. Every index is
|
|
@@ -165,12 +171,6 @@ type FolioDocumentNavigationTarget = {
|
|
|
165
171
|
story: "main";
|
|
166
172
|
blockId: string;
|
|
167
173
|
} | FolioAITextRangeHandle;
|
|
168
|
-
type FolioAIInlineFormatting = {
|
|
169
|
-
bold?: boolean;
|
|
170
|
-
italic?: boolean;
|
|
171
|
-
underline?: boolean;
|
|
172
|
-
strike?: boolean;
|
|
173
|
-
};
|
|
174
174
|
/**
|
|
175
175
|
* A party in an `insertSignatureTable` op. Mirrors the
|
|
176
176
|
* `signatureTable` helper in `docx-core/legal-source/compile.ts`:
|
package/dist/compare/compare.js
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { FolioDocxReviewer } from "../ai-edits/headless.js";
|
|
2
|
+
import { numberingReferenceKeysOf } from "../ai-edits/snapshot.js";
|
|
2
3
|
import { projectTableGeometry } from "../ai-edits/table-geometry.js";
|
|
3
4
|
import "../document-operations.js";
|
|
4
5
|
import { pairFolioDocumentStories } from "../document-stories.js";
|
|
@@ -114,11 +115,12 @@ const formattingRoundTripFailure = ({ invariant, story, changes, actualBlocks, e
|
|
|
114
115
|
};
|
|
115
116
|
const numberingKey = ({ numId, level }) => `${String(numId)}:${String(level)}`;
|
|
116
117
|
const sameNumbering = (left, right) => left.format === right.format && left.levelText === right.levelText && left.start === right.start;
|
|
117
|
-
const compareNumbering = (base, target) => {
|
|
118
|
+
const compareNumbering = (base, target, referenced) => {
|
|
118
119
|
const baseLevels = new Map(base.readNumberingDefinitions().map((level) => [numberingKey(level), level]));
|
|
119
120
|
const targetLevels = new Map(target.readNumberingDefinitions().map((level) => [numberingKey(level), level]));
|
|
120
121
|
const changes = [];
|
|
121
122
|
for (const [key, before] of baseLevels) {
|
|
123
|
+
if (!referenced.has(key)) continue;
|
|
122
124
|
const after = targetLevels.get(key) ?? null;
|
|
123
125
|
if (after === null || !sameNumbering(before, after)) changes.push({
|
|
124
126
|
kind: "numbering",
|
|
@@ -128,7 +130,7 @@ const compareNumbering = (base, target) => {
|
|
|
128
130
|
after
|
|
129
131
|
});
|
|
130
132
|
}
|
|
131
|
-
for (const [key, after] of targetLevels) if (!baseLevels.has(key)) changes.push({
|
|
133
|
+
for (const [key, after] of targetLevels) if (referenced.has(key) && !baseLevels.has(key)) changes.push({
|
|
132
134
|
kind: "numbering",
|
|
133
135
|
numId: after.numId,
|
|
134
136
|
level: after.level,
|
|
@@ -162,8 +164,15 @@ const parseComparison = async (base, target, options) => {
|
|
|
162
164
|
});
|
|
163
165
|
const pairs = [];
|
|
164
166
|
const unsupported = [];
|
|
167
|
+
const referencedNumberingLevels = /* @__PURE__ */ new Set();
|
|
168
|
+
const collectNumberingReferences = (snapshot) => {
|
|
169
|
+
if (!snapshot) return;
|
|
170
|
+
for (const referenceKey of numberingReferenceKeysOf(snapshot)) referencedNumberingLevels.add(referenceKey);
|
|
171
|
+
};
|
|
165
172
|
for (const { baseStory, revisedStory: targetStory } of pairFolioDocumentStories(reviewer.listStories().map(({ handle }) => handle), targetReviewer.listStories().map(({ handle }) => handle))) {
|
|
166
173
|
if (!baseStory) {
|
|
174
|
+
if (!targetStory) panic("A story pair contained neither a base nor a target story");
|
|
175
|
+
collectNumberingReferences(targetReviewer.snapshotStory(targetStory));
|
|
167
176
|
unsupported.push({
|
|
168
177
|
reason: "story-missing-in-base",
|
|
169
178
|
baseStory: null,
|
|
@@ -172,6 +181,7 @@ const parseComparison = async (base, target, options) => {
|
|
|
172
181
|
continue;
|
|
173
182
|
}
|
|
174
183
|
if (!targetStory) {
|
|
184
|
+
collectNumberingReferences(reviewer.snapshotStory(baseStory));
|
|
175
185
|
unsupported.push({
|
|
176
186
|
reason: "story-missing-in-target",
|
|
177
187
|
baseStory,
|
|
@@ -181,6 +191,8 @@ const parseComparison = async (base, target, options) => {
|
|
|
181
191
|
}
|
|
182
192
|
const baseSnapshot = reviewer.snapshotStory(baseStory);
|
|
183
193
|
const targetSnapshot = targetReviewer.snapshotStory(targetStory);
|
|
194
|
+
collectNumberingReferences(baseSnapshot);
|
|
195
|
+
collectNumberingReferences(targetSnapshot);
|
|
184
196
|
if (!baseSnapshot || !targetSnapshot) {
|
|
185
197
|
unsupported.push({
|
|
186
198
|
reason: "story-not-editable",
|
|
@@ -208,7 +220,7 @@ const parseComparison = async (base, target, options) => {
|
|
|
208
220
|
},
|
|
209
221
|
packageDate,
|
|
210
222
|
pairs,
|
|
211
|
-
numberingChanges: compareNumbering(reviewer, targetReviewer),
|
|
223
|
+
numberingChanges: compareNumbering(reviewer, targetReviewer, referencedNumberingLevels),
|
|
212
224
|
unsupported
|
|
213
225
|
});
|
|
214
226
|
};
|
|
@@ -15,7 +15,7 @@ type InlineFormattingSegmentsOptions = {
|
|
|
15
15
|
maxSegments: number;
|
|
16
16
|
};
|
|
17
17
|
/**
|
|
18
|
-
* Segments where `targetBlock`'s
|
|
18
|
+
* Segments where `targetBlock`'s supported run formatting differs from
|
|
19
19
|
* `baseBlock`'s, for two blocks that carry the same text. Returns `null` only
|
|
20
20
|
* when the diff would exceed `maxSegments`.
|
|
21
21
|
*
|
|
@@ -1,12 +1,41 @@
|
|
|
1
|
+
import { resolveColorToHex } from "../utils/colorResolver.js";
|
|
1
2
|
//#region src/compare/formatting.ts
|
|
2
|
-
const
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
3
|
+
const normalizeInlineFormattingColor = (color) => resolveColorToHex(color === void 0 ? void 0 : { rgb: color }, null);
|
|
4
|
+
const changedStringValue = (baseEffective, targetEffective, baseAuthored, targetAuthored) => {
|
|
5
|
+
if (baseAuthored !== targetAuthored) return targetAuthored ?? null;
|
|
6
|
+
return baseEffective === targetEffective ? void 0 : targetEffective ?? null;
|
|
7
|
+
};
|
|
8
|
+
const changedNumberValue = (baseEffective, targetEffective, baseAuthored, targetAuthored) => {
|
|
9
|
+
if (baseAuthored !== targetAuthored) return targetAuthored ?? null;
|
|
10
|
+
return baseEffective === targetEffective ? void 0 : targetEffective ?? null;
|
|
11
|
+
};
|
|
12
|
+
const changedSupportedFormatting = (base, target) => {
|
|
13
|
+
const baseDirect = base.directFormatting ?? {};
|
|
14
|
+
const targetDirect = target.directFormatting ?? {};
|
|
15
|
+
const formatting = {};
|
|
16
|
+
const changedBoolean = (property) => {
|
|
17
|
+
if (baseDirect[property] !== targetDirect[property]) return targetDirect[property] === true;
|
|
18
|
+
return Boolean(base[property]) === Boolean(target[property]) ? void 0 : Boolean(target[property]);
|
|
19
|
+
};
|
|
20
|
+
for (const property of [
|
|
21
|
+
"bold",
|
|
22
|
+
"italic",
|
|
23
|
+
"underline",
|
|
24
|
+
"strike"
|
|
25
|
+
]) {
|
|
26
|
+
const changed = changedBoolean(property);
|
|
27
|
+
if (changed !== void 0) formatting[property] = changed;
|
|
28
|
+
}
|
|
29
|
+
const fontFamily = changedStringValue(base.fontFamily, target.fontFamily, baseDirect.fontFamily, targetDirect.fontFamily);
|
|
30
|
+
if (fontFamily !== void 0) formatting.fontFamily = fontFamily;
|
|
31
|
+
const fontSizePt = changedNumberValue(base.fontSizePt, target.fontSizePt, baseDirect.fontSizePt, targetDirect.fontSizePt);
|
|
32
|
+
if (fontSizePt !== void 0) formatting.fontSizePt = fontSizePt;
|
|
33
|
+
const color = changedStringValue(normalizeInlineFormattingColor(base.color), normalizeInlineFormattingColor(target.color), normalizeInlineFormattingColor(baseDirect.color ?? void 0), normalizeInlineFormattingColor(targetDirect.color ?? void 0));
|
|
34
|
+
if (color !== void 0) formatting.color = color;
|
|
35
|
+
return formatting;
|
|
36
|
+
};
|
|
37
|
+
const sameInlineFormatting = (left, right) => left.bold === right.bold && left.italic === right.italic && left.underline === right.underline && left.strike === right.strike && left.fontFamily === right.fontFamily && left.fontSizePt === right.fontSizePt && left.color === right.color;
|
|
38
|
+
const hasInlineFormatting = (formatting) => formatting.bold !== void 0 || formatting.italic !== void 0 || formatting.underline !== void 0 || formatting.strike !== void 0 || formatting.fontFamily !== void 0 || formatting.fontSizePt !== void 0 || formatting.color !== void 0;
|
|
10
39
|
/**
|
|
11
40
|
* A block's runs, or `null` when they cannot describe the block's text.
|
|
12
41
|
* Non-text inline content (a field, an image) leaves the concatenated run text
|
|
@@ -18,7 +47,7 @@ const previewRunsForBlock = (block) => {
|
|
|
18
47
|
return runs.map(({ text }) => text).join("") === block.text ? runs : null;
|
|
19
48
|
};
|
|
20
49
|
/**
|
|
21
|
-
* Segments where `targetBlock`'s
|
|
50
|
+
* Segments where `targetBlock`'s supported run formatting differs from
|
|
22
51
|
* `baseBlock`'s, for two blocks that carry the same text. Returns `null` only
|
|
23
52
|
* when the diff would exceed `maxSegments`.
|
|
24
53
|
*
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { resolveColorToHex } from "../utils/colorResolver.js";
|
|
1
2
|
import { PARAGRAPH_MARK_CHANGE_KINDS } from "@stll/docx-core/model";
|
|
2
3
|
//#region src/compare/verification.ts
|
|
3
4
|
/**
|
|
@@ -14,6 +15,7 @@ import { PARAGRAPH_MARK_CHANGE_KINDS } from "@stll/docx-core/model";
|
|
|
14
15
|
* Never a phrase of either document, because a caller may log it, put it in a
|
|
15
16
|
* report, or quote it in a review.
|
|
16
17
|
*/
|
|
18
|
+
const normalizeInlineFormattingColor = (color) => resolveColorToHex(color === void 0 ? void 0 : { rgb: color }, null);
|
|
17
19
|
/** The two directions of the round trip, each an invariant of its own. */
|
|
18
20
|
const COMPARE_VERIFICATION_INVARIANTS = Object.freeze(["accept-reproduces-target", "reject-reproduces-base"]);
|
|
19
21
|
/**
|
|
@@ -32,7 +34,22 @@ const COMPARE_VERIFICATION_CAUSES = Object.freeze([
|
|
|
32
34
|
"whitespace",
|
|
33
35
|
"text"
|
|
34
36
|
]);
|
|
35
|
-
const supportedInlineStyle = ({ bold, italic, underline, strike
|
|
37
|
+
const supportedInlineStyle = ({ bold, italic, underline, strike, fontFamily, fontSizePt, color, directFormatting }) => JSON.stringify([
|
|
38
|
+
bold === true,
|
|
39
|
+
italic === true,
|
|
40
|
+
underline === true,
|
|
41
|
+
strike === true,
|
|
42
|
+
fontFamily ?? null,
|
|
43
|
+
fontSizePt ?? null,
|
|
44
|
+
normalizeInlineFormattingColor(color) ?? null,
|
|
45
|
+
directFormatting?.bold === true,
|
|
46
|
+
directFormatting?.italic === true,
|
|
47
|
+
directFormatting?.underline === true,
|
|
48
|
+
directFormatting?.strike === true,
|
|
49
|
+
directFormatting?.fontFamily ?? null,
|
|
50
|
+
directFormatting?.fontSizePt ?? null,
|
|
51
|
+
normalizeInlineFormattingColor(directFormatting?.color ?? void 0) ?? null
|
|
52
|
+
]);
|
|
36
53
|
/** Effective supported formatting with equivalent adjacent runs normalized. */
|
|
37
54
|
const projectSupportedInlineFormatting = ({ text, previewRuns }) => {
|
|
38
55
|
const projected = [];
|
|
@@ -10,19 +10,21 @@ import { schema } from "../prosemirror/schema/index.js";
|
|
|
10
10
|
import { EditorState } from "prosemirror-state";
|
|
11
11
|
import { EditorView } from "prosemirror-view";
|
|
12
12
|
//#region src/controller/headerFooterEditorManager.ts
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
13
|
+
const DETACHED_WATERMARK_HOST = Symbol.for("stll.detachedWatermarkHost");
|
|
14
|
+
const headerFooterToProseDocWithDetachedWatermarkHost = (headerFooter, options) => {
|
|
15
|
+
return headerFooterToProseDoc(headerFooter.content.map((block, blockIndex) => {
|
|
16
|
+
if (blockIndex !== headerFooter.watermarkBlockIndex || block.type !== "paragraph") return block;
|
|
17
|
+
return {
|
|
18
|
+
...block,
|
|
19
|
+
[DETACHED_WATERMARK_HOST]: true
|
|
20
|
+
};
|
|
21
|
+
}), options);
|
|
22
|
+
};
|
|
21
23
|
const buildInitialState = (headerFooter, styles, theme, manager) => {
|
|
22
24
|
const proseDocOptions = {};
|
|
23
25
|
if (styles) proseDocOptions.styles = styles;
|
|
24
26
|
if (theme !== void 0) proseDocOptions.theme = theme;
|
|
25
|
-
const document = headerFooterToProseDoc(headerFooter.content, proseDocOptions);
|
|
27
|
+
const document = headerFooter.watermarkBlockIndex === void 0 ? headerFooterToProseDoc(headerFooter.content, proseDocOptions) : headerFooterToProseDocWithDetachedWatermarkHost(headerFooter, proseDocOptions);
|
|
26
28
|
return ensureBaseDirectionInState(ensureParaIdsInState(EditorState.create({
|
|
27
29
|
doc: document,
|
|
28
30
|
schema,
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { createDisplayListPagePainter } from "../display-list/editor/displayListPagePainter.js";
|
|
2
2
|
import { PAGE_RENDERER, displayListFurnitureFrom } from "../display-list/editor/pageRenderer.js";
|
|
3
3
|
import { resolveDocumentGridLinePitch } from "../docx/documentGrid.js";
|
|
4
|
+
import { formatOoxmlCounter } from "../docx/ooxmlCounterFormatter.js";
|
|
4
5
|
import { buildBookmarkPageMap } from "../fields/bookmarkPages.js";
|
|
5
6
|
import { buildBookmarkText } from "../fields/bookmarkText.js";
|
|
6
7
|
import { buildHeaderFooterFieldValues, fieldValuesEqual, resolveFieldValues } from "../fields/resolveFieldValues.js";
|
|
@@ -26,6 +27,17 @@ import { getMargins, getPageNumbering, getPageSize, twipsToPixels } from "../pag
|
|
|
26
27
|
import { templatePreviewValuesKey } from "../prosemirror/plugins/templatePreviewValues.js";
|
|
27
28
|
import { getDocumentWatermark } from "../watermark/index.js";
|
|
28
29
|
//#region src/controller/layoutPipeline.ts
|
|
30
|
+
const formatEndnoteTexts = (numbers, formatNumber) => {
|
|
31
|
+
const texts = /* @__PURE__ */ new Map();
|
|
32
|
+
for (const [id, displayNumber] of numbers) texts.set(id, formatNumber(displayNumber));
|
|
33
|
+
return texts;
|
|
34
|
+
};
|
|
35
|
+
/** Resolve only package-owned picture-watermark bytes for the DOM painter. */
|
|
36
|
+
const resolveWatermarkImageSrc = (document, watermark) => {
|
|
37
|
+
if (watermark?.kind !== "picture" || watermark.imageTargetExternal === true) return;
|
|
38
|
+
const target = watermark.imageTarget;
|
|
39
|
+
return target === void 0 ? void 0 : document.package.media?.get(target)?.dataUrl;
|
|
40
|
+
};
|
|
29
41
|
function bodyMarginsClearHeaderFooter({ authoredMargins, preparedHeader, preparedFooter }) {
|
|
30
42
|
const headerBottom = preparedHeader ? (authoredMargins.header ?? 0) + (preparedHeader.marginPushBottom ?? preparedHeader.height) : authoredMargins.top;
|
|
31
43
|
const footerClearance = preparedFooter ? (authoredMargins.footer ?? 0) + (preparedFooter.marginPushBottom ?? preparedFooter.height) : authoredMargins.bottom;
|
|
@@ -123,7 +135,10 @@ function runLayoutPipeline(deps, state, options = {}) {
|
|
|
123
135
|
};
|
|
124
136
|
const finalSectionDocumentGridLinePitchTwips = resolveDocumentGridLinePitch(document?.package.document.sections?.at(-1)?.properties.docGrid);
|
|
125
137
|
if (finalSectionDocumentGridLinePitchTwips !== void 0) flowOpts.finalSectionDocumentGridLinePitchTwips = finalSectionDocumentGridLinePitchTwips;
|
|
126
|
-
let newBlocks = toFlowBlocks(state.doc
|
|
138
|
+
let newBlocks = toFlowBlocks(document === null ? state.doc.type.create({
|
|
139
|
+
...state.doc.attrs,
|
|
140
|
+
_finalSectionStart: sectionProperties?.sectionStart ?? null
|
|
141
|
+
}, state.doc.content, state.doc.marks) : state.doc, flowOpts);
|
|
127
142
|
const previewState = templatePreviewValuesKey.getState(state);
|
|
128
143
|
const previewEntries = previewState?.entries ?? EMPTY_TEMPLATE_PREVIEW_ENTRIES;
|
|
129
144
|
const previewMode = previewState?.preview?.mode ?? "plain";
|
|
@@ -144,9 +159,11 @@ function runLayoutPipeline(deps, state, options = {}) {
|
|
|
144
159
|
const footnoteDisplayNumbers = documentFootnotes ? computeNoteDisplayNumbers(documentFootnotes, footnoteRefs.map((ref) => ref.footnoteId)) : void 0;
|
|
145
160
|
const documentEndnotes = document?.package.endnotes;
|
|
146
161
|
const endnoteDisplayNumbers = documentEndnotes ? computeNoteDisplayNumbers(documentEndnotes, collectEndnoteRefs(newBlocks).map((ref) => ref.endnoteId)) : void 0;
|
|
162
|
+
const endnoteNumberFormat = document?.package.document.sections?.at(-1)?.properties.endnotePr?.numFmt ?? "lowerRoman";
|
|
163
|
+
const endnoteTexts = endnoteDisplayNumbers ? formatEndnoteTexts(endnoteDisplayNumbers, (displayNumber) => formatOoxmlCounter(displayNumber, endnoteNumberFormat)) : void 0;
|
|
147
164
|
newBlocks = remapNoteMarkerText(newBlocks, {
|
|
148
165
|
...footnoteDisplayNumbers ? { footnoteNumbers: footnoteDisplayNumbers } : {},
|
|
149
|
-
...
|
|
166
|
+
...endnoteTexts ? { endnoteTexts } : {}
|
|
150
167
|
});
|
|
151
168
|
outcome.blocks = newBlocks;
|
|
152
169
|
const hfMetricsHeader = {
|
|
@@ -479,12 +496,16 @@ function runLayoutPipeline(deps, state, options = {}) {
|
|
|
479
496
|
if (document) {
|
|
480
497
|
const watermark = getDocumentWatermark(document);
|
|
481
498
|
if (watermark) renderOpts.watermark = watermark;
|
|
499
|
+
let watermarkByHeaderRId;
|
|
482
500
|
const headers = document.package.headers;
|
|
483
501
|
if (headers) {
|
|
484
|
-
|
|
502
|
+
watermarkByHeaderRId = /* @__PURE__ */ new Map();
|
|
485
503
|
for (const [rId, header] of headers) if (header.watermark) watermarkByHeaderRId.set(rId, header.watermark);
|
|
486
504
|
if (watermarkByHeaderRId.size > 0) renderOpts.watermarkByHeaderRId = watermarkByHeaderRId;
|
|
487
505
|
}
|
|
506
|
+
const pictureSources = new Set([watermark, ...watermarkByHeaderRId?.values() ?? []].filter((candidate) => candidate !== void 0).map((candidate) => resolveWatermarkImageSrc(document, candidate)));
|
|
507
|
+
const watermarkImageSrc = pictureSources.size === 1 ? [...pictureSources].at(0) : void 0;
|
|
508
|
+
if (watermarkImageSrc !== void 0) renderOpts.watermarkImageSrc = watermarkImageSrc;
|
|
488
509
|
}
|
|
489
510
|
if (pageRenderer === PAGE_RENDERER.displayList) renderOpts.paintPage = createDisplayListPagePainter({
|
|
490
511
|
layout: newLayout,
|