@embedpdf/plugin-stamp 3.0.0-next.12 → 3.0.0-next.13

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/index.js CHANGED
@@ -1,9 +1,10 @@
1
- import { a as assetIdFor, c as stampKey, d as stampPieceInfo, i as STAMP_PIECEINFO_VERSION, l as stampKindToPdfName, n as STAMP_LIBRARY_PIECEINFO_APP, o as customStampName, r as STAMP_PIECEINFO_APP, s as parseStampKey, t as StampToken, u as stampLibraryPieceInfo } from "./contract-BFCBVNRX.js";
1
+ import { a as STAMP_PIECEINFO_VERSION, c as libraryKindFromPdfName, d as stampKey, f as stampKindToPdfName, i as STAMP_PIECEINFO_APP, l as libraryKindToPdfName, m as stampPieceInfo, n as DEFAULT_LIBRARY_KIND, o as assetIdFor, p as stampLibraryPieceInfo, r as STAMP_LIBRARY_PIECEINFO_APP, s as customStampName, t as StampToken, u as parseStampKey } from "./contract-B79Onij2.js";
2
2
  import { createEventHook, definePlugin } from "@embedpdf/core";
3
3
  import { EngineError, EngineErrorCode, resolveBinarySource, sniffBinaryMetadata } from "@embedpdf/engine-core/runtime";
4
4
  import { javaScriptProgramFromActionTree } from "@embedpdf/core-acrojs";
5
5
  import { ActionsToken } from "@embedpdf/plugin-actions/contract/host";
6
6
  import { AnnotationToken } from "@embedpdf/plugin-annotation/contract";
7
+ import { AnnotationToken as AnnotationToken$1 } from "@embedpdf/plugin-annotation/contract/host";
7
8
  import { createFormScriptingController } from "@embedpdf/plugin-form/scripting";
8
9
  //#region src/blank-library.ts
9
10
  /**
@@ -37,6 +38,51 @@ function blankLibraryPdf() {
37
38
  //#endregion
38
39
  //#region src/capability.ts
39
40
  const DEFAULT_PREVIEW_WIDTH = 256;
41
+ /** `#rrggbb` → the engine's sRGB triplet. */
42
+ const hexColor = (hex) => {
43
+ const m = /^#?([0-9a-f]{6})$/i.exec(hex.trim());
44
+ if (!m) throw new EngineError(EngineErrorCode.InvalidArg, `[stamp] mark color must be #rrggbb, got '${hex}'`);
45
+ const n = parseInt(m[1], 16);
46
+ return {
47
+ r: n >> 16 & 255,
48
+ g: n >> 8 & 255,
49
+ b: n & 255
50
+ };
51
+ };
52
+ /** An exact ArrayBuffer over a view (the engine's binary idiom). */
53
+ const toArrayBuffer = (bytes) => {
54
+ const out = new ArrayBuffer(bytes.byteLength);
55
+ new Uint8Array(out).set(bytes);
56
+ return out;
57
+ };
58
+ /** The rect an ink mark occupies, padded by its stroke. */
59
+ const inkBounds = (strokes, strokeWidth) => {
60
+ let left = Infinity;
61
+ let bottom = Infinity;
62
+ let right = -Infinity;
63
+ let top = -Infinity;
64
+ for (const stroke of strokes) for (const { x, y } of stroke) {
65
+ if (x < left) left = x;
66
+ if (x > right) right = x;
67
+ if (y < bottom) bottom = y;
68
+ if (y > top) top = y;
69
+ }
70
+ if (!Number.isFinite(left)) throw new EngineError(EngineErrorCode.InvalidArg, "[stamp] an ink mark needs at least one point");
71
+ const pad = strokeWidth;
72
+ return {
73
+ left: left - pad,
74
+ bottom: bottom - pad,
75
+ right: right + pad,
76
+ top: top + pad
77
+ };
78
+ };
79
+ /** A generous box for typed text; the export crops to the glyphs. */
80
+ const textBounds = (text, fontSize) => ({
81
+ left: 0,
82
+ bottom: 0,
83
+ right: Math.max(1, text.length) * fontSize * .75 + fontSize,
84
+ top: fontSize * 1.6
85
+ });
40
86
  /** Session-unique ids. Assets are session-scoped for now (no persistence),
41
87
  * so a timestamp + counter is enough — durable ids come with the store port. */
42
88
  let seq = 0;
@@ -128,6 +174,7 @@ function createStampCapability(ctx, config = {}) {
128
174
  format: "png"
129
175
  }));
