@embedpdf/plugin-annotation 1.0.6 → 1.0.8

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.cjs CHANGED
@@ -23,6 +23,14 @@ __export(index_exports, {
23
23
  ANNOTATION_PLUGIN_ID: () => ANNOTATION_PLUGIN_ID,
24
24
  AnnotationPlugin: () => AnnotationPlugin,
25
25
  AnnotationPluginPackage: () => AnnotationPluginPackage,
26
+ getAnnotations: () => getAnnotations,
27
+ getAnnotationsByPageIndex: () => getAnnotationsByPageIndex,
28
+ getSelectedAnnotation: () => getSelectedAnnotation,
29
+ getSelectedAnnotationByPageIndex: () => getSelectedAnnotationByPageIndex,
30
+ getSelectedAnnotationMode: () => getSelectedAnnotationMode,
31
+ getSelectedAnnotationWithPageIndex: () => getSelectedAnnotationWithPageIndex,
32
+ isAnnotationSelected: () => isAnnotationSelected,
33
+ isInAnnotationMode: () => isInAnnotationMode,
26
34
  manifest: () => manifest
27
35
  });
28
36
  module.exports = __toCommonJS(index_exports);
@@ -34,104 +42,132 @@ var manifest = {
34
42
  name: "Annotation Plugin",
35
43
  version: "1.0.0",
36
44
  provides: ["annotation"],
37
- requires: [],
38
- optional: [],
45
+ requires: ["interaction-manager", "selection"],
46
+ optional: ["history"],
39
47
  defaultConfig: {
40
- enabled: true
48
+ enabled: true,
49
+ autoCommit: true
41
50
  }
42
51
  };
43
52
 
44
53
  // src/lib/annotation-plugin.ts
45
54
  var import_core = require("@embedpdf/core");
46
-
47
- // ../models/dist/index.js
48
- var PdfSoftHyphenMarker = "\xAD";
49
- var PdfZeroWidthSpace = "\u200B";
50
- var PdfWordJoiner = "\u2060";
51
- var PdfBomOrZwnbsp = "\uFEFF";
52
- var PdfNonCharacterFFFE = "\uFFFE";
53
- var PdfNonCharacterFFFF = "\uFFFF";
54
- var PdfUnwantedTextMarkers = Object.freeze([
55
- PdfSoftHyphenMarker,
56
- PdfZeroWidthSpace,
57
- PdfWordJoiner,
58
- PdfBomOrZwnbsp,
59
- PdfNonCharacterFFFE,
60
- PdfNonCharacterFFFF
61
- ]);
62
- var PdfUnwantedTextRegex = new RegExp(`[${PdfUnwantedTextMarkers.join("")}]`, "g");
63
- var PdfAnnotationSubtype = /* @__PURE__ */ ((PdfAnnotationSubtype2) => {
64
- PdfAnnotationSubtype2[PdfAnnotationSubtype2["UNKNOWN"] = 0] = "UNKNOWN";
65
- PdfAnnotationSubtype2[PdfAnnotationSubtype2["TEXT"] = 1] = "TEXT";
66
- PdfAnnotationSubtype2[PdfAnnotationSubtype2["LINK"] = 2] = "LINK";
67
- PdfAnnotationSubtype2[PdfAnnotationSubtype2["FREETEXT"] = 3] = "FREETEXT";
68
- PdfAnnotationSubtype2[PdfAnnotationSubtype2["LINE"] = 4] = "LINE";
69
- PdfAnnotationSubtype2[PdfAnnotationSubtype2["SQUARE"] = 5] = "SQUARE";
70
- PdfAnnotationSubtype2[PdfAnnotationSubtype2["CIRCLE"] = 6] = "CIRCLE";
71
- PdfAnnotationSubtype2[PdfAnnotationSubtype2["POLYGON"] = 7] = "POLYGON";
72
- PdfAnnotationSubtype2[PdfAnnotationSubtype2["POLYLINE"] = 8] = "POLYLINE";
73
- PdfAnnotationSubtype2[PdfAnnotationSubtype2["HIGHLIGHT"] = 9] = "HIGHLIGHT";
74
- PdfAnnotationSubtype2[PdfAnnotationSubtype2["UNDERLINE"] = 10] = "UNDERLINE";
75
- PdfAnnotationSubtype2[PdfAnnotationSubtype2["SQUIGGLY"] = 11] = "SQUIGGLY";
76
- PdfAnnotationSubtype2[PdfAnnotationSubtype2["STRIKEOUT"] = 12] = "STRIKEOUT";
77
- PdfAnnotationSubtype2[PdfAnnotationSubtype2["STAMP"] = 13] = "STAMP";
78
- PdfAnnotationSubtype2[PdfAnnotationSubtype2["CARET"] = 14] = "CARET";
79
- PdfAnnotationSubtype2[PdfAnnotationSubtype2["INK"] = 15] = "INK";
80
- PdfAnnotationSubtype2[PdfAnnotationSubtype2["POPUP"] = 16] = "POPUP";
81
- PdfAnnotationSubtype2[PdfAnnotationSubtype2["FILEATTACHMENT"] = 17] = "FILEATTACHMENT";
82
- PdfAnnotationSubtype2[PdfAnnotationSubtype2["SOUND"] = 18] = "SOUND";
83
- PdfAnnotationSubtype2[PdfAnnotationSubtype2["MOVIE"] = 19] = "MOVIE";
84
- PdfAnnotationSubtype2[PdfAnnotationSubtype2["WIDGET"] = 20] = "WIDGET";
85
- PdfAnnotationSubtype2[PdfAnnotationSubtype2["SCREEN"] = 21] = "SCREEN";
86
- PdfAnnotationSubtype2[PdfAnnotationSubtype2["PRINTERMARK"] = 22] = "PRINTERMARK";
87
- PdfAnnotationSubtype2[PdfAnnotationSubtype2["TRAPNET"] = 23] = "TRAPNET";
88
- PdfAnnotationSubtype2[PdfAnnotationSubtype2["WATERMARK"] = 24] = "WATERMARK";
89
- PdfAnnotationSubtype2[PdfAnnotationSubtype2["THREED"] = 25] = "THREED";
90
- PdfAnnotationSubtype2[PdfAnnotationSubtype2["RICHMEDIA"] = 26] = "RICHMEDIA";
91
- PdfAnnotationSubtype2[PdfAnnotationSubtype2["XFAWIDGET"] = 27] = "XFAWIDGET";
92
- PdfAnnotationSubtype2[PdfAnnotationSubtype2["REDACT"] = 28] = "REDACT";
93
- return PdfAnnotationSubtype2;
94
- })(PdfAnnotationSubtype || {});
95
- function ignore() {
96
- }
55
+ var import_models = require("@embedpdf/models");
97
56
 
