@embedpdf/plugin-stamp 3.0.0-next.10 → 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 +13 -0
- package/dist/contract.d.cts +2 -0
- package/dist/contract.d.ts +2 -0
- package/dist/contract.js +2 -0
- package/dist/index.cjs +511 -231
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +2 -175
- package/dist/index.d.ts +2 -175
- package/dist/index.js +495 -228
- package/dist/index.js.map +1 -1
- package/package.json +15 -8
package/dist/index.cjs
CHANGED
|
@@ -1,13 +1,43 @@
|
|
|
1
1
|
Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
|
|
2
|
+
const require_contract = require("./contract-C6CgKD69.cjs");
|
|
2
3
|
let _embedpdf_core = require("@embedpdf/core");
|
|
3
4
|
let _embedpdf_engine_core_runtime = require("@embedpdf/engine-core/runtime");
|
|
4
|
-
let
|
|
5
|
-
let
|
|
5
|
+
let _embedpdf_core_acrojs = require("@embedpdf/core-acrojs");
|
|
6
|
+
let _embedpdf_plugin_actions_contract_host = require("@embedpdf/plugin-actions/contract/host");
|
|
7
|
+
let _embedpdf_plugin_annotation_contract = require("@embedpdf/plugin-annotation/contract");
|
|
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
|
|
6
39
|
//#region src/capability.ts
|
|
7
40
|
const DEFAULT_PREVIEW_WIDTH = 256;
|
|
8
|
-
const LIBRARY_PIECEINFO_APP = "EMBD_StampLibrary";
|
|
9
|
-
const STAMP_PIECEINFO_APP = "EMBD_Stamp";
|
|
10
|
-
const STAMP_SCHEMA_VERSION = 1;
|
|
11
41
|
/** Session-unique ids. Assets are session-scoped for now (no persistence),
|
|
12
42
|
* so a timestamp + counter is enough — durable ids come with the store port. */
|
|
13
43
|
let seq = 0;
|
|
@@ -24,7 +54,6 @@ const entryStringArray = (entries, key) => {
|
|
|
24
54
|
const entry = entries[key];
|
|
25
55
|
return entry?.type === "string-array" ? [...entry.value] : void 0;
|
|
26
56
|
};
|
|
27
|
-
const kindToPdfName = (kind) => kind === "signature" ? "Signature" : kind === "initials" ? "Initials" : "Stamp";
|
|
28
57
|
const kindFromPdfName = (name) => {
|
|
29
58
|
switch (name?.toLowerCase()) {
|
|
30
59
|
case "stamp": return "stamp";
|
|
@@ -33,21 +62,6 @@ const kindFromPdfName = (name) => {
|
|
|
33
62
|
default: return;
|
|
34
63
|
}
|
|
35
64
|
};
|
|
36
|
-
const metadataPatch = (id, name, kind, subject, categories) => ({
|
|
37
|
-
Version: STAMP_SCHEMA_VERSION,
|
|
38
|
-
Id: id,
|
|
39
|
-
Name: name,
|
|
40
|
-
Kind: { name: kindToPdfName(kind) },
|
|
41
|
-
Subject: subject ?? null,
|
|
42
|
-
Categories: categories ?? null
|
|
43
|
-
});
|
|
44
|
-
const libraryMetadataPatch = (id, name, categories) => ({
|
|
45
|
-
Version: STAMP_SCHEMA_VERSION,
|
|
46
|
-
Id: id,
|
|
47
|
-
Name: name,
|
|
48
|
-
Kind: { name: "StampLibrary" },
|
|
49
|
-
Categories: categories ?? null
|
|
50
|
-
});
|
|
51
65
|
function createStampCapability(ctx, config = {}) {
|
|
52
66
|
/** Binary sidecar of the serializable store: asset bytes + cached preview,
|
|
53
67
|
* keyed by asset id. The reducer never sees these (kernel rule 1). */
|
|
@@ -74,16 +88,13 @@ function createStampCapability(ctx, config = {}) {
|
|
|
74
88
|
}
|
|
75
89
|
return assetEngineRef;
|
|
76
90
|
};
|
|
77
|
-
const openAssetDocument = async (bytes
|
|
91
|
+
const openAssetDocument = async (bytes) => {
|
|
78
92
|
try {
|
|
79
93
|
return await (await assetEngine()).open({
|
|
80
94
|
kind: "bytes",
|
|
81
95
|
id: uid("stamp-import"),
|
|
82
96
|
bytes
|
|
83
|
-
}, {
|
|
84
|
-
scope: ["*"],
|
|
85
|
-
...identity ? { identity } : {}
|
|
86
|
-
});
|
|
97
|
+
}, { scope: ["*"] });
|
|
87
98
|
} catch (err) {
|
|
88
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.");
|
|
89
100
|
throw err;
|
|
@@ -106,94 +117,151 @@ function createStampCapability(ctx, config = {}) {
|
|
|
106
117
|
taken.add(id);
|
|
107
118
|
return id;
|
|
108
119
|
};
|
|
109
|
-
const
|
|
110
|
-
|
|
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);
|
|
111
145
|
ctx.dispatch({
|
|
112
146
|
type: "LIBRARY_ADDED",
|
|
113
147
|
library: {
|
|
114
148
|
id,
|
|
115
149
|
name,
|
|
116
|
-
|
|
117
|
-
|
|
150
|
+
assetIds: [],
|
|
151
|
+
...opts?.categories ? { categories: opts.categories } : {}
|
|
118
152
|
}
|
|
119
153
|
});
|
|
154
|
+
libraryChanged.emit({
|
|
155
|
+
libraryId: id,
|
|
156
|
+
reason: "created"
|
|
157
|
+
});
|
|
120
158
|
return id;
|
|
121
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
|
+
};
|
|
122
164
|
const importLibraryPdf = async (source, opts) => {
|
|
123
165
|
const resolved = await (0, _embedpdf_engine_core_runtime.resolveBinarySource)(source);
|
|
124
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)");
|
|
125
167
|
const doc = await openAssetDocument(new Uint8Array(resolved.bytes));
|
|
126
168
|
let imported;
|
|
127
169
|
try {
|
|
128
|
-
|
|
129
|
-
const
|
|
130
|
-
if (
|
|
131
|
-
const
|
|
132
|
-
|
|
133
|
-
if (!handle.pieceInfo) throw new _embedpdf_engine_core_runtime.EngineError(_embedpdf_engine_core_runtime.EngineErrorCode.NotImplemented, "[stamp] canonical PDF libraries need page pieceInfo support");
|
|
134
|
-
return {
|
|
135
|
-
layout,
|
|
136
|
-
handle,
|
|
137
|
-
metadata: null
|
|
138
|
-
};
|
|
139
|
-
});
|
|
140
|
-
const catalogMetadata = await doc.pieceInfo.read(LIBRARY_PIECEINFO_APP);
|
|
141
|
-
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 ?? {};
|
|
142
175
|
const state = ctx.getState();
|
|
143
176
|
const takenLibraryIds = new Set(Object.keys(state.libraries));
|
|
144
|
-
const takenAssetIds = new Set(Object.keys(state.assets));
|
|
145
|
-
const catalogEntries = catalogMetadata?.entries ?? {};
|
|
146
177
|
const libraryId = allocateId(entryString(catalogEntries, "Id"), "stamp-lib", takenLibraryIds);
|
|
147
|
-
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 });
|
|
148
181
|
const libraryCategories = opts?.categories ?? entryStringArray(catalogEntries, "Categories");
|
|
149
|
-
const
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
});
|
|
171
|
-
|
|
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
|
+
}));
|
|
172
226
|
const assets = [];
|
|
173
|
-
for (const
|
|
174
|
-
const
|
|
175
|
-
|
|
176
|
-
const
|
|
177
|
-
const
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
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
|
|
181
244
|
},
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
}
|
|
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]);
|
|
186
254
|
assets.push({
|
|
187
255
|
asset,
|
|
188
256
|
bytes,
|
|
189
|
-
preview:
|
|
257
|
+
preview: await renderThumbnail(handle)
|
|
190
258
|
});
|
|
191
259
|
}
|
|
192
260
|
imported = {
|
|
193
261
|
library: {
|
|
194
262
|
id: libraryId,
|
|
195
263
|
name: libraryName,
|
|
196
|
-
|
|
264
|
+
...libraryLocale ? { locale: libraryLocale } : {},
|
|
197
265
|
categories: libraryCategories,
|
|
198
266
|
assetIds: []
|
|
199
267
|
},
|
|
@@ -218,15 +286,26 @@ function createStampCapability(ctx, config = {}) {
|
|
|
218
286
|
asset
|
|
219
287
|
});
|
|
220
288
|
}
|
|
289
|
+
libraryChanged.emit({
|
|
290
|
+
libraryId: imported.library.id,
|
|
291
|
+
reason: "imported"
|
|
292
|
+
});
|
|
221
293
|
return imported.library.id;
|
|
222
294
|
};
|
|
223
295
|
const addAsset = async (input) => {
|
|
224
|
-
|
|
225
|
-
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}'`);
|
|
226
297
|
const resolved = await (0, _embedpdf_engine_core_runtime.resolveBinarySource)(input.source);
|
|
227
298
|
const meta = (0, _embedpdf_engine_core_runtime.sniffBinaryMetadata)(resolved.bytes);
|
|
228
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");
|
|
229
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)");
|
|
230
309
|
let suppliedPreview = null;
|
|
231
310
|
if (input.preview) {
|
|
232
311
|
const preview = await (0, _embedpdf_engine_core_runtime.resolveBinarySource)(input.preview);
|
|
@@ -235,111 +314,106 @@ function createStampCapability(ctx, config = {}) {
|
|
|
235
314
|
mimeType: preview.mimeType ?? "image/png"
|
|
236
315
|
};
|
|
237
316
|
}
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
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) {
|
|
249
331
|
const result = await doc.pages.insert(new Uint8Array(resolved.bytes));
|
|
250
|
-
if (result.insertedPageObjectNumbers.length !== 1) throw new _embedpdf_engine_core_runtime.EngineError(_embedpdf_engine_core_runtime.EngineErrorCode.InvalidArg, "[stamp] a
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
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
|
|
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
|
|
276
344
|
},
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
}));
|
|
281
|
-
appended = {
|
|
282
|
-
asset,
|
|
283
|
-
bytes,
|
|
284
|
-
preview,
|
|
285
|
-
canonical: await doc.download()
|
|
286
|
-
};
|
|
287
|
-
} finally {
|
|
288
|
-
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");
|
|
289
349
|
}
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
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
|
|
294
356
|
});
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
binaries.set(asset.id, {
|
|
324
|
-
bytes:
|
|
325
|
-
preview
|
|
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
|
|
326
388
|
});
|
|
327
389
|
ctx.dispatch({
|
|
328
390
|
type: "ASSET_ADDED",
|
|
329
|
-
asset
|
|
391
|
+
asset: appended.asset
|
|
330
392
|
});
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
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
|
|
338
409
|
});
|
|
339
410
|
};
|
|
340
411
|
const dropLibrary = (id) => {
|
|
341
412
|
const library = ctx.getState().libraries[id];
|
|
342
|
-
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
|
+
}
|
|
343
417
|
libraryBinaries.delete(id);
|
|
344
418
|
ctx.dispatch({
|
|
345
419
|
type: "LIBRARY_REMOVED",
|
|
@@ -353,71 +427,128 @@ function createStampCapability(ctx, config = {}) {
|
|
|
353
427
|
const asset = ctx.getState().assets[id];
|
|
354
428
|
if (!asset) return;
|
|
355
429
|
const library = ctx.getState().libraries[asset.libraryId];
|
|
356
|
-
if (library
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
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);
|
|
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();
|
|
373
440
|
}
|
|
441
|
+
libraryBinaries.set(library.id, rewritten);
|
|
374
442
|
binaries.delete(id);
|
|
443
|
+
ghostRenders.delete(id);
|
|
375
444
|
ctx.dispatch({
|
|
376
445
|
type: "ASSET_REMOVED",
|
|
377
446
|
assetId: id
|
|
378
447
|
});
|
|
448
|
+
libraryChanged.emit({
|
|
449
|
+
libraryId: library.id,
|
|
450
|
+
reason: "asset-removed"
|
|
451
|
+
});
|
|
379
452
|
});
|
|
380
453
|
};
|
|
381
|
-
const removeLibrary = (id) => mutateLibrary(id, async () =>
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
}
|
|
388
|
-
|
|
389
|
-
|
|
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
|
+
});
|
|
390
505
|
};
|
|
391
506
|
/**
|
|
392
507
|
* Form-backed stamps are executable templates only until placement. Work
|
|
393
508
|
* on an isolated copy so neither the canonical library nor its reusable
|
|
394
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.
|
|
395
515
|
*/
|
|
396
516
|
const materializeForPlacement = async (documentId, asset, bin) => {
|
|
397
|
-
if (
|
|
398
|
-
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;
|
|
399
521
|
const targetMeta = ctx.core().documents[documentId] ?? null;
|
|
400
|
-
if (!
|
|
522
|
+
if (!targetMeta) throw new _embedpdf_engine_core_runtime.EngineError(_embedpdf_engine_core_runtime.EngineErrorCode.NotFound, `[stamp] target document '${documentId}' is not open`);
|
|
401
523
|
const canonicalBytes = libraryBinaries.get(asset.libraryId);
|
|
402
524
|
const sourceBytes = canonicalBytes ?? bin.bytes;
|
|
403
|
-
const doc = await openAssetDocument(new Uint8Array(sourceBytes)
|
|
525
|
+
const doc = await openAssetDocument(new Uint8Array(sourceBytes));
|
|
526
|
+
let realm = null;
|
|
404
527
|
let scripting = null;
|
|
405
528
|
try {
|
|
406
529
|
const layout = await doc.pages.list();
|
|
407
530
|
const selectedPage = asset.pageObjectNumber === void 0 ? layout.pageCount === 1 ? layout.pages[0] : void 0 : layout.pages.find(({ pageObjectNumber }) => pageObjectNumber === asset.pageObjectNumber);
|
|
408
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");
|
|
409
|
-
|
|
410
|
-
if (!snapshot.fields.some((field) => field.widgets.some(({ pageObjectNumber }) => pageObjectNumber === selectedPage.pageObjectNumber))) return bin;
|
|
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");
|
|
412
|
-
|
|
413
|
-
scripting = (0, _embedpdf_plugin_form.createFormScriptingController)({
|
|
534
|
+
realm = mintRealm.call(actions, {
|
|
414
535
|
doc,
|
|
415
536
|
document: () => targetMeta,
|
|
416
|
-
|
|
417
|
-
|
|
537
|
+
bootSources: async () => {
|
|
538
|
+
return (doc.actions ? await doc.actions.read() : null)?.nameTreeScripts.map(({ action }) => (0, _embedpdf_core_acrojs.javaScriptProgramFromActionTree)(action)) ?? [];
|
|
539
|
+
}
|
|
540
|
+
});
|
|
541
|
+
scripting = (0, _embedpdf_plugin_form_scripting.createFormScriptingController)({
|
|
542
|
+
doc,
|
|
543
|
+
document: () => targetMeta,
|
|
544
|
+
transaction: realm.transaction.bind(realm),
|
|
545
|
+
budget: realm.budget
|
|
546
|
+
});
|
|
547
|
+
const result = await scripting.recalculate();
|
|
548
|
+
actions.surfaceScriptCommit(result, {
|
|
549
|
+
origin: "user",
|
|
550
|
+
realm: "detached"
|
|
418
551
|
});
|
|
419
|
-
const result = await scripting.recalculate(snapshot);
|
|
420
|
-
surfaceScriptingResult(result);
|
|
421
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"}`);
|
|
422
553
|
const pageObjectNumber = selectedPage.pageObjectNumber;
|
|
423
554
|
const failed = (await doc.pages.flatten([pageObjectNumber], "display")).results.find(({ status }) => status === "failed" || status === "skipped");
|
|
@@ -438,6 +569,7 @@ function createStampCapability(ctx, config = {}) {
|
|
|
438
569
|
};
|
|
439
570
|
} finally {
|
|
440
571
|
scripting?.dispose();
|
|
572
|
+
realm?.dispose();
|
|
441
573
|
await doc.close();
|
|
442
574
|
}
|
|
443
575
|
};
|
|
@@ -446,18 +578,73 @@ function createStampCapability(ctx, config = {}) {
|
|
|
446
578
|
const bin = binaries.get(assetId);
|
|
447
579
|
if (!asset || !bin) throw new _embedpdf_engine_core_runtime.EngineError(_embedpdf_engine_core_runtime.EngineErrorCode.NotFound, `[stamp] unknown asset '${assetId}'`);
|
|
448
580
|
const placement = await materializeForPlacement(documentId, asset, bin);
|
|
449
|
-
await ctx.forDocument(
|
|
450
|
-
|
|
451
|
-
preview: placement.preview ? {
|
|
452
|
-
data: placement.preview.bytes,
|
|
453
|
-
mimeType: placement.preview.mimeType
|
|
454
|
-
} : void 0,
|
|
455
|
-
intrinsicSize: asset.size,
|
|
581
|
+
await ctx.forDocument(_embedpdf_plugin_annotation_contract.AnnotationToken, documentId).armStamp({
|
|
582
|
+
...stampPayload(asset, placement, placement !== bin),
|
|
456
583
|
targetWidth: opts?.targetWidth
|
|
457
584
|
});
|
|
458
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
|
+
};
|
|
459
645
|
ctx.cleanup(() => {
|
|
460
646
|
binaries.clear();
|
|
647
|
+
ghostRenders.clear();
|
|
461
648
|
libraryBinaries.clear();
|
|
462
649
|
libraryMutationTails.clear();
|
|
463
650
|
const ownedAssetEngine = typeof config.assetEngine === "function" ? assetEngineRef : null;
|
|
@@ -481,13 +668,18 @@ function createStampCapability(ctx, config = {}) {
|
|
|
481
668
|
asset: (id) => ctx.getState().assets[id] ?? null,
|
|
482
669
|
assetPreview: (id) => binaries.get(id)?.preview ?? null,
|
|
483
670
|
assetBytes: (id) => binaries.get(id)?.bytes ?? null,
|
|
484
|
-
|
|
671
|
+
exportLibrary: (id) => libraryBinaries.get(id) ?? null,
|
|
672
|
+
onLibraryChanged: libraryChanged.on,
|
|
673
|
+
createLibrary,
|
|
485
674
|
importLibraryPdf,
|
|
486
675
|
addAsset,
|
|
676
|
+
addAssetFromAnnotations,
|
|
677
|
+
updateAsset,
|
|
487
678
|
removeAsset,
|
|
488
679
|
removeLibrary,
|
|
489
680
|
armAsset,
|
|
490
|
-
|
|
681
|
+
placeAsset,
|
|
682
|
+
disarm: (documentId) => ctx.forDocument(_embedpdf_plugin_annotation_contract.AnnotationToken, documentId).disarmStamp()
|
|
491
683
|
};
|
|
492
684
|
}
|
|
493
685
|
//#endregion
|
|
@@ -541,6 +733,13 @@ const stampReducer = (state, action) => {
|
|
|
541
733
|
}
|
|
542
734
|
};
|
|
543
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;
|
|
544
743
|
case "ASSET_REMOVED": {
|
|
545
744
|
const asset = state.assets[action.assetId];
|
|
546
745
|
if (!asset) return state;
|
|
@@ -563,9 +762,6 @@ const stampReducer = (state, action) => {
|
|
|
563
762
|
}
|
|
564
763
|
};
|
|
565
764
|
//#endregion
|
|
566
|
-
//#region src/types.ts
|
|
567
|
-
const StampToken = (0, _embedpdf_core.createCapabilityToken)("stamp");
|
|
568
|
-
//#endregion
|
|
569
765
|
//#region src/stamp.plugin.ts
|
|
570
766
|
/**
|
|
571
767
|
* The stamp plugin. WORKSPACE-scoped: libraries are shared across every open
|
|
@@ -577,14 +773,98 @@ const StampToken = (0, _embedpdf_core.createCapabilityToken)("stamp");
|
|
|
577
773
|
*/
|
|
578
774
|
const stampPlugin = (config = {}) => (0, _embedpdf_core.definePlugin)({
|
|
579
775
|
id: "stamp",
|
|
580
|
-
token: StampToken,
|
|
776
|
+
token: require_contract.StampToken,
|
|
581
777
|
scope: "workspace",
|
|
582
778
|
initialState: initialStampState,
|
|
583
779
|
reduce: stampReducer,
|
|
584
780
|
capability: (ctx) => createStampCapability(ctx, config)
|
|
585
781
|
});
|
|
586
782
|
//#endregion
|
|
587
|
-
|
|
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;
|
|
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;
|
|
588
868
|
exports.stampPlugin = stampPlugin;
|
|
589
869
|
|
|
590
870
|
//# sourceMappingURL=index.cjs.map
|