130
176
  const createLibrary = async (name, opts) => {
177
+ const kind = opts?.kind ?? "stamps";
131
178
  const taken = new Set(Object.keys(ctx.getState().libraries));
132
179
  const id = allocateId(opts?.id, "stamp-lib", taken);
133
180
  const doc = await openAssetDocument(blankLibraryPdf());
@@ -135,7 +182,10 @@ function createStampCapability(ctx, config = {}) {
135
182
  try {
136
183
  requireCanonicalServices(doc);
137
184
  await doc.metadata.update({ title: name });
138
- await doc.pieceInfo.update(STAMP_LIBRARY_PIECEINFO_APP, stampLibraryPieceInfo(id, { categories: opts?.categories }));
185
+ await doc.pieceInfo.update(STAMP_LIBRARY_PIECEINFO_APP, stampLibraryPieceInfo(id, {
186
+ kind,
187
+ categories: opts?.categories
188
+ }));
139
189
  bytes = await doc.download();
140
190
  } finally {
141
191
  await doc.close();
@@ -146,6 +196,7 @@ function createStampCapability(ctx, config = {}) {
146
196
  library: {
147
197
  id,
148
198
  name,
199
+ kind,
149
200
  assetIds: [],
150
201
  ...opts?.categories ? { categories: opts.categories } : {}
151
202
  }
@@ -179,6 +230,7 @@ function createStampCapability(ctx, config = {}) {
179
230
  if (!docMeta.title) await doc.metadata.update({ title: libraryName });
180
231
  const libraryCategories = opts?.categories ?? entryStringArray(catalogEntries, "Categories");
181
232
  const libraryLocale = entryString(catalogEntries, "Locale");
233
+ const libraryKind = opts?.libraryKind ?? libraryKindFromPdfName(entryName(catalogEntries, "Kind"));
182
234
  const registry = (layout.namedPages ?? []).filter((entry) => entry.target.kind === "page");
183
235
  const hidden = (layout.namedPages ?? []).filter((entry) => entry.target.kind !== "page");
184
236
  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`);
@@ -219,6 +271,7 @@ function createStampCapability(ctx, config = {}) {
219
271
  });
220
272
  }
221
273
  await doc.pieceInfo.update(STAMP_LIBRARY_PIECEINFO_APP, stampLibraryPieceInfo(libraryId, {
274
+ kind: libraryKind,
222
275
  categories: libraryCategories,
223
276
  locale: libraryLocale
224
277
  }));
@@ -260,6 +313,7 @@ function createStampCapability(ctx, config = {}) {
260
313
  library: {
261
314
  id: libraryId,
262
315
  name: libraryName,
316
+ kind: libraryKind,
263
317
  ...libraryLocale ? { locale: libraryLocale } : {},
264
318
  categories: libraryCategories,
265
319
  assetIds: []
@@ -291,9 +345,76 @@ function createStampCapability(ctx, config = {}) {
291
345
  });
292
346
  return imported.library.id;
293
347
  };
348
+ /**
349
+ * A drawn or typed mark rendered to a one-page PDF: the annotation is
350
+ * authored on a scratch page and exported the way "stamp from selection"
351
+ * exports — a fresh page the size of the mark, paths kept as paths, the
352
+ * font subset-embedded. Self-contained and Acrobat-readable.
353
+ */
354
+ const renderMark = async (mark) => {
355
+ const doc = await openAssetDocument(blankLibraryPdf());
356
+ try {
357
+ const scratch = (await doc.pages.list()).pages[0];
358
+ if (!scratch) throw new EngineError(EngineErrorCode.Unknown, "[stamp] no scratch page");
359
+ const page = doc.page(scratch.pageObjectNumber);
360
+ if (!page.annotations.exportAppearance) throw new EngineError(EngineErrorCode.NotImplemented, "[stamp] authoring a mark needs an asset engine that can export annotation appearances");
361
+ const color = hexColor(mark.color ?? "#1d2b53");
362
+ const created = mark.kind === "ink" ? await page.annotations.create({
363
+ subtype: "ink",
364
+ inkList: mark.strokes.map((stroke) => stroke.map(({ x, y }) => ({
365
+ x,
366
+ y
367
+ }))),
368
+ rect: inkBounds(mark.strokes, mark.strokeWidth ?? 2),
369
+ color,
370
+ strokeWidth: mark.strokeWidth ?? 2
371
+ }) : await page.annotations.create({
372
+ subtype: "free-text",
373
+ intent: "free-text",
374
+ contents: mark.text,
375
+ fontFamily: mark.fontFamily,
376
+ fontSize: mark.fontSize ?? 36,
377
+ textAlign: "left",
378
+ rect: textBounds(mark.text, mark.fontSize ?? 36),
379
+ fontColor: color,
380
+ strokeWidth: 0
381
+ });
382
+ return new Uint8Array(await page.annotations.exportAppearance([created.created.ref]));
383
+ } finally {
384
+ await doc.close();
385
+ }
386
+ };
387
+ /** One page of a PDF as a single-page PDF (a `pdf` mark with `pageIndex`). */
388
+ const extractPage = async (bytes, pageIndex) => {
389
+ const doc = await openAssetDocument(bytes);
390
+ try {
391
+ const layout = await doc.pages.list();
392
+ const page = layout.pages[pageIndex];
393
+ if (!page) throw new EngineError(EngineErrorCode.InvalidArg, `[stamp] the PDF has no page ${pageIndex} (${layout.pageCount} pages)`);
394
+ return await doc.pages.extract([page.pageObjectNumber]);
395
+ } finally {
396
+ await doc.close();
397
+ }
398
+ };
399
+ /** The bytes an asset is made from: the caller's `source`, or its `mark` authored into one. */
400
+ const resolveAssetSource = async (input) => {
401
+ if (input.source === void 0 === (input.mark === void 0)) throw new EngineError(EngineErrorCode.InvalidArg, "[stamp] addAsset takes exactly one of `source` / `mark`");
402
+ if (input.source !== void 0) return resolveBinarySource(input.source);
403
+ const mark = input.mark;
404
+ switch (mark.kind) {
405
+ case "image": return resolveBinarySource(mark.source);
406
+ case "pdf": {
407
+ const resolved = await resolveBinarySource(mark.source);
408
+ if (mark.pageIndex === void 0) return resolved;
409
+ return { bytes: toArrayBuffer(await extractPage(new Uint8Array(resolved.bytes), mark.pageIndex)) };
410
+ }
411
+ case "ink":
412
+ case "text": return { bytes: toArrayBuffer(await renderMark(mark)) };
413
+ }
414
+ };
294
415
  const addAsset = async (input) => {
295
416
  if (input.libraryId && !ctx.getState().libraries[input.libraryId]) throw new EngineError(EngineErrorCode.NotFound, `[stamp] unknown library '${input.libraryId}'`);
296
- const resolved = await resolveBinarySource(input.source);
417
+ const resolved = await resolveAssetSource(input);
297
418
  const meta = sniffBinaryMetadata(resolved.bytes);
298
419
  if (!meta) throw new EngineError(EngineErrorCode.InvalidArg, "[stamp] asset source must be PNG, JPEG, or single-page PDF bytes");
299
420
  const isPdf = meta.mimeType === "application/pdf";
@@ -502,6 +623,43 @@ function createStampCapability(ctx, config = {}) {
502
623
  });
503
624
  });
504
625
  };
626
+ const updateLibrary = async (id, patch) => {
627
+ if (!ctx.getState().libraries[id]) throw new EngineError(EngineErrorCode.NotFound, `[stamp] unknown library '${id}'`);
628
+ return mutateLibrary(id, async () => {
629
+ const library = ctx.getState().libraries[id];
630
+ if (!library) throw new EngineError(EngineErrorCode.NotFound, `[stamp] unknown library '${id}'`);
631
+ const canonicalBytes = libraryBinaries.get(id);
632
+ if (!canonicalBytes) throw new EngineError(EngineErrorCode.Unknown, `[stamp] canonical bytes are missing for library '${id}'`);
633
+ const next = {
634
+ ...library,
635
+ ...patch.name !== void 0 ? { name: patch.name } : {},
636
+ ...patch.categories !== void 0 ? { categories: patch.categories } : {}
637
+ };
638
+ const doc = await openAssetDocument(canonicalBytes);
639
+ let rewritten;
640
+ try {
641
+ requireCanonicalServices(doc);
642
+ if (next.name !== library.name) await doc.metadata.update({ title: next.name });
643
+ await doc.pieceInfo.update(STAMP_LIBRARY_PIECEINFO_APP, stampLibraryPieceInfo(id, {
644
+ kind: next.kind,
645
+ categories: next.categories,
646
+ locale: next.locale
647
+ }));
648
+ rewritten = await doc.download();
649
+ } finally {
650
+ await doc.close();
651
+ }
652
+ libraryBinaries.set(id, rewritten);
653
+ ctx.dispatch({
654
+ type: "LIBRARY_UPDATED",
655
+ library: next
656
+ });
657
+ libraryChanged.emit({
658
+ libraryId: id,
659
+ reason: "updated"
660
+ });
661
+ });
662
+ };
505
663
  /**
506
664
  * Form-backed stamps are executable templates only until placement. Work
507
665
  * on an isolated copy so neither the canonical library nor its reusable
@@ -581,6 +739,20 @@ function createStampCapability(ctx, config = {}) {
581
739
  ...stampPayload(asset, placement, placement !== bin),
582
740
  targetWidth: opts?.targetWidth
583
741
  });
742
+ armedByDocument.set(documentId, assetId);
743
+ };
744
+ /** Which asset this plugin armed per document. The annotation plugin owns
745
+ * the arm itself (a tool switch disarms it), so the answer is checked
746
+ * against it and forgotten once it no longer holds one. */
747
+ const armedByDocument = /* @__PURE__ */ new Map();
748
+ const armedAsset = (documentId) => {
749
+ const assetId = armedByDocument.get(documentId);
750
+ if (assetId === void 0) return null;
751
+ if (!ctx.tryForDocument(AnnotationToken$1, documentId)?.hasArmedStamp()) {
752
+ armedByDocument.delete(documentId);
753
+ return null;
754
+ }
755
+ return ctx.getState().assets[assetId] ?? null;
584
756
  };
585
757
  /**
586
758
  * Ghost renders, one per size bucket per asset, rendered lazily on the
@@ -651,9 +823,10 @@ function createStampCapability(ctx, config = {}) {
651
823
  if (ownedAssetEngine) Promise.resolve(ownedAssetEngine).then((engine) => engine.destroy()).catch(() => {});
652
824
  });
653
825
  return {
654
- libraries: () => {
826
+ libraries: (query) => {
655
827
  const s = ctx.getState();
656
- return s.libraryOrder.map((id) => s.libraries[id]).filter((l) => l != null);
828
+ const kinds = query?.kind === void 0 ? null : new Set(typeof query.kind === "string" ? [query.kind] : query.kind);
829
+ return s.libraryOrder.map((id) => s.libraries[id]).filter((l) => l != null && (kinds === null || kinds.has(l.kind)));
657
830
  },
658
831
  library: (id) => ctx.getState().libraries[id] ?? null,
659
832
  assets: (libraryId) => {
@@ -670,6 +843,7 @@ function createStampCapability(ctx, config = {}) {
670
843
  exportLibrary: (id) => libraryBinaries.get(id) ?? null,
671
844
  onLibraryChanged: libraryChanged.on,
672
845
  createLibrary,
846
+ updateLibrary,
673
847
  importLibraryPdf,
674
848
  addAsset,
675
849
  addAssetFromAnnotations,
@@ -678,7 +852,11 @@ function createStampCapability(ctx, config = {}) {
678
852
  removeLibrary,
679
853
  armAsset,
680
854
  placeAsset,
681
- disarm: (documentId) => ctx.forDocument(AnnotationToken, documentId).disarmStamp()
855
+ disarm: (documentId) => {
856
+ armedByDocument.delete(documentId);
857
+ ctx.forDocument(AnnotationToken, documentId).disarmStamp();
858
+ },
859
+ armedAsset
682
860
  };
683
861
  }
684
862
  //#endregion
@@ -700,6 +878,13 @@ const stampReducer = (state, action) => {
700
878
  },
701
879
  libraryOrder: [...state.libraryOrder, action.library.id]
702
880
  };
881
+ case "LIBRARY_UPDATED": return state.libraries[action.library.id] ? {
882
+ ...state,
883
+ libraries: {
884
+ ...state.libraries,
885
+ [action.library.id]: action.library
886
+ }
887
+ } : state;
703
888
  case "LIBRARY_REMOVED": {
704
889
  const library = state.libraries[action.libraryId];
705
890
  if (!library) return state;
@@ -850,6 +1035,6 @@ function persistStampLibraries(stamp, store, opts = {}) {
850
1035
  };
851
1036
  }
852
1037
  //#endregion
853
- export { STAMP_LIBRARY_PIECEINFO_APP, STAMP_PIECEINFO_APP, STAMP_PIECEINFO_VERSION, StampToken, assetIdFor, customStampName, memoryStampStore, parseStampKey, persistStampLibraries, restoreStampLibraries, stampKey, stampKindToPdfName, stampLibraryPieceInfo, stampPieceInfo, stampPlugin };
1038
+ export { DEFAULT_LIBRARY_KIND, STAMP_LIBRARY_PIECEINFO_APP, STAMP_PIECEINFO_APP, STAMP_PIECEINFO_VERSION, StampToken, assetIdFor, customStampName, libraryKindFromPdfName, libraryKindToPdfName, memoryStampStore, parseStampKey, persistStampLibraries, restoreStampLibraries, stampKey, stampKindToPdfName, stampLibraryPieceInfo, stampPieceInfo, stampPlugin };
854
1039
 
855
1040
  //# sourceMappingURL=index.js.map