98
57
  // src/lib/actions.ts
99
- var SET_ANNOTATIONS = "SET_ANNOTATIONS";
100
- var SELECT_ANNOTATION = "SELECT_ANNOTATION";
101
- var DESELECT_ANNOTATION = "DESELECT_ANNOTATION";
102
- var SET_ANNOTATION_MODE = "SET_ANNOTATION_MODE";
103
- var UPDATE_ANNOTATION_COLOR = "UPDATE_ANNOTATION_COLOR";
104
- function setAnnotations(payload) {
105
- return { type: SET_ANNOTATIONS, payload };
106
- }
107
- function selectAnnotation(pageIndex, annotationId, annotation) {
108
- return {
109
- type: SELECT_ANNOTATION,
110
- payload: { pageIndex, annotationId, annotation }
111
- };
112
- }
113
- function deselectAnnotation() {
114
- return { type: DESELECT_ANNOTATION };
115
- }
116
- function setAnnotationMode(mode) {
117
- return {
118
- type: SET_ANNOTATION_MODE,
119
- payload: mode
120
- };
121
- }
122
- function updateAnnotationColor(pageIndex, annotationId, color) {
123
- return {
124
- type: UPDATE_ANNOTATION_COLOR,
125
- payload: { pageIndex, annotationId, color }
126
- };
127
- }
58
+ var SET_ANNOTATIONS = "ANNOTATION/SET_ANNOTATIONS";
59
+ var REINDEX_PAGE_ANNOTATIONS = "ANNOTATION/REINDEX_PAGE";
60
+ var SELECT_ANNOTATION = "ANNOTATION/SELECT_ANNOTATION";
61
+ var DESELECT_ANNOTATION = "ANNOTATION/DESELECT_ANNOTATION";
62
+ var SET_ANNOTATION_MODE = "ANNOTATION/SET_ANNOTATION_MODE";
63
+ var UPDATE_TOOL_DEFAULTS = "ANNOTATION/UPDATE_TOOL_DEFAULTS";
64
+ var ADD_COLOR_PRESET = "ANNOTATION/ADD_COLOR_PRESET";
65
+ var CREATE_ANNOTATION = "ANNOTATION/CREATE_ANNOTATION";
66
+ var PATCH_ANNOTATION = "ANNOTATION/PATCH_ANNOTATION";
67
+ var DELETE_ANNOTATION = "ANNOTATION/DELETE_ANNOTATION";
68
+ var COMMIT_PENDING_CHANGES = "ANNOTATION/COMMIT";
69
+ var STORE_PDF_ID = "ANNOTATION/STORE_PDF_ID";
70
+ var PURGE_ANNOTATION = "ANNOTATION/PURGE_ANNOTATION";
71
+ var setAnnotations = (p) => ({
72
+ type: SET_ANNOTATIONS,
73
+ payload: p
74
+ });
75
+ var reindexPageAnnotations = (pageIndex) => ({
76
+ type: REINDEX_PAGE_ANNOTATIONS,
77
+ payload: { pageIndex }
78
+ });
79
+ var selectAnnotation = (pageIndex, localId) => ({
80
+ type: SELECT_ANNOTATION,
81
+ payload: { pageIndex, localId }
82
+ });
83
+ var deselectAnnotation = () => ({ type: DESELECT_ANNOTATION });
84
+ var setAnnotationMode = (m) => ({
85
+ type: SET_ANNOTATION_MODE,
86
+ payload: m
87
+ });
88
+ var updateToolDefaults = (subtype, patch) => ({ type: UPDATE_TOOL_DEFAULTS, payload: { subtype, patch } });
89
+ var addColorPreset = (c) => ({
90
+ type: ADD_COLOR_PRESET,
91
+ payload: c
92
+ });
93
+ var createAnnotation = (pageIndex, localId, annotation) => ({
94
+ type: CREATE_ANNOTATION,
95
+ payload: { pageIndex, localId, annotation }
96
+ });
97
+ var patchAnnotation = (pageIndex, localId, patch) => ({
98
+ type: PATCH_ANNOTATION,
99
+ payload: { pageIndex, localId, patch }
100
+ });
101
+ var deleteAnnotation = (pageIndex, localId) => ({
102
+ type: DELETE_ANNOTATION,
103
+ payload: { pageIndex, localId }
104
+ });
105
+ var commitPendingChanges = () => ({ type: COMMIT_PENDING_CHANGES });
106
+ var storePdfId = (uid, pdfId) => ({
107
+ type: STORE_PDF_ID,
108
+ payload: { uid, pdfId }
109
+ });
110
+ var purgeAnnotation = (uid) => ({
111
+ type: PURGE_ANNOTATION,
112
+ payload: { uid }
113
+ });
114
+
115
+ // src/lib/utils.ts
116
+ var makeUid = (pageIndex, localId) => `p${pageIndex}#${localId}`;
117
+ var parseUid = (uid) => {
118
+ const [pg, rest] = uid.slice(1).split("#");
119
+ return { pageIndex: Number(pg), localId: Number(rest) };
120
+ };
121
+
122
+ // src/lib/selectors.ts
123
+ var makeUid2 = (page, id) => `p${page}#${id}`;
124
+ var getAnnotationsByPageIndex = (s, page) => (s.pages[page] ?? []).map((uid) => s.byUid[uid]);
125
+ var getAnnotations = (s) => {
126
+ const out = {};
127
+ for (const p of Object.keys(s.pages).map(Number)) out[p] = getAnnotationsByPageIndex(s, p);
128
+ return out;
129
+ };
130
+ var getSelectedAnnotation = (s) => s.selectedUid ? s.byUid[s.selectedUid] : null;
131
+ var getSelectedAnnotationWithPageIndex = (s) => {
132
+ if (!s.selectedUid) return null;
133
+ const { pageIndex, localId } = parseUid(s.selectedUid);
134
+ return { pageIndex, localId, annotation: s.byUid[s.selectedUid].object };
135
+ };
136
+ var getSelectedAnnotationByPageIndex = (s, pageIndex) => {
137
+ if (!s.selectedUid) return null;
138
+ const pageUids = s.pages[pageIndex] ?? [];
139
+ if (pageUids.includes(s.selectedUid)) {
140
+ return s.byUid[s.selectedUid];
141
+ }
142
+ return null;
143
+ };
144
+ var isInAnnotationMode = (s) => s.annotationMode !== null;
145
+ var getSelectedAnnotationMode = (s) => s.annotationMode;
146
+ var isAnnotationSelected = (s, page, id) => s.selectedUid === makeUid2(page, id);
128
147
 
