@embedpdf/plugin-stamp 3.0.0-next.11 → 3.0.0-next.12
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/contract-BFCBVNRX.js +69 -0
- package/dist/contract-BFCBVNRX.js.map +1 -0
- package/dist/contract-C6CgKD69.cjs +134 -0
- package/dist/contract-C6CgKD69.cjs.map +1 -0
- package/dist/contract-PLwYe8Zn.d.cts +322 -0
- package/dist/contract-WXRZPOWE.d.ts +322 -0
- package/dist/contract.cjs +11 -1
- package/dist/contract.d.cts +2 -2
- package/dist/contract.d.ts +2 -2
- package/dist/contract.js +2 -2
- package/dist/index.cjs +502 -217
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +2 -2
- package/dist/index.d.ts +2 -2
- package/dist/index.js +491 -219
- package/dist/index.js.map +1 -1
- package/package.json +12 -10
- package/dist/contract-BaN6-ypU.d.cts +0 -190
- package/dist/contract-BaN6-ypU.d.ts +0 -190
- package/dist/contract-CkPaVNAJ.js +0 -7
- package/dist/contract-CkPaVNAJ.js.map +0 -1
- package/dist/contract-DmQ4peF2.cjs +0 -11
- package/dist/contract-DmQ4peF2.cjs.map +0 -1
package/dist/index.cjs
CHANGED
|
@@ -1,14 +1,43 @@
|
|
|
1
1
|
Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
|
|
2
|
-
const require_contract = require("./contract-
|
|
2
|
+
const require_contract = require("./contract-C6CgKD69.cjs");
|
|
3
3
|
let _embedpdf_core = require("@embedpdf/core");
|
|
4
4
|
let _embedpdf_engine_core_runtime = require("@embedpdf/engine-core/runtime");
|
|
5
|
+
let _embedpdf_core_acrojs = require("@embedpdf/core-acrojs");
|
|
6
|
+
let _embedpdf_plugin_actions_contract_host = require("@embedpdf/plugin-actions/contract/host");
|
|
5
7
|
let _embedpdf_plugin_annotation_contract = require("@embedpdf/plugin-annotation/contract");
|
|
6
8
|
let _embedpdf_plugin_form_scripting = require("@embedpdf/plugin-form/scripting");
|
|
9
|
+
//#region src/blank-library.ts
|
|
10
|
+
/**
|
|
11
|
+
* The seed of a new library: a one-page PDF whose page is UNREGISTERED — the
|
|
12
|
+
* shape Acrobat itself gives a fresh stamp library (its first page is a
|
|
13
|
+
* blank starter that no `/Names /Pages` key points at). A library must be a
|
|
14
|
+
* PDF from the moment it exists, and a PDF must have a page; the unregistered
|
|
15
|
+
* blank is that page. It is never an asset, never placed, never listed.
|
|
16
|
+
*/
|
|
17
|
+
const BLANK_OBJECTS = [
|
|
18
|
+
"<< /Type /Catalog /Pages 2 0 R >>",
|
|
19
|
+
"<< /Type /Pages /Kids [3 0 R] /Count 1 >>",
|
|
20
|
+
"<< /Type /Page /Parent 2 0 R /MediaBox [0 0 612 792] >>"
|
|
21
|
+
];
|
|
22
|
+
let cached = null;
|
|
23
|
+
function blankLibraryPdf() {
|
|
24
|
+
if (cached) return new Uint8Array(cached);
|
|
25
|
+
let body = "%PDF-1.7\n";
|
|
26
|
+
const offsets = [];
|
|
27
|
+
BLANK_OBJECTS.forEach((object, index) => {
|
|
28
|
+
offsets.push(body.length);
|
|
29
|
+
body += `${index + 1} 0 obj\n${object}\nendobj\n`;
|
|
30
|
+
});
|
|
31
|
+
const xref = body.length;
|
|
32
|
+
body += `xref\n0 ${BLANK_OBJECTS.length + 1}\n0000000000 65535 f \n`;
|
|
33
|
+
for (const offset of offsets) body += `${String(offset).padStart(10, "0")} 00000 n \n`;
|
|
34
|
+
body += `trailer\n<< /Size ${BLANK_OBJECTS.length + 1} /Root 1 0 R >>\nstartxref\n${xref}\n%%EOF\n`;
|
|
35
|
+
cached = new TextEncoder().encode(body);
|
|
36
|
+
return new Uint8Array(cached);
|
|
37
|
+
}
|
|
38
|
+
//#endregion
|
|
7
39
|
//#region src/capability.ts
|
|
8
40
|
const DEFAULT_PREVIEW_WIDTH = 256;
|
|
9
|
-
const LIBRARY_PIECEINFO_APP = "EMBD_StampLibrary";
|
|
10
|
-
const STAMP_PIECEINFO_APP = "EMBD_Stamp";
|
|
11
|
-
const STAMP_SCHEMA_VERSION = 1;
|
|
12
41
|
/** Session-unique ids. Assets are session-scoped for now (no persistence),
|
|
13
42
|
* so a timestamp + counter is enough — durable ids come with the store port. */
|
|
14
43
|
let seq = 0;
|
|
@@ -25,7 +54,6 @@ const entryStringArray = (entries, key) => {
|
|
|
25
54
|
const entry = entries[key];
|
|
26
55
|
return entry?.type === "string-array" ? [...entry.value] : void 0;
|
|
27
56
|
};
|
|
28
|
-
const kindToPdfName = (kind) => kind === "signature" ? "Signature" : kind === "initials" ? "Initials" : "Stamp";
|
|
29
57
|
const kindFromPdfName = (name) => {
|
|
30
58
|
switch (name?.toLowerCase()) {
|
|
31
59
|
case "stamp": return "stamp";
|
|
@@ -34,21 +62,6 @@ const kindFromPdfName = (name) => {
|
|
|
34
62
|
default: return;
|
|
35
63
|
}
|
|
36
64
|
};
|
|
37
|
-
const metadataPatch = (id, name, kind, subject, categories) => ({
|
|
38
|
-
Version: STAMP_SCHEMA_VERSION,
|
|
39
|
-
Id: id,
|
|
40
|
-
Name: name,
|
|
41
|
-
Kind: { name: kindToPdfName(kind) },
|
|
42
|
-
Subject: subject ?? null,
|
|
43
|
-
Categories: categories ?? null
|
|
44
|
-
});
|
|
45
|
-
const libraryMetadataPatch = (id, name, categories) => ({
|
|
46
|
-
Version: STAMP_SCHEMA_VERSION,
|
|
47
|
-
Id: id,
|
|
48
|
-
Name: name,
|
|
49
|
-
Kind: { name: "StampLibrary" },
|
|
50
|
-
Categories: categories ?? null
|
|
51
|
-
});
|
|
52
65
|
function createStampCapability(ctx, config = {}) {
|
|
53
66
|
/** Binary sidecar of the serializable store: asset bytes + cached preview,
|
|
54
67
|
* keyed by asset id. The reducer never sees these (kernel rule 1). */
|
|
@@ -75,16 +88,13 @@ function createStampCapability(ctx, config = {}) {
|
|
|
75
88
|
}
|
|
76
89
|
return assetEngineRef;
|
|
77
90
|
};
|
|
78
|
-
const openAssetDocument = async (bytes
|
|
91
|
+
const openAssetDocument = async (bytes) => {
|
|
79
92
|
try {
|
|
80
93
|
return await (await assetEngine()).open({
|
|
81
94
|
kind: "bytes",
|
|
82
95
|
id: uid("stamp-import"),
|
|
83
96
|
bytes
|
|
84
|
-
}, {
|
|
85
|
-
scope: ["*"],
|
|
86
|
-
...identity ? { identity } : {}
|
|
87
|
-
});
|
|
97
|
+
}, { scope: ["*"] });
|
|
88
98
|
} catch (err) {
|
|
89
99
|
if (!config.assetEngine && _embedpdf_engine_core_runtime.EngineError.is(err, _embedpdf_engine_core_runtime.EngineErrorCode.InvalidArg)) throw new _embedpdf_engine_core_runtime.EngineError(_embedpdf_engine_core_runtime.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.");
|
|
90
100
|
throw err;
|
|
@@ -107,94 +117,151 @@ function createStampCapability(ctx, config = {}) {
|
|
|
107
117
|
taken.add(id);
|
|
108
118
|
return id;
|
|
109
119
|
};
|
|
110
|
-
const
|
|
111
|
-
|
|
120
|
+
const libraryChanged = (0, _embedpdf_core.createEventHook)((error) => globalThis.console?.error("[stamp] onLibraryChanged observer failed:", error));
|
|
121
|
+
/** Thumbnail render of one library page (the small picker image). */
|
|
122
|
+
const renderThumbnail = async (handle) => imageToPreview(await handle.render.image({
|
|
123
|
+
viewport: {
|
|
124
|
+
kind: "width",
|
|
125
|
+
width: config.previewWidth ?? DEFAULT_PREVIEW_WIDTH
|
|
126
|
+
},
|
|
127
|
+
background: "transparent",
|
|
128
|
+
includeAnnotations: true,
|
|
129
|
+
format: "png"
|
|
130
|
+
}));
|
|
131
|
+
const createLibrary = async (name, opts) => {
|
|
132
|
+
const taken = new Set(Object.keys(ctx.getState().libraries));
|
|
133
|
+
const id = allocateId(opts?.id, "stamp-lib", taken);
|
|
134
|
+
const doc = await openAssetDocument(blankLibraryPdf());
|
|
135
|
+
let bytes;
|
|
136
|
+
try {
|
|
137
|
+
requireCanonicalServices(doc);
|
|
138
|
+
await doc.metadata.update({ title: name });
|
|
139
|
+
await doc.pieceInfo.update(require_contract.STAMP_LIBRARY_PIECEINFO_APP, require_contract.stampLibraryPieceInfo(id, { categories: opts?.categories }));
|
|
140
|
+
bytes = await doc.download();
|
|
141
|
+
} finally {
|
|
142
|
+
await doc.close();
|
|
143
|
+
}
|
|
144
|
+
libraryBinaries.set(id, bytes);
|
|
112
145
|
ctx.dispatch({
|
|
113
146
|
type: "LIBRARY_ADDED",
|
|
114
147
|
library: {
|
|
115
148
|
id,
|
|
116
149
|
name,
|
|
117
|
-
|
|
118
|
-
|
|
150
|
+
assetIds: [],
|
|
151
|
+
...opts?.categories ? { categories: opts.categories } : {}
|
|
119
152
|
}
|
|
120
153
|
});
|
|
154
|
+
libraryChanged.emit({
|
|
155
|
+
libraryId: id,
|
|
156
|
+
reason: "created"
|
|
157
|
+
});
|
|
121
158
|
return id;
|
|
122
159
|
};
|
|
160
|
+
const requireCanonicalServices = (doc) => {
|
|
161
|
+
if (!doc.pieceInfo) throw new _embedpdf_engine_core_runtime.EngineError(_embedpdf_engine_core_runtime.EngineErrorCode.NotImplemented, "[stamp] canonical PDF libraries need an asset engine with pieceInfo");
|
|
162
|
+
if (!doc.pages.setName || !doc.pages.removeName) throw new _embedpdf_engine_core_runtime.EngineError(_embedpdf_engine_core_runtime.EngineErrorCode.NotImplemented, "[stamp] canonical PDF libraries need an asset engine with named pages (pages.setName)");
|
|
163
|
+
};
|
|
123
164
|
const importLibraryPdf = async (source, opts) => {
|
|
124
165
|
const resolved = await (0, _embedpdf_engine_core_runtime.resolveBinarySource)(source);
|
|
125
166
|
if ((0, _embedpdf_engine_core_runtime.sniffBinaryMetadata)(resolved.bytes)?.mimeType !== "application/pdf") throw new _embedpdf_engine_core_runtime.EngineError(_embedpdf_engine_core_runtime.EngineErrorCode.InvalidArg, "[stamp] importLibraryPdf needs PDF bytes (use addAsset for a raster image)");
|
|
126
167
|
const doc = await openAssetDocument(new Uint8Array(resolved.bytes));
|
|
127
168
|
let imported;
|
|
128
169
|
try {
|
|
129
|
-
|
|
130
|
-
const
|
|
131
|
-
if (
|
|
132
|
-
const
|
|
133
|
-
|
|
134
|
-
if (!handle.pieceInfo) throw new _embedpdf_engine_core_runtime.EngineError(_embedpdf_engine_core_runtime.EngineErrorCode.NotImplemented, "[stamp] canonical PDF libraries need page pieceInfo support");
|
|
135
|
-
return {
|
|
136
|
-
layout,
|
|
137
|
-
handle,
|
|
138
|
-
metadata: null
|
|
139
|
-
};
|
|
140
|
-
});
|
|
141
|
-
const catalogMetadata = await doc.pieceInfo.read(LIBRARY_PIECEINFO_APP);
|
|
142
|
-
for (const page of pages) page.metadata = await page.handle.pieceInfo.read(STAMP_PIECEINFO_APP);
|
|
170
|
+
requireCanonicalServices(doc);
|
|
171
|
+
const layout = await doc.pages.list();
|
|
172
|
+
if (layout.pageCount === 0) throw new _embedpdf_engine_core_runtime.EngineError(_embedpdf_engine_core_runtime.EngineErrorCode.InvalidArg, "[stamp] a canonical stamp library PDF must contain at least one page");
|
|
173
|
+
const byPon = new Map(layout.pages.map((page) => [page.pageObjectNumber, page]));
|
|
174
|
+
const catalogEntries = (await doc.pieceInfo.read("EMBD_StampLibrary"))?.entries ?? {};
|
|
143
175
|
const state = ctx.getState();
|
|
144
176
|
const takenLibraryIds = new Set(Object.keys(state.libraries));
|
|
145
|
-
const takenAssetIds = new Set(Object.keys(state.assets));
|
|
146
|
-
const catalogEntries = catalogMetadata?.entries ?? {};
|
|
147
177
|
const libraryId = allocateId(entryString(catalogEntries, "Id"), "stamp-lib", takenLibraryIds);
|
|
148
|
-
const
|
|
178
|
+
const docMeta = await doc.metadata.read();
|
|
179
|
+
const libraryName = (docMeta.title && docMeta.title.length > 0 ? docMeta.title : void 0) ?? entryString(catalogEntries, "Name") ?? opts?.name ?? "Stamps";
|
|
180
|
+
if (!docMeta.title) await doc.metadata.update({ title: libraryName });
|
|
149
181
|
const libraryCategories = opts?.categories ?? entryStringArray(catalogEntries, "Categories");
|
|
150
|
-
const
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
});
|
|
172
|
-
|
|
182
|
+
const libraryLocale = entryString(catalogEntries, "Locale");
|
|
183
|
+
const registry = (layout.namedPages ?? []).filter((entry) => entry.target.kind === "page");
|
|
184
|
+
const hidden = (layout.namedPages ?? []).filter((entry) => entry.target.kind !== "page");
|
|
185
|
+
if (hidden.length > 0) globalThis.console?.warn(`[stamp] library '${libraryName}' carries ${hidden.length} hidden-template or dangling registration(s); they are not stamps and were ignored`);
|
|
186
|
+
let descriptors;
|
|
187
|
+
if (registry.length > 0) {
|
|
188
|
+
const seen = /* @__PURE__ */ new Set();
|
|
189
|
+
descriptors = registry.map((entry) => {
|
|
190
|
+
const { name, label } = require_contract.parseStampKey(entry.name);
|
|
191
|
+
if (!name) throw new _embedpdf_engine_core_runtime.EngineError(_embedpdf_engine_core_runtime.EngineErrorCode.InvalidArg, `[stamp] library '${libraryName}': empty stamp identifier in key '${entry.name}'`);
|
|
192
|
+
if (seen.has(name)) throw new _embedpdf_engine_core_runtime.EngineError(_embedpdf_engine_core_runtime.EngineErrorCode.InvalidArg, `[stamp] library '${libraryName}': duplicate stamp identifier '${name}'`);
|
|
193
|
+
seen.add(name);
|
|
194
|
+
const page = byPon.get(entry.target.pageObjectNumber);
|
|
195
|
+
return {
|
|
196
|
+
pageObjectNumber: page.pageObjectNumber,
|
|
197
|
+
index: page.index,
|
|
198
|
+
name,
|
|
199
|
+
label
|
|
200
|
+
};
|
|
201
|
+
});
|
|
202
|
+
descriptors.sort((a, b) => a.index - b.index);
|
|
203
|
+
} else if (layout.namedPages === void 0) throw new _embedpdf_engine_core_runtime.EngineError(_embedpdf_engine_core_runtime.EngineErrorCode.NotImplemented, "[stamp] the asset engine reports no named-page registry (an older engine); upgrade it to import libraries");
|
|
204
|
+
else {
|
|
205
|
+
descriptors = [];
|
|
206
|
+
for (const page of layout.pages) {
|
|
207
|
+
const v1 = (await doc.page(page.pageObjectNumber).pieceInfo?.read(require_contract.STAMP_PIECEINFO_APP))?.entries;
|
|
208
|
+
const name = (v1 && entryString(v1, "Name")) ?? `Stamp${page.index + 1}`;
|
|
209
|
+
const label = (v1 && entryString(v1, "Subject")) ?? opts?.assetName?.(page.index) ?? name;
|
|
210
|
+
descriptors.push({
|
|
211
|
+
pageObjectNumber: page.pageObjectNumber,
|
|
212
|
+
index: page.index,
|
|
213
|
+
name,
|
|
214
|
+
label
|
|
215
|
+
});
|
|
216
|
+
}
|
|
217
|
+
for (const d of descriptors) await doc.pages.setName({
|
|
218
|
+
name: require_contract.stampKey(d.name, d.label),
|
|
219
|
+
pageObjectNumber: d.pageObjectNumber
|
|
220
|
+
});
|
|
221
|
+
}
|
|
222
|
+
await doc.pieceInfo.update(require_contract.STAMP_LIBRARY_PIECEINFO_APP, require_contract.stampLibraryPieceInfo(libraryId, {
|
|
223
|
+
categories: libraryCategories,
|
|
224
|
+
locale: libraryLocale
|
|
225
|
+
}));
|
|
173
226
|
const assets = [];
|
|
174
|
-
for (const
|
|
175
|
-
const
|
|
176
|
-
|
|
177
|
-
const
|
|
178
|
-
const
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
227
|
+
for (const d of descriptors) {
|
|
228
|
+
const handle = doc.page(d.pageObjectNumber);
|
|
229
|
+
if (!handle.pieceInfo) throw new _embedpdf_engine_core_runtime.EngineError(_embedpdf_engine_core_runtime.EngineErrorCode.NotImplemented, "[stamp] canonical PDF libraries need page pieceInfo support");
|
|
230
|
+
const entries = (await handle.pieceInfo.read("EMBD_Stamp"))?.entries ?? {};
|
|
231
|
+
const kind = opts?.kind ?? kindFromPdfName(entryName(entries, "Kind")) ?? "stamp";
|
|
232
|
+
const subject = entryString(entries, "SubjectOverride");
|
|
233
|
+
const categories = entryStringArray(entries, "Categories");
|
|
234
|
+
const page = byPon.get(d.pageObjectNumber);
|
|
235
|
+
const asset = {
|
|
236
|
+
id: require_contract.assetIdFor(libraryId, d.name),
|
|
237
|
+
libraryId,
|
|
238
|
+
kind,
|
|
239
|
+
name: d.name,
|
|
240
|
+
label: d.label,
|
|
241
|
+
size: {
|
|
242
|
+
width: page.size.width,
|
|
243
|
+
height: page.size.height
|
|
182
244
|
},
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
}
|
|
245
|
+
pageObjectNumber: d.pageObjectNumber,
|
|
246
|
+
...subject !== void 0 ? { subject } : {},
|
|
247
|
+
...categories !== void 0 ? { categories } : {}
|
|
248
|
+
};
|
|
249
|
+
await handle.pieceInfo.update(require_contract.STAMP_PIECEINFO_APP, require_contract.stampPieceInfo(kind, {
|
|
250
|
+
subject,
|
|
251
|
+
categories
|
|
252
|
+
}));
|
|
253
|
+
const bytes = await doc.pages.extract([d.pageObjectNumber]);
|
|
187
254
|
assets.push({
|
|
188
255
|
asset,
|
|
189
256
|
bytes,
|
|
190
|
-
preview:
|
|
257
|
+
preview: await renderThumbnail(handle)
|
|
191
258
|
});
|
|
192
259
|
}
|
|
193
260
|
imported = {
|
|
194
261
|
library: {
|
|
195
262
|
id: libraryId,
|
|
196
263
|
name: libraryName,
|
|
197
|
-
|
|
264
|
+
...libraryLocale ? { locale: libraryLocale } : {},
|
|
198
265
|
categories: libraryCategories,
|
|
199
266
|
assetIds: []
|
|
200
267
|
},
|
|
@@ -219,15 +286,26 @@ function createStampCapability(ctx, config = {}) {
|
|
|
219
286
|
asset
|
|
220
287
|
});
|
|
221
288
|
}
|
|
289
|
+
libraryChanged.emit({
|
|
290
|
+
libraryId: imported.library.id,
|
|
291
|
+
reason: "imported"
|
|
292
|
+
});
|
|
222
293
|
return imported.library.id;
|
|
223
294
|
};
|
|
224
295
|
const addAsset = async (input) => {
|
|
225
|
-
|
|
226
|
-
if (input.libraryId && !targetLibrary) throw new _embedpdf_engine_core_runtime.EngineError(_embedpdf_engine_core_runtime.EngineErrorCode.NotFound, `[stamp] unknown library '${input.libraryId}'`);
|
|
296
|
+
if (input.libraryId && !ctx.getState().libraries[input.libraryId]) throw new _embedpdf_engine_core_runtime.EngineError(_embedpdf_engine_core_runtime.EngineErrorCode.NotFound, `[stamp] unknown library '${input.libraryId}'`);
|
|
227
297
|
const resolved = await (0, _embedpdf_engine_core_runtime.resolveBinarySource)(input.source);
|
|
228
298
|
const meta = (0, _embedpdf_engine_core_runtime.sniffBinaryMetadata)(resolved.bytes);
|
|
229
299
|
if (!meta) throw new _embedpdf_engine_core_runtime.EngineError(_embedpdf_engine_core_runtime.EngineErrorCode.InvalidArg, "[stamp] asset source must be PNG, JPEG, or single-page PDF bytes");
|
|
230
300
|
const isPdf = meta.mimeType === "application/pdf";
|
|
301
|
+
const name = input.name ?? require_contract.customStampName();
|
|
302
|
+
const label = input.label ?? name;
|
|
303
|
+
if (name.length === 0 || name.includes("=")) throw new _embedpdf_engine_core_runtime.EngineError(_embedpdf_engine_core_runtime.EngineErrorCode.InvalidArg, `[stamp] invalid stamp identifier '${name}': must be non-empty and contain no '='`);
|
|
304
|
+
const rasterSize = input.size ?? ("width" in meta ? {
|
|
305
|
+
width: meta.width,
|
|
306
|
+
height: meta.height
|
|
307
|
+
} : null);
|
|
308
|
+
if (!isPdf && (!rasterSize || rasterSize.width <= 0 || rasterSize.height <= 0)) throw new _embedpdf_engine_core_runtime.EngineError(_embedpdf_engine_core_runtime.EngineErrorCode.InvalidArg, "[stamp] a raster asset needs a positive `size` (its page size in points)");
|
|
231
309
|
let suppliedPreview = null;
|
|
232
310
|
if (input.preview) {
|
|
233
311
|
const preview = await (0, _embedpdf_engine_core_runtime.resolveBinarySource)(input.preview);
|
|
@@ -236,111 +314,106 @@ function createStampCapability(ctx, config = {}) {
|
|
|
236
314
|
mimeType: preview.mimeType ?? "image/png"
|
|
237
315
|
};
|
|
238
316
|
}
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
317
|
+
const libraryId = input.libraryId ?? await createLibrary(label);
|
|
318
|
+
return mutateLibrary(libraryId, async () => {
|
|
319
|
+
const liveLibrary = ctx.getState().libraries[libraryId];
|
|
320
|
+
if (!liveLibrary) throw new _embedpdf_engine_core_runtime.EngineError(_embedpdf_engine_core_runtime.EngineErrorCode.NotFound, `[stamp] library '${libraryId}' no longer exists`);
|
|
321
|
+
const assetId = require_contract.assetIdFor(liveLibrary.id, name);
|
|
322
|
+
if (ctx.getState().assets[assetId]) throw new _embedpdf_engine_core_runtime.EngineError(_embedpdf_engine_core_runtime.EngineErrorCode.InvalidArg, `[stamp] library '${liveLibrary.name}' already has a stamp named '${name}'`);
|
|
323
|
+
const canonicalBytes = libraryBinaries.get(liveLibrary.id);
|
|
324
|
+
if (!canonicalBytes) throw new _embedpdf_engine_core_runtime.EngineError(_embedpdf_engine_core_runtime.EngineErrorCode.Unknown, `[stamp] canonical bytes are missing for library '${liveLibrary.id}'`);
|
|
325
|
+
const doc = await openAssetDocument(canonicalBytes);
|
|
326
|
+
let appended;
|
|
327
|
+
try {
|
|
328
|
+
requireCanonicalServices(doc);
|
|
329
|
+
let pageObjectNumber;
|
|
330
|
+
if (isPdf) {
|
|
250
331
|
const result = await doc.pages.insert(new Uint8Array(resolved.bytes));
|
|
251
|
-
if (result.insertedPageObjectNumbers.length !== 1) throw new _embedpdf_engine_core_runtime.EngineError(_embedpdf_engine_core_runtime.EngineErrorCode.InvalidArg, "[stamp] a
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
height: layout.size.height
|
|
264
|
-
},
|
|
265
|
-
format: "pdf",
|
|
266
|
-
pageObjectNumber,
|
|
267
|
-
subject: input.subject,
|
|
268
|
-
categories: input.categories
|
|
269
|
-
};
|
|
270
|
-
await page.pieceInfo.update(STAMP_PIECEINFO_APP, metadataPatch(asset.id, asset.name, asset.kind, asset.subject, asset.categories));
|
|
271
|
-
const bytes = await doc.pages.extract([pageObjectNumber]);
|
|
272
|
-
let preview = suppliedPreview;
|
|
273
|
-
if (!preview) preview = imageToPreview(await page.render.image({
|
|
274
|
-
viewport: {
|
|
275
|
-
kind: "width",
|
|
276
|
-
width: config.previewWidth ?? DEFAULT_PREVIEW_WIDTH
|
|
332
|
+
if (result.insertedPageObjectNumbers.length !== 1) throw new _embedpdf_engine_core_runtime.EngineError(_embedpdf_engine_core_runtime.EngineErrorCode.InvalidArg, "[stamp] a library asset must be a single-page PDF");
|
|
333
|
+
pageObjectNumber = result.insertedPageObjectNumbers[0];
|
|
334
|
+
} else {
|
|
335
|
+
if (!doc.pages.flatten) throw new _embedpdf_engine_core_runtime.EngineError(_embedpdf_engine_core_runtime.EngineErrorCode.NotImplemented, "[stamp] raster assets need an asset engine with pages.flatten");
|
|
336
|
+
pageObjectNumber = (await doc.pages.insertBlank({ size: rasterSize })).insertedPageObjectNumbers[0];
|
|
337
|
+
await doc.page(pageObjectNumber).annotations.create({
|
|
338
|
+
subtype: "stamp",
|
|
339
|
+
rect: {
|
|
340
|
+
left: 0,
|
|
341
|
+
bottom: 0,
|
|
342
|
+
right: rasterSize.width,
|
|
343
|
+
top: rasterSize.height
|
|
277
344
|
},
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
}));
|
|
282
|
-
appended = {
|
|
283
|
-
asset,
|
|
284
|
-
bytes,
|
|
285
|
-
preview,
|
|
286
|
-
canonical: await doc.download()
|
|
287
|
-
};
|
|
288
|
-
} finally {
|
|
289
|
-
await doc.close();
|
|
345
|
+
source: new Uint8Array(resolved.bytes),
|
|
346
|
+
fit: "fill"
|
|
347
|
+
});
|
|
348
|
+
if ((await doc.pages.flatten([pageObjectNumber], "display")).results.some(({ status }) => status !== "applied")) throw new _embedpdf_engine_core_runtime.EngineError(_embedpdf_engine_core_runtime.EngineErrorCode.Unknown, "[stamp] flattening the raster into its page failed");
|
|
290
349
|
}
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
type: "ASSET_ADDED",
|
|
298
|
-
asset: appended.asset
|
|
350
|
+
const layout = (await doc.pages.list()).pages.find((candidate) => candidate.pageObjectNumber === pageObjectNumber);
|
|
351
|
+
const page = doc.page(pageObjectNumber);
|
|
352
|
+
if (!layout || !page.pieceInfo) throw new _embedpdf_engine_core_runtime.EngineError(_embedpdf_engine_core_runtime.EngineErrorCode.NotImplemented, "[stamp] canonical PDF libraries need page layout and pieceInfo support");
|
|
353
|
+
await doc.pages.setName({
|
|
354
|
+
name: require_contract.stampKey(name, label),
|
|
355
|
+
pageObjectNumber
|
|
299
356
|
});
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
357
|
+
const asset = {
|
|
358
|
+
id: assetId,
|
|
359
|
+
libraryId: liveLibrary.id,
|
|
360
|
+
kind: input.kind ?? "stamp",
|
|
361
|
+
name,
|
|
362
|
+
label,
|
|
363
|
+
size: {
|
|
364
|
+
width: layout.size.width,
|
|
365
|
+
height: layout.size.height
|
|
366
|
+
},
|
|
367
|
+
pageObjectNumber,
|
|
368
|
+
...input.subject !== void 0 ? { subject: input.subject } : {},
|
|
369
|
+
...input.categories !== void 0 ? { categories: input.categories } : {}
|
|
370
|
+
};
|
|
371
|
+
await page.pieceInfo.update(require_contract.STAMP_PIECEINFO_APP, require_contract.stampPieceInfo(asset.kind, {
|
|
372
|
+
subject: asset.subject,
|
|
373
|
+
categories: asset.categories
|
|
374
|
+
}));
|
|
375
|
+
appended = {
|
|
376
|
+
asset,
|
|
377
|
+
bytes: await doc.pages.extract([pageObjectNumber]),
|
|
378
|
+
preview: suppliedPreview ?? await renderThumbnail(page),
|
|
379
|
+
canonical: await doc.download()
|
|
380
|
+
};
|
|
381
|
+
} finally {
|
|
382
|
+
await doc.close();
|
|
383
|
+
}
|
|
384
|
+
libraryBinaries.set(liveLibrary.id, appended.canonical);
|
|
385
|
+
binaries.set(appended.asset.id, {
|
|
386
|
+
bytes: appended.bytes,
|
|
387
|
+
preview: appended.preview
|
|
327
388
|
});
|
|
328
389
|
ctx.dispatch({
|
|
329
390
|
type: "ASSET_ADDED",
|
|
330
|
-
asset
|
|
391
|
+
asset: appended.asset
|
|
331
392
|
});
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
393
|
+
libraryChanged.emit({
|
|
394
|
+
libraryId: liveLibrary.id,
|
|
395
|
+
reason: "asset-added"
|
|
396
|
+
});
|
|
397
|
+
return appended.asset.id;
|
|
398
|
+
});
|
|
399
|
+
};
|
|
400
|
+
const addAssetFromAnnotations = async (documentId, pageObjectNumber, refs, input) => {
|
|
401
|
+
const doc = ctx.documentHandle(documentId);
|
|
402
|
+
if (!doc) throw new _embedpdf_engine_core_runtime.EngineError(_embedpdf_engine_core_runtime.EngineErrorCode.NotFound, `[stamp] target document '${documentId}' is not open`);
|
|
403
|
+
const page = doc.page(pageObjectNumber);
|
|
404
|
+
if (!page.annotations.exportAppearance) throw new _embedpdf_engine_core_runtime.EngineError(_embedpdf_engine_core_runtime.EngineErrorCode.NotImplemented, "[stamp] this document's engine cannot export annotation appearances");
|
|
405
|
+
const source = await page.annotations.exportAppearance(refs);
|
|
406
|
+
return addAsset({
|
|
407
|
+
...input,
|
|
408
|
+
source
|
|
339
409
|
});
|
|
340
410
|
};
|
|
341
411
|
const dropLibrary = (id) => {
|
|
342
412
|
const library = ctx.getState().libraries[id];
|
|
343
|
-
if (library) for (const assetId of library.assetIds)
|
|
413
|
+
if (library) for (const assetId of library.assetIds) {
|
|
414
|
+
binaries.delete(assetId);
|
|
415
|
+
ghostRenders.delete(assetId);
|
|
416
|
+
}
|
|
344
417
|
libraryBinaries.delete(id);
|
|
345
418
|
ctx.dispatch({
|
|
346
419
|
type: "LIBRARY_REMOVED",
|
|
@@ -354,54 +427,103 @@ function createStampCapability(ctx, config = {}) {
|
|
|
354
427
|
const asset = ctx.getState().assets[id];
|
|
355
428
|
if (!asset) return;
|
|
356
429
|
const library = ctx.getState().libraries[asset.libraryId];
|
|
357
|
-
if (library
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
try {
|
|
368
|
-
await doc.pages.delete([asset.pageObjectNumber]);
|
|
369
|
-
rewritten = await doc.download();
|
|
370
|
-
} finally {
|
|
371
|
-
await doc.close();
|
|
372
|
-
}
|
|
373
|
-
libraryBinaries.set(library.id, rewritten);
|
|
430
|
+
if (!library) return;
|
|
431
|
+
const canonicalBytes = libraryBinaries.get(library.id);
|
|
432
|
+
if (!canonicalBytes) throw new _embedpdf_engine_core_runtime.EngineError(_embedpdf_engine_core_runtime.EngineErrorCode.Unknown, `[stamp] canonical bytes are missing for library '${library.id}'`);
|
|
433
|
+
const doc = await openAssetDocument(canonicalBytes);
|
|
434
|
+
let rewritten;
|
|
435
|
+
try {
|
|
436
|
+
await doc.pages.delete([asset.pageObjectNumber]);
|
|
437
|
+
rewritten = await doc.download();
|
|
438
|
+
} finally {
|
|
439
|
+
await doc.close();
|
|
374
440
|
}
|
|
441
|
+
libraryBinaries.set(library.id, rewritten);
|
|
375
442
|
binaries.delete(id);
|
|
443
|
+
ghostRenders.delete(id);
|
|
376
444
|
ctx.dispatch({
|
|
377
445
|
type: "ASSET_REMOVED",
|
|
378
446
|
assetId: id
|
|
379
447
|
});
|
|
448
|
+
libraryChanged.emit({
|
|
449
|
+
libraryId: library.id,
|
|
450
|
+
reason: "asset-removed"
|
|
451
|
+
});
|
|
380
452
|
});
|
|
381
453
|
};
|
|
382
|
-
const removeLibrary = (id) => mutateLibrary(id, async () =>
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
}
|
|
389
|
-
|
|
390
|
-
|
|
454
|
+
const removeLibrary = (id) => mutateLibrary(id, async () => {
|
|
455
|
+
const existed = ctx.getState().libraries[id] !== void 0;
|
|
456
|
+
dropLibrary(id);
|
|
457
|
+
if (existed) libraryChanged.emit({
|
|
458
|
+
libraryId: id,
|
|
459
|
+
reason: "removed"
|
|
460
|
+
});
|
|
461
|
+
});
|
|
462
|
+
const updateAsset = async (id, patch) => {
|
|
463
|
+
const initial = ctx.getState().assets[id];
|
|
464
|
+
if (!initial) throw new _embedpdf_engine_core_runtime.EngineError(_embedpdf_engine_core_runtime.EngineErrorCode.NotFound, `[stamp] unknown asset '${id}'`);
|
|
465
|
+
return mutateLibrary(initial.libraryId, async () => {
|
|
466
|
+
const asset = ctx.getState().assets[id];
|
|
467
|
+
const library = asset ? ctx.getState().libraries[asset.libraryId] : void 0;
|
|
468
|
+
if (!asset || !library) throw new _embedpdf_engine_core_runtime.EngineError(_embedpdf_engine_core_runtime.EngineErrorCode.NotFound, `[stamp] unknown asset '${id}'`);
|
|
469
|
+
const next = {
|
|
470
|
+
...asset,
|
|
471
|
+
...patch.label !== void 0 ? { label: patch.label } : {},
|
|
472
|
+
...patch.categories !== void 0 ? { categories: patch.categories } : {}
|
|
473
|
+
};
|
|
474
|
+
if (patch.subject === null) delete next.subject;
|
|
475
|
+
else if (patch.subject !== void 0) next.subject = patch.subject;
|
|
476
|
+
const canonicalBytes = libraryBinaries.get(library.id);
|
|
477
|
+
if (!canonicalBytes) throw new _embedpdf_engine_core_runtime.EngineError(_embedpdf_engine_core_runtime.EngineErrorCode.Unknown, `[stamp] canonical bytes are missing for library '${library.id}'`);
|
|
478
|
+
const doc = await openAssetDocument(canonicalBytes);
|
|
479
|
+
let rewritten;
|
|
480
|
+
try {
|
|
481
|
+
requireCanonicalServices(doc);
|
|
482
|
+
if (next.label !== asset.label) await doc.pages.setName({
|
|
483
|
+
name: require_contract.stampKey(asset.name, next.label),
|
|
484
|
+
pageObjectNumber: asset.pageObjectNumber,
|
|
485
|
+
replace: require_contract.stampKey(asset.name, asset.label)
|
|
486
|
+
});
|
|
487
|
+
await doc.page(asset.pageObjectNumber).pieceInfo?.update(require_contract.STAMP_PIECEINFO_APP, require_contract.stampPieceInfo(next.kind, {
|
|
488
|
+
subject: next.subject,
|
|
489
|
+
categories: next.categories
|
|
490
|
+
}));
|
|
491
|
+
rewritten = await doc.download();
|
|
492
|
+
} finally {
|
|
493
|
+
await doc.close();
|
|
494
|
+
}
|
|
495
|
+
libraryBinaries.set(library.id, rewritten);
|
|
496
|
+
ctx.dispatch({
|
|
497
|
+
type: "ASSET_UPDATED",
|
|
498
|
+
asset: next
|
|
499
|
+
});
|
|
500
|
+
libraryChanged.emit({
|
|
501
|
+
libraryId: library.id,
|
|
502
|
+
reason: "asset-updated"
|
|
503
|
+
});
|
|
504
|
+
});
|
|
391
505
|
};
|
|
392
506
|
/**
|
|
393
507
|
* Form-backed stamps are executable templates only until placement. Work
|
|
394
508
|
* on an isolated copy so neither the canonical library nor its reusable
|
|
395
509
|
* per-page extraction becomes target/user/time specific.
|
|
510
|
+
*
|
|
511
|
+
* The realm comes from the TARGET document's actions plugin — a detached
|
|
512
|
+
* realm under that document's policy and environment (WP4: its own
|
|
513
|
+
* sandbox, none of the viewer realm's globals). No actions plugin, or
|
|
514
|
+
* scripting off, or `dynamic: false` → the template is armed as-is.
|
|
396
515
|
*/
|
|
397
516
|
const materializeForPlacement = async (documentId, asset, bin) => {
|
|
398
|
-
if (
|
|
399
|
-
const
|
|
517
|
+
if (config.dynamic === false) return bin;
|
|
518
|
+
const actions = ctx.tryForDocument(_embedpdf_plugin_actions_contract_host.ActionsToken, documentId);
|
|
519
|
+
const mintRealm = actions?.createDetachedScriptRealm;
|
|
520
|
+
if (!actions || !mintRealm) return bin;
|
|
400
521
|
const targetMeta = ctx.core().documents[documentId] ?? null;
|
|
401
|
-
if (!
|
|
522
|
+
if (!targetMeta) throw new _embedpdf_engine_core_runtime.EngineError(_embedpdf_engine_core_runtime.EngineErrorCode.NotFound, `[stamp] target document '${documentId}' is not open`);
|
|
402
523
|
const canonicalBytes = libraryBinaries.get(asset.libraryId);
|
|
403
524
|
const sourceBytes = canonicalBytes ?? bin.bytes;
|
|
404
|
-
const doc = await openAssetDocument(new Uint8Array(sourceBytes)
|
|
525
|
+
const doc = await openAssetDocument(new Uint8Array(sourceBytes));
|
|
526
|
+
let realm = null;
|
|
405
527
|
let scripting = null;
|
|
406
528
|
try {
|
|
407
529
|
const layout = await doc.pages.list();
|
|
@@ -409,13 +531,24 @@ function createStampCapability(ctx, config = {}) {
|
|
|
409
531
|
if (!selectedPage) throw new _embedpdf_engine_core_runtime.EngineError(_embedpdf_engine_core_runtime.EngineErrorCode.InvalidArg, canonicalBytes ? `[stamp] canonical page ${asset.pageObjectNumber ?? "unknown"} no longer exists` : "[stamp] a loose dynamic stamp asset must contain exactly one page");
|
|
410
532
|
if (!(await doc.forms.list()).fields.some((field) => field.widgets.some(({ pageObjectNumber }) => pageObjectNumber === selectedPage.pageObjectNumber))) return bin;
|
|
411
533
|
if (!doc.pages.flatten || !doc.pages.extract) throw new _embedpdf_engine_core_runtime.EngineError(_embedpdf_engine_core_runtime.EngineErrorCode.NotImplemented, "[stamp] dynamic PDF stamps need an asset engine with pages.flatten and pages.extract");
|
|
534
|
+
realm = mintRealm.call(actions, {
|
|
535
|
+
doc,
|
|
536
|
+
document: () => targetMeta,
|
|
537
|
+
bootSources: async () => {
|
|
538
|
+
return (doc.actions ? await doc.actions.read() : null)?.nameTreeScripts.map(({ action }) => (0, _embedpdf_core_acrojs.javaScriptProgramFromActionTree)(action)) ?? [];
|
|
539
|
+
}
|
|
540
|
+
});
|
|
412
541
|
scripting = (0, _embedpdf_plugin_form_scripting.createFormScriptingController)({
|
|
413
542
|
doc,
|
|
414
543
|
document: () => targetMeta,
|
|
415
|
-
|
|
544
|
+
transaction: realm.transaction.bind(realm),
|
|
545
|
+
budget: realm.budget
|
|
416
546
|
});
|
|
417
547
|
const result = await scripting.recalculate();
|
|
418
|
-
|
|
548
|
+
actions.surfaceScriptCommit(result, {
|
|
549
|
+
origin: "user",
|
|
550
|
+
realm: "detached"
|
|
551
|
+
});
|
|
419
552
|
if (result.status === "failed") throw new _embedpdf_engine_core_runtime.EngineError(_embedpdf_engine_core_runtime.EngineErrorCode.Unknown, `[stamp] dynamic stamp scripting failed: ${result.error?.message ?? "native form effect failed"}`);
|
|
420
553
|
const pageObjectNumber = selectedPage.pageObjectNumber;
|
|
421
554
|
const failed = (await doc.pages.flatten([pageObjectNumber], "display")).results.find(({ status }) => status === "failed" || status === "skipped");
|
|
@@ -436,6 +569,7 @@ function createStampCapability(ctx, config = {}) {
|
|
|
436
569
|
};
|
|
437
570
|
} finally {
|
|
438
571
|
scripting?.dispose();
|
|
572
|
+
realm?.dispose();
|
|
439
573
|
await doc.close();
|
|
440
574
|
}
|
|
441
575
|
};
|
|
@@ -445,17 +579,72 @@ function createStampCapability(ctx, config = {}) {
|
|
|
445
579
|
if (!asset || !bin) throw new _embedpdf_engine_core_runtime.EngineError(_embedpdf_engine_core_runtime.EngineErrorCode.NotFound, `[stamp] unknown asset '${assetId}'`);
|
|
446
580
|
const placement = await materializeForPlacement(documentId, asset, bin);
|
|
447
581
|
await ctx.forDocument(_embedpdf_plugin_annotation_contract.AnnotationToken, documentId).armStamp({
|
|
448
|
-
|
|
449
|
-
preview: placement.preview ? {
|
|
450
|
-
data: placement.preview.bytes,
|
|
451
|
-
mimeType: placement.preview.mimeType
|
|
452
|
-
} : void 0,
|
|
453
|
-
intrinsicSize: asset.size,
|
|
582
|
+
...stampPayload(asset, placement, placement !== bin),
|
|
454
583
|
targetWidth: opts?.targetWidth
|
|
455
584
|
});
|
|
456
585
|
};
|
|
586
|
+
/**
|
|
587
|
+
* Ghost renders, one per size bucket per asset, rendered lazily on the
|
|
588
|
+
* asset engine the first time the ghost is shown at that size and kept
|
|
589
|
+
* until the asset goes away. Materialized (dynamic) placements are
|
|
590
|
+
* user/time specific and cache only for their own arm (`cacheKey` null).
|
|
591
|
+
*/
|
|
592
|
+
const ghostRenders = /* @__PURE__ */ new Map();
|
|
593
|
+
const renderGhost = async (bytes, devicePixelWidth) => {
|
|
594
|
+
const doc = await openAssetDocument(new Uint8Array(bytes));
|
|
595
|
+
try {
|
|
596
|
+
const page = (await doc.pages.list()).pages[0];
|
|
597
|
+
if (!page) return null;
|
|
598
|
+
return imageToPreview(await doc.page(page.pageObjectNumber).render.image({
|
|
599
|
+
viewport: {
|
|
600
|
+
kind: "width",
|
|
601
|
+
width: devicePixelWidth
|
|
602
|
+
},
|
|
603
|
+
background: "transparent",
|
|
604
|
+
includeAnnotations: true,
|
|
605
|
+
format: "png"
|
|
606
|
+
}));
|
|
607
|
+
} finally {
|
|
608
|
+
await doc.close();
|
|
609
|
+
}
|
|
610
|
+
};
|
|
611
|
+
/** A resolution-aware ghost source for placement bytes: the page renders
|
|
612
|
+
* at the requested width, cached per bucket under `cacheKey` when given. */
|
|
613
|
+
const ghostProvider = (bytes, fallback, cacheKey) => {
|
|
614
|
+
const local = cacheKey ? ghostRenders.get(cacheKey) ?? /* @__PURE__ */ new Map() : /* @__PURE__ */ new Map();
|
|
615
|
+
if (cacheKey) ghostRenders.set(cacheKey, local);
|
|
616
|
+
return (devicePixelWidth) => {
|
|
617
|
+
let pending = local.get(devicePixelWidth);
|
|
618
|
+
if (!pending) {
|
|
619
|
+
pending = renderGhost(bytes, devicePixelWidth).catch((error) => {
|
|
620
|
+
globalThis.console?.warn("[stamp] ghost render failed, using the thumbnail:", error);
|
|
621
|
+
return fallback;
|
|
622
|
+
});
|
|
623
|
+
local.set(devicePixelWidth, pending);
|
|
624
|
+
}
|
|
625
|
+
return pending;
|
|
626
|
+
};
|
|
627
|
+
};
|
|
628
|
+
/** The one payload both entry points hand the annotation plugin: artwork,
|
|
629
|
+
* a resolution-aware ghost, true size, and the identity a placement
|
|
630
|
+
* writes (`/Name` = identifier, `/Subj` = the subject override or label). */
|
|
631
|
+
const stampPayload = (asset, placement, materialized) => ({
|
|
632
|
+
source: placement.bytes,
|
|
633
|
+
preview: ghostProvider(placement.bytes, placement.preview, materialized ? null : asset.id),
|
|
634
|
+
intrinsicSize: asset.size,
|
|
635
|
+
name: asset.name,
|
|
636
|
+
subject: asset.subject ?? asset.label
|
|
637
|
+
});
|
|
638
|
+
const placeAsset = async (documentId, assetId, placement) => {
|
|
639
|
+
const asset = ctx.getState().assets[assetId];
|
|
640
|
+
const bin = binaries.get(assetId);
|
|
641
|
+
if (!asset || !bin) throw new _embedpdf_engine_core_runtime.EngineError(_embedpdf_engine_core_runtime.EngineErrorCode.NotFound, `[stamp] unknown asset '${assetId}'`);
|
|
642
|
+
const materialized = await materializeForPlacement(documentId, asset, bin);
|
|
643
|
+
return ctx.forDocument(_embedpdf_plugin_annotation_contract.AnnotationToken, documentId).placeStamp(stampPayload(asset, materialized, materialized !== bin), placement);
|
|
644
|
+
};
|
|
457
645
|
ctx.cleanup(() => {
|
|
458
646
|
binaries.clear();
|
|
647
|
+
ghostRenders.clear();
|
|
459
648
|
libraryBinaries.clear();
|
|
460
649
|
libraryMutationTails.clear();
|
|
461
650
|
const ownedAssetEngine = typeof config.assetEngine === "function" ? assetEngineRef : null;
|
|
@@ -479,12 +668,17 @@ function createStampCapability(ctx, config = {}) {
|
|
|
479
668
|
asset: (id) => ctx.getState().assets[id] ?? null,
|
|
480
669
|
assetPreview: (id) => binaries.get(id)?.preview ?? null,
|
|
481
670
|
assetBytes: (id) => binaries.get(id)?.bytes ?? null,
|
|
482
|
-
|
|
671
|
+
exportLibrary: (id) => libraryBinaries.get(id) ?? null,
|
|
672
|
+
onLibraryChanged: libraryChanged.on,
|
|
673
|
+
createLibrary,
|
|
483
674
|
importLibraryPdf,
|
|
484
675
|
addAsset,
|
|
676
|
+
addAssetFromAnnotations,
|
|
677
|
+
updateAsset,
|
|
485
678
|
removeAsset,
|
|
486
679
|
removeLibrary,
|
|
487
680
|
armAsset,
|
|
681
|
+
placeAsset,
|
|
488
682
|
disarm: (documentId) => ctx.forDocument(_embedpdf_plugin_annotation_contract.AnnotationToken, documentId).disarmStamp()
|
|
489
683
|
};
|
|
490
684
|
}
|
|
@@ -539,6 +733,13 @@ const stampReducer = (state, action) => {
|
|
|
539
733
|
}
|
|
540
734
|
};
|
|
541
735
|
}
|
|
736
|
+
case "ASSET_UPDATED": return state.assets[action.asset.id] ? {
|
|
737
|
+
...state,
|
|
738
|
+
assets: {
|
|
739
|
+
...state.assets,
|
|
740
|
+
[action.asset.id]: action.asset
|
|
741
|
+
}
|
|
742
|
+
} : state;
|
|
542
743
|
case "ASSET_REMOVED": {
|
|
543
744
|
const asset = state.assets[action.assetId];
|
|
544
745
|
if (!asset) return state;
|
|
@@ -579,7 +780,91 @@ const stampPlugin = (config = {}) => (0, _embedpdf_core.definePlugin)({
|
|
|
579
780
|
capability: (ctx) => createStampCapability(ctx, config)
|
|
580
781
|
});
|
|
581
782
|
//#endregion
|
|
783
|
+
//#region src/persistence.ts
|
|
784
|
+
/** An in-memory store — tests and SSR. */
|
|
785
|
+
function memoryStampStore() {
|
|
786
|
+
const rows = /* @__PURE__ */ new Map();
|
|
787
|
+
return {
|
|
788
|
+
list: async () => [...rows.entries()].map(([id, bytes]) => ({
|
|
789
|
+
id,
|
|
790
|
+
bytes
|
|
791
|
+
})),
|
|
792
|
+
put: async (id, bytes) => {
|
|
793
|
+
rows.set(id, new Uint8Array(bytes));
|
|
794
|
+
},
|
|
795
|
+
delete: async (id) => {
|
|
796
|
+
rows.delete(id);
|
|
797
|
+
}
|
|
798
|
+
};
|
|
799
|
+
}
|
|
800
|
+
/**
|
|
801
|
+
* Bring every stored library back: each PDF is imported as-is (its title,
|
|
802
|
+
* registry, and PieceInfo id come from the file — the store's key is only a
|
|
803
|
+
* hint). Call once at boot, before seeding defaults, so "already has
|
|
804
|
+
* libraries" means the user's own.
|
|
805
|
+
*/
|
|
806
|
+
async function restoreStampLibraries(stamp, store) {
|
|
807
|
+
const restored = [];
|
|
808
|
+
for (const { id, bytes } of await store.list()) try {
|
|
809
|
+
restored.push(await stamp.importLibraryPdf(bytes));
|
|
810
|
+
} catch (error) {
|
|
811
|
+
globalThis.console?.warn(`[stamp] stored library '${id}' could not be restored:`, error);
|
|
812
|
+
}
|
|
813
|
+
return restored;
|
|
814
|
+
}
|
|
815
|
+
/**
|
|
816
|
+
* Keep the store in sync from now on: every canonical change writes the
|
|
817
|
+
* library's PDF (coalesced per library so a burst of edits saves once),
|
|
818
|
+
* a removal deletes it. `except` names libraries never to persist — the
|
|
819
|
+
* bundled default set, typically. Returns the unsubscribe.
|
|
820
|
+
*/
|
|
821
|
+
function persistStampLibraries(stamp, store, opts = {}) {
|
|
822
|
+
const except = new Set(opts.except ?? []);
|
|
823
|
+
const timers = /* @__PURE__ */ new Map();
|
|
824
|
+
const write = (libraryId) => {
|
|
825
|
+
timers.delete(libraryId);
|
|
826
|
+
const bytes = stamp.exportLibrary(libraryId);
|
|
827
|
+
if (!bytes) return;
|
|
828
|
+
store.put(libraryId, bytes).catch((error) => {
|
|
829
|
+
globalThis.console?.warn(`[stamp] persisting library '${libraryId}' failed:`, error);
|
|
830
|
+
});
|
|
831
|
+
};
|
|
832
|
+
const off = stamp.onLibraryChanged(({ libraryId, reason }) => {
|
|
833
|
+
if (except.has(libraryId)) return;
|
|
834
|
+
const pending = timers.get(libraryId);
|
|
835
|
+
if (pending) clearTimeout(pending);
|
|
836
|
+
if (reason === "removed") {
|
|
837
|
+
timers.delete(libraryId);
|
|
838
|
+
store.delete(libraryId).catch((error) => {
|
|
839
|
+
globalThis.console?.warn(`[stamp] deleting stored library '${libraryId}' failed:`, error);
|
|
840
|
+
});
|
|
841
|
+
return;
|
|
842
|
+
}
|
|
843
|
+
timers.set(libraryId, setTimeout(() => write(libraryId), opts.debounceMs ?? 250));
|
|
844
|
+
});
|
|
845
|
+
return () => {
|
|
846
|
+
off();
|
|
847
|
+
for (const [libraryId, timer] of timers) {
|
|
848
|
+
clearTimeout(timer);
|
|
849
|
+
write(libraryId);
|
|
850
|
+
}
|
|
851
|
+
};
|
|
852
|
+
}
|
|
853
|
+
//#endregion
|
|
854
|
+
exports.STAMP_LIBRARY_PIECEINFO_APP = require_contract.STAMP_LIBRARY_PIECEINFO_APP;
|
|
855
|
+
exports.STAMP_PIECEINFO_APP = require_contract.STAMP_PIECEINFO_APP;
|
|
856
|
+
exports.STAMP_PIECEINFO_VERSION = require_contract.STAMP_PIECEINFO_VERSION;
|
|
582
857
|
exports.StampToken = require_contract.StampToken;
|
|
858
|
+
exports.assetIdFor = require_contract.assetIdFor;
|
|
859
|
+
exports.customStampName = require_contract.customStampName;
|
|
860
|
+
exports.memoryStampStore = memoryStampStore;
|
|
861
|
+
exports.parseStampKey = require_contract.parseStampKey;
|
|
862
|
+
exports.persistStampLibraries = persistStampLibraries;
|
|
863
|
+
exports.restoreStampLibraries = restoreStampLibraries;
|
|
864
|
+
exports.stampKey = require_contract.stampKey;
|
|
865
|
+
exports.stampKindToPdfName = require_contract.stampKindToPdfName;
|
|
866
|
+
exports.stampLibraryPieceInfo = require_contract.stampLibraryPieceInfo;
|
|
867
|
+
exports.stampPieceInfo = require_contract.stampPieceInfo;
|
|
583
868
|
exports.stampPlugin = stampPlugin;
|
|
584
869
|
|
|
585
870
|
//# sourceMappingURL=index.cjs.map
|