@embedpdf/plugin-stamp 2.15.0 → 3.0.0-next.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/LICENSE +192 -21
- package/dist/index.cjs +591 -2
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +187 -0
- package/dist/index.d.ts +187 -1
- package/dist/index.js +584 -975
- package/dist/index.js.map +1 -1
- package/package.json +23 -61
- package/dist/lib/actions.d.ts +0 -14
- package/dist/lib/defaults/name-map.d.ts +0 -2
- package/dist/lib/handlers/rubber-stamp.handler.d.ts +0 -4
- package/dist/lib/index.d.ts +0 -11
- package/dist/lib/manifest.d.ts +0 -4
- package/dist/lib/reducer.d.ts +0 -5
- package/dist/lib/stamp-plugin.d.ts +0 -54
- package/dist/lib/tools.d.ts +0 -58
- package/dist/lib/types.d.ts +0 -130
- package/dist/preact/adapter.d.ts +0 -5
- package/dist/preact/annotation.d.ts +0 -1
- package/dist/preact/core.d.ts +0 -1
- package/dist/preact/index.cjs +0 -2
- package/dist/preact/index.cjs.map +0 -1
- package/dist/preact/index.d.ts +0 -1
- package/dist/preact/index.js +0 -116
- package/dist/preact/index.js.map +0 -1
- package/dist/react/adapter.d.ts +0 -2
- package/dist/react/annotation.d.ts +0 -1
- package/dist/react/core.d.ts +0 -1
- package/dist/react/index.cjs +0 -2
- package/dist/react/index.cjs.map +0 -1
- package/dist/react/index.d.ts +0 -1
- package/dist/react/index.js +0 -115
- package/dist/react/index.js.map +0 -1
- package/dist/shared/components/index.d.ts +0 -1
- package/dist/shared/components/stamp-img.d.ts +0 -10
- package/dist/shared/hooks/index.d.ts +0 -1
- package/dist/shared/hooks/use-stamp.d.ts +0 -18
- package/dist/shared/index.d.ts +0 -4
- package/dist/shared-preact/components/index.d.ts +0 -1
- package/dist/shared-preact/components/stamp-img.d.ts +0 -10
- package/dist/shared-preact/hooks/index.d.ts +0 -1
- package/dist/shared-preact/hooks/use-stamp.d.ts +0 -18
- package/dist/shared-preact/index.d.ts +0 -4
- package/dist/shared-react/components/index.d.ts +0 -1
- package/dist/shared-react/components/stamp-img.d.ts +0 -10
- package/dist/shared-react/hooks/index.d.ts +0 -1
- package/dist/shared-react/hooks/use-stamp.d.ts +0 -18
- package/dist/shared-react/index.d.ts +0 -4
- package/dist/svelte/components/StampImg.svelte.d.ts +0 -10
- package/dist/svelte/components/index.d.ts +0 -1
- package/dist/svelte/hooks/index.d.ts +0 -1
- package/dist/svelte/hooks/use-stamp.svelte.d.ts +0 -23
- package/dist/svelte/index.cjs +0 -2
- package/dist/svelte/index.cjs.map +0 -1
- package/dist/svelte/index.d.ts +0 -4
- package/dist/svelte/index.js +0 -187
- package/dist/svelte/index.js.map +0 -1
- package/dist/vue/components/index.d.ts +0 -1
- package/dist/vue/components/stamp-img.vue.d.ts +0 -9
- package/dist/vue/hooks/index.d.ts +0 -1
- package/dist/vue/hooks/use-stamp.d.ts +0 -460
- package/dist/vue/index.cjs +0 -2
- package/dist/vue/index.cjs.map +0 -1
- package/dist/vue/index.d.ts +0 -4
- package/dist/vue/index.js +0 -189
- package/dist/vue/index.js.map +0 -1
package/dist/index.js
CHANGED
|
@@ -1,980 +1,589 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import {
|
|
3
|
-
import {
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
categories: ["custom", "sidebar"]
|
|
18
|
-
},
|
|
19
|
-
manifests: [
|
|
20
|
-
{
|
|
21
|
-
url: "https://cdn.jsdelivr.net/npm/@embedpdf/default-stamps/{locale}/manifest.json",
|
|
22
|
-
fallbackLocale: "en"
|
|
23
|
-
}
|
|
24
|
-
]
|
|
25
|
-
}
|
|
1
|
+
import { createCapabilityToken, definePlugin } from "@embedpdf/core";
|
|
2
|
+
import { EngineError, EngineErrorCode, resolveBinarySource, sniffBinaryMetadata } from "@embedpdf/engine-core/runtime";
|
|
3
|
+
import { AnnotationToken } from "@embedpdf/plugin-annotation";
|
|
4
|
+
import { createFormScriptingController } from "@embedpdf/plugin-form";
|
|
5
|
+
//#region src/capability.ts
|
|
6
|
+
const DEFAULT_PREVIEW_WIDTH = 256;
|
|
7
|
+
const LIBRARY_PIECEINFO_APP = "EMBD_StampLibrary";
|
|
8
|
+
const STAMP_PIECEINFO_APP = "EMBD_Stamp";
|
|
9
|
+
const STAMP_SCHEMA_VERSION = 1;
|
|
10
|
+
/** Session-unique ids. Assets are session-scoped for now (no persistence),
|
|
11
|
+
* so a timestamp + counter is enough — durable ids come with the store port. */
|
|
12
|
+
let seq = 0;
|
|
13
|
+
const uid = (prefix) => `${prefix}-${Date.now().toString(36)}-${(seq++).toString(36)}`;
|
|
14
|
+
const entryString = (entries, key) => {
|
|
15
|
+
const entry = entries[key];
|
|
16
|
+
return entry?.type === "string" && entry.value.length > 0 ? entry.value : void 0;
|
|
26
17
|
};
|
|
27
|
-
const
|
|
28
|
-
const
|
|
29
|
-
|
|
30
|
-
return { type: ADD_STAMP_LIBRARY, payload: id };
|
|
31
|
-
}
|
|
32
|
-
function removeStampLibrary(id) {
|
|
33
|
-
return { type: REMOVE_STAMP_LIBRARY, payload: id };
|
|
34
|
-
}
|
|
35
|
-
const rubberStampHandlerFactory = {
|
|
36
|
-
annotationType: PdfAnnotationSubtype.STAMP,
|
|
37
|
-
create(context) {
|
|
38
|
-
const { onPreview, onCommit, getTool, pageSize, pageRotation, getToolContext } = context;
|
|
39
|
-
return {
|
|
40
|
-
onPointerMove: (pos) => {
|
|
41
|
-
const ctx = getToolContext();
|
|
42
|
-
if (!(ctx == null ? void 0 : ctx.stampSize)) return;
|
|
43
|
-
const { width, height } = ctx.stampSize;
|
|
44
|
-
onPreview({
|
|
45
|
-
type: PdfAnnotationSubtype.STAMP,
|
|
46
|
-
bounds: {
|
|
47
|
-
origin: { x: pos.x - width / 2, y: pos.y - height / 2 },
|
|
48
|
-
size: { width, height }
|
|
49
|
-
},
|
|
50
|
-
data: {
|
|
51
|
-
rect: {
|
|
52
|
-
origin: { x: pos.x - width / 2, y: pos.y - height / 2 },
|
|
53
|
-
size: { width, height }
|
|
54
|
-
},
|
|
55
|
-
ghostUrl: ctx.ghostUrl,
|
|
56
|
-
pageRotation
|
|
57
|
-
}
|
|
58
|
-
});
|
|
59
|
-
},
|
|
60
|
-
onPointerDown: (pos) => {
|
|
61
|
-
var _a;
|
|
62
|
-
const tool = getTool();
|
|
63
|
-
const ctx = getToolContext();
|
|
64
|
-
if (!tool || !(ctx == null ? void 0 : ctx.stampSize)) return;
|
|
65
|
-
const { width, height } = ctx.stampSize;
|
|
66
|
-
const rect = {
|
|
67
|
-
origin: { x: pos.x - width / 2, y: pos.y - height / 2 },
|
|
68
|
-
size: { width, height }
|
|
69
|
-
};
|
|
70
|
-
let anno = {
|
|
71
|
-
...tool.defaults,
|
|
72
|
-
rect,
|
|
73
|
-
type: PdfAnnotationSubtype.STAMP,
|
|
74
|
-
name: ctx.stamp.name ?? tool.defaults.name ?? PdfAnnotationName.Custom,
|
|
75
|
-
subject: ctx.stamp.subject ?? tool.defaults.subject ?? "Custom Stamp",
|
|
76
|
-
flags: tool.defaults.flags ?? ["print"],
|
|
77
|
-
pageIndex: context.pageIndex,
|
|
78
|
-
id: uuidV4(),
|
|
79
|
-
created: /* @__PURE__ */ new Date()
|
|
80
|
-
};
|
|
81
|
-
if ((_a = tool.behavior) == null ? void 0 : _a.insertUpright) {
|
|
82
|
-
anno = applyInsertUpright(anno, pageRotation, false);
|
|
83
|
-
}
|
|
84
|
-
anno = clampAnnotationToPage(anno, pageSize);
|
|
85
|
-
onCommit(anno, { appearance: ctx.appearance });
|
|
86
|
-
onPreview(null);
|
|
87
|
-
},
|
|
88
|
-
onPointerLeave: () => {
|
|
89
|
-
onPreview(null);
|
|
90
|
-
}
|
|
91
|
-
};
|
|
92
|
-
}
|
|
93
|
-
};
|
|
94
|
-
const RUBBER_STAMP_TOOL_ID = "rubberStamp";
|
|
95
|
-
const rubberStampTool = defineAnnotationTool({
|
|
96
|
-
id: RUBBER_STAMP_TOOL_ID,
|
|
97
|
-
name: "Rubber Stamp",
|
|
98
|
-
labelKey: "stamp.rubberStamp",
|
|
99
|
-
categories: ["annotation", "stamp", "insert"],
|
|
100
|
-
matchScore: () => 0,
|
|
101
|
-
interaction: {
|
|
102
|
-
exclusive: true,
|
|
103
|
-
cursor: "copy",
|
|
104
|
-
isDraggable: true,
|
|
105
|
-
isResizable: true,
|
|
106
|
-
isRotatable: false,
|
|
107
|
-
lockAspectRatio: true,
|
|
108
|
-
isGroupDraggable: true,
|
|
109
|
-
isGroupResizable: false,
|
|
110
|
-
isGroupRotatable: false
|
|
111
|
-
},
|
|
112
|
-
defaults: {
|
|
113
|
-
type: PdfAnnotationSubtype.STAMP
|
|
114
|
-
},
|
|
115
|
-
behavior: {
|
|
116
|
-
deactivateToolAfterCreate: true,
|
|
117
|
-
selectAfterCreate: true,
|
|
118
|
-
insertUpright: true,
|
|
119
|
-
useAppearanceStream: true
|
|
120
|
-
},
|
|
121
|
-
pointerHandler: rubberStampHandlerFactory
|
|
122
|
-
});
|
|
123
|
-
const stampTools = [rubberStampTool];
|
|
124
|
-
const ANNOTATION_NAME_MAP = {
|
|
125
|
-
Approved: PdfAnnotationName.Approved,
|
|
126
|
-
Experimental: PdfAnnotationName.Experimental,
|
|
127
|
-
NotApproved: PdfAnnotationName.NotApproved,
|
|
128
|
-
AsIs: PdfAnnotationName.AsIs,
|
|
129
|
-
Expired: PdfAnnotationName.Expired,
|
|
130
|
-
NotForPublicRelease: PdfAnnotationName.NotForPublicRelease,
|
|
131
|
-
Confidential: PdfAnnotationName.Confidential,
|
|
132
|
-
Final: PdfAnnotationName.Final,
|
|
133
|
-
Sold: PdfAnnotationName.Sold,
|
|
134
|
-
Departmental: PdfAnnotationName.Departmental,
|
|
135
|
-
ForComment: PdfAnnotationName.ForComment,
|
|
136
|
-
TopSecret: PdfAnnotationName.TopSecret,
|
|
137
|
-
Draft: PdfAnnotationName.Draft,
|
|
138
|
-
ForPublicRelease: PdfAnnotationName.ForPublicRelease,
|
|
139
|
-
Completed: PdfAnnotationName.Completed,
|
|
140
|
-
Void: PdfAnnotationName.Void,
|
|
141
|
-
PreliminaryResults: PdfAnnotationName.PreliminaryResults,
|
|
142
|
-
InformationOnly: PdfAnnotationName.InformationOnly,
|
|
143
|
-
Rejected: PdfAnnotationName.Rejected,
|
|
144
|
-
Witness: PdfAnnotationName.Witness,
|
|
145
|
-
InitialHere: PdfAnnotationName.InitialHere,
|
|
146
|
-
SignHere: PdfAnnotationName.SignHere,
|
|
147
|
-
Accepted: PdfAnnotationName.Accepted,
|
|
148
|
-
Custom: PdfAnnotationName.Custom,
|
|
149
|
-
Image: PdfAnnotationName.Image
|
|
150
|
-
};
|
|
151
|
-
function parseAnnotationName(name) {
|
|
152
|
-
if (!name) return PdfAnnotationName.Custom;
|
|
153
|
-
return ANNOTATION_NAME_MAP[name] ?? PdfAnnotationName.Custom;
|
|
154
|
-
}
|
|
155
|
-
const _StampPlugin = class _StampPlugin extends BasePlugin {
|
|
156
|
-
constructor(id, registry, config) {
|
|
157
|
-
var _a, _b;
|
|
158
|
-
super(id, registry);
|
|
159
|
-
this.config = config;
|
|
160
|
-
this.instanceId = uuidV4();
|
|
161
|
-
this.libraries = /* @__PURE__ */ new Map();
|
|
162
|
-
this.libraryChange$ = createEmitter();
|
|
163
|
-
this.activeStamp$ = createScopedEmitter((documentId, activeStamp) => ({ documentId, activeStamp }));
|
|
164
|
-
this.managedManifests = [];
|
|
165
|
-
this.annotation = null;
|
|
166
|
-
this.i18n = null;
|
|
167
|
-
this.localeUnsubscribe = null;
|
|
168
|
-
this.toolChangeUnsubscribe = null;
|
|
169
|
-
this.currentGhostUrl = null;
|
|
170
|
-
this.annotation = ((_a = registry.getPlugin("annotation")) == null ? void 0 : _a.provides()) ?? null;
|
|
171
|
-
if (this.annotation) {
|
|
172
|
-
for (const tool of stampTools) {
|
|
173
|
-
this.annotation.addTool(tool);
|
|
174
|
-
}
|
|
175
|
-
this.toolChangeUnsubscribe = this.annotation.onActiveToolChange(({ documentId, tool }) => {
|
|
176
|
-
if ((tool == null ? void 0 : tool.id) !== RUBBER_STAMP_TOOL_ID) {
|
|
177
|
-
if (this.currentGhostUrl) {
|
|
178
|
-
URL.revokeObjectURL(this.currentGhostUrl);
|
|
179
|
-
this.currentGhostUrl = null;
|
|
180
|
-
}
|
|
181
|
-
this.activeStamp$.emit(documentId, null);
|
|
182
|
-
}
|
|
183
|
-
});
|
|
184
|
-
}
|
|
185
|
-
this.i18n = ((_b = registry.getPlugin("i18n")) == null ? void 0 : _b.provides()) ?? null;
|
|
186
|
-
}
|
|
187
|
-
async initialize() {
|
|
188
|
-
if (this.config.libraries) {
|
|
189
|
-
for (const libConfig of this.config.libraries) {
|
|
190
|
-
await this.loadLibraryInternal(libConfig).toPromise();
|
|
191
|
-
}
|
|
192
|
-
}
|
|
193
|
-
if (this.config.manifests && this.config.manifests.length > 0) {
|
|
194
|
-
await this.initializeManifests();
|
|
195
|
-
}
|
|
196
|
-
}
|
|
197
|
-
buildCapability() {
|
|
198
|
-
return {
|
|
199
|
-
getLibraries: () => this.getLibraries(),
|
|
200
|
-
getStampsByCategory: (category) => this.getStampsByCategory(category),
|
|
201
|
-
renderStamp: (libraryId, pageIndex, width, dpr) => this.renderStamp(libraryId, pageIndex, width, dpr),
|
|
202
|
-
loadLibrary: (config) => this.loadLibrary(config),
|
|
203
|
-
loadLibraryFromManifest: (url) => this.loadLibraryFromManifest(url),
|
|
204
|
-
createNewLibrary: (name, options) => this.createNewLibrary(name, options),
|
|
205
|
-
addStampToLibrary: (libraryId, stamp, pdf) => this.addStampToLibrary(libraryId, stamp, pdf),
|
|
206
|
-
removeStampFromLibrary: (libraryId, stampId) => this.removeStampFromLibrary(libraryId, stampId),
|
|
207
|
-
updateStamp: (libraryId, stampId, updates) => this.updateStamp(libraryId, stampId, updates),
|
|
208
|
-
updateLibrary: (libraryId, updates) => this.updateLibrary(libraryId, updates),
|
|
209
|
-
removeLibrary: (id) => this.removeLibrary(id),
|
|
210
|
-
exportLibrary: (id) => this.exportLibrary(id),
|
|
211
|
-
forDocument: (documentId) => this.createStampScope(documentId),
|
|
212
|
-
onActiveStampChange: this.activeStamp$.onGlobal,
|
|
213
|
-
onLibraryChange: this.libraryChange$.on
|
|
214
|
-
};
|
|
215
|
-
}
|
|
216
|
-
getLibraries() {
|
|
217
|
-
return Array.from(this.libraries.values());
|
|
218
|
-
}
|
|
219
|
-
getStampsByCategory(category) {
|
|
220
|
-
var _a, _b;
|
|
221
|
-
const results = [];
|
|
222
|
-
for (const library of this.libraries.values()) {
|
|
223
|
-
const libraryMatches = ((_a = library.categories) == null ? void 0 : _a.includes(category)) ?? false;
|
|
224
|
-
for (const stamp of library.stamps) {
|
|
225
|
-
const stampMatches = ((_b = stamp.categories) == null ? void 0 : _b.includes(category)) ?? false;
|
|
226
|
-
if (libraryMatches || stampMatches) {
|
|
227
|
-
results.push({ library, stamp });
|
|
228
|
-
}
|
|
229
|
-
}
|
|
230
|
-
}
|
|
231
|
-
return results;
|
|
232
|
-
}
|
|
233
|
-
renderStamp(libraryId, pageIndex, width, dpr) {
|
|
234
|
-
const library = this.libraries.get(libraryId);
|
|
235
|
-
if (!library) {
|
|
236
|
-
const task = new Task();
|
|
237
|
-
task.reject({
|
|
238
|
-
code: PdfErrorCode.NotFound,
|
|
239
|
-
message: `Stamp library not found: ${libraryId}`
|
|
240
|
-
});
|
|
241
|
-
return task;
|
|
242
|
-
}
|
|
243
|
-
const page = library.document.pages[pageIndex];
|
|
244
|
-
if (!page) {
|
|
245
|
-
const task = new Task();
|
|
246
|
-
task.reject({
|
|
247
|
-
code: PdfErrorCode.NotFound,
|
|
248
|
-
message: `Page ${pageIndex} not found in stamp library: ${libraryId}`
|
|
249
|
-
});
|
|
250
|
-
return task;
|
|
251
|
-
}
|
|
252
|
-
const scaleFactor = width / page.size.width;
|
|
253
|
-
return this.engine.renderPageRect(
|
|
254
|
-
library.document,
|
|
255
|
-
page,
|
|
256
|
-
{ origin: { x: 0, y: 0 }, size: page.size },
|
|
257
|
-
{
|
|
258
|
-
scaleFactor,
|
|
259
|
-
dpr: dpr ?? 1,
|
|
260
|
-
withAnnotations: true,
|
|
261
|
-
rotation: page.rotation,
|
|
262
|
-
transparentBackground: true
|
|
263
|
-
}
|
|
264
|
-
);
|
|
265
|
-
}
|
|
266
|
-
loadLibrary(config) {
|
|
267
|
-
return this.loadLibraryInternal(config);
|
|
268
|
-
}
|
|
269
|
-
createNewLibrary(name, options) {
|
|
270
|
-
const task = new Task();
|
|
271
|
-
const libraryId = (options == null ? void 0 : options.id) ?? this.generateLibraryId();
|
|
272
|
-
const documentId = this.getLibraryDocumentId(libraryId);
|
|
273
|
-
this.engine.createDocument(documentId).wait(
|
|
274
|
-
(doc) => {
|
|
275
|
-
const library = {
|
|
276
|
-
id: libraryId,
|
|
277
|
-
name,
|
|
278
|
-
nameKey: options == null ? void 0 : options.nameKey,
|
|
279
|
-
document: doc,
|
|
280
|
-
stamps: [],
|
|
281
|
-
categories: options == null ? void 0 : options.categories,
|
|
282
|
-
readonly: (options == null ? void 0 : options.readonly) ?? false
|
|
283
|
-
};
|
|
284
|
-
this.libraries.set(libraryId, library);
|
|
285
|
-
this.dispatch(addStampLibrary(libraryId));
|
|
286
|
-
this.emitLibraryChange();
|
|
287
|
-
task.resolve(libraryId);
|
|
288
|
-
},
|
|
289
|
-
(error) => {
|
|
290
|
-
this.logger.error(
|
|
291
|
-
"StampPlugin",
|
|
292
|
-
"CreateNewLibrary",
|
|
293
|
-
`Failed to create library: ${name}`,
|
|
294
|
-
error
|
|
295
|
-
);
|
|
296
|
-
task.fail(error);
|
|
297
|
-
}
|
|
298
|
-
);
|
|
299
|
-
return task;
|
|
300
|
-
}
|
|
301
|
-
addStampToLibrary(libraryId, stamp, pdf) {
|
|
302
|
-
const task = new Task();
|
|
303
|
-
const library = this.libraries.get(libraryId);
|
|
304
|
-
if (!library) {
|
|
305
|
-
task.reject({
|
|
306
|
-
code: PdfErrorCode.NotFound,
|
|
307
|
-
message: `Stamp library not found: ${libraryId}`
|
|
308
|
-
});
|
|
309
|
-
return task;
|
|
310
|
-
}
|
|
311
|
-
if (library.readonly) {
|
|
312
|
-
task.reject({
|
|
313
|
-
code: PdfErrorCode.NotSupport,
|
|
314
|
-
message: `Cannot add stamps to readonly library: ${libraryId}`
|
|
315
|
-
});
|
|
316
|
-
return task;
|
|
317
|
-
}
|
|
318
|
-
const tempDocId = `stamp-temp-${Date.now()}`;
|
|
319
|
-
this.engine.openDocumentBuffer({ id: tempDocId, content: pdf }).wait(
|
|
320
|
-
(tempDoc) => {
|
|
321
|
-
this.engine.importPages(library.document, tempDoc, [0]).wait(
|
|
322
|
-
(newPages) => {
|
|
323
|
-
const newPage = newPages[0];
|
|
324
|
-
library.document.pages.push(newPage);
|
|
325
|
-
library.document.pageCount = library.document.pages.length;
|
|
326
|
-
const stampDef = {
|
|
327
|
-
...stamp,
|
|
328
|
-
id: uuidV4(),
|
|
329
|
-
pageIndex: newPage.index
|
|
330
|
-
};
|
|
331
|
-
library.stamps.push(stampDef);
|
|
332
|
-
this.engine.closeDocument(tempDoc).wait(
|
|
333
|
-
() => {
|
|
334
|
-
this.emitLibraryChange();
|
|
335
|
-
task.resolve();
|
|
336
|
-
},
|
|
337
|
-
() => {
|
|
338
|
-
this.emitLibraryChange();
|
|
339
|
-
task.resolve();
|
|
340
|
-
}
|
|
341
|
-
);
|
|
342
|
-
},
|
|
343
|
-
(error) => {
|
|
344
|
-
this.logger.error("StampPlugin", "AddStampToLibrary", "Failed to import page", error);
|
|
345
|
-
this.engine.closeDocument(tempDoc).wait(
|
|
346
|
-
() => task.fail(error),
|
|
347
|
-
() => task.fail(error)
|
|
348
|
-
);
|
|
349
|
-
}
|
|
350
|
-
);
|
|
351
|
-
},
|
|
352
|
-
(error) => {
|
|
353
|
-
this.logger.error(
|
|
354
|
-
"StampPlugin",
|
|
355
|
-
"AddStampToLibrary",
|
|
356
|
-
"Failed to open temp document",
|
|
357
|
-
error
|
|
358
|
-
);
|
|
359
|
-
task.fail(error);
|
|
360
|
-
}
|
|
361
|
-
);
|
|
362
|
-
return task;
|
|
363
|
-
}
|
|
364
|
-
createStampScope(documentId) {
|
|
365
|
-
return {
|
|
366
|
-
createStampFromAnnotation: (annotation, stamp, libraryId) => this.createStampFromAnnotation(documentId, annotation, stamp, libraryId),
|
|
367
|
-
createStampFromAnnotations: (annotations, stamp, libraryId) => this.createStampFromAnnotations(documentId, annotations, stamp, libraryId),
|
|
368
|
-
activateStampPlacement: (libraryId, stamp) => this.activateStampPlacement(documentId, libraryId, stamp),
|
|
369
|
-
activateStampPlacementById: (libraryId, stampId) => this.activateStampPlacementById(documentId, libraryId, stampId),
|
|
370
|
-
getActiveStamp: () => this.getActiveStamp(documentId),
|
|
371
|
-
onActiveStampChange: this.activeStamp$.forScope(documentId)
|
|
372
|
-
};
|
|
373
|
-
}
|
|
374
|
-
activateStampPlacementById(documentId, libraryId, stampId) {
|
|
375
|
-
const task = new Task();
|
|
376
|
-
const library = this.libraries.get(libraryId);
|
|
377
|
-
if (!library) {
|
|
378
|
-
task.reject({
|
|
379
|
-
code: PdfErrorCode.NotFound,
|
|
380
|
-
message: `Stamp library not found: ${libraryId}`
|
|
381
|
-
});
|
|
382
|
-
return task;
|
|
383
|
-
}
|
|
384
|
-
const stamp = library.stamps.find((s) => s.id === stampId);
|
|
385
|
-
if (!stamp) {
|
|
386
|
-
task.reject({
|
|
387
|
-
code: PdfErrorCode.NotFound,
|
|
388
|
-
message: `Stamp ${stampId} not found in library: ${libraryId}`
|
|
389
|
-
});
|
|
390
|
-
return task;
|
|
391
|
-
}
|
|
392
|
-
return this.activateStampPlacement(documentId, libraryId, stamp);
|
|
393
|
-
}
|
|
394
|
-
getActiveStamp(documentId) {
|
|
395
|
-
return this.activeStamp$.getValue(documentId) ?? null;
|
|
396
|
-
}
|
|
397
|
-
activateStampPlacement(documentId, libraryId, stamp) {
|
|
398
|
-
const task = new Task();
|
|
399
|
-
const library = this.libraries.get(libraryId);
|
|
400
|
-
if (!library) {
|
|
401
|
-
task.reject({
|
|
402
|
-
code: PdfErrorCode.NotFound,
|
|
403
|
-
message: `Stamp library not found: ${libraryId}`
|
|
404
|
-
});
|
|
405
|
-
return task;
|
|
406
|
-
}
|
|
407
|
-
const page = library.document.pages[stamp.pageIndex];
|
|
408
|
-
if (!page) {
|
|
409
|
-
task.reject({
|
|
410
|
-
code: PdfErrorCode.NotFound,
|
|
411
|
-
message: `Page ${stamp.pageIndex} not found in library`
|
|
412
|
-
});
|
|
413
|
-
return task;
|
|
414
|
-
}
|
|
415
|
-
const stampSize = page.size;
|
|
416
|
-
this.engine.extractPages(library.document, [stamp.pageIndex]).wait(
|
|
417
|
-
(appearance) => {
|
|
418
|
-
this.renderStamp(libraryId, stamp.pageIndex, stampSize.width, 2).wait(
|
|
419
|
-
(blob) => {
|
|
420
|
-
var _a;
|
|
421
|
-
if (this.currentGhostUrl) {
|
|
422
|
-
URL.revokeObjectURL(this.currentGhostUrl);
|
|
423
|
-
}
|
|
424
|
-
const ghostUrl = URL.createObjectURL(blob);
|
|
425
|
-
this.currentGhostUrl = ghostUrl;
|
|
426
|
-
(_a = this.annotation) == null ? void 0 : _a.setActiveTool(RUBBER_STAMP_TOOL_ID, {
|
|
427
|
-
appearance,
|
|
428
|
-
ghostUrl,
|
|
429
|
-
stampSize,
|
|
430
|
-
libraryId,
|
|
431
|
-
stamp
|
|
432
|
-
});
|
|
433
|
-
this.activeStamp$.emit(documentId, { libraryId, stamp });
|
|
434
|
-
task.resolve();
|
|
435
|
-
},
|
|
436
|
-
(error) => {
|
|
437
|
-
this.logger.error(
|
|
438
|
-
"StampPlugin",
|
|
439
|
-
"ActivateStampPlacement",
|
|
440
|
-
"Failed to render stamp preview",
|
|
441
|
-
error
|
|
442
|
-
);
|
|
443
|
-
task.fail(error);
|
|
444
|
-
}
|
|
445
|
-
);
|
|
446
|
-
},
|
|
447
|
-
(error) => {
|
|
448
|
-
this.logger.error(
|
|
449
|
-
"StampPlugin",
|
|
450
|
-
"ActivateStampPlacement",
|
|
451
|
-
"Failed to extract stamp page",
|
|
452
|
-
error
|
|
453
|
-
);
|
|
454
|
-
task.fail(error);
|
|
455
|
-
}
|
|
456
|
-
);
|
|
457
|
-
return task;
|
|
458
|
-
}
|
|
459
|
-
createStampFromAnnotation(documentId, annotation, stamp, libraryId) {
|
|
460
|
-
const task = new Task();
|
|
461
|
-
const docState = this.getCoreDocument(documentId);
|
|
462
|
-
if (!(docState == null ? void 0 : docState.document)) {
|
|
463
|
-
task.reject({ code: PdfErrorCode.DocNotOpen, message: "document is not open" });
|
|
464
|
-
return task;
|
|
465
|
-
}
|
|
466
|
-
const doc = docState.document;
|
|
467
|
-
const page = doc.pages[annotation.pageIndex];
|
|
468
|
-
if (!page) {
|
|
469
|
-
task.reject({
|
|
470
|
-
code: PdfErrorCode.NotFound,
|
|
471
|
-
message: `page ${annotation.pageIndex} not found`
|
|
472
|
-
});
|
|
473
|
-
return task;
|
|
474
|
-
}
|
|
475
|
-
this.resolveTargetLibrary(libraryId).wait(
|
|
476
|
-
(targetId) => {
|
|
477
|
-
this.engine.exportAnnotationAppearanceAsPdf(doc, page, annotation).wait(
|
|
478
|
-
(pdf) => {
|
|
479
|
-
this.addStampToLibrary(targetId, stamp, pdf).wait(
|
|
480
|
-
() => task.resolve(),
|
|
481
|
-
(error) => task.fail(error)
|
|
482
|
-
);
|
|
483
|
-
},
|
|
484
|
-
(error) => {
|
|
485
|
-
this.logger.error(
|
|
486
|
-
"StampPlugin",
|
|
487
|
-
"CreateStampFromAnnotation",
|
|
488
|
-
"Failed to export annotation",
|
|
489
|
-
error
|
|
490
|
-
);
|
|
491
|
-
task.fail(error);
|
|
492
|
-
}
|
|
493
|
-
);
|
|
494
|
-
},
|
|
495
|
-
(error) => task.fail(error)
|
|
496
|
-
);
|
|
497
|
-
return task;
|
|
498
|
-
}
|
|
499
|
-
createStampFromAnnotations(documentId, annotations, stamp, libraryId) {
|
|
500
|
-
const task = new Task();
|
|
501
|
-
if (annotations.length === 0) {
|
|
502
|
-
task.reject({ code: PdfErrorCode.NotFound, message: "no annotations provided" });
|
|
503
|
-
return task;
|
|
504
|
-
}
|
|
505
|
-
const docState = this.getCoreDocument(documentId);
|
|
506
|
-
if (!(docState == null ? void 0 : docState.document)) {
|
|
507
|
-
task.reject({ code: PdfErrorCode.DocNotOpen, message: "document is not open" });
|
|
508
|
-
return task;
|
|
509
|
-
}
|
|
510
|
-
const doc = docState.document;
|
|
511
|
-
const page = doc.pages[annotations[0].pageIndex];
|
|
512
|
-
if (!page) {
|
|
513
|
-
task.reject({
|
|
514
|
-
code: PdfErrorCode.NotFound,
|
|
515
|
-
message: `page ${annotations[0].pageIndex} not found`
|
|
516
|
-
});
|
|
517
|
-
return task;
|
|
518
|
-
}
|
|
519
|
-
this.resolveTargetLibrary(libraryId).wait(
|
|
520
|
-
(targetId) => {
|
|
521
|
-
this.engine.exportAnnotationsAppearanceAsPdf(doc, page, annotations).wait(
|
|
522
|
-
(pdf) => {
|
|
523
|
-
this.addStampToLibrary(targetId, stamp, pdf).wait(
|
|
524
|
-
() => task.resolve(),
|
|
525
|
-
(error) => task.fail(error)
|
|
526
|
-
);
|
|
527
|
-
},
|
|
528
|
-
(error) => {
|
|
529
|
-
this.logger.error(
|
|
530
|
-
"StampPlugin",
|
|
531
|
-
"CreateStampFromAnnotations",
|
|
532
|
-
"Failed to export annotations",
|
|
533
|
-
error
|
|
534
|
-
);
|
|
535
|
-
task.fail(error);
|
|
536
|
-
}
|
|
537
|
-
);
|
|
538
|
-
},
|
|
539
|
-
(error) => task.fail(error)
|
|
540
|
-
);
|
|
541
|
-
return task;
|
|
542
|
-
}
|
|
543
|
-
resolveTargetLibrary(libraryId) {
|
|
544
|
-
if (libraryId && this.libraries.has(libraryId)) {
|
|
545
|
-
const task = new Task();
|
|
546
|
-
task.resolve(libraryId);
|
|
547
|
-
return task;
|
|
548
|
-
}
|
|
549
|
-
const defaults = this.config.defaultLibrary;
|
|
550
|
-
if (this.config.defaultLibrary === false || !defaults) {
|
|
551
|
-
const task = new Task();
|
|
552
|
-
task.reject({
|
|
553
|
-
code: PdfErrorCode.NotSupport,
|
|
554
|
-
message: "Default library creation is disabled"
|
|
555
|
-
});
|
|
556
|
-
return task;
|
|
557
|
-
}
|
|
558
|
-
const defaultId = defaults.id ?? "custom";
|
|
559
|
-
if (this.libraries.has(defaultId)) {
|
|
560
|
-
const task = new Task();
|
|
561
|
-
task.resolve(defaultId);
|
|
562
|
-
return task;
|
|
563
|
-
}
|
|
564
|
-
return this.createNewLibrary(defaults.name ?? "Custom Stamps", {
|
|
565
|
-
id: defaultId,
|
|
566
|
-
nameKey: defaults.nameKey,
|
|
567
|
-
categories: defaults.categories
|
|
568
|
-
});
|
|
569
|
-
}
|
|
570
|
-
removeLibrary(id) {
|
|
571
|
-
const task = new Task();
|
|
572
|
-
const library = this.libraries.get(id);
|
|
573
|
-
if (!library) {
|
|
574
|
-
task.resolve();
|
|
575
|
-
return task;
|
|
576
|
-
}
|
|
577
|
-
this.engine.closeDocument(library.document).wait(
|
|
578
|
-
() => {
|
|
579
|
-
this.libraries.delete(id);
|
|
580
|
-
this.dispatch(removeStampLibrary(id));
|
|
581
|
-
this.emitLibraryChange();
|
|
582
|
-
task.resolve();
|
|
583
|
-
},
|
|
584
|
-
() => {
|
|
585
|
-
this.logger.warn(
|
|
586
|
-
"StampPlugin",
|
|
587
|
-
"RemoveLibrary",
|
|
588
|
-
`Failed to close document for library: ${id}`
|
|
589
|
-
);
|
|
590
|
-
this.libraries.delete(id);
|
|
591
|
-
this.dispatch(removeStampLibrary(id));
|
|
592
|
-
this.emitLibraryChange();
|
|
593
|
-
task.resolve();
|
|
594
|
-
}
|
|
595
|
-
);
|
|
596
|
-
return task;
|
|
597
|
-
}
|
|
598
|
-
exportLibrary(id) {
|
|
599
|
-
const task = new Task();
|
|
600
|
-
const library = this.libraries.get(id);
|
|
601
|
-
if (!library) {
|
|
602
|
-
task.reject({
|
|
603
|
-
code: PdfErrorCode.NotFound,
|
|
604
|
-
message: `Stamp library not found: ${id}`
|
|
605
|
-
});
|
|
606
|
-
return task;
|
|
607
|
-
}
|
|
608
|
-
this.engine.saveAsCopy(library.document).wait(
|
|
609
|
-
(pdf) => {
|
|
610
|
-
task.resolve({
|
|
611
|
-
name: library.name,
|
|
612
|
-
pdf,
|
|
613
|
-
stamps: library.stamps,
|
|
614
|
-
categories: library.categories
|
|
615
|
-
});
|
|
616
|
-
},
|
|
617
|
-
(error) => {
|
|
618
|
-
this.logger.error("StampPlugin", "ExportLibrary", `Failed to export library: ${id}`, error);
|
|
619
|
-
task.fail(error);
|
|
620
|
-
}
|
|
621
|
-
);
|
|
622
|
-
return task;
|
|
623
|
-
}
|
|
624
|
-
loadLibraryInternal(config) {
|
|
625
|
-
const task = new Task();
|
|
626
|
-
const libraryId = config.id ?? this.generateLibraryId();
|
|
627
|
-
const documentId = this.getLibraryDocumentId(libraryId);
|
|
628
|
-
const engineTask = typeof config.pdf === "string" ? this.engine.openDocumentUrl({ id: documentId, url: config.pdf }) : this.engine.openDocumentBuffer({ id: documentId, content: config.pdf });
|
|
629
|
-
engineTask.wait(
|
|
630
|
-
(doc) => {
|
|
631
|
-
const stamps = config.stamps.map((s) => s.id ? s : { ...s, id: uuidV4() });
|
|
632
|
-
const library = {
|
|
633
|
-
id: libraryId,
|
|
634
|
-
name: config.name,
|
|
635
|
-
nameKey: config.nameKey,
|
|
636
|
-
document: doc,
|
|
637
|
-
stamps,
|
|
638
|
-
categories: config.categories,
|
|
639
|
-
readonly: config.readonly ?? false
|
|
640
|
-
};
|
|
641
|
-
this.libraries.set(libraryId, library);
|
|
642
|
-
this.dispatch(addStampLibrary(libraryId));
|
|
643
|
-
this.emitLibraryChange();
|
|
644
|
-
task.resolve(libraryId);
|
|
645
|
-
},
|
|
646
|
-
(error) => {
|
|
647
|
-
this.logger.error(
|
|
648
|
-
"StampPlugin",
|
|
649
|
-
"LoadLibrary",
|
|
650
|
-
`Failed to load stamp library: ${config.name}`,
|
|
651
|
-
error
|
|
652
|
-
);
|
|
653
|
-
task.fail(error);
|
|
654
|
-
}
|
|
655
|
-
);
|
|
656
|
-
return task;
|
|
657
|
-
}
|
|
658
|
-
removeStampFromLibrary(libraryId, stampId) {
|
|
659
|
-
const task = new Task();
|
|
660
|
-
const library = this.libraries.get(libraryId);
|
|
661
|
-
if (!library) {
|
|
662
|
-
task.reject({
|
|
663
|
-
code: PdfErrorCode.NotFound,
|
|
664
|
-
message: `Stamp library not found: ${libraryId}`
|
|
665
|
-
});
|
|
666
|
-
return task;
|
|
667
|
-
}
|
|
668
|
-
if (library.readonly) {
|
|
669
|
-
task.reject({
|
|
670
|
-
code: PdfErrorCode.NotSupport,
|
|
671
|
-
message: `Cannot remove stamps from readonly library: ${libraryId}`
|
|
672
|
-
});
|
|
673
|
-
return task;
|
|
674
|
-
}
|
|
675
|
-
const stampIdx = library.stamps.findIndex((s) => s.id === stampId);
|
|
676
|
-
if (stampIdx === -1) {
|
|
677
|
-
task.reject({
|
|
678
|
-
code: PdfErrorCode.NotFound,
|
|
679
|
-
message: `Stamp ${stampId} not found in library: ${libraryId}`
|
|
680
|
-
});
|
|
681
|
-
return task;
|
|
682
|
-
}
|
|
683
|
-
const pageIndex = library.stamps[stampIdx].pageIndex;
|
|
684
|
-
this.engine.deletePage(library.document, pageIndex).wait(
|
|
685
|
-
() => {
|
|
686
|
-
library.stamps.splice(stampIdx, 1);
|
|
687
|
-
library.document.pages.splice(pageIndex, 1);
|
|
688
|
-
library.document.pageCount = library.document.pages.length;
|
|
689
|
-
for (const s of library.stamps) {
|
|
690
|
-
if (s.pageIndex > pageIndex) {
|
|
691
|
-
s.pageIndex--;
|
|
692
|
-
}
|
|
693
|
-
}
|
|
694
|
-
for (let i = 0; i < library.document.pages.length; i++) {
|
|
695
|
-
library.document.pages[i].index = i;
|
|
696
|
-
}
|
|
697
|
-
this.emitLibraryChange();
|
|
698
|
-
task.resolve();
|
|
699
|
-
},
|
|
700
|
-
(error) => {
|
|
701
|
-
this.logger.error(
|
|
702
|
-
"StampPlugin",
|
|
703
|
-
"RemoveStampFromLibrary",
|
|
704
|
-
`Failed to delete page for stamp ${stampId}`,
|
|
705
|
-
error
|
|
706
|
-
);
|
|
707
|
-
task.fail(error);
|
|
708
|
-
}
|
|
709
|
-
);
|
|
710
|
-
return task;
|
|
711
|
-
}
|
|
712
|
-
updateStamp(libraryId, stampId, updates) {
|
|
713
|
-
const task = new Task();
|
|
714
|
-
const library = this.libraries.get(libraryId);
|
|
715
|
-
if (!library) {
|
|
716
|
-
task.reject({
|
|
717
|
-
code: PdfErrorCode.NotFound,
|
|
718
|
-
message: `Stamp library not found: ${libraryId}`
|
|
719
|
-
});
|
|
720
|
-
return task;
|
|
721
|
-
}
|
|
722
|
-
if (library.readonly) {
|
|
723
|
-
task.reject({
|
|
724
|
-
code: PdfErrorCode.NotSupport,
|
|
725
|
-
message: `Cannot update stamps in readonly library: ${libraryId}`
|
|
726
|
-
});
|
|
727
|
-
return task;
|
|
728
|
-
}
|
|
729
|
-
const stamp = library.stamps.find((s) => s.id === stampId);
|
|
730
|
-
if (!stamp) {
|
|
731
|
-
task.reject({
|
|
732
|
-
code: PdfErrorCode.NotFound,
|
|
733
|
-
message: `Stamp ${stampId} not found in library: ${libraryId}`
|
|
734
|
-
});
|
|
735
|
-
return task;
|
|
736
|
-
}
|
|
737
|
-
Object.assign(stamp, updates);
|
|
738
|
-
this.emitLibraryChange();
|
|
739
|
-
task.resolve();
|
|
740
|
-
return task;
|
|
741
|
-
}
|
|
742
|
-
updateLibrary(libraryId, updates) {
|
|
743
|
-
const task = new Task();
|
|
744
|
-
const library = this.libraries.get(libraryId);
|
|
745
|
-
if (!library) {
|
|
746
|
-
task.reject({
|
|
747
|
-
code: PdfErrorCode.NotFound,
|
|
748
|
-
message: `Stamp library not found: ${libraryId}`
|
|
749
|
-
});
|
|
750
|
-
return task;
|
|
751
|
-
}
|
|
752
|
-
if (library.readonly) {
|
|
753
|
-
task.reject({
|
|
754
|
-
code: PdfErrorCode.NotSupport,
|
|
755
|
-
message: `Cannot update readonly library: ${libraryId}`
|
|
756
|
-
});
|
|
757
|
-
return task;
|
|
758
|
-
}
|
|
759
|
-
if (updates.name !== void 0) library.name = updates.name;
|
|
760
|
-
if (updates.nameKey !== void 0) library.nameKey = updates.nameKey;
|
|
761
|
-
if (updates.categories !== void 0) library.categories = updates.categories;
|
|
762
|
-
if (updates.readonly !== void 0) library.readonly = updates.readonly;
|
|
763
|
-
this.emitLibraryChange();
|
|
764
|
-
task.resolve();
|
|
765
|
-
return task;
|
|
766
|
-
}
|
|
767
|
-
loadLibraryFromManifest(url) {
|
|
768
|
-
return this.loadManifestUrl(url, true);
|
|
769
|
-
}
|
|
770
|
-
async initializeManifests() {
|
|
771
|
-
var _a;
|
|
772
|
-
const currentLocale = ((_a = this.i18n) == null ? void 0 : _a.getLocale()) ?? "en";
|
|
773
|
-
for (const source of this.config.manifests) {
|
|
774
|
-
const localeAware = source.url.includes("{locale}");
|
|
775
|
-
const locale = localeAware ? currentLocale : "";
|
|
776
|
-
const resolvedUrl = source.url.replace("{locale}", locale || currentLocale);
|
|
777
|
-
const managed = {
|
|
778
|
-
source,
|
|
779
|
-
localeAware,
|
|
780
|
-
currentLocale: localeAware ? currentLocale : "",
|
|
781
|
-
libraryId: null
|
|
782
|
-
};
|
|
783
|
-
this.managedManifests.push(managed);
|
|
784
|
-
try {
|
|
785
|
-
const libraryId = await this.loadManifestUrl(resolvedUrl, true).toPromise();
|
|
786
|
-
managed.libraryId = libraryId;
|
|
787
|
-
} catch {
|
|
788
|
-
if (localeAware && source.fallbackLocale && source.fallbackLocale !== currentLocale) {
|
|
789
|
-
const fallbackUrl = source.url.replace("{locale}", source.fallbackLocale);
|
|
790
|
-
try {
|
|
791
|
-
const libraryId = await this.loadManifestUrl(fallbackUrl, true).toPromise();
|
|
792
|
-
managed.libraryId = libraryId;
|
|
793
|
-
managed.currentLocale = source.fallbackLocale;
|
|
794
|
-
} catch {
|
|
795
|
-
this.logger.warn(
|
|
796
|
-
"StampPlugin",
|
|
797
|
-
"InitManifests",
|
|
798
|
-
`Failed to load manifest (including fallback): ${source.url}`
|
|
799
|
-
);
|
|
800
|
-
}
|
|
801
|
-
} else {
|
|
802
|
-
this.logger.warn(
|
|
803
|
-
"StampPlugin",
|
|
804
|
-
"InitManifests",
|
|
805
|
-
`Failed to load manifest: ${resolvedUrl}`
|
|
806
|
-
);
|
|
807
|
-
}
|
|
808
|
-
}
|
|
809
|
-
}
|
|
810
|
-
if (this.i18n) {
|
|
811
|
-
this.localeUnsubscribe = this.i18n.onLocaleChange((event) => {
|
|
812
|
-
this.handleLocaleChange(event.currentLocale);
|
|
813
|
-
});
|
|
814
|
-
}
|
|
815
|
-
}
|
|
816
|
-
async handleLocaleChange(newLocale) {
|
|
817
|
-
for (const managed of this.managedManifests) {
|
|
818
|
-
if (!managed.localeAware || managed.currentLocale === newLocale) {
|
|
819
|
-
continue;
|
|
820
|
-
}
|
|
821
|
-
if (managed.libraryId) {
|
|
822
|
-
try {
|
|
823
|
-
await this.removeLibrary(managed.libraryId).toPromise();
|
|
824
|
-
} catch {
|
|
825
|
-
}
|
|
826
|
-
managed.libraryId = null;
|
|
827
|
-
}
|
|
828
|
-
const resolvedUrl = managed.source.url.replace("{locale}", newLocale);
|
|
829
|
-
try {
|
|
830
|
-
const libraryId = await this.loadManifestUrl(resolvedUrl, true).toPromise();
|
|
831
|
-
managed.libraryId = libraryId;
|
|
832
|
-
managed.currentLocale = newLocale;
|
|
833
|
-
} catch {
|
|
834
|
-
const fallback = managed.source.fallbackLocale ?? "en";
|
|
835
|
-
if (fallback !== newLocale) {
|
|
836
|
-
const fallbackUrl = managed.source.url.replace("{locale}", fallback);
|
|
837
|
-
try {
|
|
838
|
-
const libraryId = await this.loadManifestUrl(fallbackUrl, true).toPromise();
|
|
839
|
-
managed.libraryId = libraryId;
|
|
840
|
-
managed.currentLocale = fallback;
|
|
841
|
-
} catch {
|
|
842
|
-
this.logger.warn(
|
|
843
|
-
"StampPlugin",
|
|
844
|
-
"LocaleChange",
|
|
845
|
-
`Failed to load manifest for locale ${newLocale} (including fallback): ${managed.source.url}`
|
|
846
|
-
);
|
|
847
|
-
}
|
|
848
|
-
} else {
|
|
849
|
-
this.logger.warn(
|
|
850
|
-
"StampPlugin",
|
|
851
|
-
"LocaleChange",
|
|
852
|
-
`Failed to load manifest for locale ${newLocale}: ${managed.source.url}`
|
|
853
|
-
);
|
|
854
|
-
}
|
|
855
|
-
}
|
|
856
|
-
}
|
|
857
|
-
}
|
|
858
|
-
loadManifestUrl(url, readonly) {
|
|
859
|
-
const task = new Task();
|
|
860
|
-
fetch(url).then((response) => {
|
|
861
|
-
if (!response.ok) {
|
|
862
|
-
throw new Error(`HTTP ${response.status}: ${response.statusText}`);
|
|
863
|
-
}
|
|
864
|
-
return response.json();
|
|
865
|
-
}).then((manifest2) => {
|
|
866
|
-
const pdfUrl = this.resolveManifestPdfUrl(url, manifest2.pdf);
|
|
867
|
-
const stamps = manifest2.stamps.map((entry) => ({
|
|
868
|
-
id: entry.id ?? uuidV4(),
|
|
869
|
-
pageIndex: entry.pageIndex,
|
|
870
|
-
name: parseAnnotationName(entry.name),
|
|
871
|
-
subject: entry.subject,
|
|
872
|
-
subjectKey: entry.subjectKey,
|
|
873
|
-
label: entry.label,
|
|
874
|
-
categories: entry.categories
|
|
875
|
-
}));
|
|
876
|
-
const config = {
|
|
877
|
-
id: manifest2.id,
|
|
878
|
-
name: manifest2.name,
|
|
879
|
-
nameKey: manifest2.nameKey,
|
|
880
|
-
pdf: pdfUrl,
|
|
881
|
-
stamps,
|
|
882
|
-
categories: manifest2.categories,
|
|
883
|
-
readonly
|
|
884
|
-
};
|
|
885
|
-
this.loadLibraryInternal(config).wait(
|
|
886
|
-
(libraryId) => task.resolve(libraryId),
|
|
887
|
-
(error) => task.fail(error)
|
|
888
|
-
);
|
|
889
|
-
}).catch((error) => {
|
|
890
|
-
this.logger.error("StampPlugin", "LoadManifest", `Failed to fetch manifest: ${url}`, error);
|
|
891
|
-
task.reject({
|
|
892
|
-
code: PdfErrorCode.NotFound,
|
|
893
|
-
message: `Failed to load stamp manifest: ${url}`
|
|
894
|
-
});
|
|
895
|
-
});
|
|
896
|
-
return task;
|
|
897
|
-
}
|
|
898
|
-
resolveManifestPdfUrl(manifestUrl, pdfPath) {
|
|
899
|
-
if (pdfPath.startsWith("http://") || pdfPath.startsWith("https://") || pdfPath.startsWith("/")) {
|
|
900
|
-
return pdfPath;
|
|
901
|
-
}
|
|
902
|
-
const base = manifestUrl.substring(0, manifestUrl.lastIndexOf("/") + 1);
|
|
903
|
-
return base + pdfPath;
|
|
904
|
-
}
|
|
905
|
-
generateLibraryId() {
|
|
906
|
-
return uuidV4();
|
|
907
|
-
}
|
|
908
|
-
getLibraryDocumentId(libraryId) {
|
|
909
|
-
return `stamp-doc-${this.instanceId}-${libraryId}`;
|
|
910
|
-
}
|
|
911
|
-
emitLibraryChange() {
|
|
912
|
-
this.libraryChange$.emit(this.getLibraries());
|
|
913
|
-
}
|
|
914
|
-
async destroy() {
|
|
915
|
-
if (this.currentGhostUrl) {
|
|
916
|
-
URL.revokeObjectURL(this.currentGhostUrl);
|
|
917
|
-
this.currentGhostUrl = null;
|
|
918
|
-
}
|
|
919
|
-
if (this.toolChangeUnsubscribe) {
|
|
920
|
-
this.toolChangeUnsubscribe();
|
|
921
|
-
this.toolChangeUnsubscribe = null;
|
|
922
|
-
}
|
|
923
|
-
if (this.localeUnsubscribe) {
|
|
924
|
-
this.localeUnsubscribe();
|
|
925
|
-
this.localeUnsubscribe = null;
|
|
926
|
-
}
|
|
927
|
-
this.activeStamp$.clear();
|
|
928
|
-
const libs = Array.from(this.libraries.values());
|
|
929
|
-
for (const library of libs) {
|
|
930
|
-
try {
|
|
931
|
-
await this.engine.closeDocument(library.document).toPromise();
|
|
932
|
-
} catch {
|
|
933
|
-
}
|
|
934
|
-
}
|
|
935
|
-
this.libraries.clear();
|
|
936
|
-
this.managedManifests.length = 0;
|
|
937
|
-
super.destroy();
|
|
938
|
-
}
|
|
18
|
+
const entryName = (entries, key) => {
|
|
19
|
+
const entry = entries[key];
|
|
20
|
+
return entry?.type === "name" && entry.value.length > 0 ? entry.value : void 0;
|
|
939
21
|
};
|
|
940
|
-
|
|
941
|
-
|
|
942
|
-
|
|
943
|
-
libraryIds: []
|
|
22
|
+
const entryStringArray = (entries, key) => {
|
|
23
|
+
const entry = entries[key];
|
|
24
|
+
return entry?.type === "string-array" ? [...entry.value] : void 0;
|
|
944
25
|
};
|
|
945
|
-
const
|
|
946
|
-
|
|
947
|
-
|
|
948
|
-
|
|
949
|
-
|
|
950
|
-
|
|
951
|
-
|
|
952
|
-
|
|
953
|
-
return {
|
|
954
|
-
...state,
|
|
955
|
-
libraryIds: state.libraryIds.filter((id) => id !== action.payload)
|
|
956
|
-
};
|
|
957
|
-
default:
|
|
958
|
-
return state;
|
|
959
|
-
}
|
|
26
|
+
const kindToPdfName = (kind) => kind === "signature" ? "Signature" : kind === "initials" ? "Initials" : "Stamp";
|
|
27
|
+
const kindFromPdfName = (name) => {
|
|
28
|
+
switch (name?.toLowerCase()) {
|
|
29
|
+
case "stamp": return "stamp";
|
|
30
|
+
case "signature": return "signature";
|
|
31
|
+
case "initials": return "initials";
|
|
32
|
+
default: return;
|
|
33
|
+
}
|
|
960
34
|
};
|
|
961
|
-
const
|
|
962
|
-
|
|
963
|
-
|
|
964
|
-
|
|
965
|
-
|
|
966
|
-
|
|
967
|
-
|
|
968
|
-
|
|
969
|
-
|
|
970
|
-
|
|
971
|
-
|
|
972
|
-
|
|
973
|
-
|
|
974
|
-
|
|
975
|
-
|
|
976
|
-
|
|
977
|
-
|
|
978
|
-
|
|
35
|
+
const metadataPatch = (id, name, kind, subject, categories) => ({
|
|
36
|
+
Version: STAMP_SCHEMA_VERSION,
|
|
37
|
+
Id: id,
|
|
38
|
+
Name: name,
|
|
39
|
+
Kind: { name: kindToPdfName(kind) },
|
|
40
|
+
Subject: subject ?? null,
|
|
41
|
+
Categories: categories ?? null
|
|
42
|
+
});
|
|
43
|
+
const libraryMetadataPatch = (id, name, categories) => ({
|
|
44
|
+
Version: STAMP_SCHEMA_VERSION,
|
|
45
|
+
Id: id,
|
|
46
|
+
Name: name,
|
|
47
|
+
Kind: { name: "StampLibrary" },
|
|
48
|
+
Categories: categories ?? null
|
|
49
|
+
});
|
|
50
|
+
function createStampCapability(ctx, config = {}) {
|
|
51
|
+
/** Binary sidecar of the serializable store: asset bytes + cached preview,
|
|
52
|
+
* keyed by asset id. The reducer never sees these (kernel rule 1). */
|
|
53
|
+
const binaries = /* @__PURE__ */ new Map();
|
|
54
|
+
/** The durable source of truth for a canonical library. Asset PDFs above
|
|
55
|
+
* are derived page extractions; this map owns the rewritten whole PDF. */
|
|
56
|
+
const libraryBinaries = /* @__PURE__ */ new Map();
|
|
57
|
+
/** Whole-PDF rewrites must be serialized per library or two concurrent
|
|
58
|
+
* appends could both start from the same bytes and lose one page. */
|
|
59
|
+
const libraryMutationTails = /* @__PURE__ */ new Map();
|
|
60
|
+
const mutateLibrary = (libraryId, mutation) => {
|
|
61
|
+
const result = (libraryMutationTails.get(libraryId) ?? Promise.resolve()).then(mutation);
|
|
62
|
+
const tail = result.then(() => void 0, () => void 0);
|
|
63
|
+
libraryMutationTails.set(libraryId, tail);
|
|
64
|
+
return result.finally(() => {
|
|
65
|
+
if (libraryMutationTails.get(libraryId) === tail) libraryMutationTails.delete(libraryId);
|
|
66
|
+
});
|
|
67
|
+
};
|
|
68
|
+
let assetEngineRef = null;
|
|
69
|
+
const assetEngine = () => {
|
|
70
|
+
if (!assetEngineRef) {
|
|
71
|
+
const cfg = config.assetEngine;
|
|
72
|
+
assetEngineRef = !cfg ? ctx.engine : typeof cfg === "function" ? cfg() : cfg;
|
|
73
|
+
}
|
|
74
|
+
return assetEngineRef;
|
|
75
|
+
};
|
|
76
|
+
const openAssetDocument = async (bytes, identity) => {
|
|
77
|
+
try {
|
|
78
|
+
return await (await assetEngine()).open({
|
|
79
|
+
kind: "bytes",
|
|
80
|
+
id: uid("stamp-import"),
|
|
81
|
+
bytes
|
|
82
|
+
}, {
|
|
83
|
+
scope: ["*"],
|
|
84
|
+
...identity ? { identity } : {}
|
|
85
|
+
});
|
|
86
|
+
} catch (err) {
|
|
87
|
+
if (!config.assetEngine && EngineError.is(err, EngineErrorCode.InvalidArg)) throw new EngineError(EngineErrorCode.NotImplemented, "[stamp] importing a library PDF needs an engine that can open local bytes, and this viewer's engine cannot (cloud). Pass stampPlugin({ assetEngine: () => import('@embedpdf/engine').then((m) => m.createLocalEngine()) }) — it loads lazily, on first import.");
|
|
88
|
+
throw err;
|
|
89
|
+
}
|
|
90
|
+
};
|
|
91
|
+
/** Import-time preview render → bytes. The asset engine is local by
|
|
92
|
+
* definition, so the image source is always inline bytes. */
|
|
93
|
+
const imageToPreview = (image) => {
|
|
94
|
+
if (image.source.kind !== "bytes") throw new EngineError(EngineErrorCode.NotImplemented, "[stamp] asset engine returned a URL-sourced render; asset engines must be local");
|
|
95
|
+
return {
|
|
96
|
+
bytes: image.source.bytes,
|
|
97
|
+
mimeType: image.contentType
|
|
98
|
+
};
|
|
99
|
+
};
|
|
100
|
+
const allocateId = (preferred, prefix, taken) => {
|
|
101
|
+
let id = preferred;
|
|
102
|
+
if (!id || taken.has(id)) do
|
|
103
|
+
id = uid(prefix);
|
|
104
|
+
while (taken.has(id));
|
|
105
|
+
taken.add(id);
|
|
106
|
+
return id;
|
|
107
|
+
};
|
|
108
|
+
const createLooseLibrary = (name) => {
|
|
109
|
+
const id = uid("stamp-lib");
|
|
110
|
+
ctx.dispatch({
|
|
111
|
+
type: "LIBRARY_ADDED",
|
|
112
|
+
library: {
|
|
113
|
+
id,
|
|
114
|
+
name,
|
|
115
|
+
storage: "loose",
|
|
116
|
+
assetIds: []
|
|
117
|
+
}
|
|
118
|
+
});
|
|
119
|
+
return id;
|
|
120
|
+
};
|
|
121
|
+
const importLibraryPdf = async (source, opts) => {
|
|
122
|
+
const resolved = await resolveBinarySource(source);
|
|
123
|
+
if (sniffBinaryMetadata(resolved.bytes)?.mimeType !== "application/pdf") throw new EngineError(EngineErrorCode.InvalidArg, "[stamp] importLibraryPdf needs PDF bytes (use addAsset for a raster image)");
|
|
124
|
+
const doc = await openAssetDocument(new Uint8Array(resolved.bytes));
|
|
125
|
+
let imported;
|
|
126
|
+
try {
|
|
127
|
+
if (!doc.pieceInfo || !doc.pages.extract) throw new EngineError(EngineErrorCode.NotImplemented, "[stamp] canonical PDF libraries need an asset engine with pieceInfo and pages.extract");
|
|
128
|
+
const snapshot = await doc.pages.list();
|
|
129
|
+
if (snapshot.pageCount === 0) throw new EngineError(EngineErrorCode.InvalidArg, "[stamp] a canonical stamp library PDF must contain at least one page");
|
|
130
|
+
const pages = snapshot.pages.map((layout) => {
|
|
131
|
+
const handle = doc.page(layout.pageObjectNumber);
|
|
132
|
+
if (!handle.pieceInfo) throw new EngineError(EngineErrorCode.NotImplemented, "[stamp] canonical PDF libraries need page pieceInfo support");
|
|
133
|
+
return {
|
|
134
|
+
layout,
|
|
135
|
+
handle,
|
|
136
|
+
metadata: null
|
|
137
|
+
};
|
|
138
|
+
});
|
|
139
|
+
const catalogMetadata = await doc.pieceInfo.read(LIBRARY_PIECEINFO_APP);
|
|
140
|
+
for (const page of pages) page.metadata = await page.handle.pieceInfo.read(STAMP_PIECEINFO_APP);
|
|
141
|
+
const state = ctx.getState();
|
|
142
|
+
const takenLibraryIds = new Set(Object.keys(state.libraries));
|
|
143
|
+
const takenAssetIds = new Set(Object.keys(state.assets));
|
|
144
|
+
const catalogEntries = catalogMetadata?.entries ?? {};
|
|
145
|
+
const libraryId = allocateId(entryString(catalogEntries, "Id"), "stamp-lib", takenLibraryIds);
|
|
146
|
+
const libraryName = opts?.name ?? entryString(catalogEntries, "Name") ?? "Stamps";
|
|
147
|
+
const libraryCategories = opts?.categories ?? entryStringArray(catalogEntries, "Categories");
|
|
148
|
+
const descriptors = pages.map(({ layout, handle, metadata: pageMetadata }) => {
|
|
149
|
+
const entries = pageMetadata?.entries ?? {};
|
|
150
|
+
const id = allocateId(entryString(entries, "Id"), "stamp", takenAssetIds);
|
|
151
|
+
const kind = opts?.kind ?? kindFromPdfName(entryName(entries, "Kind")) ?? "stamp";
|
|
152
|
+
return {
|
|
153
|
+
handle,
|
|
154
|
+
asset: {
|
|
155
|
+
id,
|
|
156
|
+
libraryId,
|
|
157
|
+
kind,
|
|
158
|
+
name: opts?.assetName?.(layout.index) ?? entryString(entries, "Name") ?? `Stamp ${layout.index + 1}`,
|
|
159
|
+
size: {
|
|
160
|
+
width: layout.size.width,
|
|
161
|
+
height: layout.size.height
|
|
162
|
+
},
|
|
163
|
+
format: "pdf",
|
|
164
|
+
pageObjectNumber: layout.pageObjectNumber,
|
|
165
|
+
subject: entryString(entries, "Subject"),
|
|
166
|
+
categories: entryStringArray(entries, "Categories")
|
|
167
|
+
}
|
|
168
|
+
};
|
|
169
|
+
});
|
|
170
|
+
await doc.pieceInfo.update(LIBRARY_PIECEINFO_APP, libraryMetadataPatch(libraryId, libraryName, libraryCategories));
|
|
171
|
+
const assets = [];
|
|
172
|
+
for (const descriptor of descriptors) {
|
|
173
|
+
const { asset, handle } = descriptor;
|
|
174
|
+
await handle.pieceInfo.update(STAMP_PIECEINFO_APP, metadataPatch(asset.id, asset.name, asset.kind, asset.subject, asset.categories));
|
|
175
|
+
const bytes = await doc.pages.extract([asset.pageObjectNumber]);
|
|
176
|
+
const image = await handle.render.image({
|
|
177
|
+
viewport: {
|
|
178
|
+
kind: "width",
|
|
179
|
+
width: config.previewWidth ?? DEFAULT_PREVIEW_WIDTH
|
|
180
|
+
},
|
|
181
|
+
background: "transparent",
|
|
182
|
+
includeAnnotations: true,
|
|
183
|
+
format: "png"
|
|
184
|
+
});
|
|
185
|
+
assets.push({
|
|
186
|
+
asset,
|
|
187
|
+
bytes,
|
|
188
|
+
preview: imageToPreview(image)
|
|
189
|
+
});
|
|
190
|
+
}
|
|
191
|
+
imported = {
|
|
192
|
+
library: {
|
|
193
|
+
id: libraryId,
|
|
194
|
+
name: libraryName,
|
|
195
|
+
storage: "canonical-pdf",
|
|
196
|
+
categories: libraryCategories,
|
|
197
|
+
assetIds: []
|
|
198
|
+
},
|
|
199
|
+
canonicalBytes: await doc.download(),
|
|
200
|
+
assets
|
|
201
|
+
};
|
|
202
|
+
} finally {
|
|
203
|
+
await doc.close();
|
|
204
|
+
}
|
|
205
|
+
libraryBinaries.set(imported.library.id, imported.canonicalBytes);
|
|
206
|
+
ctx.dispatch({
|
|
207
|
+
type: "LIBRARY_ADDED",
|
|
208
|
+
library: imported.library
|
|
209
|
+
});
|
|
210
|
+
for (const { asset, bytes, preview } of imported.assets) {
|
|
211
|
+
binaries.set(asset.id, {
|
|
212
|
+
bytes,
|
|
213
|
+
preview
|
|
214
|
+
});
|
|
215
|
+
ctx.dispatch({
|
|
216
|
+
type: "ASSET_ADDED",
|
|
217
|
+
asset
|
|
218
|
+
});
|
|
219
|
+
}
|
|
220
|
+
return imported.library.id;
|
|
221
|
+
};
|
|
222
|
+
const addAsset = async (input) => {
|
|
223
|
+
const targetLibrary = input.libraryId ? ctx.getState().libraries[input.libraryId] ?? null : null;
|
|
224
|
+
if (input.libraryId && !targetLibrary) throw new EngineError(EngineErrorCode.NotFound, `[stamp] unknown library '${input.libraryId}'`);
|
|
225
|
+
const resolved = await resolveBinarySource(input.source);
|
|
226
|
+
const meta = sniffBinaryMetadata(resolved.bytes);
|
|
227
|
+
if (!meta) throw new EngineError(EngineErrorCode.InvalidArg, "[stamp] asset source must be PNG, JPEG, or single-page PDF bytes");
|
|
228
|
+
const isPdf = meta.mimeType === "application/pdf";
|
|
229
|
+
let suppliedPreview = null;
|
|
230
|
+
if (input.preview) {
|
|
231
|
+
const preview = await resolveBinarySource(input.preview);
|
|
232
|
+
suppliedPreview = {
|
|
233
|
+
bytes: new Uint8Array(preview.bytes),
|
|
234
|
+
mimeType: preview.mimeType ?? "image/png"
|
|
235
|
+
};
|
|
236
|
+
}
|
|
237
|
+
if (targetLibrary?.storage === "canonical-pdf") {
|
|
238
|
+
if (!isPdf) throw new EngineError(EngineErrorCode.InvalidArg, "[stamp] raster assets cannot be appended to a canonical PDF library");
|
|
239
|
+
return mutateLibrary(targetLibrary.id, async () => {
|
|
240
|
+
const liveLibrary = ctx.getState().libraries[targetLibrary.id];
|
|
241
|
+
if (liveLibrary?.storage !== "canonical-pdf") throw new EngineError(EngineErrorCode.NotFound, `[stamp] canonical library '${targetLibrary.id}' no longer exists`);
|
|
242
|
+
const canonicalBytes = libraryBinaries.get(liveLibrary.id);
|
|
243
|
+
if (!canonicalBytes) throw new EngineError(EngineErrorCode.Unknown, `[stamp] canonical bytes are missing for library '${liveLibrary.id}'`);
|
|
244
|
+
const assetId = uid("stamp");
|
|
245
|
+
const doc = await openAssetDocument(canonicalBytes);
|
|
246
|
+
let appended;
|
|
247
|
+
try {
|
|
248
|
+
if (!doc.pages.insert || !doc.pages.extract) throw new EngineError(EngineErrorCode.NotImplemented, "[stamp] appending to a canonical library needs pages.insert and pages.extract");
|
|
249
|
+
const result = await doc.pages.insert(new Uint8Array(resolved.bytes));
|
|
250
|
+
if (result.insertedPageObjectNumbers.length !== 1) throw new EngineError(EngineErrorCode.InvalidArg, "[stamp] a canonical library asset must be a single-page PDF");
|
|
251
|
+
const pageObjectNumber = result.insertedPageObjectNumbers[0];
|
|
252
|
+
const layout = result.layout.pages.find((candidate) => candidate.pageObjectNumber === pageObjectNumber);
|
|
253
|
+
const page = doc.page(pageObjectNumber);
|
|
254
|
+
if (!layout || !page.pieceInfo) throw new EngineError(EngineErrorCode.NotImplemented, "[stamp] canonical PDF libraries need page layout and pieceInfo support");
|
|
255
|
+
const asset = {
|
|
256
|
+
id: assetId,
|
|
257
|
+
libraryId: liveLibrary.id,
|
|
258
|
+
kind: input.kind ?? "stamp",
|
|
259
|
+
name: input.name,
|
|
260
|
+
size: {
|
|
261
|
+
width: layout.size.width,
|
|
262
|
+
height: layout.size.height
|
|
263
|
+
},
|
|
264
|
+
format: "pdf",
|
|
265
|
+
pageObjectNumber,
|
|
266
|
+
subject: input.subject,
|
|
267
|
+
categories: input.categories
|
|
268
|
+
};
|
|
269
|
+
await page.pieceInfo.update(STAMP_PIECEINFO_APP, metadataPatch(asset.id, asset.name, asset.kind, asset.subject, asset.categories));
|
|
270
|
+
const bytes = await doc.pages.extract([pageObjectNumber]);
|
|
271
|
+
let preview = suppliedPreview;
|
|
272
|
+
if (!preview) preview = imageToPreview(await page.render.image({
|
|
273
|
+
viewport: {
|
|
274
|
+
kind: "width",
|
|
275
|
+
width: config.previewWidth ?? DEFAULT_PREVIEW_WIDTH
|
|
276
|
+
},
|
|
277
|
+
background: "transparent",
|
|
278
|
+
includeAnnotations: true,
|
|
279
|
+
format: "png"
|
|
280
|
+
}));
|
|
281
|
+
appended = {
|
|
282
|
+
asset,
|
|
283
|
+
bytes,
|
|
284
|
+
preview,
|
|
285
|
+
canonical: await doc.download()
|
|
286
|
+
};
|
|
287
|
+
} finally {
|
|
288
|
+
await doc.close();
|
|
289
|
+
}
|
|
290
|
+
libraryBinaries.set(liveLibrary.id, appended.canonical);
|
|
291
|
+
binaries.set(appended.asset.id, {
|
|
292
|
+
bytes: appended.bytes,
|
|
293
|
+
preview: appended.preview
|
|
294
|
+
});
|
|
295
|
+
ctx.dispatch({
|
|
296
|
+
type: "ASSET_ADDED",
|
|
297
|
+
asset: appended.asset
|
|
298
|
+
});
|
|
299
|
+
return appended.asset.id;
|
|
300
|
+
});
|
|
301
|
+
}
|
|
302
|
+
const size = input.size ?? ("width" in meta ? {
|
|
303
|
+
width: meta.width,
|
|
304
|
+
height: meta.height
|
|
305
|
+
} : null);
|
|
306
|
+
if (!size) throw new EngineError(EngineErrorCode.InvalidArg, "[stamp] a PDF asset needs `size` (its page size in points) — PDF bytes carry no sniffable dimensions");
|
|
307
|
+
let preview = suppliedPreview;
|
|
308
|
+
if (!preview && !isPdf) preview = {
|
|
309
|
+
bytes: new Uint8Array(resolved.bytes),
|
|
310
|
+
mimeType: meta.mimeType
|
|
311
|
+
};
|
|
312
|
+
const commitLooseAsset = (libraryId) => {
|
|
313
|
+
const asset = {
|
|
314
|
+
id: uid("stamp"),
|
|
315
|
+
libraryId,
|
|
316
|
+
kind: input.kind ?? "stamp",
|
|
317
|
+
name: input.name,
|
|
318
|
+
size,
|
|
319
|
+
format: isPdf ? "pdf" : meta.mimeType === "image/png" ? "png" : "jpeg",
|
|
320
|
+
subject: input.subject,
|
|
321
|
+
categories: input.categories
|
|
322
|
+
};
|
|
323
|
+
binaries.set(asset.id, {
|
|
324
|
+
bytes: new Uint8Array(resolved.bytes),
|
|
325
|
+
preview
|
|
326
|
+
});
|
|
327
|
+
ctx.dispatch({
|
|
328
|
+
type: "ASSET_ADDED",
|
|
329
|
+
asset
|
|
330
|
+
});
|
|
331
|
+
return asset.id;
|
|
332
|
+
};
|
|
333
|
+
if (!targetLibrary) return commitLooseAsset(createLooseLibrary(input.name));
|
|
334
|
+
return mutateLibrary(targetLibrary.id, async () => {
|
|
335
|
+
const liveLibrary = ctx.getState().libraries[targetLibrary.id];
|
|
336
|
+
if (liveLibrary?.storage !== "loose") throw new EngineError(EngineErrorCode.NotFound, `[stamp] loose library '${targetLibrary.id}' no longer exists`);
|
|
337
|
+
return commitLooseAsset(liveLibrary.id);
|
|
338
|
+
});
|
|
339
|
+
};
|
|
340
|
+
const dropLibrary = (id) => {
|
|
341
|
+
const library = ctx.getState().libraries[id];
|
|
342
|
+
if (library) for (const assetId of library.assetIds) binaries.delete(assetId);
|
|
343
|
+
libraryBinaries.delete(id);
|
|
344
|
+
ctx.dispatch({
|
|
345
|
+
type: "LIBRARY_REMOVED",
|
|
346
|
+
libraryId: id
|
|
347
|
+
});
|
|
348
|
+
};
|
|
349
|
+
const removeAsset = async (id) => {
|
|
350
|
+
const initialAsset = ctx.getState().assets[id];
|
|
351
|
+
if (!initialAsset) return;
|
|
352
|
+
return mutateLibrary(initialAsset.libraryId, async () => {
|
|
353
|
+
const asset = ctx.getState().assets[id];
|
|
354
|
+
if (!asset) return;
|
|
355
|
+
const library = ctx.getState().libraries[asset.libraryId];
|
|
356
|
+
if (library?.storage === "canonical-pdf") {
|
|
357
|
+
if (library.assetIds.length === 1) {
|
|
358
|
+
dropLibrary(library.id);
|
|
359
|
+
return;
|
|
360
|
+
}
|
|
361
|
+
if (asset.pageObjectNumber === void 0) throw new EngineError(EngineErrorCode.Unknown, `[stamp] canonical asset '${id}' has no page object number`);
|
|
362
|
+
const canonicalBytes = libraryBinaries.get(library.id);
|
|
363
|
+
if (!canonicalBytes) throw new EngineError(EngineErrorCode.Unknown, `[stamp] canonical bytes are missing for library '${library.id}'`);
|
|
364
|
+
const doc = await openAssetDocument(canonicalBytes);
|
|
365
|
+
let rewritten;
|
|
366
|
+
try {
|
|
367
|
+
await doc.pages.delete([asset.pageObjectNumber]);
|
|
368
|
+
rewritten = await doc.download();
|
|
369
|
+
} finally {
|
|
370
|
+
await doc.close();
|
|
371
|
+
}
|
|
372
|
+
libraryBinaries.set(library.id, rewritten);
|
|
373
|
+
}
|
|
374
|
+
binaries.delete(id);
|
|
375
|
+
ctx.dispatch({
|
|
376
|
+
type: "ASSET_REMOVED",
|
|
377
|
+
assetId: id
|
|
378
|
+
});
|
|
379
|
+
});
|
|
380
|
+
};
|
|
381
|
+
const removeLibrary = (id) => mutateLibrary(id, async () => dropLibrary(id));
|
|
382
|
+
const surfaceScriptingResult = (result) => {
|
|
383
|
+
try {
|
|
384
|
+
for (const effect of result.uiEffects) config.scripting?.onUiEffect?.(effect);
|
|
385
|
+
for (const diagnostic of result.diagnostics) config.scripting?.onDiagnostic?.(diagnostic);
|
|
386
|
+
if (result.error) config.scripting?.onError?.(result.error);
|
|
387
|
+
} catch (error) {
|
|
388
|
+
globalThis.console?.error("[stamp] scripting observer failed:", error);
|
|
389
|
+
}
|
|
390
|
+
};
|
|
391
|
+
/**
|
|
392
|
+
* Form-backed stamps are executable templates only until placement. Work
|
|
393
|
+
* on an isolated copy so neither the canonical library nor its reusable
|
|
394
|
+
* per-page extraction becomes target/user/time specific.
|
|
395
|
+
*/
|
|
396
|
+
const materializeForPlacement = async (documentId, asset, bin) => {
|
|
397
|
+
if (asset.format !== "pdf" || !config.scripting?.enabled) return bin;
|
|
398
|
+
const targetDoc = ctx.documentHandle(documentId);
|
|
399
|
+
const targetMeta = ctx.core().documents[documentId] ?? null;
|
|
400
|
+
if (!targetDoc || !targetMeta) throw new EngineError(EngineErrorCode.NotFound, `[stamp] target document '${documentId}' is not open`);
|
|
401
|
+
const canonicalBytes = libraryBinaries.get(asset.libraryId);
|
|
402
|
+
const sourceBytes = canonicalBytes ?? bin.bytes;
|
|
403
|
+
const doc = await openAssetDocument(new Uint8Array(sourceBytes), targetDoc.security.identity);
|
|
404
|
+
let scripting = null;
|
|
405
|
+
try {
|
|
406
|
+
const layout = await doc.pages.list();
|
|
407
|
+
const selectedPage = asset.pageObjectNumber === void 0 ? layout.pageCount === 1 ? layout.pages[0] : void 0 : layout.pages.find(({ pageObjectNumber }) => pageObjectNumber === asset.pageObjectNumber);
|
|
408
|
+
if (!selectedPage) throw new EngineError(EngineErrorCode.InvalidArg, canonicalBytes ? `[stamp] canonical page ${asset.pageObjectNumber ?? "unknown"} no longer exists` : "[stamp] a loose dynamic stamp asset must contain exactly one page");
|
|
409
|
+
const snapshot = await doc.forms.list();
|
|
410
|
+
if (!snapshot.fields.some((field) => field.widgets.some(({ pageObjectNumber }) => pageObjectNumber === selectedPage.pageObjectNumber))) return bin;
|
|
411
|
+
if (!doc.pages.flatten || !doc.pages.extract) throw new EngineError(EngineErrorCode.NotImplemented, "[stamp] dynamic PDF stamps need an asset engine with pages.flatten and pages.extract");
|
|
412
|
+
const scriptingConfig = config.scripting;
|
|
413
|
+
scripting = createFormScriptingController({
|
|
414
|
+
doc,
|
|
415
|
+
document: () => targetMeta,
|
|
416
|
+
config: scriptingConfig,
|
|
417
|
+
sandboxFactory: scriptingConfig.sandboxFactory ?? (() => import("@embedpdf/core-js-sandbox").then(({ createQuickJsSandbox }) => createQuickJsSandbox()))
|
|
418
|
+
});
|
|
419
|
+
const result = await scripting.recalculate(snapshot);
|
|
420
|
+
surfaceScriptingResult(result);
|
|
421
|
+
if (result.status === "failed") throw new EngineError(EngineErrorCode.Unknown, `[stamp] dynamic stamp scripting failed: ${result.error?.message ?? "native form effect failed"}`);
|
|
422
|
+
const pageObjectNumber = selectedPage.pageObjectNumber;
|
|
423
|
+
const failed = (await doc.pages.flatten([pageObjectNumber], "display")).results.find(({ status }) => status === "failed" || status === "skipped");
|
|
424
|
+
if (failed) throw new EngineError(EngineErrorCode.Unknown, `[stamp] dynamic stamp flatten failed for page ${failed.pageObjectNumber}`);
|
|
425
|
+
const bytes = await doc.pages.extract([pageObjectNumber]);
|
|
426
|
+
const image = await doc.page(pageObjectNumber).render.image({
|
|
427
|
+
viewport: {
|
|
428
|
+
kind: "width",
|
|
429
|
+
width: config.previewWidth ?? DEFAULT_PREVIEW_WIDTH
|
|
430
|
+
},
|
|
431
|
+
background: "transparent",
|
|
432
|
+
includeAnnotations: false,
|
|
433
|
+
format: "png"
|
|
434
|
+
});
|
|
435
|
+
return {
|
|
436
|
+
bytes,
|
|
437
|
+
preview: imageToPreview(image)
|
|
438
|
+
};
|
|
439
|
+
} finally {
|
|
440
|
+
scripting?.dispose();
|
|
441
|
+
await doc.close();
|
|
442
|
+
}
|
|
443
|
+
};
|
|
444
|
+
const armAsset = async (documentId, assetId, opts) => {
|
|
445
|
+
const asset = ctx.getState().assets[assetId];
|
|
446
|
+
const bin = binaries.get(assetId);
|
|
447
|
+
if (!asset || !bin) throw new EngineError(EngineErrorCode.NotFound, `[stamp] unknown asset '${assetId}'`);
|
|
448
|
+
const placement = await materializeForPlacement(documentId, asset, bin);
|
|
449
|
+
await ctx.forDocument(AnnotationToken, documentId).armStamp({
|
|
450
|
+
source: placement.bytes,
|
|
451
|
+
preview: placement.preview ? {
|
|
452
|
+
data: placement.preview.bytes,
|
|
453
|
+
mimeType: placement.preview.mimeType
|
|
454
|
+
} : void 0,
|
|
455
|
+
intrinsicSize: asset.size,
|
|
456
|
+
targetWidth: opts?.targetWidth
|
|
457
|
+
});
|
|
458
|
+
};
|
|
459
|
+
ctx.cleanup(() => {
|
|
460
|
+
binaries.clear();
|
|
461
|
+
libraryBinaries.clear();
|
|
462
|
+
libraryMutationTails.clear();
|
|
463
|
+
const ownedAssetEngine = typeof config.assetEngine === "function" ? assetEngineRef : null;
|
|
464
|
+
assetEngineRef = null;
|
|
465
|
+
if (ownedAssetEngine) Promise.resolve(ownedAssetEngine).then((engine) => engine.destroy()).catch(() => {});
|
|
466
|
+
});
|
|
467
|
+
return {
|
|
468
|
+
libraries: () => {
|
|
469
|
+
const s = ctx.getState();
|
|
470
|
+
return s.libraryOrder.map((id) => s.libraries[id]).filter((l) => l != null);
|
|
471
|
+
},
|
|
472
|
+
library: (id) => ctx.getState().libraries[id] ?? null,
|
|
473
|
+
assets: (libraryId) => {
|
|
474
|
+
const s = ctx.getState();
|
|
475
|
+
if (libraryId) {
|
|
476
|
+
const library = s.libraries[libraryId];
|
|
477
|
+
return library ? library.assetIds.map((id) => s.assets[id]).filter((a) => a != null) : [];
|
|
478
|
+
}
|
|
479
|
+
return s.libraryOrder.flatMap((lid) => (s.libraries[lid]?.assetIds ?? []).map((id) => s.assets[id]).filter((a) => a != null));
|
|
480
|
+
},
|
|
481
|
+
asset: (id) => ctx.getState().assets[id] ?? null,
|
|
482
|
+
assetPreview: (id) => binaries.get(id)?.preview ?? null,
|
|
483
|
+
assetBytes: (id) => binaries.get(id)?.bytes ?? null,
|
|
484
|
+
libraryBytes: (id) => libraryBinaries.get(id) ?? null,
|
|
485
|
+
importLibraryPdf,
|
|
486
|
+
addAsset,
|
|
487
|
+
removeAsset,
|
|
488
|
+
removeLibrary,
|
|
489
|
+
armAsset,
|
|
490
|
+
disarm: (documentId) => ctx.forDocument(AnnotationToken, documentId).disarmStamp()
|
|
491
|
+
};
|
|
492
|
+
}
|
|
493
|
+
//#endregion
|
|
494
|
+
//#region src/reducer.ts
|
|
495
|
+
const initialStampState = () => ({
|
|
496
|
+
libraries: {},
|
|
497
|
+
libraryOrder: [],
|
|
498
|
+
assets: {}
|
|
499
|
+
});
|
|
500
|
+
/** Pure and serializable (kernel rule 1) — descriptors only; the bytes and
|
|
501
|
+
* previews live in the capability and never enter the store. */
|
|
502
|
+
const stampReducer = (state, action) => {
|
|
503
|
+
switch (action.type) {
|
|
504
|
+
case "LIBRARY_ADDED": return {
|
|
505
|
+
...state,
|
|
506
|
+
libraries: {
|
|
507
|
+
...state.libraries,
|
|
508
|
+
[action.library.id]: action.library
|
|
509
|
+
},
|
|
510
|
+
libraryOrder: [...state.libraryOrder, action.library.id]
|
|
511
|
+
};
|
|
512
|
+
case "LIBRARY_REMOVED": {
|
|
513
|
+
const library = state.libraries[action.libraryId];
|
|
514
|
+
if (!library) return state;
|
|
515
|
+
const libraries = { ...state.libraries };
|
|
516
|
+
delete libraries[action.libraryId];
|
|
517
|
+
const assets = { ...state.assets };
|
|
518
|
+
for (const id of library.assetIds) delete assets[id];
|
|
519
|
+
return {
|
|
520
|
+
...state,
|
|
521
|
+
libraries,
|
|
522
|
+
libraryOrder: state.libraryOrder.filter((id) => id !== action.libraryId),
|
|
523
|
+
assets
|
|
524
|
+
};
|
|
525
|
+
}
|
|
526
|
+
case "ASSET_ADDED": {
|
|
527
|
+
const library = state.libraries[action.asset.libraryId];
|
|
528
|
+
if (!library) return state;
|
|
529
|
+
return {
|
|
530
|
+
...state,
|
|
531
|
+
assets: {
|
|
532
|
+
...state.assets,
|
|
533
|
+
[action.asset.id]: action.asset
|
|
534
|
+
},
|
|
535
|
+
libraries: {
|
|
536
|
+
...state.libraries,
|
|
537
|
+
[library.id]: {
|
|
538
|
+
...library,
|
|
539
|
+
assetIds: [...library.assetIds, action.asset.id]
|
|
540
|
+
}
|
|
541
|
+
}
|
|
542
|
+
};
|
|
543
|
+
}
|
|
544
|
+
case "ASSET_REMOVED": {
|
|
545
|
+
const asset = state.assets[action.assetId];
|
|
546
|
+
if (!asset) return state;
|
|
547
|
+
const assets = { ...state.assets };
|
|
548
|
+
delete assets[action.assetId];
|
|
549
|
+
const library = state.libraries[asset.libraryId];
|
|
550
|
+
return {
|
|
551
|
+
...state,
|
|
552
|
+
assets,
|
|
553
|
+
libraries: library ? {
|
|
554
|
+
...state.libraries,
|
|
555
|
+
[library.id]: {
|
|
556
|
+
...library,
|
|
557
|
+
assetIds: library.assetIds.filter((id) => id !== action.assetId)
|
|
558
|
+
}
|
|
559
|
+
} : state.libraries
|
|
560
|
+
};
|
|
561
|
+
}
|
|
562
|
+
default: return state;
|
|
563
|
+
}
|
|
979
564
|
};
|
|
980
|
-
//#
|
|
565
|
+
//#endregion
|
|
566
|
+
//#region src/types.ts
|
|
567
|
+
const StampToken = createCapabilityToken("stamp");
|
|
568
|
+
//#endregion
|
|
569
|
+
//#region src/stamp.plugin.ts
|
|
570
|
+
/**
|
|
571
|
+
* The stamp plugin. WORKSPACE-scoped: libraries are shared across every open
|
|
572
|
+
* document; placement names its document explicitly (`armAsset(documentId, …)`)
|
|
573
|
+
* and delegates to that document's annotation plugin — no hard `requires`,
|
|
574
|
+
* because the annotation token is document-scoped and resolved lazily per
|
|
575
|
+
* placement (a viewer without the annotation plugin fails at the arm call,
|
|
576
|
+
* with the kernel's own missing-capability error).
|
|
577
|
+
*/
|
|
578
|
+
const stampPlugin = (config = {}) => definePlugin({
|
|
579
|
+
id: "stamp",
|
|
580
|
+
token: StampToken,
|
|
581
|
+
scope: "workspace",
|
|
582
|
+
initialState: initialStampState,
|
|
583
|
+
reduce: stampReducer,
|
|
584
|
+
capability: (ctx) => createStampCapability(ctx, config)
|
|
585
|
+
});
|
|
586
|
+
//#endregion
|
|
587
|
+
export { StampToken, stampPlugin };
|
|
588
|
+
|
|
589
|
+
//# sourceMappingURL=index.js.map
|