129
148
  // src/lib/annotation-plugin.ts
130
149
  var AnnotationPlugin = class extends import_core.BasePlugin {
131
- constructor(id, registry, engine) {
150
+ constructor(id, registry, engine, config) {
132
151
  super(id, registry);
152
+ this.ANNOTATION_HISTORY_TOPIC = "annotations";
133
153
  this.state$ = (0, import_core.createBehaviorEmitter)();
154
+ /** Map <subtype> → <modeId>. Filled once in `initialize()`. */
155
+ this.modeBySubtype = /* @__PURE__ */ new Map();
156
+ /** The inverse map for quick lookup in onModeChange(). */
157
+ this.subtypeByMode = /* @__PURE__ */ new Map();
158
+ this.modeChange$ = (0, import_core.createBehaviorEmitter)();
159
+ this.activeTool$ = (0, import_core.createBehaviorEmitter)({
160
+ mode: null,
161
+ defaults: null
162
+ });
134
163
  this.engine = engine;
164
+ this.config = config;
165
+ const selection = registry.getPlugin("selection");
166
+ this.selection = selection?.provides() ?? null;
167
+ const history = registry.getPlugin("history");
168
+ this.history = history?.provides() ?? null;
169
+ const interactionManager = registry.getPlugin("interaction-manager");
170
+ this.interactionManager = interactionManager?.provides() ?? null;
135
171
  this.coreStore.onAction(import_core.SET_DOCUMENT, (_action, state) => {
136
172
  const doc = state.core.document;
137
173
  if (doc) {
@@ -140,146 +176,493 @@ var AnnotationPlugin = class extends import_core.BasePlugin {
140
176
  });
141
177
  }
142
178
  async initialize() {
179
+ for (const [subtype, defaults] of (0, import_core.enumEntries)(this.state.toolDefaults)) {
180
+ this.registerTool(subtype, defaults);
181
+ }
182
+ this.history?.onHistoryChange((topic) => {
183
+ if (topic === this.ANNOTATION_HISTORY_TOPIC && this.config.autoCommit !== false) {
184
+ this.commit();
185
+ }
186
+ });
187
+ this.interactionManager?.onModeChange((s) => {
188
+ const newSubtype = this.subtypeByMode.get(s.activeMode) ?? null;
189
+ if (newSubtype !== this.state.annotationMode) {
190
+ this.dispatch(setAnnotationMode(newSubtype));
191
+ this.modeChange$.emit(newSubtype);
192
+ }
193
+ });
194
+ this.selection?.onEndSelection(() => {
195
+ if (!this.state.annotationMode) return;
196
+ if (!(this.state.annotationMode === import_models.PdfAnnotationSubtype.HIGHLIGHT || this.state.annotationMode === import_models.PdfAnnotationSubtype.UNDERLINE || this.state.annotationMode === import_models.PdfAnnotationSubtype.STRIKEOUT || this.state.annotationMode === import_models.PdfAnnotationSubtype.SQUIGGLY)) {
197
+ return;
198
+ }
199
+ const formattedSelection = this.selection?.getFormattedSelection();
200
+ if (!formattedSelection) return;
201
+ for (const selection of formattedSelection) {
202
+ const rect = selection.rect;
203
+ const segmentRects = selection.segmentRects;
204
+ const type = this.state.annotationMode;
205
+ const color = this.state.toolDefaults[type].color;
206
+ const opacity = this.state.toolDefaults[type].opacity;
207
+ this.createAnnotation(selection.pageIndex, {
208
+ type,
209
+ rect,
210
+ segmentRects,
211
+ color,
212
+ opacity,
213
+ pageIndex: selection.pageIndex,
214
+ id: Date.now() + Math.random()
215
+ });
216
+ }
217
+ this.selection?.clear();
218
+ });
219
+ }
220
+ registerTool(subtype, defaults) {
221
+ const modeId = defaults.interaction.mode;
222
+ const interactionMode = {
223
+ id: modeId,
224
+ scope: "page",
225
+ exclusive: defaults.interaction.exclusive,
226
+ cursor: defaults.interaction.cursor
227
+ };
228
+ this.interactionManager?.registerMode(interactionMode);
229
+ if (defaults.textSelection) {
230
+ this.selection?.enableForMode(modeId);
231
+ }
232
+ this.modeBySubtype.set(subtype, modeId);
233
+ this.subtypeByMode.set(modeId, subtype);
143
234
  }
144
235
  buildCapability() {
145
236
  return {
146
237
  getPageAnnotations: (options) => {
147
238
  return this.getPageAnnotations(options);
148
239
  },
240
+ getSelectedAnnotation: () => {
241
+ return getSelectedAnnotation(this.state);
242
+ },
149
243
  selectAnnotation: (pageIndex, annotationId) => {
150
244
  this.selectAnnotation(pageIndex, annotationId);
151
245
  },
152
246
  deselectAnnotation: () => {
153
247
  this.dispatch(deselectAnnotation());
154
248
  },
155
- updateAnnotationColor: async (color) => {
156
- return this.updateSelectedAnnotationColor(color);
249
+ getAnnotationMode: () => {
250
+ return this.state.annotationMode;
157
251
  },
158
- setAnnotationMode: (mode) => {
159
- this.dispatch(setAnnotationMode(mode));
252
+ setAnnotationMode: (subtype) => {
253
+ if (subtype === this.state.annotationMode) return;
254
+ if (subtype) {
255
+ const mode = this.modeBySubtype.get(subtype);
256
+ if (!mode) throw new Error(`Mode missing for subtype ${subtype}`);
257
+ this.interactionManager?.activate(mode);
258
+ } else {
259
+ this.interactionManager?.activate("default");
260
+ }
160
261
  },
161
- onStateChange: this.state$.on
262
+ getToolDefaults: (subtype) => {
263
+ const defaults = this.state.toolDefaults[subtype];
264
+ if (!defaults) {
265
+ throw new Error(`No defaults found for subtype: ${subtype}`);
266
+ }
267
+ return defaults;
268
+ },
269
+ setToolDefaults: (subtype, patch) => {
270
+ this.dispatch(updateToolDefaults(subtype, patch));
271
+ },
272
+ getColorPresets: () => [...this.state.colorPresets],
273
+ addColorPreset: (color) => this.dispatch(addColorPreset(color)),
274
+ createAnnotation: (pageIndex, annotation) => this.createAnnotation(pageIndex, annotation),
275
+ updateAnnotation: (pageIndex, localId, patch) => this.updateAnnotation(pageIndex, localId, patch),
276
+ deleteAnnotation: (pageIndex, localId) => this.deleteAnnotation(pageIndex, localId),
277
+ renderAnnotation: (options) => this.renderAnnotation(options),
278
+ onStateChange: this.state$.on,
279
+ onModeChange: this.modeChange$.on,
280
+ onActiveToolChange: this.activeTool$.on,
281
+ commit: () => this.commit()
162
282
  };
163
283
  }
164
- onStoreUpdated(_prevState, newState) {
165
- this.state$.emit(newState);
284
+ createActiveTool(mode, toolDefaults) {
285
+ if (mode === null) {
286
+ return { mode: null, defaults: null };
287
+ }
288
+ return { mode, defaults: toolDefaults[mode] };
289
+ }
290
+ emitActiveTool(state) {
291
+ const activeTool = this.createActiveTool(state.annotationMode, state.toolDefaults);
292
+ this.activeTool$.emit(activeTool);
293
+ }
294
+ onStoreUpdated(prev, next) {
295
+ this.state$.emit(next);
296
+ if (prev.annotationMode !== next.annotationMode || prev.toolDefaults[prev.annotationMode ?? import_models.PdfAnnotationSubtype.HIGHLIGHT] !== next.toolDefaults[next.annotationMode ?? import_models.PdfAnnotationSubtype.HIGHLIGHT]) {
297
+ this.emitActiveTool(next);
298
+ }
166
299
  }
167
300
  getAllAnnotations(doc) {
168
301
  const task = this.engine.getAllAnnotations(doc);
169
- task.wait((annotations) => this.dispatch(setAnnotations(annotations)), ignore);
302
+ task.wait((annotations) => this.dispatch(setAnnotations(annotations)), import_models.ignore);
170
303
  }
171
304
  getPageAnnotations(options) {
172
305
  const { pageIndex } = options;
173
306
  const doc = this.coreState.core.document;
174
307
  if (!doc) {
175
- throw new Error("document does not open");
308
+ return import_models.PdfTaskHelper.reject({ code: import_models.PdfErrorCode.NotFound, message: "Document not found" });
176
309
  }
177
310
  const page = doc.pages.find((p) => p.index === pageIndex);
178
311
  if (!page) {
179
- throw new Error("page does not open");
312
+ return import_models.PdfTaskHelper.reject({ code: import_models.PdfErrorCode.NotFound, message: "Page not found" });
180
313
  }
181
314
  return this.engine.getPageAnnotations(doc, page);
182
315
  }
316
+ renderAnnotation({
317
+ pageIndex,
318
+ annotation,
319
+ scaleFactor = 1,
320
+ rotation = import_models.Rotation.Degree0,
321
+ dpr = 1,
322
+ mode = import_models.AppearanceMode.Normal,
323
+ imageType = "image/webp"
324
+ }) {
325
+ const coreState = this.coreState.core;
326
+ if (!coreState.document) {
327
+ throw new Error("document does not open");
328
+ }
329
+ const page = coreState.document.pages.find((page2) => page2.index === pageIndex);
330
+ if (!page) {
331
+ throw new Error("page does not exist");
332
+ }
333
+ return this.engine.renderAnnotation(
334
+ coreState.document,
335
+ page,
336
+ annotation,
337
+ scaleFactor,
338
+ rotation,
339
+ dpr,
340
+ mode,
341
+ imageType
342
+ );
343
+ }
183
344
  selectAnnotation(pageIndex, annotationId) {
184
- const pageAnnotations = this.state.annotations[pageIndex];
185
- if (!pageAnnotations) {
345
+ this.dispatch(selectAnnotation(pageIndex, annotationId));
346
+ }
347
+ createAnnotation(pageIndex, annotation) {
348
+ const localId = annotation.id;
349
+ const execute = () => this.dispatch(createAnnotation(pageIndex, localId, annotation));
350
+ if (!this.history) {
351
+ execute();
352
+ if (this.config.autoCommit) this.commit();
186
353
  return;
187
354
  }
188
- const annotation = pageAnnotations.find((a) => a.id === annotationId);
189
- if (annotation) {
190
- this.dispatch(selectAnnotation(pageIndex, annotationId, annotation));
191
- }
355
+ const command = {
356
+ execute,
357
+ undo: () => {
358
+ this.dispatch(deselectAnnotation());
359
+ this.dispatch(deleteAnnotation(pageIndex, localId));
360
+ }
361
+ };
362
+ this.history.register(command, this.ANNOTATION_HISTORY_TOPIC);
192
363
  }
193
- async updateSelectedAnnotationColor(color) {
194
- const selected = this.state.selectedAnnotation;
195
- if (!selected) {
196
- return false;
364
+ updateAnnotation(pageIndex, localId, patch) {
365
+ if (!this.history) {
366
+ this.dispatch(patchAnnotation(pageIndex, localId, patch));
367
+ if (this.config.autoCommit !== false) {
368
+ this.commit();
369
+ }
370
+ return;
197
371
  }
198
- if (selected.annotation.type !== PdfAnnotationSubtype.HIGHLIGHT) {
199
- return false;
372
+ const originalObject = this.state.byUid[makeUid(pageIndex, localId)].object;
373
+ const originalPatch = Object.fromEntries(
374
+ Object.keys(patch).map((key) => [key, originalObject[key]])
375
+ );
376
+ const command = {
377
+ execute: () => this.dispatch(patchAnnotation(pageIndex, localId, patch)),
378
+ undo: () => this.dispatch(patchAnnotation(pageIndex, localId, originalPatch))
379
+ };
380
+ this.history.register(command, this.ANNOTATION_HISTORY_TOPIC);
381
+ }
382
+ deleteAnnotation(pageIndex, localId) {
383
+ if (!this.history) {
384
+ this.dispatch(deselectAnnotation());
385
+ this.dispatch(deleteAnnotation(pageIndex, localId));
386
+ if (this.config.autoCommit !== false) {
387
+ this.commit();
388
+ }
389
+ return;
200
390
  }
391
+ const originalAnnotation = this.state.byUid[makeUid(pageIndex, localId)].object;
392
+ const command = {
393
+ execute: () => {
394
+ this.dispatch(deselectAnnotation());
395
+ this.dispatch(deleteAnnotation(pageIndex, localId));
396
+ },
397
+ undo: () => this.dispatch(createAnnotation(pageIndex, localId, originalAnnotation))
398
+ };
399
+ this.history.register(command, this.ANNOTATION_HISTORY_TOPIC);
400
+ }
401
+ commit() {
402
+ const task = new import_models.Task();
403
+ if (!this.state.hasPendingChanges) return import_models.PdfTaskHelper.resolve(true);
201
404
  const doc = this.coreState.core.document;
202
- if (!doc) {
203
- return false;
405
+ if (!doc)
406
+ return import_models.PdfTaskHelper.reject({ code: import_models.PdfErrorCode.NotFound, message: "Document not found" });
407
+ const creations = [];
408
+ const updates = [];
409
+ const deletionsByPage = /* @__PURE__ */ new Map();
410
+ const affectedPages = /* @__PURE__ */ new Set();
411
+ for (const [uid, ta] of Object.entries(this.state.byUid)) {
412
+ if (ta.commitState === "synced") continue;
413
+ const { pageIndex } = parseUid(uid);
414
+ const page = doc.pages.find((p) => p.index === pageIndex);
415
+ if (!page) continue;
416
+ affectedPages.add(pageIndex);
417
+ switch (ta.commitState) {
418
+ case "new":
419
+ const task2 = this.engine.createPageAnnotation(doc, page, ta.object);
420
+ task2.wait((annoId) => this.dispatch(storePdfId(uid, annoId)), import_models.ignore);
421
+ creations.push(task2);
422
+ break;
423
+ case "dirty":
424
+ updates.push(
425
+ this.engine.updatePageAnnotation(doc, page, { ...ta.object, id: ta.pdfId })
426
+ );
427
+ break;
428
+ case "deleted":
429
+ if (!deletionsByPage.has(pageIndex)) {
430
+ deletionsByPage.set(pageIndex, []);
431
+ }
432
+ deletionsByPage.get(pageIndex).push({ ta, uid });
433
+ break;
434
+ }
204
435
  }
205
- this.dispatch(updateAnnotationColor(selected.pageIndex, selected.annotationId, color));
206
- try {
207
- return true;
208
- } catch (error) {
209
- console.error("Failed to update annotation color:", error);
210
- return false;
436
+ const deletionTasks = [];
437
+ for (const [pageIndex, deletions] of deletionsByPage.entries()) {
438
+ const page = doc.pages.find((p) => p.index === pageIndex);
439
+ deletions.sort((a, b) => (b.ta.pdfId ?? -1) - (a.ta.pdfId ?? -1));
440
+ for (const { ta, uid } of deletions) {
441
+ if (ta.pdfId !== void 0) {
442
+ const task2 = new import_models.Task();
443
+ const removeTask = this.engine.removePageAnnotation(doc, page, {
444
+ ...ta.object,
445
+ id: ta.pdfId
446
+ });
447
+ removeTask.wait(() => {
448
+ this.dispatch(purgeAnnotation(uid));
449
+ task2.resolve(true);
450
+ }, task2.fail);
451
+ deletionTasks.push(task2);
452
+ } else {
453
+ this.dispatch(purgeAnnotation(uid));
454
+ }
455
+ }
211
456
  }
457
+ const allWriteTasks = [...creations, ...updates, ...deletionTasks];
458
+ import_models.Task.allSettled(allWriteTasks).wait(() => {
459
+ for (const pageIndex of affectedPages) {
460
+ this.dispatch(reindexPageAnnotations(pageIndex));
461
+ }
462
+ this.dispatch(commitPendingChanges());
463
+ task.resolve(true);
464
+ }, task.fail);
465
+ return task;
212
466
  }
213
467
  };
214
468
  AnnotationPlugin.id = "annotation";
215
469
 
216
470
  // src/lib/reducer.ts
217
- var initialState = {
218
- annotations: {},
219
- selectedAnnotation: null,
220
- annotationMode: null
471
+ var import_models2 = require("@embedpdf/models");
472
+ var DEFAULT_COLORS = [
473
+ "#E44234",
474
+ "#FF8D00",
475
+ "#FFCD45",
476
+ "#5CC96E",
477
+ "#25D2D1",
478
+ "#597CE2",
479
+ "#C544CE",
480
+ "#7D2E25"
481
+ ];
482
+ var patchAnno = (state, uid, patch) => {
483
+ const prev = state.byUid[uid];
484
+ if (!prev) return state;
485
+ return {
486
+ ...state,
487
+ byUid: {
488
+ ...state.byUid,
489
+ [uid]: {
490
+ ...prev,
491
+ commitState: prev.commitState === "synced" ? "dirty" : prev.commitState,
492
+ object: { ...prev.object, ...patch }
493
+ }
494
+ },
495
+ hasPendingChanges: true
496
+ };
221
497
  };
498
+ var initialState = (cfg) => ({
499
+ pages: {},
500
+ byUid: {},
501
+ selectedUid: null,
502
+ annotationMode: null,
503
+ toolDefaults: {
504
+ [import_models2.PdfAnnotationSubtype.HIGHLIGHT]: {
505
+ name: "Highlight",
506
+ color: "#FFCD45",
507
+ opacity: 1,
508
+ interaction: { mode: "highlight", exclusive: false },
509
+ textSelection: true
510
+ },
511
+ [import_models2.PdfAnnotationSubtype.UNDERLINE]: {
512
+ name: "Underline",
513
+ color: "#E44234",
514
+ opacity: 1,
515
+ interaction: { mode: "underline", exclusive: false },
516
+ textSelection: true
517
+ },
518
+ [import_models2.PdfAnnotationSubtype.STRIKEOUT]: {
519
+ name: "Strikeout",
520
+ color: "#E44234",
521
+ opacity: 1,
522
+ interaction: { mode: "strikeout", exclusive: false },
523
+ textSelection: true
524
+ },
525
+ [import_models2.PdfAnnotationSubtype.SQUIGGLY]: {
526
+ name: "Squiggly",
527
+ color: "#E44234",
528
+ opacity: 1,
529
+ interaction: { mode: "squiggly", exclusive: false },
530
+ textSelection: true
531
+ },
532
+ [import_models2.PdfAnnotationSubtype.INK]: {
533
+ name: "Ink",
534
+ color: "#E44234",
535
+ opacity: 1,
536
+ strokeWidth: 11,
537
+ interaction: { mode: "ink", exclusive: true, cursor: "crosshair" },
538
+ textSelection: false
539
+ },
540
+ ...cfg.toolDefaults
541
+ },
542
+ colorPresets: cfg.colorPresets ?? DEFAULT_COLORS,
543
+ hasPendingChanges: false
544
+ });
222
545
  var reducer = (state, action) => {
223
546
  switch (action.type) {
224
- case SET_ANNOTATIONS:
547
+ /* ───── bulk load from engine ───── */
548
+ case SET_ANNOTATIONS: {
549
+ const newPages = { ...state.pages };
550
+ const newByUid = { ...state.byUid };
551
+ for (const [pgStr, list] of Object.entries(action.payload)) {
552
+ const pageIndex = Number(pgStr);
553
+ const oldUidsOnPage = state.pages[pageIndex] || [];
554
+ for (const uid of oldUidsOnPage) {
555
+ delete newByUid[uid];
556
+ }
557
+ const newUidsOnPage = list.map((a, index) => {
558
+ const localId = Date.now() + Math.random() + index;
559
+ const uid = makeUid(pageIndex, localId);
560
+ newByUid[uid] = { localId, pdfId: a.id, commitState: "synced", object: a };
561
+ return uid;
562
+ });
563
+ newPages[pageIndex] = newUidsOnPage;
564
+ }
565
+ return { ...state, pages: newPages, byUid: newByUid };
566
+ }
567
+ /* ───── GUI bits ───── */
568
+ case SET_ANNOTATION_MODE:
569
+ return { ...state, annotationMode: action.payload };
570
+ case SELECT_ANNOTATION:
225
571
  return {
226
572
  ...state,
227
- annotations: action.payload,
228
- // Clear selection if the annotations have changed
229
- selectedAnnotation: null
573
+ selectedUid: makeUid(action.payload.pageIndex, action.payload.localId)
230
574
  };
231
- case SELECT_ANNOTATION:
575
+ case DESELECT_ANNOTATION:
576
+ return { ...state, selectedUid: null };
577
+ case ADD_COLOR_PRESET:
578
+ return state.colorPresets.includes(action.payload) ? state : { ...state, colorPresets: [...state.colorPresets, action.payload] };
579
+ case UPDATE_TOOL_DEFAULTS: {
580
+ const { subtype, patch } = action.payload;
232
581
  return {
233
582
  ...state,
234
- selectedAnnotation: action.payload
583
+ toolDefaults: {
584
+ ...state.toolDefaults,
585
+ [subtype]: { ...state.toolDefaults[subtype], ...patch }
586
+ }
235
587
  };
236
- case DESELECT_ANNOTATION:
588
+ }
589
+ /* ───── create ───── */
590
+ case CREATE_ANNOTATION: {
591
+ const { pageIndex, localId, annotation } = action.payload;
592
+ const uid = makeUid(pageIndex, localId);
237
593
  return {
238
594
  ...state,
239
- selectedAnnotation: null
595
+ pages: { ...state.pages, [pageIndex]: [...state.pages[pageIndex] ?? [], uid] },
596
+ byUid: {
597
+ ...state.byUid,
598
+ [uid]: { localId, pdfId: void 0, commitState: "new", object: annotation }
599
+ },
600
+ hasPendingChanges: true
240
601
  };
241
- case SET_ANNOTATION_MODE:
602
+ }
603
+ /* ───── delete ───── */
604
+ case DELETE_ANNOTATION: {
605
+ const { pageIndex, localId } = action.payload;
606
+ const uid = makeUid(pageIndex, localId);
607
+ if (!state.byUid[uid]) return state;
242
608
  return {
243
609
  ...state,
244
- annotationMode: action.payload
610
+ pages: {
611
+ ...state.pages,
612
+ [pageIndex]: (state.pages[pageIndex] ?? []).filter((u) => u !== uid)
613
+ },
614
+ byUid: {
615
+ ...state.byUid,
616
+ [uid]: { ...state.byUid[uid], commitState: "deleted" }
617
+ },
618
+ hasPendingChanges: true
245
619
  };
246
- case UPDATE_ANNOTATION_COLOR: {
247
- const { pageIndex, annotationId, color } = action.payload;
248
- const pageAnnotations = state.annotations[pageIndex];
249
- if (!pageAnnotations) {
250
- return state;
620
+ }
621
+ /* ───── field edits ───── */
622
+ case PATCH_ANNOTATION: {
623
+ const uid = makeUid(action.payload.pageIndex, action.payload.localId);
624
+ return patchAnno(state, uid, action.payload.patch);
625
+ }
626
+ /* ───── commit bookkeeping ───── */
627
+ case COMMIT_PENDING_CHANGES: {
628
+ const cleaned = {};
629
+ for (const [uid, ta] of Object.entries(state.byUid)) {
630
+ cleaned[uid] = {
631
+ ...ta,
632
+ commitState: ta.commitState === "dirty" || ta.commitState === "new" ? "synced" : ta.commitState
633
+ };
251
634
  }
252
- const updatedAnnotations = pageAnnotations.map((annotation) => {
253
- if (annotation.id === annotationId) {
254
- if (annotation.type === PdfAnnotationSubtype.HIGHLIGHT) {
255
- return {
256
- ...annotation,
257
- color
258
- };
259
- }
260
- }
261
- return annotation;
635
+ return { ...state, byUid: cleaned, hasPendingChanges: false };
636
+ }
637
+ case REINDEX_PAGE_ANNOTATIONS: {
638
+ const { pageIndex } = action.payload;
639
+ const newByUid = { ...state.byUid };
640
+ const uidsOnPage = state.pages[pageIndex] || [];
641
+ const annosOnPage = uidsOnPage.map((uid) => state.byUid[uid]).filter((ta) => ta && ta.commitState !== "deleted");
642
+ annosOnPage.sort((a, b) => (a.pdfId ?? Infinity) - (b.pdfId ?? Infinity));
643
+ annosOnPage.forEach((ta, newPdfId) => {
644
+ const uid = makeUid(pageIndex, ta.localId);
645
+ newByUid[uid] = { ...newByUid[uid], pdfId: newPdfId };
262
646
  });
263
- const newAnnotations = {
264
- ...state.annotations,
265
- [pageIndex]: updatedAnnotations
266
- };
267
- let newSelectedAnnotation = state.selectedAnnotation;
268
- if (newSelectedAnnotation && newSelectedAnnotation.pageIndex === pageIndex && newSelectedAnnotation.annotationId === annotationId) {
269
- const updatedAnnotation = updatedAnnotations.find((a) => a.id === annotationId);
270
- if (updatedAnnotation) {
271
- newSelectedAnnotation = {
272
- ...newSelectedAnnotation,
273
- annotation: updatedAnnotation
274
- };
275
- }
276
- }
647
+ return { ...state, byUid: newByUid };
648
+ }
649
+ case STORE_PDF_ID: {
650
+ const { uid, pdfId } = action.payload;
651
+ const ta = state.byUid[uid];
652
+ if (!ta) return state;
277
653
  return {
278
654
  ...state,
279
- annotations: newAnnotations,
280
- selectedAnnotation: newSelectedAnnotation
655
+ byUid: {
656
+ ...state.byUid,
657
+ [uid]: { ...ta, pdfId, commitState: "synced" }
658
+ }
281
659
  };
282
660
  }
661
+ case PURGE_ANNOTATION: {
662
+ const { uid } = action.payload;
663
+ const { [uid]: _gone, ...rest } = state.byUid;
664
+ return { ...state, byUid: rest };
665
+ }
283
666
  default:
284
667
  return state;
285
668
  }
@@ -288,15 +671,23 @@ var reducer = (state, action) => {
288
671
  // src/lib/index.ts
289
672
  var AnnotationPluginPackage = {
290
673
  manifest,
291
- create: (registry, engine) => new AnnotationPlugin(ANNOTATION_PLUGIN_ID, registry, engine),
674
+ create: (registry, engine, config) => new AnnotationPlugin(ANNOTATION_PLUGIN_ID, registry, engine, config),
292
675
  reducer,
293
- initialState
676
+ initialState: (_, config) => initialState(config)
294
677
  };
295
678
  // Annotate the CommonJS export names for ESM import in node:
296
679
  0 && (module.exports = {
297
680
  ANNOTATION_PLUGIN_ID,
298
681
  AnnotationPlugin,
299
682
  AnnotationPluginPackage,
683
+ getAnnotations,
684
+ getAnnotationsByPageIndex,
685
+ getSelectedAnnotation,
686
+ getSelectedAnnotationByPageIndex,
687
+ getSelectedAnnotationMode,
688
+ getSelectedAnnotationWithPageIndex,
689
+ isAnnotationSelected,
690
+ isInAnnotationMode,
300
691
  manifest
301
692
  });
302
693
  //# sourceMappingURL=index.cjs.map