@embedpdf/angular 3.0.0-next.14 → 3.0.0-next.16

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.
Files changed (87) hide show
  1. package/README.md +309 -0
  2. package/dist/README.md +309 -0
  3. package/dist/actions/index.d.ts +34 -16
  4. package/dist/anchored/index.d.ts +67 -0
  5. package/dist/annotation/index.d.ts +602 -0
  6. package/dist/attachment/index.d.ts +29 -0
  7. package/dist/commands/index.d.ts +109 -0
  8. package/dist/fesm2022/embedpdf-angular-actions.mjs +117 -36
  9. package/dist/fesm2022/embedpdf-angular-actions.mjs.map +1 -1
  10. package/dist/fesm2022/embedpdf-angular-anchored.mjs +148 -0
  11. package/dist/fesm2022/embedpdf-angular-anchored.mjs.map +1 -0
  12. package/dist/fesm2022/embedpdf-angular-annotation.mjs +1933 -0
  13. package/dist/fesm2022/embedpdf-angular-annotation.mjs.map +1 -0
  14. package/dist/fesm2022/embedpdf-angular-attachment.mjs +67 -0
  15. package/dist/fesm2022/embedpdf-angular-attachment.mjs.map +1 -0
  16. package/dist/fesm2022/embedpdf-angular-commands.mjs +235 -0
  17. package/dist/fesm2022/embedpdf-angular-commands.mjs.map +1 -0
  18. package/dist/fesm2022/embedpdf-angular-form.mjs +1074 -0
  19. package/dist/fesm2022/embedpdf-angular-form.mjs.map +1 -0
  20. package/dist/fesm2022/embedpdf-angular-history.mjs +54 -0
  21. package/dist/fesm2022/embedpdf-angular-history.mjs.map +1 -0
  22. package/dist/fesm2022/embedpdf-angular-i18n.mjs +100 -0
  23. package/dist/fesm2022/embedpdf-angular-i18n.mjs.map +1 -0
  24. package/dist/fesm2022/embedpdf-angular-interaction.mjs +157 -0
  25. package/dist/fesm2022/embedpdf-angular-interaction.mjs.map +1 -0
  26. package/dist/fesm2022/embedpdf-angular-link.mjs +306 -0
  27. package/dist/fesm2022/embedpdf-angular-link.mjs.map +1 -0
  28. package/dist/fesm2022/embedpdf-angular-measurement.mjs +106 -0
  29. package/dist/fesm2022/embedpdf-angular-measurement.mjs.map +1 -0
  30. package/dist/fesm2022/embedpdf-angular-metadata.mjs +77 -0
  31. package/dist/fesm2022/embedpdf-angular-metadata.mjs.map +1 -0
  32. package/dist/fesm2022/embedpdf-angular-outline.mjs +76 -0
  33. package/dist/fesm2022/embedpdf-angular-outline.mjs.map +1 -0
  34. package/dist/fesm2022/embedpdf-angular-page-edit.mjs +68 -0
  35. package/dist/fesm2022/embedpdf-angular-page-edit.mjs.map +1 -0
  36. package/dist/fesm2022/embedpdf-angular-page-view.mjs +297 -0
  37. package/dist/fesm2022/embedpdf-angular-page-view.mjs.map +1 -0
  38. package/dist/fesm2022/embedpdf-angular-redaction.mjs +93 -0
  39. package/dist/fesm2022/embedpdf-angular-redaction.mjs.map +1 -0
  40. package/dist/fesm2022/embedpdf-angular-render.mjs +331 -69
  41. package/dist/fesm2022/embedpdf-angular-render.mjs.map +1 -1
  42. package/dist/fesm2022/embedpdf-angular-runtime.mjs +1151 -390
  43. package/dist/fesm2022/embedpdf-angular-runtime.mjs.map +1 -1
  44. package/dist/fesm2022/embedpdf-angular-search.mjs +251 -0
  45. package/dist/fesm2022/embedpdf-angular-search.mjs.map +1 -0
  46. package/dist/fesm2022/embedpdf-angular-selection.mjs +477 -0
  47. package/dist/fesm2022/embedpdf-angular-selection.mjs.map +1 -0
  48. package/dist/fesm2022/embedpdf-angular-shell.mjs +177 -0
  49. package/dist/fesm2022/embedpdf-angular-shell.mjs.map +1 -0
  50. package/dist/fesm2022/embedpdf-angular-signature.mjs +120 -0
  51. package/dist/fesm2022/embedpdf-angular-signature.mjs.map +1 -0
  52. package/dist/fesm2022/embedpdf-angular-stage.mjs +688 -288
  53. package/dist/fesm2022/embedpdf-angular-stage.mjs.map +1 -1
  54. package/dist/fesm2022/embedpdf-angular-stamp.mjs +169 -0
  55. package/dist/fesm2022/embedpdf-angular-stamp.mjs.map +1 -0
  56. package/dist/fesm2022/embedpdf-angular-testing.mjs +89 -0
  57. package/dist/fesm2022/embedpdf-angular-testing.mjs.map +1 -0
  58. package/dist/fesm2022/embedpdf-angular-toolbar.mjs +1123 -0
  59. package/dist/fesm2022/embedpdf-angular-toolbar.mjs.map +1 -0
  60. package/dist/fesm2022/embedpdf-angular-view-manager.mjs +69 -0
  61. package/dist/fesm2022/embedpdf-angular-view-manager.mjs.map +1 -0
  62. package/dist/fesm2022/embedpdf-angular.mjs +25 -3
  63. package/dist/fesm2022/embedpdf-angular.mjs.map +1 -1
  64. package/dist/form/index.d.ts +79 -0
  65. package/dist/history/index.d.ts +28 -0
  66. package/dist/i18n/index.d.ts +52 -0
  67. package/dist/index.d.ts +22 -0
  68. package/dist/interaction/index.d.ts +58 -0
  69. package/dist/link/index.d.ts +77 -0
  70. package/dist/measurement/index.d.ts +46 -0
  71. package/dist/metadata/index.d.ts +51 -0
  72. package/dist/outline/index.d.ts +32 -0
  73. package/dist/page-edit/index.d.ts +27 -0
  74. package/dist/page-view/index.d.ts +94 -0
  75. package/dist/redaction/index.d.ts +38 -0
  76. package/dist/render/index.d.ts +45 -6
  77. package/dist/runtime/index.d.ts +579 -199
  78. package/dist/search/index.d.ts +71 -0
  79. package/dist/selection/index.d.ts +141 -0
  80. package/dist/shell/index.d.ts +88 -0
  81. package/dist/signature/index.d.ts +45 -0
  82. package/dist/stage/index.d.ts +242 -141
  83. package/dist/stamp/index.d.ts +58 -0
  84. package/dist/testing/index.d.ts +53 -0
  85. package/dist/toolbar/index.d.ts +257 -0
  86. package/dist/view-manager/index.d.ts +23 -0
  87. package/package.json +145 -14
@@ -0,0 +1,1933 @@
1
+ import { AnnotationToken, annotationState, annotationPlugin, ANNOTATION_DEFAULTS } from '@embedpdf/plugin-annotation';
2
+ export * from '@embedpdf/plugin-annotation';
3
+ import * as i0 from '@angular/core';
4
+ import { computed, Injectable, inject, DestroyRef, input, ChangeDetectionStrategy, Component, Directive, booleanAttribute, TemplateRef, InjectionToken, afterRenderEffect, untracked, ElementRef, Injector, contentChildren, contentChild, signal, PLATFORM_ID, effect } from '@angular/core';
5
+ import { EpdfPluginService, injectDocumentScope, pluginService, CapabilityBinding, injectKernelHost, devWarn, injectPage, EpdfSvgShapes, paintsPagePart, outsideStageError } from '@embedpdf/angular/runtime';
6
+ import { AnnotationToken as AnnotationToken$1, previewBucket } from '@embedpdf/plugin-annotation/contract/host';
7
+ import { enrichCommentThreads, sameAnnotationAnchor, pickRequestedFile, annotationChromePaint, chromeInPixels, sceneViewBox, svgShapesOf, ghostOpacity, rasterInFrame, createTextBoxEditorFollower, freeTextBoxOf, textBoxEditorScaleOf, ownTextBoxOf, createShownUrls, editingTextKeyOf, frameInPixels, bakedAppearanceOf, pictureOrPainting, shownAppearance, annotationDrawingOf, lookFrameOf, layerTextBoxesOf, rectInPixels, registerRendererBehaviors, loadAppearanceUrls, loadObjectUrl, sameSelectionAnchor, sameCreationDraftAnchor, sameRotationAnchor, paintDefault, paint } from '@embedpdf/web';
8
+ import { annotationKey } from '@embedpdf/core';
9
+ import { NgTemplateOutlet, isPlatformBrowser } from '@angular/common';
10
+ import { scene, MITER_LIMIT, hasLiveDrawing } from '@embedpdf/core-annotation';
11
+ import { InteractionToken } from '@embedpdf/plugin-interaction/contract';
12
+ import { RenderToken } from '@embedpdf/plugin-render/contract/host';
13
+ import { samePagePlacement } from '@embedpdf/core-geometry';
14
+ import { EPDF_PROJECTOR, EpdfAnchored } from '@embedpdf/angular/anchored';
15
+
16
+ /**
17
+ * Comments and replies, as their own service: `inject(EpdfComments)`. A comments sidebar needs
18
+ * the threads and the verbs on them, not the rest of the annotation API, so they get a service
19
+ * of their own (React's `useComments()` and `useCommentThreads()`). `withAnnotation()` provides
20
+ * it.
21
+ *
22
+ * The threads come with where their page is shown (`pageIndex`, `pageLabel`): the join of the
23
+ * plugin's threads and the document's page list is `@embedpdf/web`'s, the same for every
24
+ * framework.
25
+ */
26
+ const NO_THREADS = Object.freeze([]);
27
+ const NO_PAGES = Object.freeze([]);
28
+ /**
29
+ * The comments of the document in scope (`[epdfDocumentScope]`), else the active one: every
30
+ * thread as `threads()`, one as `threadOf(ref)`, the verbs (`reply()`, `setText()`,
31
+ * `setStatus()`, `setMarked()`, `delete()`, `deleteThread()`), the checks (`canReply(ref)`, …),
32
+ * which follow the document and its permissions in a template, and `threadChanged$`. Without a
33
+ * document the threads are empty and the verbs refuse with `not-ready`.
34
+ */
35
+ class EpdfComments extends EpdfPluginService {
36
+ constructor() {
37
+ super({ name: 'EpdfComments', feature: 'withAnnotation()', token: AnnotationToken });
38
+ }
39
+ /** Every thread, in reading order, read once. In a template, use `threads()`. */
40
+ listThreads = this.binding.namespaceMethod('comments', 'listThreads');
41
+ /** The thread a comment is in (its first comment, a reply or a review state), read once, or null. */
42
+ getThread = this.binding.namespaceMethod('comments', 'getThread');
43
+ /** Reply to the thread's first comment, whichever comment you pass. Resolves `{ annotation }`. */
44
+ reply = this.binding.namespaceMethod('comments', 'reply');
45
+ /** Change a comment's text. */
46
+ setText = this.binding.namespaceMethod('comments', 'setText');
47
+ /** Set the user's review state on a thread, such as `'accepted'`. */
48
+ setStatus = this.binding.namespaceMethod('comments', 'setStatus');
49
+ /** Check a thread off for the user, or clear the check mark. */
50
+ setMarked = this.binding.namespaceMethod('comments', 'setMarked');
51
+ /** Delete a comment with its replies; the first comment of a thread deletes the thread. */
52
+ delete = this.binding.namespaceMethod('comments', 'delete');
53
+ /** Delete a whole thread, all of it or none. Resolves `{ deleted, failed }`. */
54
+ deleteThread = this.binding.namespaceMethod('comments', 'deleteThread');
55
+ /** Whether the user may reply to this thread. */
56
+ canReply = this.binding.namespaceMethod('comments', 'canReply');
57
+ /** Whether the user may change this comment's text. */
58
+ canSetText = this.binding.namespaceMethod('comments', 'canSetText');
59
+ /** Whether the user may set a review state on this thread. */
60
+ canSetStatus = this.binding.namespaceMethod('comments', 'canSetStatus');
61
+ /** Whether the user may check this thread off. */
62
+ canSetMarked = this.binding.namespaceMethod('comments', 'canSetMarked');
63
+ /** Whether the user may delete this comment. */
64
+ canDelete = this.binding.namespaceMethod('comments', 'canDelete');
65
+ /** Whether the user may delete every comment in this thread. */
66
+ canDeleteThread = this.binding.namespaceMethod('comments', 'canDeleteThread');
67
+ /** A thread changed through this viewer: a reply, a text, a review state, a check mark or a delete. */
68
+ threadChanged$ = this.binding.stream((annotation) => annotation.comments.onThreadChanged);
69
+ scope = injectDocumentScope();
70
+ rawThreads = this.binding.select((annotation) => annotation.comments.listThreads(), NO_THREADS);
71
+ /** The pages of this service's document: a page moved or deleted moves its threads' labels. */
72
+ pages = this.binding.host.read((kernel) => {
73
+ const id = this.scope() ?? kernel.documents.getActiveId();
74
+ return id ? kernel.documents.listPages(id) : NO_PAGES;
75
+ }, () => NO_PAGES);
76
+ /**
77
+ * Every thread in the document, in reading order (by page, then from the top), each with its
78
+ * page's `pageIndex` and `pageLabel`. Own, remote and reloaded changes all show. The same
79
+ * array until a thread or a page changes; empty without a document.
80
+ */
81
+ threads = computed(() => enrichCommentThreads(this.rawThreads(), this.pages()), ...(ngDevMode ? [{ debugName: "threads" }] : []));
82
+ /**
83
+ * The thread a comment is in, whether you pass its first comment, a reply or a review state,
84
+ * with its page's label; null for null, or a comment in no thread. Pass the ref, or a function
85
+ * that reads it (`() => this.note().ref`).
86
+ */
87
+ threadOf(ref) {
88
+ const refOf = typeof ref === 'function' ? ref : () => ref;
89
+ const thread = this.binding.select((annotation) => {
90
+ const member = refOf();
91
+ return member ? annotation.comments.getThread(member) : null;
92
+ }, null, Object.is);
93
+ return computed(() => {
94
+ const found = thread();
95
+ if (!found)
96
+ return null;
97
+ const root = annotationKey(found.root.ref);
98
+ return this.threads().find((view) => annotationKey(view.root.ref) === root) ?? null;
99
+ });
100
+ }
101
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.2.4", ngImport: i0, type: EpdfComments, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
102
+ static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "20.2.4", ngImport: i0, type: EpdfComments, providedIn: 'root' });
103
+ }
104
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.2.4", ngImport: i0, type: EpdfComments, decorators: [{
105
+ type: Injectable,
106
+ args: [{ providedIn: 'root' }]
107
+ }], ctorParameters: () => [] });
108
+
109
+ /**
110
+ * The annotation plugin's service and feature:
111
+ *
112
+ * withAnnotation(options) the plugin, for provideEmbedPdf() (it needs withInteraction())
113
+ * inject(EpdfAnnotation) create, change and delete annotations; the State table as
114
+ * signals (`status()`, `selected()`, `hovered()`, `editing()`);
115
+ * the parts (`selection`, `draft`, `text`, `tools`, `stamps`,
116
+ * `links`); the events as streams (`created$`, …); the settings;
117
+ * and the reads a template follows (`watch(filter)`,
118
+ * `anchorOf(ref)`, `tools.defaultsOf(id)`)
119
+ *
120
+ * The service is both the plugin's API and its state, so the React hooks around it become
121
+ * members: `useAnnotationList(filter)` is `annotation.watch(filter)`, `useAnnotationAnchor(ref)`
122
+ * is `annotation.anchorOf(ref)`, `useAnnotationDefaults(id)` is `annotation.tools.defaultsOf(id)`
123
+ * and `useAnnotationProperties()` is `annotation.selection.properties()`. Each of them takes a
124
+ * value or a function that reads signals (`() => this.stamp().ref`), and follows it.
125
+ */
126
+ const NO_ANNOTATIONS = Object.freeze([]);
127
+ const NO_DEFAULTS = Object.freeze({});
128
+ const NO_PROPERTIES = Object.freeze({
129
+ properties: [],
130
+ values: {},
131
+ mixed: [],
132
+ });
133
+ /**
134
+ * Annotations: `create()`, `update()`, `delete()` and the rest of the Methods tables, the
135
+ * selecting page's State table as signals (`annotation.selected()`), the parts the capability
136
+ * has (`annotation.selection.set(refs)`, `annotation.tools.updateDefaults(id, changes)`), its
137
+ * events as streams (`annotation.created$`), and its settings (`annotation.settings()`,
138
+ * `annotation.updateSettings({ chrome })`). Comments have their own service, `EpdfComments`.
139
+ *
140
+ * Without a document the signals read empty and the methods refuse with `not-ready`. The checks
141
+ * (`canCreate()`, `canDelete(ref)`, `selection.canGroup()`, `stamps.isArmed()`) are state:
142
+ * read in a template, they follow the document, its permissions and the selection.
143
+ */
144
+ class EpdfAnnotation extends pluginService({
145
+ name: 'EpdfAnnotation',
146
+ feature: 'withAnnotation()',
147
+ token: AnnotationToken,
148
+ state: annotationState,
149
+ methods: [
150
+ 'get',
151
+ 'list',
152
+ 'getAt',
153
+ 'isPending',
154
+ 'getStatus',
155
+ 'getHovered',
156
+ 'refresh',
157
+ 'create',
158
+ 'createFromSelection',
159
+ 'update',
160
+ 'delete',
161
+ 'reorder',
162
+ 'export',
163
+ 'import',
164
+ 'downloadResource',
165
+ 'setFilePickerProvider',
166
+ 'canRead',
167
+ 'canCreate',
168
+ 'canUpdate',
169
+ 'canDelete',
170
+ 'cancel',
171
+ ],
172
+ events: [
173
+ 'onCreated',
174
+ 'onUpdated',
175
+ 'onDeleted',
176
+ 'onReordered',
177
+ 'onResynced',
178
+ 'onWriteFailed',
179
+ 'onSelectionChanged',
180
+ 'onDraftChanged',
181
+ 'onEditingChanged',
182
+ 'onHoverChanged',
183
+ ],
184
+ }) {
185
+ /** The same capability, typed with the reads only framework code has (an annotation's anchor). */
186
+ host = new CapabilityBinding(this.binding.host, () => AnnotationToken$1, injectDocumentScope());
187
+ /** The selection: `set(refs)`, `update(changes)`, `delete()`, … and `properties()`. */
188
+ selection = {
189
+ set: this.binding.namespaceMethod('selection', 'set'),
190
+ add: this.binding.namespaceMethod('selection', 'add'),
191
+ selectAll: this.binding.namespaceMethod('selection', 'selectAll'),
192
+ selectInRect: this.binding.namespaceMethod('selection', 'selectInRect'),
193
+ clear: this.binding.namespaceMethod('selection', 'clear'),
194
+ list: this.binding.namespaceMethod('selection', 'list'),
195
+ update: this.binding.namespaceMethod('selection', 'update'),
196
+ updateLink: this.binding.namespaceMethod('selection', 'updateLink'),
197
+ getProperties: this.binding.namespaceMethod('selection', 'getProperties'),
198
+ delete: this.binding.namespaceMethod('selection', 'delete'),
199
+ rotateBy: this.binding.namespaceMethod('selection', 'rotateBy'),
200
+ resetRotation: this.binding.namespaceMethod('selection', 'resetRotation'),
201
+ group: this.binding.namespaceMethod('selection', 'group'),
202
+ ungroup: this.binding.namespaceMethod('selection', 'ungroup'),
203
+ canGroup: this.binding.namespaceMethod('selection', 'canGroup'),
204
+ canUngroup: this.binding.namespaceMethod('selection', 'canUngroup'),
205
+ getAnchor: this.binding.namespaceMethod('selection', 'getAnchor'),
206
+ getRotationAnchor: this.binding.namespaceMethod('selection', 'getRotationAnchor'),
207
+ // The plugin hands out the same object while nothing changes.
208
+ properties: this.binding.select((annotation) => annotation.selection.getProperties(), NO_PROPERTIES, Object.is),
209
+ };
210
+ /** A polygon or polyline being drawn: `get()`, `finish()`, `cancel()`. */
211
+ draft = {
212
+ get: this.binding.namespaceMethod('draft', 'get'),
213
+ finish: this.binding.namespaceMethod('draft', 'finish'),
214
+ cancel: this.binding.namespaceMethod('draft', 'cancel'),
215
+ };
216
+ /** Typing in a text box: `begin(ref)`, `end()`, `getEditing()`, `toggleFormat(format)`. */
217
+ text = {
218
+ begin: this.binding.namespaceMethod('text', 'begin'),
219
+ end: this.binding.namespaceMethod('text', 'end'),
220
+ getEditing: this.binding.namespaceMethod('text', 'getEditing'),
221
+ toggleFormat: this.binding.namespaceMethod('text', 'toggleFormat'),
222
+ };
223
+ /** The tools: `list()`, `register(tool)`, `updateDefaults(id, changes)`, `defaultsOf(id)`, … */
224
+ tools = {
225
+ list: this.binding.namespaceMethod('tools', 'list'),
226
+ get: this.binding.namespaceMethod('tools', 'get'),
227
+ register: this.binding.namespaceMethod('tools', 'register'),
228
+ getDefaults: this.binding.namespaceMethod('tools', 'getDefaults'),
229
+ updateDefaults: this.binding.namespaceMethod('tools', 'updateDefaults'),
230
+ getProperties: this.binding.namespaceMethod('tools', 'getProperties'),
231
+ // The plugin hands out the same object until the tool's defaults change.
232
+ defaultsOf: (id) => {
233
+ const idOf = typeof id === 'function' ? id : () => id;
234
+ return this.binding.select((annotation) => annotation.tools.getDefaults(idOf()), NO_DEFAULTS, Object.is);
235
+ },
236
+ propertiesOf: (id) => {
237
+ const idOf = typeof id === 'function' ? id : () => id;
238
+ return this.binding.select((annotation) => annotation.tools.getProperties(idOf()), NO_PROPERTIES);
239
+ },
240
+ defaultsChanged$: this.binding.stream((annotation) => annotation.tools.onDefaultsChanged),
241
+ };
242
+ /** Stamps from bytes: `arm(stamp)`, `disarm()`, `isArmed()`, `place(stamp, placement)`. */
243
+ stamps = {
244
+ arm: this.binding.namespaceMethod('stamps', 'arm'),
245
+ disarm: this.binding.namespaceMethod('stamps', 'disarm'),
246
+ isArmed: this.binding.namespaceMethod('stamps', 'isArmed'),
247
+ place: this.binding.namespaceMethod('stamps', 'place'),
248
+ };
249
+ /** An annotation's link: `get(ref)`, `set(ref, target)`, `clear(ref)`. */
250
+ links = {
251
+ get: this.binding.namespaceMethod('links', 'get'),
252
+ set: this.binding.namespaceMethod('links', 'set'),
253
+ clear: this.binding.namespaceMethod('links', 'clear'),
254
+ };
255
+ /**
256
+ * The annotations matching `filter` (some pages, one kind), in drawing order, as the user sees
257
+ * them: a signal that keeps the same array while they stay the same. Pass the filter, or a
258
+ * function that reads it (`() => ({ pages: [this.page()] })`). Empty without a document.
259
+ * Call it once, in a field or the constructor, and read the signal in your template.
260
+ */
261
+ watch(filter) {
262
+ const filterOf = typeof filter === 'function' ? filter : () => filter;
263
+ return this.binding.select((annotation) => annotation.list(filterOf() ?? undefined), NO_ANNOTATIONS);
264
+ }
265
+ /**
266
+ * An anchor for `<epdf-anchored>` that keeps a card or a badge on one annotation: its page and
267
+ * the box around what it shows, following a move as it happens. Pass the ref, or a function
268
+ * that reads it (`() => this.hovered()?.ref ?? null`). Null for null, or an annotation that
269
+ * isn't here. It changes when the annotation moves, not while people scroll or zoom.
270
+ */
271
+ anchorOf(ref) {
272
+ const refOf = typeof ref === 'function' ? ref : () => ref;
273
+ return this.host.select((annotation) => {
274
+ const target = refOf();
275
+ return target ? annotation.getAnnotationAnchor(target) : null;
276
+ }, null, sameAnnotationAnchor);
277
+ }
278
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.2.4", ngImport: i0, type: EpdfAnnotation, deps: null, target: i0.ɵɵFactoryTarget.Injectable });
279
+ static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "20.2.4", ngImport: i0, type: EpdfAnnotation, providedIn: 'root' });
280
+ }
281
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.2.4", ngImport: i0, type: EpdfAnnotation, decorators: [{
282
+ type: Injectable,
283
+ args: [{ providedIn: 'root' }]
284
+ }] });
285
+ /**
286
+ * The annotation plugin: drawing, selecting, changing and saving annotations, and comments. It
287
+ * works with the interaction plugin, so give `withInteraction()` too, and an
288
+ * `<epdf-annotation-layer>` in each page:
289
+ * `provideEmbedPdf({ engine }, withStage(), withRender(), withInteraction(), withAnnotation())`.
290
+ */
291
+ function withAnnotation(options) {
292
+ return { plugins: [annotationPlugin(options)], services: [EpdfAnnotation, EpdfComments] };
293
+ }
294
+
295
+ /**
296
+ * `withFilePicker(provider?)`: where the `stamp` and `attachment` tools get their file after a
297
+ * click. The plugin has no DOM, so it asks a provider, and this feature gives it one: the
298
+ * browser's file dialog by default, or your own function (a stamp library, a cloud drive).
299
+ *
300
+ * provideEmbedPdf({ engine }, withStage(), withInteraction(), withAnnotation(), withFilePicker())
301
+ *
302
+ * React installs it with a hook in an always-mounted component; in Angular it is app-wide setup,
303
+ * so it is a feature of `provideEmbedPdf()`.
304
+ */
305
+ /**
306
+ * The browser's file dialog, filtered by the tool's `accept`: what `withFilePicker()` installs
307
+ * when you give it nothing. A picked `File` carries its own name and type.
308
+ */
309
+ const filePickerProvider = pickRequestedFile;
310
+ /**
311
+ * The file dialog behind every click-then-pick tool, for every document the viewer opens.
312
+ * Without an argument it is the browser's dialog ({@link filePickerProvider}); pass your own
313
+ * function to pick from your library (it gets the tool, the page and the point, and returns the
314
+ * file or `null` to cancel), or `null` to make those tools do nothing.
315
+ */
316
+ function withFilePicker(provider = filePickerProvider) {
317
+ return { plugins: [], setup: () => installFilePicker(provider) };
318
+ }
319
+ /** Give every open document's annotation plugin the provider, for as long as the viewer lives. */
320
+ function installFilePicker(provider) {
321
+ const host = injectKernelHost('withFilePicker()');
322
+ if (!host.provides(AnnotationToken)) {
323
+ devWarn('file-picker-without-annotation', 'withFilePicker() has no annotation plugin to give its file dialog to. Add withAnnotation() to provideEmbedPdf().');
324
+ return;
325
+ }
326
+ // One install per document's plugin, removed when the document closes.
327
+ const installed = new Map();
328
+ // On every change of the kernel, not in an effect: a document is given the provider in the
329
+ // same change that makes it ready, before anyone can click with a stamp tool.
330
+ const follow = () => {
331
+ const kernel = host.kernel();
332
+ if (!kernel)
333
+ return;
334
+ const open = new Set();
335
+ for (const document of kernel.documents.list()) {
336
+ const annotation = kernel.tryCapability(AnnotationToken, document.id);
337
+ if (!annotation)
338
+ continue;
339
+ open.add(annotation);
340
+ if (!installed.has(annotation)) {
341
+ installed.set(annotation, annotation.setFilePickerProvider(provider));
342
+ }
343
+ }
344
+ for (const [annotation, remove] of installed) {
345
+ if (open.has(annotation))
346
+ continue;
347
+ remove();
348
+ installed.delete(annotation);
349
+ }
350
+ };
351
+ const stop = host.subscribe(follow);
352
+ follow();
353
+ inject(DestroyRef).onDestroy(() => {
354
+ stop();
355
+ for (const remove of installed.values())
356
+ remove();
357
+ installed.clear();
358
+ });
359
+ }
360
+
361
+ /**
362
+ * What the annotation layer's parts read about their page: the plugin's host side for the page's
363
+ * own document, and the page's view as signals that change only when what they hold does. A
364
+ * Stage hands every page a new transform on every frame, also during a plain scroll, which moves
365
+ * the page but changes nothing inside it; these keep a scroll from redrawing the annotations.
366
+ */
367
+ /**
368
+ * The annotation plugin's host side (the reads only framework code has: items, chrome, text
369
+ * boxes), for the document the page belongs to. Call it in an injection context.
370
+ */
371
+ function annotationHostOf(page, what) {
372
+ return new CapabilityBinding(injectKernelHost(what), () => AnnotationToken$1, () => page.documentId);
373
+ }
374
+ /**
375
+ * The page's zoom and turn, for the plugin's reads: it projects what keeps its size or stays
376
+ * upright on screen (`noZoom`, `noRotate`). `zoom`, not `viewScale`: 1 is the page's 100%.
377
+ */
378
+ function pageViewOf(page) {
379
+ return computed(() => {
380
+ const transform = page.transform();
381
+ return { zoom: transform.zoom, rotation: transform.rotation };
382
+ }, { equal: (left, right) => left.zoom === right.zoom && left.rotation === right.rotation });
383
+ }
384
+ /** The page's transform for placing things in its pixels: the same while only a scroll moves it. */
385
+ function pagePixelsOf(page) {
386
+ return computed(() => page.transform(), { equal: samePagePlacement });
387
+ }
388
+
389
+ /**
390
+ * The selection chrome's settings, for the parts that paint it: the plugin's `chrome` settings
391
+ * and the viewer's accent, which a chrome color left `null` follows. Settings belong to the
392
+ * plugin, not to a document, so they read before any document opens.
393
+ */
394
+ /** The chrome settings and the accent, as signals. Call it in an injection context. */
395
+ function injectChromeSettings() {
396
+ const host = injectKernelHost('<epdf-annotation-layer>');
397
+ // Without the plugin there is nothing to paint; the defaults keep the reads simple.
398
+ const settings = host.provides(AnnotationToken)
399
+ ? host.settingsOf(AnnotationToken).current
400
+ : computed(() => ANNOTATION_DEFAULTS);
401
+ return {
402
+ chrome: computed(() => settings().chrome),
403
+ accent: computed(() => host.viewerSettings().accent),
404
+ };
405
+ }
406
+
407
+ /**
408
+ * The selection's chrome on one page: its outline, handles and rotation handle, the guides of a
409
+ * snapped move or a turn, and the box dragged to select. The plugin lists the chrome in page
410
+ * points, already sized for the page's zoom; `@embedpdf/web` puts it in the layer's pixels and
411
+ * paints it from the `chrome` settings, each color, width and dash behind its
412
+ * `--epdf-annotation-*` variable. This component only draws: the viewer decides where a handle
413
+ * can be grabbed, so handles drawn by the app's templates can't break dragging.
414
+ *
415
+ * Paint goes in `style`: an SVG attribute doesn't read `var()`.
416
+ */
417
+ const NO_NODES = Object.freeze([]);
418
+ class EpdfAnnotationChrome {
419
+ /** The app's handle template, drawn in place of the layer's handles. */
420
+ handle = input(null, ...(ngDevMode ? [{ debugName: "handle" }] : []));
421
+ /** The app's rotation handle template, drawn in place of the layer's. */
422
+ rotationHandle = input(null, ...(ngDevMode ? [{ debugName: "rotationHandle" }] : []));
423
+ page = injectPage('<epdf-annotation-layer>');
424
+ annotation = annotationHostOf(this.page, '<epdf-annotation-layer>');
425
+ pixels = pagePixelsOf(this.page);
426
+ settings = injectChromeSettings();
427
+ chrome = computed(() => this.settings.chrome(), ...(ngDevMode ? [{ debugName: "chrome" }] : []));
428
+ paint = computed(() => annotationChromePaint(this.settings.chrome(), this.settings.accent()), ...(ngDevMode ? [{ debugName: "paint" }] : []));
429
+ // The page's view scale turns the screen-pixel chrome settings into page units inside the
430
+ // plugin (the rotation handle's offset, the grab zones): the same size on screen at every
431
+ // zoom. What it lists comes back in page points; `chromeInPixels` places it.
432
+ listed = this.annotation.select((annotation) => {
433
+ const transform = this.pixels();
434
+ return annotation.listChromeNodes(this.page.ref, transform.viewScale, transform.rotation, transform.zoom);
435
+ }, NO_NODES);
436
+ nodes = computed(() => {
437
+ const handle = this.handle();
438
+ const rotationHandle = this.rotationHandle();
439
+ const chrome = this.settings.chrome();
440
+ return chromeInPixels(this.listed(), this.pixels()).map((node) => {
441
+ if (node.kind === 'handle' && handle) {
442
+ const custom = {
443
+ at: node.at,
444
+ size: chrome.handles.size,
445
+ rotation: node.rotation,
446
+ kind: node.role,
447
+ active: node.active,
448
+ };
449
+ return { ...node, custom };
450
+ }
451
+ if (node.kind === 'rotation-handle' && rotationHandle) {
452
+ // A drawn rotation handle doesn't turn, and isn't told when it's dragged.
453
+ const custom = {
454
+ at: node.at,
455
+ from: node.from,
456
+ size: chrome.rotationHandle.size,
457
+ rotation: 0,
458
+ active: false,
459
+ };
460
+ return { ...node, custom };
461
+ }
462
+ return node;
463
+ });
464
+ }, ...(ngDevMode ? [{ debugName: "nodes" }] : []));
465
+ hasCustom = computed(() => this.nodes().some((node) => node.custom), ...(ngDevMode ? [{ debugName: "hasCustom" }] : []));
466
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.2.4", ngImport: i0, type: EpdfAnnotationChrome, deps: [], target: i0.ɵɵFactoryTarget.Component });
467
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "20.2.4", type: EpdfAnnotationChrome, isStandalone: true, selector: "epdf-annotation-chrome", inputs: { handle: { classPropertyName: "handle", publicName: "handle", isSignal: true, isRequired: false, transformFunction: null }, rotationHandle: { classPropertyName: "rotationHandle", publicName: "rotationHandle", isSignal: true, isRequired: false, transformFunction: null } }, host: { styleAttribute: "display: contents" }, ngImport: i0, template: `
468
+ <svg style="position: absolute; inset: 0; overflow: visible; pointer-events: none">
469
+ @for (node of nodes(); track $index) {
470
+ @switch (node.kind) {
471
+ @case ('handle') {
472
+ @if (node.kind === 'handle' && !node.custom) {
473
+ @if (chrome().handles.shape === 'circle') {
474
+ <svg:circle
475
+ [attr.cx]="node.at.x"
476
+ [attr.cy]="node.at.y"
477
+ [attr.r]="chrome().handles.size / 2"
478
+ stroke-width="1.5"
479
+ [style.fill]="paint().handle.fill"
480
+ [style.stroke]="paint().handle.stroke"
481
+ ></svg:circle>
482
+ } @else {
483
+ <!-- The square rides a turned box: it spins about itself. -->
484
+ <svg:rect
485
+ [attr.x]="node.at.x - chrome().handles.size / 2"
486
+ [attr.y]="node.at.y - chrome().handles.size / 2"
487
+ [attr.width]="chrome().handles.size"
488
+ [attr.height]="chrome().handles.size"
489
+ [attr.transform]="
490
+ node.rotation ? 'rotate(' + node.rotation + ' ' + node.at.x + ' ' + node.at.y + ')' : null
491
+ "
492
+ stroke-width="1.5"
493
+ [style.fill]="paint().handle.fill"
494
+ [style.stroke]="paint().handle.stroke"
495
+ ></svg:rect>
496
+ }
497
+ }
498
+ }
499
+ @case ('guide') {
500
+ @if (node.kind === 'guide') {
501
+ <svg:line
502
+ [attr.x1]="node.from.x"
503
+ [attr.y1]="node.from.y"
504
+ [attr.x2]="node.to.x"
505
+ [attr.y2]="node.to.y"
506
+ shape-rendering="crispEdges"
507
+ [style.stroke]="paint().guide.stroke"
508
+ [style.stroke-width]="paint().guide.strokeWidth"
509
+ [style.stroke-dasharray]="paint().guide.strokeDasharray"
510
+ ></svg:line>
511
+ }
512
+ }
513
+ @case ('turned-outline') {
514
+ @if (node.kind === 'turned-outline') {
515
+ <svg:polygon
516
+ [attr.points]="node.points"
517
+ fill="none"
518
+ [style.stroke]="paint().outline.stroke"
519
+ [style.stroke-width]="paint().outline.strokeWidth"
520
+ [style.stroke-dasharray]="paint().outline.strokeDasharray"
521
+ ></svg:polygon>
522
+ }
523
+ }
524
+ @case ('rotation-guides') {
525
+ @if (node.kind === 'rotation-guides') {
526
+ <svg:g>
527
+ @for (line of node.lines; track $index) {
528
+ <svg:line
529
+ [attr.x1]="line.from.x"
530
+ [attr.y1]="line.from.y"
531
+ [attr.x2]="line.to.x"
532
+ [attr.y2]="line.to.y"
533
+ [attr.opacity]="line.opacity"
534
+ [style.stroke]="paint().rotationGuide.stroke"
535
+ [style.stroke-width]="paint().rotationGuide.strokeWidth"
536
+ [style.stroke-dasharray]="paint().rotationGuide.strokeDasharray"
537
+ ></svg:line>
538
+ }
539
+ </svg:g>
540
+ }
541
+ }
542
+ @case ('rotation-handle') {
543
+ @if (node.kind === 'rotation-handle' && !node.custom) {
544
+ <svg:g>
545
+ @if (chrome().rotationHandle.stalk) {
546
+ <svg:line
547
+ [attr.x1]="node.from.x"
548
+ [attr.y1]="node.from.y"
549
+ [attr.x2]="node.at.x"
550
+ [attr.y2]="node.at.y"
551
+ stroke-width="1"
552
+ [style.stroke]="paint().rotationHandle.stroke"
553
+ ></svg:line>
554
+ }
555
+ <svg:circle
556
+ [attr.cx]="node.at.x"
557
+ [attr.cy]="node.at.y"
558
+ [attr.r]="chrome().rotationHandle.size / 2"
559
+ stroke-width="1.5"
560
+ [style.fill]="paint().rotationHandle.fill"
561
+ [style.stroke]="paint().rotationHandle.stroke"
562
+ ></svg:circle>
563
+ </svg:g>
564
+ }
565
+ }
566
+ <!-- The box dragged to select keeps its own look (a see-through accent fill, always
567
+ dashed); the selection outline follows the settings. -->
568
+ @case ('marquee') {
569
+ @if (node.kind === 'marquee') {
570
+ <svg:rect
571
+ [attr.x]="node.box.left"
572
+ [attr.y]="node.box.top"
573
+ [attr.width]="node.box.width"
574
+ [attr.height]="node.box.height"
575
+ stroke-width="1"
576
+ stroke-dasharray="4 3"
577
+ [style.fill]="paint().marquee.fill"
578
+ [style.stroke]="paint().marquee.stroke"
579
+ ></svg:rect>
580
+ }
581
+ }
582
+ @case ('outline') {
583
+ @if (node.kind === 'outline') {
584
+ <svg:rect
585
+ [attr.x]="node.box.left"
586
+ [attr.y]="node.box.top"
587
+ [attr.width]="node.box.width"
588
+ [attr.height]="node.box.height"
589
+ fill="none"
590
+ [style.stroke]="paint().outline.stroke"
591
+ [style.stroke-width]="paint().outline.strokeWidth"
592
+ [style.stroke-dasharray]="paint().outline.strokeDasharray"
593
+ ></svg:rect>
594
+ }
595
+ }
596
+ }
597
+ }
598
+ </svg>
599
+ <!-- The app's own handles draw as HTML over the page; the layer's as SVG. -->
600
+ @if (hasCustom()) {
601
+ <div style="position: absolute; inset: 0; pointer-events: none">
602
+ @for (node of nodes(); track $index) {
603
+ @if (node.custom; as props) {
604
+ @if (node.kind === 'handle') {
605
+ <ng-container
606
+ [ngTemplateOutlet]="handle()"
607
+ [ngTemplateOutletContext]="{ $implicit: props }"
608
+ />
609
+ } @else {
610
+ <ng-container
611
+ [ngTemplateOutlet]="rotationHandle()"
612
+ [ngTemplateOutletContext]="{ $implicit: props }"
613
+ />
614
+ }
615
+ }
616
+ }
617
+ </div>
618
+ }
619
+ `, isInline: true, dependencies: [{ kind: "directive", type: NgTemplateOutlet, selector: "[ngTemplateOutlet]", inputs: ["ngTemplateOutletContext", "ngTemplateOutlet", "ngTemplateOutletInjector"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
620
+ }
621
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.2.4", ngImport: i0, type: EpdfAnnotationChrome, decorators: [{
622
+ type: Component,
623
+ args: [{
624
+ selector: 'epdf-annotation-chrome',
625
+ imports: [NgTemplateOutlet],
626
+ changeDetection: ChangeDetectionStrategy.OnPush,
627
+ host: { style: 'display: contents' },
628
+ template: `
629
+ <svg style="position: absolute; inset: 0; overflow: visible; pointer-events: none">
630
+ @for (node of nodes(); track $index) {
631
+ @switch (node.kind) {
632
+ @case ('handle') {
633
+ @if (node.kind === 'handle' && !node.custom) {
634
+ @if (chrome().handles.shape === 'circle') {
635
+ <svg:circle
636
+ [attr.cx]="node.at.x"
637
+ [attr.cy]="node.at.y"
638
+ [attr.r]="chrome().handles.size / 2"
639
+ stroke-width="1.5"
640
+ [style.fill]="paint().handle.fill"
641
+ [style.stroke]="paint().handle.stroke"
642
+ ></svg:circle>
643
+ } @else {
644
+ <!-- The square rides a turned box: it spins about itself. -->
645
+ <svg:rect
646
+ [attr.x]="node.at.x - chrome().handles.size / 2"
647
+ [attr.y]="node.at.y - chrome().handles.size / 2"
648
+ [attr.width]="chrome().handles.size"
649
+ [attr.height]="chrome().handles.size"
650
+ [attr.transform]="
651
+ node.rotation ? 'rotate(' + node.rotation + ' ' + node.at.x + ' ' + node.at.y + ')' : null
652
+ "
653
+ stroke-width="1.5"
654
+ [style.fill]="paint().handle.fill"
655
+ [style.stroke]="paint().handle.stroke"
656
+ ></svg:rect>
657
+ }
658
+ }
659
+ }
660
+ @case ('guide') {
661
+ @if (node.kind === 'guide') {
662
+ <svg:line
663
+ [attr.x1]="node.from.x"
664
+ [attr.y1]="node.from.y"
665
+ [attr.x2]="node.to.x"
666
+ [attr.y2]="node.to.y"
667
+ shape-rendering="crispEdges"
668
+ [style.stroke]="paint().guide.stroke"
669
+ [style.stroke-width]="paint().guide.strokeWidth"
670
+ [style.stroke-dasharray]="paint().guide.strokeDasharray"
671
+ ></svg:line>
672
+ }
673
+ }
674
+ @case ('turned-outline') {
675
+ @if (node.kind === 'turned-outline') {
676
+ <svg:polygon
677
+ [attr.points]="node.points"
678
+ fill="none"
679
+ [style.stroke]="paint().outline.stroke"
680
+ [style.stroke-width]="paint().outline.strokeWidth"
681
+ [style.stroke-dasharray]="paint().outline.strokeDasharray"
682
+ ></svg:polygon>
683
+ }
684
+ }
685
+ @case ('rotation-guides') {
686
+ @if (node.kind === 'rotation-guides') {
687
+ <svg:g>
688
+ @for (line of node.lines; track $index) {
689
+ <svg:line
690
+ [attr.x1]="line.from.x"
691
+ [attr.y1]="line.from.y"
692
+ [attr.x2]="line.to.x"
693
+ [attr.y2]="line.to.y"
694
+ [attr.opacity]="line.opacity"
695
+ [style.stroke]="paint().rotationGuide.stroke"
696
+ [style.stroke-width]="paint().rotationGuide.strokeWidth"
697
+ [style.stroke-dasharray]="paint().rotationGuide.strokeDasharray"
698
+ ></svg:line>
699
+ }
700
+ </svg:g>
701
+ }
702
+ }
703
+ @case ('rotation-handle') {
704
+ @if (node.kind === 'rotation-handle' && !node.custom) {
705
+ <svg:g>
706
+ @if (chrome().rotationHandle.stalk) {
707
+ <svg:line
708
+ [attr.x1]="node.from.x"
709
+ [attr.y1]="node.from.y"
710
+ [attr.x2]="node.at.x"
711
+ [attr.y2]="node.at.y"
712
+ stroke-width="1"
713
+ [style.stroke]="paint().rotationHandle.stroke"
714
+ ></svg:line>
715
+ }
716
+ <svg:circle
717
+ [attr.cx]="node.at.x"
718
+ [attr.cy]="node.at.y"
719
+ [attr.r]="chrome().rotationHandle.size / 2"
720
+ stroke-width="1.5"
721
+ [style.fill]="paint().rotationHandle.fill"
722
+ [style.stroke]="paint().rotationHandle.stroke"
723
+ ></svg:circle>
724
+ </svg:g>
725
+ }
726
+ }
727
+ <!-- The box dragged to select keeps its own look (a see-through accent fill, always
728
+ dashed); the selection outline follows the settings. -->
729
+ @case ('marquee') {
730
+ @if (node.kind === 'marquee') {
731
+ <svg:rect
732
+ [attr.x]="node.box.left"
733
+ [attr.y]="node.box.top"
734
+ [attr.width]="node.box.width"
735
+ [attr.height]="node.box.height"
736
+ stroke-width="1"
737
+ stroke-dasharray="4 3"
738
+ [style.fill]="paint().marquee.fill"
739
+ [style.stroke]="paint().marquee.stroke"
740
+ ></svg:rect>
741
+ }
742
+ }
743
+ @case ('outline') {
744
+ @if (node.kind === 'outline') {
745
+ <svg:rect
746
+ [attr.x]="node.box.left"
747
+ [attr.y]="node.box.top"
748
+ [attr.width]="node.box.width"
749
+ [attr.height]="node.box.height"
750
+ fill="none"
751
+ [style.stroke]="paint().outline.stroke"
752
+ [style.stroke-width]="paint().outline.strokeWidth"
753
+ [style.stroke-dasharray]="paint().outline.strokeDasharray"
754
+ ></svg:rect>
755
+ }
756
+ }
757
+ }
758
+ }
759
+ </svg>
760
+ <!-- The app's own handles draw as HTML over the page; the layer's as SVG. -->
761
+ @if (hasCustom()) {
762
+ <div style="position: absolute; inset: 0; pointer-events: none">
763
+ @for (node of nodes(); track $index) {
764
+ @if (node.custom; as props) {
765
+ @if (node.kind === 'handle') {
766
+ <ng-container
767
+ [ngTemplateOutlet]="handle()"
768
+ [ngTemplateOutletContext]="{ $implicit: props }"
769
+ />
770
+ } @else {
771
+ <ng-container
772
+ [ngTemplateOutlet]="rotationHandle()"
773
+ [ngTemplateOutletContext]="{ $implicit: props }"
774
+ />
775
+ }
776
+ }
777
+ }
778
+ </div>
779
+ }
780
+ `,
781
+ }]
782
+ }] });
783
+
784
+ /**
785
+ * How the layer draws one annotation, the parts its template is built from:
786
+ *
787
+ * [epdfAnnotationFrame] the box an annotation draws into, placed and turned like it
788
+ * <epdf-annotation-native> its own drawing in that box: the engine's picture or its scene
789
+ *
790
+ * No bounds math and no per-kind logic: the core computes each item's frame, box and scene,
791
+ * and `@embedpdf/web` turns them into pixels and SVG elements, the same for every framework.
792
+ */
793
+ /**
794
+ * The box an annotation draws into, in the page layer: placed, sized and turned like the
795
+ * annotation (`item.frame`), inside the page layer that the page itself turns. Its blend is on
796
+ * the box: a turned box groups what is in it, so blending an element inside would stop
797
+ * blending with the page. It takes the pointer only for a look that does (`interactive`), and
798
+ * `inert` keeps the pointer and the focus out of a look that only draws.
799
+ */
800
+ class EpdfAnnotationFrame {
801
+ frame = input.required(...(ngDevMode ? [{ debugName: "frame", alias: 'epdfAnnotationFrame' }] : [{ alias: 'epdfAnnotationFrame' }]));
802
+ blend = input(undefined, ...(ngDevMode ? [{ debugName: "blend", alias: 'frameBlend' }] : [{ alias: 'frameBlend' }]));
803
+ interactive = input(false, ...(ngDevMode ? [{ debugName: "interactive", alias: 'frameInteractive' }] : [{ alias: 'frameInteractive' }]));
804
+ inert = input(false, ...(ngDevMode ? [{ debugName: "inert", alias: 'frameInert' }] : [{ alias: 'frameInert' }]));
805
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.2.4", ngImport: i0, type: EpdfAnnotationFrame, deps: [], target: i0.ɵɵFactoryTarget.Directive });
806
+ static ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "17.1.0", version: "20.2.4", type: EpdfAnnotationFrame, isStandalone: true, selector: "[epdfAnnotationFrame]", inputs: { frame: { classPropertyName: "frame", publicName: "epdfAnnotationFrame", isSignal: true, isRequired: true, transformFunction: null }, blend: { classPropertyName: "blend", publicName: "frameBlend", isSignal: true, isRequired: false, transformFunction: null }, interactive: { classPropertyName: "interactive", publicName: "frameInteractive", isSignal: true, isRequired: false, transformFunction: null }, inert: { classPropertyName: "inert", publicName: "frameInert", isSignal: true, isRequired: false, transformFunction: null } }, host: { properties: { "style.left.px": "frame().left", "style.top.px": "frame().top", "style.width.px": "frame().width", "style.height.px": "frame().height", "style.transform": "frame().transform ?? null", "style.mix-blend-mode": "blend() ?? null", "style.pointer-events": "interactive() ? 'auto' : 'none'", "attr.inert": "inert() ? '' : null" }, styleAttribute: "position: absolute; transform-origin: center" }, ngImport: i0 });
807
+ }
808
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.2.4", ngImport: i0, type: EpdfAnnotationFrame, decorators: [{
809
+ type: Directive,
810
+ args: [{
811
+ selector: '[epdfAnnotationFrame]',
812
+ host: {
813
+ style: 'position: absolute; transform-origin: center',
814
+ '[style.left.px]': 'frame().left',
815
+ '[style.top.px]': 'frame().top',
816
+ '[style.width.px]': 'frame().width',
817
+ '[style.height.px]': 'frame().height',
818
+ '[style.transform]': 'frame().transform ?? null',
819
+ '[style.mix-blend-mode]': 'blend() ?? null',
820
+ // An interactive look takes the pointer: the layer's own `none` ends here.
821
+ '[style.pointer-events]': "interactive() ? 'auto' : 'none'",
822
+ '[attr.inert]': "inert() ? '' : null",
823
+ },
824
+ }]
825
+ }] });
826
+ /**
827
+ * The scene, filling the item's frame: drawn upright in it, the frame turns it. Nothing until
828
+ * the item has area (the 0×0 drawing at a press). The SVG and its viewBox stay proportional:
829
+ * never clamp either one.
830
+ */
831
+ class EpdfAnnotationScene {
832
+ item = input.required(...(ngDevMode ? [{ debugName: "item" }] : []));
833
+ viewBox = computed(() => sceneViewBox(this.item().box), ...(ngDevMode ? [{ debugName: "viewBox" }] : []));
834
+ shapes = computed(() => svgShapesOf(scene(this.item()), { miterLimit: MITER_LIMIT }), ...(ngDevMode ? [{ debugName: "shapes" }] : []));
835
+ /** A ghost is see-through as a whole, so its fill and stroke don't stack. */
836
+ opacity = computed(() => {
837
+ const item = this.item();
838
+ return item.source === 'ghost' ? ghostOpacity(item.ghostOpacity ?? 0.5) : null;
839
+ }, ...(ngDevMode ? [{ debugName: "opacity" }] : []));
840
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.2.4", ngImport: i0, type: EpdfAnnotationScene, deps: [], target: i0.ɵɵFactoryTarget.Component });
841
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "20.2.4", type: EpdfAnnotationScene, isStandalone: true, selector: "epdf-annotation-scene", inputs: { item: { classPropertyName: "item", publicName: "item", isSignal: true, isRequired: true, transformFunction: null } }, host: { styleAttribute: "display: contents" }, ngImport: i0, template: `
842
+ @if (viewBox(); as viewBox) {
843
+ <svg
844
+ style="position: absolute; left: 0; top: 0; width: 100%; height: 100%; overflow: visible; pointer-events: none"
845
+ [attr.viewBox]="viewBox"
846
+ [style.opacity]="opacity()"
847
+ >
848
+ <svg:g [epdfSvgShapes]="shapes()"></svg:g>
849
+ </svg>
850
+ }
851
+ `, isInline: true, dependencies: [{ kind: "component", type: EpdfSvgShapes, selector: "g[epdfSvgShapes]", inputs: ["epdfSvgShapes"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
852
+ }
853
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.2.4", ngImport: i0, type: EpdfAnnotationScene, decorators: [{
854
+ type: Component,
855
+ args: [{
856
+ selector: 'epdf-annotation-scene',
857
+ imports: [EpdfSvgShapes],
858
+ changeDetection: ChangeDetectionStrategy.OnPush,
859
+ host: { style: 'display: contents' },
860
+ template: `
861
+ @if (viewBox(); as viewBox) {
862
+ <svg
863
+ style="position: absolute; left: 0; top: 0; width: 100%; height: 100%; overflow: visible; pointer-events: none"
864
+ [attr.viewBox]="viewBox"
865
+ [style.opacity]="opacity()"
866
+ >
867
+ <svg:g [epdfSvgShapes]="shapes()"></svg:g>
868
+ </svg>
869
+ }
870
+ `,
871
+ }]
872
+ }] });
873
+ /**
874
+ * An annotation's own drawing, filling its frame: the engine's picture where the core places it
875
+ * (following a move as it happens), or its scene, as the layer's one rule picks (`shows`,
876
+ * `pictureOrPainting`).
877
+ */
878
+ class EpdfAnnotationNative {
879
+ item = input.required(...(ngDevMode ? [{ debugName: "item" }] : []));
880
+ /** What it shows: the engine's picture, its scene, or nothing yet. */
881
+ shows = input(null, ...(ngDevMode ? [{ debugName: "shows" }] : []));
882
+ picture = computed(() => {
883
+ const shows = this.shows();
884
+ return shows?.kind === 'picture' ? shows.picture.url : null;
885
+ }, ...(ngDevMode ? [{ debugName: "picture" }] : []));
886
+ raster = computed(() => {
887
+ const item = this.item();
888
+ return item.raster ? rasterInFrame(item.raster, item.frame) : null;
889
+ }, ...(ngDevMode ? [{ debugName: "raster" }] : []));
890
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.2.4", ngImport: i0, type: EpdfAnnotationNative, deps: [], target: i0.ɵɵFactoryTarget.Component });
891
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "20.2.4", type: EpdfAnnotationNative, isStandalone: true, selector: "epdf-annotation-native", inputs: { item: { classPropertyName: "item", publicName: "item", isSignal: true, isRequired: true, transformFunction: null }, shows: { classPropertyName: "shows", publicName: "shows", isSignal: true, isRequired: false, transformFunction: null } }, host: { styleAttribute: "display: contents" }, ngImport: i0, template: `
892
+ @if (shows()?.kind === 'painting') {
893
+ <epdf-annotation-scene [item]="item()" />
894
+ } @else if (picture() && raster(); as box) {
895
+ <!-- An explicit size, never clamped by a global img reset (a landscape stamp on a
896
+ turned page is wider than its containing block); the turn the engine took out
897
+ of the picture goes back on about its middle. -->
898
+ <img
899
+ alt=""
900
+ draggable="false"
901
+ style="position: absolute; max-width: none; max-height: none; pointer-events: none; transform-origin: center"
902
+ [attr.src]="picture()"
903
+ [style.left]="box.left"
904
+ [style.top]="box.top"
905
+ [style.width]="box.width"
906
+ [style.height]="box.height"
907
+ [style.transform]="box.transform ?? null"
908
+ />
909
+ }
910
+ `, isInline: true, dependencies: [{ kind: "component", type: EpdfAnnotationScene, selector: "epdf-annotation-scene", inputs: ["item"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
911
+ }
912
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.2.4", ngImport: i0, type: EpdfAnnotationNative, decorators: [{
913
+ type: Component,
914
+ args: [{
915
+ selector: 'epdf-annotation-native',
916
+ imports: [EpdfAnnotationScene],
917
+ changeDetection: ChangeDetectionStrategy.OnPush,
918
+ host: { style: 'display: contents' },
919
+ template: `
920
+ @if (shows()?.kind === 'painting') {
921
+ <epdf-annotation-scene [item]="item()" />
922
+ } @else if (picture() && raster(); as box) {
923
+ <!-- An explicit size, never clamped by a global img reset (a landscape stamp on a
924
+ turned page is wider than its containing block); the turn the engine took out
925
+ of the picture goes back on about its middle. -->
926
+ <img
927
+ alt=""
928
+ draggable="false"
929
+ style="position: absolute; max-width: none; max-height: none; pointer-events: none; transform-origin: center"
930
+ [attr.src]="picture()"
931
+ [style.left]="box.left"
932
+ [style.top]="box.top"
933
+ [style.width]="box.width"
934
+ [style.height]="box.height"
935
+ [style.transform]="box.transform ?? null"
936
+ />
937
+ }
938
+ `,
939
+ }]
940
+ }] });
941
+
942
+ /**
943
+ * The templates `<epdf-annotation-layer>` draws with, in place of React's `renderers` and
944
+ * `components` props:
945
+ *
946
+ * <epdf-annotation-layer>
947
+ * <ng-template [epdfAnnotation]="isNote" let-annotation let-frame="frame">… your look
948
+ * <ng-template epdfHandle let-handle>… your handles
949
+ * <ng-template epdfRotationHandle let-handle>… your rotation handle
950
+ * </epdf-annotation-layer>
951
+ *
952
+ * A look (`epdfAnnotation`) is a renderer: which annotations it draws (a function of the
953
+ * annotation, or a subtype such as `'stamp'`), whether it takes the pointer
954
+ * (`epdfAnnotationInteractive`) and whether the layer scales it with the page
955
+ * (`epdfAnnotationScale`). The first look that matches an annotation draws it.
956
+ *
957
+ * Which drawing each annotation gets, and the behaviors an interactive look registers, are
958
+ * `@embedpdf/web`'s, keyed by the identity of each renderer entry. The page template, and with it
959
+ * every template inside it, is made once per page on screen, so the entries here are interned:
960
+ * the same rule (the same function or subtype, the same options) is the same entry on every
961
+ * page, registered once.
962
+ */
963
+ const subtypeMatchers = new Map();
964
+ const ALWAYS = {};
965
+ const NEVER = {};
966
+ const SCALED = {};
967
+ const UNSCALED = {};
968
+ const looks = new WeakMap();
969
+ const behaviors = new Map();
970
+ /** A table under `key` in `table`, made on first use. */
971
+ function tableIn(table, key, make) {
972
+ let found = table.get(key);
973
+ if (!found)
974
+ table.set(key, (found = make()));
975
+ return found;
976
+ }
977
+ /** The one entry for a look's rule: the same match, interactivity and scaling give the same object. */
978
+ function lookRendererOf(match, interactive, scale) {
979
+ let matcher;
980
+ if (typeof match === 'function') {
981
+ matcher = match;
982
+ }
983
+ else {
984
+ matcher = subtypeMatchers.get(match) ?? ((annotation) => annotation.subtype === match);
985
+ subtypeMatchers.set(match, matcher);
986
+ }
987
+ const interactiveKey = typeof interactive === 'function' ? interactive : interactive ? ALWAYS : NEVER;
988
+ const byInteractive = tableIn(looks, matcher, () => new WeakMap());
989
+ const byScale = tableIn(byInteractive, interactiveKey, () => new WeakMap());
990
+ return tableIn(byScale, scale ? SCALED : UNSCALED, () => Object.freeze({ for: matcher, interactive, scale }));
991
+ }
992
+ /** `epdfAnnotationInteractive` with no value (`<ng-template … epdfAnnotationInteractive>`) is `true`. */
993
+ function interactiveAttribute(value) {
994
+ if (typeof value === 'function')
995
+ return value;
996
+ return value === '' || value === true || value === 'true';
997
+ }
998
+ // ── the marker directives ────────────────────────────────────────────────────
999
+ /**
1000
+ * Your own look for some annotations: `<ng-template [epdfAnnotation]="isNote" let-annotation>`.
1001
+ * Give it a function that picks annotations by any field, or a subtype
1002
+ * (`epdfAnnotation="stamp"`). Without `epdfAnnotationInteractive` it only draws: the layer keeps
1003
+ * handling the pointer (select, move, resize). With it (or a function the layer asks whenever it
1004
+ * matters) your template takes the pointer, and the annotation can't be selected or moved while
1005
+ * it does. `[epdfAnnotationScale]="false"` draws at the size on screen instead of the
1006
+ * annotation's 100% size.
1007
+ */
1008
+ class EpdfAnnotationTemplate {
1009
+ /**
1010
+ * Which annotations it draws: a function of the annotation, or a subtype. (Not a required
1011
+ * input: a template inside `@if` is in the layer's query a moment before its inputs are set.)
1012
+ */
1013
+ match = input(undefined, ...(ngDevMode ? [{ debugName: "match", alias: 'epdfAnnotation' }] : [{ alias: 'epdfAnnotation' }]));
1014
+ /** Whether it takes the pointer: present (or `true`), or a function of `{ annotation, toolId }`. */
1015
+ interactive = input(false, ...(ngDevMode ? [{ debugName: "interactive", alias: 'epdfAnnotationInteractive', transform: interactiveAttribute }] : [{ alias: 'epdfAnnotationInteractive', transform: interactiveAttribute }]));
1016
+ /** Scale what you draw with the page (the default), or draw at the size on screen (`false`). */
1017
+ scale = input(true, ...(ngDevMode ? [{ debugName: "scale", alias: 'epdfAnnotationScale', transform: booleanAttribute }] : [{ alias: 'epdfAnnotationScale', transform: booleanAttribute }]));
1018
+ template = inject(TemplateRef);
1019
+ /** Its renderer entry, the same object on every page for the same rule; null until it has one. */
1020
+ renderer = computed(() => {
1021
+ const match = this.match();
1022
+ return match ? lookRendererOf(match, this.interactive(), this.scale()) : null;
1023
+ }, ...(ngDevMode ? [{ debugName: "renderer" }] : []));
1024
+ static ngTemplateContextGuard(_directive, context) {
1025
+ return true;
1026
+ }
1027
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.2.4", ngImport: i0, type: EpdfAnnotationTemplate, deps: [], target: i0.ɵɵFactoryTarget.Directive });
1028
+ static ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "17.1.0", version: "20.2.4", type: EpdfAnnotationTemplate, isStandalone: true, selector: "ng-template[epdfAnnotation]", inputs: { match: { classPropertyName: "match", publicName: "epdfAnnotation", isSignal: true, isRequired: false, transformFunction: null }, interactive: { classPropertyName: "interactive", publicName: "epdfAnnotationInteractive", isSignal: true, isRequired: false, transformFunction: null }, scale: { classPropertyName: "scale", publicName: "epdfAnnotationScale", isSignal: true, isRequired: false, transformFunction: null } }, ngImport: i0 });
1029
+ }
1030
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.2.4", ngImport: i0, type: EpdfAnnotationTemplate, decorators: [{
1031
+ type: Directive,
1032
+ args: [{ selector: 'ng-template[epdfAnnotation]' }]
1033
+ }] });
1034
+ /**
1035
+ * What a sibling plugin's behavior owns, drawn by your template while the behavior is engaged:
1036
+ * `<ng-template epdfAnnotationBehavior="form-fill" let-annotation let-item="item">`. The
1037
+ * template places its own controls and takes the input.
1038
+ */
1039
+ class EpdfAnnotationBehaviorTemplate {
1040
+ /** The behavior's id, as its plugin registers it. */
1041
+ behavior = input(undefined, ...(ngDevMode ? [{ debugName: "behavior", alias: 'epdfAnnotationBehavior' }] : [{ alias: 'epdfAnnotationBehavior' }]));
1042
+ template = inject(TemplateRef);
1043
+ /** Its renderer entry, the same object for the same behavior; null until it has one. */
1044
+ renderer = computed(() => {
1045
+ const behavior = this.behavior();
1046
+ if (!behavior)
1047
+ return null;
1048
+ let entry = behaviors.get(behavior);
1049
+ if (!entry)
1050
+ behaviors.set(behavior, (entry = Object.freeze({ behavior })));
1051
+ return entry;
1052
+ }, ...(ngDevMode ? [{ debugName: "renderer" }] : []));
1053
+ static ngTemplateContextGuard(_directive, context) {
1054
+ return true;
1055
+ }
1056
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.2.4", ngImport: i0, type: EpdfAnnotationBehaviorTemplate, deps: [], target: i0.ɵɵFactoryTarget.Directive });
1057
+ static ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "17.1.0", version: "20.2.4", type: EpdfAnnotationBehaviorTemplate, isStandalone: true, selector: "ng-template[epdfAnnotationBehavior]", inputs: { behavior: { classPropertyName: "behavior", publicName: "epdfAnnotationBehavior", isSignal: true, isRequired: false, transformFunction: null } }, ngImport: i0 });
1058
+ }
1059
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.2.4", ngImport: i0, type: EpdfAnnotationBehaviorTemplate, decorators: [{
1060
+ type: Directive,
1061
+ args: [{ selector: 'ng-template[epdfAnnotationBehavior]' }]
1062
+ }] });
1063
+ /**
1064
+ * Each handle, drawn your way in place of the layer's: `<ng-template epdfHandle let-handle>`.
1065
+ * The viewer still decides where a handle can be grabbed, so what you draw can't break dragging.
1066
+ */
1067
+ class EpdfHandleTemplate {
1068
+ template = inject(TemplateRef);
1069
+ static ngTemplateContextGuard(_directive, context) {
1070
+ return true;
1071
+ }
1072
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.2.4", ngImport: i0, type: EpdfHandleTemplate, deps: [], target: i0.ɵɵFactoryTarget.Directive });
1073
+ static ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "14.0.0", version: "20.2.4", type: EpdfHandleTemplate, isStandalone: true, selector: "ng-template[epdfHandle]", ngImport: i0 });
1074
+ }
1075
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.2.4", ngImport: i0, type: EpdfHandleTemplate, decorators: [{
1076
+ type: Directive,
1077
+ args: [{ selector: 'ng-template[epdfHandle]' }]
1078
+ }] });
1079
+ /** The rotation handle, drawn your way: `<ng-template epdfRotationHandle let-handle>`. */
1080
+ class EpdfRotationHandleTemplate {
1081
+ template = inject(TemplateRef);
1082
+ static ngTemplateContextGuard(_directive, context) {
1083
+ return true;
1084
+ }
1085
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.2.4", ngImport: i0, type: EpdfRotationHandleTemplate, deps: [], target: i0.ɵɵFactoryTarget.Directive });
1086
+ static ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "14.0.0", version: "20.2.4", type: EpdfRotationHandleTemplate, isStandalone: true, selector: "ng-template[epdfRotationHandle]", ngImport: i0 });
1087
+ }
1088
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.2.4", ngImport: i0, type: EpdfRotationHandleTemplate, decorators: [{
1089
+ type: Directive,
1090
+ args: [{ selector: 'ng-template[epdfRotationHandle]' }]
1091
+ }] });
1092
+ /**
1093
+ * The rotation badge's content, drawn your way: inside `<epdf-annotation-rotation-badge>`,
1094
+ * `<ng-template epdfRotationBadge let-rotation>{{ rotation.angle }}°</ng-template>`.
1095
+ */
1096
+ class EpdfRotationBadgeTemplate {
1097
+ template = inject(TemplateRef);
1098
+ static ngTemplateContextGuard(_directive, context) {
1099
+ return true;
1100
+ }
1101
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.2.4", ngImport: i0, type: EpdfRotationBadgeTemplate, deps: [], target: i0.ɵɵFactoryTarget.Directive });
1102
+ static ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "14.0.0", version: "20.2.4", type: EpdfRotationBadgeTemplate, isStandalone: true, selector: "ng-template[epdfRotationBadge]", ngImport: i0 });
1103
+ }
1104
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.2.4", ngImport: i0, type: EpdfRotationBadgeTemplate, decorators: [{
1105
+ type: Directive,
1106
+ args: [{ selector: 'ng-template[epdfRotationBadge]' }]
1107
+ }] });
1108
+
1109
+ /**
1110
+ * Free text boxes: the element someone types in. `@embedpdf/web`'s text box editor makes an
1111
+ * element the editor of a text box (rich text, focus and `contentEditable` following the
1112
+ * plugin's `editing`, presses kept inside), and its follower attaches again for another document
1113
+ * and takes the box and the scale; this file binds the follower to signals.
1114
+ *
1115
+ * [epdfFreeTextBox] the layer's own text box, one per free text on the page (internal)
1116
+ * [epdfRichTextEditor] your own element in a look you draw: it becomes the box's editor
1117
+ *
1118
+ * The editor owns the element's children: Angular puts none in it, and the element is never
1119
+ * made again while someone types (the caret would jump).
1120
+ */
1121
+ /**
1122
+ * How much the layer scales the look around an element: 1 outside a look, or in one drawn at its
1123
+ * size on screen. The layer provides it to each scaled look, so a rich text editor inside draws
1124
+ * its text at the annotation's 100% size and lets the look's scale do the rest.
1125
+ */
1126
+ const EPDF_LOOK_SCALE = new InjectionToken('EPDF_LOOK_SCALE');
1127
+ /**
1128
+ * Make `element` the editor of the text box `item()` reads, at `scale()` pixels per point: the
1129
+ * binding attaches when the plugin is there (again for another document), and redraws only
1130
+ * when the text differs. Call it in an injection context; it lets go when the caller goes.
1131
+ *
1132
+ * It follows after the render, not before: when typing starts, the look's frame around the
1133
+ * element stops being inert in that same render, and an element inside an inert one can't take
1134
+ * focus (the editor's `focus()` would do nothing, and the keys would go to the page).
1135
+ */
1136
+ function bindTextBoxEditor(element, annotation, item, scale) {
1137
+ const follower = createTextBoxEditorFollower();
1138
+ afterRenderEffect({
1139
+ write: () => {
1140
+ const host = annotation();
1141
+ const next = item();
1142
+ const at = scale();
1143
+ untracked(() => follower.follow(element, host, next, at));
1144
+ },
1145
+ });
1146
+ inject(DestroyRef).onDestroy(() => follower.detach());
1147
+ }
1148
+ /**
1149
+ * The layer's own text box for one free text: the engine's text plate (the box inset by its
1150
+ * padding), styled like the box's body and laid out at a legible size (`freeTextBoxOf`). The
1151
+ * box's fill and border are the vector scene's, under it. It takes the pointer only while
1152
+ * someone types in it; otherwise clicks fall through to the shapes (select, move, resize).
1153
+ */
1154
+ class EpdfFreeTextBox {
1155
+ /** The text box, as the plugin lists it for the page. */
1156
+ item = input.required(...(ngDevMode ? [{ debugName: "item", alias: 'epdfFreeTextBox' }] : [{ alias: 'epdfFreeTextBox' }]));
1157
+ /** The outline color while someone types: the chrome's `textOutline`, painted. */
1158
+ outline = input.required(...(ngDevMode ? [{ debugName: "outline" }] : []));
1159
+ page = injectPage('[epdfFreeTextBox]');
1160
+ pixels = pagePixelsOf(this.page);
1161
+ box = computed(() => freeTextBoxOf(this.item(), this.pixels(), this.outline()), ...(ngDevMode ? [{ debugName: "box" }] : []));
1162
+ constructor() {
1163
+ const annotation = annotationHostOf(this.page, '[epdfFreeTextBox]').capability;
1164
+ bindTextBoxEditor(inject(ElementRef).nativeElement, annotation, this.item, computed(() => this.box().scale));
1165
+ }
1166
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.2.4", ngImport: i0, type: EpdfFreeTextBox, deps: [], target: i0.ɵɵFactoryTarget.Directive });
1167
+ static ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "17.1.0", version: "20.2.4", type: EpdfFreeTextBox, isStandalone: true, selector: "[epdfFreeTextBox]", inputs: { item: { classPropertyName: "item", publicName: "epdfFreeTextBox", isSignal: true, isRequired: true, transformFunction: null }, outline: { classPropertyName: "outline", publicName: "outline", isSignal: true, isRequired: true, transformFunction: null } }, host: { properties: { "style": "box().style" } }, ngImport: i0 });
1168
+ }
1169
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.2.4", ngImport: i0, type: EpdfFreeTextBox, decorators: [{
1170
+ type: Directive,
1171
+ args: [{
1172
+ selector: '[epdfFreeTextBox]',
1173
+ // Property by property: the editor's own line height on the element stays.
1174
+ host: { '[style]': 'box().style' },
1175
+ }]
1176
+ }], ctorParameters: () => [] });
1177
+ /**
1178
+ * Your own element as the editor of a text box you draw yourself, in a look
1179
+ * (`<ng-template [epdfAnnotation]="isTextBox" let-annotation>`), inside the element that draws
1180
+ * the box's frame:
1181
+ *
1182
+ * <div class="text-box" [class.editing]="editor.editing()">
1183
+ * <div [epdfRichTextEditor]="annotation" #editor="epdfRichTextEditor"></div>
1184
+ * </div>
1185
+ *
1186
+ * It draws the runs, turns typing back into runs, keeps the caret when the style changes, and
1187
+ * handles pasting, input methods and the format shortcuts; `text.toggleFormat()` and
1188
+ * `selection.update()` work on it as on the layer's own box. It gives the element the box's
1189
+ * body style (font, size, color and alignment), filling its parent at a legible size
1190
+ * (`ownTextBoxOf`), so draw the box's fill, border and padding on the parent; while someone
1191
+ * types it takes the pointer. Leave the element empty: the editor fills it.
1192
+ */
1193
+ class EpdfRichTextEditor {
1194
+ /** The free text annotation this element edits: the template's `let-annotation`. */
1195
+ annotation = input.required(...(ngDevMode ? [{ debugName: "annotation", alias: 'epdfRichTextEditor' }] : [{ alias: 'epdfRichTextEditor' }]));
1196
+ page = injectPage('[epdfRichTextEditor]');
1197
+ host = annotationHostOf(this.page, '[epdfRichTextEditor]');
1198
+ lookScale = inject(EPDF_LOOK_SCALE, { optional: true });
1199
+ view = pageViewOf(this.page);
1200
+ pixels = pagePixelsOf(this.page);
1201
+ /** The annotation's text box, saved and idle too (this element draws its text), or null. */
1202
+ item = this.host.select((annotation) => annotation.getTextItem(this.annotation().ref, this.view()), null);
1203
+ /** Pixels per point where the element is: in a scaled look, at the annotation's 100% size. */
1204
+ scale = computed(() => textBoxEditorScaleOf(this.item(), this.pixels(), this.lookScale?.() ?? 1), ...(ngDevMode ? [{ debugName: "scale" }] : []));
1205
+ /** The element's style and the scale its text is laid out at, or null while there is no box. */
1206
+ own = computed(() => {
1207
+ const item = this.item();
1208
+ return item ? ownTextBoxOf(item, this.scale()) : null;
1209
+ }, ...(ngDevMode ? [{ debugName: "own" }] : []));
1210
+ /** True while someone types in the box. */
1211
+ editing = computed(() => this.item()?.editing ?? false, ...(ngDevMode ? [{ debugName: "editing" }] : []));
1212
+ constructor() {
1213
+ bindTextBoxEditor(inject(ElementRef).nativeElement, this.host.capability, this.item, computed(() => this.own()?.scale ?? this.scale()));
1214
+ }
1215
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.2.4", ngImport: i0, type: EpdfRichTextEditor, deps: [], target: i0.ɵɵFactoryTarget.Directive });
1216
+ static ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "17.1.0", version: "20.2.4", type: EpdfRichTextEditor, isStandalone: true, selector: "[epdfRichTextEditor]", inputs: { annotation: { classPropertyName: "annotation", publicName: "epdfRichTextEditor", isSignal: true, isRequired: true, transformFunction: null } }, host: { properties: { "style": "own()?.style ?? null" } }, exportAs: ["epdfRichTextEditor"], ngImport: i0 });
1217
+ }
1218
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.2.4", ngImport: i0, type: EpdfRichTextEditor, decorators: [{
1219
+ type: Directive,
1220
+ args: [{
1221
+ selector: '[epdfRichTextEditor]',
1222
+ exportAs: 'epdfRichTextEditor',
1223
+ // Property by property: the editor's own line height on the element stays.
1224
+ host: { '[style]': 'own()?.style ?? null' },
1225
+ }]
1226
+ }], ctorParameters: () => [] });
1227
+
1228
+ /**
1229
+ * `<epdf-annotation-layer>`: a page's annotations, the selection's outline and handles, the
1230
+ * tool's preview, and the text boxes being typed in. Put it in each page, above the rendered
1231
+ * page; while it's there, the render layer leaves the annotations out of the page's picture:
1232
+ *
1233
+ * <ng-template epdfPage>
1234
+ * <epdf-render-layer />
1235
+ * <epdf-annotation-layer />
1236
+ * </ng-template>
1237
+ *
1238
+ * Pure paint: it reads the plugin's items and chrome for the page and draws them. The pointer
1239
+ * reaches the plugin through the interaction hub (the Stage forwards it), and so does the
1240
+ * cursor. Each annotation is one drawing: its vector scene, the engine's baked picture, or a
1241
+ * look of yours from an `<ng-template [epdfAnnotation]>` inside the layer, which may keep the
1242
+ * layer's own drawing (`native`) and add to it. `<ng-template epdfHandle>` and
1243
+ * `<ng-template epdfRotationHandle>` draw the handles your way.
1244
+ *
1245
+ * What isn't Angular is `@embedpdf/web`'s, the same for every framework: which drawing each
1246
+ * annotation gets and the behaviors interactive looks register, the frames and chrome in
1247
+ * pixels, the scene as SVG elements, the text box editor, and the object URLs of baked pictures
1248
+ * and the stamp ghost.
1249
+ */
1250
+ const NO_ITEMS = Object.freeze([]);
1251
+ const NO_TEXTS = Object.freeze([]);
1252
+ class EpdfAnnotationLayer {
1253
+ page = injectPage('<epdf-annotation-layer>');
1254
+ injector = inject(Injector);
1255
+ annotation = annotationHostOf(this.page, '<epdf-annotation-layer>');
1256
+ interaction = new CapabilityBinding(injectKernelHost('<epdf-annotation-layer>'), () => InteractionToken, () => this.page.documentId);
1257
+ render = new CapabilityBinding(injectKernelHost('<epdf-annotation-layer>'), () => RenderToken, () => this.page.documentId);
1258
+ chrome = injectChromeSettings();
1259
+ // ── your templates ───────────────────────────────────────────────────────────
1260
+ looks = contentChildren(EpdfAnnotationTemplate, ...(ngDevMode ? [{ debugName: "looks", descendants: true }] : [{ descendants: true }]));
1261
+ behaviors = contentChildren(EpdfAnnotationBehaviorTemplate, ...(ngDevMode ? [{ debugName: "behaviors", descendants: true }] : [{
1262
+ descendants: true,
1263
+ }]));
1264
+ handleTemplate = contentChild(EpdfHandleTemplate, ...(ngDevMode ? [{ debugName: "handleTemplate", descendants: true }] : [{ descendants: true }]));
1265
+ rotationHandleTemplate = contentChild(EpdfRotationHandleTemplate, ...(ngDevMode ? [{ debugName: "rotationHandleTemplate", descendants: true }] : [{
1266
+ descendants: true,
1267
+ }]));
1268
+ /**
1269
+ * The renderer entries, in the order the templates are written: the first look that matches
1270
+ * wins. The same array while every entry stays the same.
1271
+ */
1272
+ renderers = computed(() => [
1273
+ ...this.behaviors().map((template) => template.renderer()),
1274
+ ...this.looks().map((template) => template.renderer()),
1275
+ ].filter((entry) => entry !== null), ...(ngDevMode ? [{ debugName: "renderers", equal: (left, right) => left.length === right.length && left.every((entry, index) => entry === right[index]) }] : [{
1276
+ equal: (left, right) => left.length === right.length && left.every((entry, index) => entry === right[index]),
1277
+ }]));
1278
+ /** Goes up when the behaviors register again, so each drawing is decided again. */
1279
+ registrations = signal(0, ...(ngDevMode ? [{ debugName: "registrations" }] : []));
1280
+ // ── what the page shows ──────────────────────────────────────────────────────
1281
+ view = pageViewOf(this.page);
1282
+ pixels = pagePixelsOf(this.page);
1283
+ items = this.annotation.select((annotation) => annotation.listPageItems(this.page.ref, this.view()), NO_ITEMS);
1284
+ allTexts = this.annotation.select((annotation) => annotation.listTextItems(this.page.ref, this.view()), NO_TEXTS);
1285
+ /** The active tool: an interactive look's function is asked about it, so a tool change redraws. */
1286
+ activeToolId = this.interaction.select((interaction) => interaction.getActiveToolId() ?? '', '');
1287
+ /** The pictures' URLs, by annotation and state; `null` until the page's first ones arrive. */
1288
+ urls = signal(null, ...(ngDevMode ? [{ debugName: "urls" }] : []));
1289
+ /** The pictures shown stay valid until the next ones are shown. */
1290
+ shown = createShownUrls();
1291
+ // A move or a turn leaves the epoch as it is (the same pixels, placed elsewhere), and live
1292
+ // gestures don't touch it: nothing loads mid-drag.
1293
+ appearanceEpoch = this.annotation.select((annotation) => annotation.getAppearanceEpoch(this.page.ref), '');
1294
+ // Zoom steps inside one rung of the document's appearance lattice bake nothing again.
1295
+ // Without the render plugin, only this session's pictures load.
1296
+ appearanceScale = this.render.select((render) => render.getAppearanceScale(this.page.transform().renderScale), 0);
1297
+ bakeScale = computed(() => this.render.capability() ? this.appearanceScale() : this.page.transform().renderScale, ...(ngDevMode ? [{ debugName: "bakeScale" }] : []));
1298
+ /** The text box being typed in, by key: while it is, a look's editor takes the keys. */
1299
+ editingKey = computed(() => editingTextKeyOf(this.allTexts(), annotationKey), ...(ngDevMode ? [{ debugName: "editingKey" }] : []));
1300
+ /** One injector per look, by item: kept while the item is drawn, so the look is never made again. */
1301
+ lookInjectors = new Map();
1302
+ drawings = computed(() => {
1303
+ this.activeToolId();
1304
+ this.registrations();
1305
+ const annotation = this.annotation.capability();
1306
+ const renderers = this.renderers();
1307
+ const looks = this.looks();
1308
+ const behaviors = this.behaviors();
1309
+ const pixels = this.pixels();
1310
+ const urls = this.urls();
1311
+ const editingKey = this.editingKey();
1312
+ const drawings = this.items().map((item) => {
1313
+ const frame = frameInPixels(item.frame, pixels);
1314
+ const state = item.annotation?.appearanceState ?? null;
1315
+ const drawn = {
1316
+ item,
1317
+ frame,
1318
+ shows: pictureOrPainting({
1319
+ pending: item.source !== 'baked',
1320
+ picture: urls && shownAppearance(urls, item.id, state),
1321
+ version: item.pictureVersion ?? 0,
1322
+ loaded: urls !== null,
1323
+ paint: () => (hasLiveDrawing(item) ? item : null),
1324
+ }),
1325
+ appearance: urls && bakedAppearanceOf(urls, item.id, state),
1326
+ look: null,
1327
+ owner: null,
1328
+ interactive: false,
1329
+ inert: false,
1330
+ };
1331
+ const record = item.annotation;
1332
+ if (!record || !annotation)
1333
+ return drawn;
1334
+ // Ownership beats looks: an engaged behavior's template is authoritative (a form
1335
+ // plugin's controls own their DOM); looks apply only to what the layer owns, and draw
1336
+ // without the pointer. While its text box is typed in, a look's editor takes the keys.
1337
+ const typing = editingKey !== null && annotationKey(record.ref) === editingKey;
1338
+ const drawing = annotationDrawingOf(record, annotation, renderers, typing);
1339
+ switch (drawing.kind) {
1340
+ case 'native':
1341
+ return { ...drawn, inert: drawing.inert };
1342
+ case 'owned': {
1343
+ const template = behaviors.find((behavior) => behavior.renderer() === drawing.entry);
1344
+ return template ? { ...drawn, owner: { template, annotation: record } } : drawn;
1345
+ }
1346
+ case 'look': {
1347
+ const template = looks.find((look) => look.renderer() === drawing.entry);
1348
+ if (!template)
1349
+ return drawn;
1350
+ const scaled = drawing.entry.scale !== false;
1351
+ return {
1352
+ ...drawn,
1353
+ look: {
1354
+ template,
1355
+ annotation: record,
1356
+ frame: lookFrameOf(frame, scaled),
1357
+ scaled,
1358
+ injector: this.lookInjectorOf(item.id),
1359
+ },
1360
+ interactive: drawing.interactive,
1361
+ inert: drawing.inert,
1362
+ };
1363
+ }
1364
+ }
1365
+ });
1366
+ // Forget the injectors of looks no longer drawn: a look drawn again starts afresh.
1367
+ const drawnLooks = new Set(drawings.flatMap((drawn) => (drawn.look ? [drawn.item.id] : [])));
1368
+ for (const id of this.lookInjectors.keys()) {
1369
+ if (!drawnLooks.has(id))
1370
+ this.lookInjectors.delete(id);
1371
+ }
1372
+ return drawings;
1373
+ }, ...(ngDevMode ? [{ debugName: "drawings" }] : []));
1374
+ /** How much the layer scales each look, by item: 1 for a look drawn at its size on screen. */
1375
+ lookScales = computed(() => {
1376
+ const scales = new Map();
1377
+ for (const drawn of this.drawings()) {
1378
+ if (drawn.look)
1379
+ scales.set(drawn.item.id, drawn.look.scaled ? drawn.frame.scale : 1);
1380
+ }
1381
+ return scales;
1382
+ }, ...(ngDevMode ? [{ debugName: "lookScales" }] : []));
1383
+ /** The text boxes the layer draws: not the ones a look of yours draws, which edits them there. */
1384
+ texts = computed(() => layerTextBoxesOf(this.allTexts(), this.items(), this.renderers(), annotationKey), ...(ngDevMode ? [{ debugName: "texts" }] : []));
1385
+ textOutline = computed(() => annotationChromePaint(this.chrome.chrome(), this.chrome.accent()).textOutline, ...(ngDevMode ? [{ debugName: "textOutline" }] : []));
1386
+ // ── the armed stamp's ghost ──────────────────────────────────────────────────
1387
+ imageGhost = this.annotation.select((annotation) => annotation.getImageGhost(this.page.ref), null);
1388
+ armedStamp = this.annotation.select((annotation) => annotation.getArmedStamp(), null, Object.is);
1389
+ /**
1390
+ * The ghost is a picture of vector artwork, sharp at one size: the size class that covers the
1391
+ * box's device width. The plugin keeps one picture per class, so a zoom never renders per frame.
1392
+ */
1393
+ ghostBucket = computed(() => {
1394
+ const ghost = this.imageGhost();
1395
+ return ghost ? previewBucket(ghost.box.width * this.page.transform().renderScale) : 0;
1396
+ }, ...(ngDevMode ? [{ debugName: "ghostBucket" }] : []));
1397
+ ghostUrl = signal(null, ...(ngDevMode ? [{ debugName: "ghostUrl" }] : []));
1398
+ ghost = computed(() => {
1399
+ const ghost = this.imageGhost();
1400
+ const url = this.ghostUrl();
1401
+ if (!ghost || !url)
1402
+ return null;
1403
+ return {
1404
+ url,
1405
+ box: rectInPixels(ghost.box, this.pixels()),
1406
+ opacity: ghostOpacity(ghost.opacity),
1407
+ rotation: ghost.rot,
1408
+ };
1409
+ }, ...(ngDevMode ? [{ debugName: "ghost" }] : []));
1410
+ constructor() {
1411
+ // While it's here, the page's picture leaves the annotations to it.
1412
+ paintsPagePart(() => this.page.ref, 'annotations');
1413
+ this.registerBehaviors();
1414
+ if (!isPlatformBrowser(inject(PLATFORM_ID)))
1415
+ return;
1416
+ inject(DestroyRef).onDestroy(() => this.shown.release());
1417
+ this.loadAppearances();
1418
+ this.loadGhost();
1419
+ }
1420
+ /** The injector a look's template gets for one item: the look's scale, followed live. */
1421
+ lookInjectorOf(id) {
1422
+ let injector = this.lookInjectors.get(id);
1423
+ if (!injector) {
1424
+ const scale = computed(() => this.lookScales().get(id) ?? 1, ...(ngDevMode ? [{ debugName: "scale" }] : []));
1425
+ injector = Injector.create({
1426
+ providers: [{ provide: EPDF_LOOK_SCALE, useValue: scale }],
1427
+ parent: this.injector,
1428
+ });
1429
+ this.lookInjectors.set(id, injector);
1430
+ }
1431
+ return injector;
1432
+ }
1433
+ /**
1434
+ * An interactive look takes the pointer through a behavior the plugin knows, registered once
1435
+ * per document and look however many pages show the layer (`@embedpdf/web` counts them). The
1436
+ * looks are the same entries on every page, so keep the functions you give `epdfAnnotation`
1437
+ * and `epdfAnnotationInteractive` in fields: a function made anew registers again.
1438
+ */
1439
+ registerBehaviors() {
1440
+ effect((onCleanup) => {
1441
+ const annotation = this.annotation.capability();
1442
+ const renderers = this.renderers();
1443
+ if (annotation && renderers.length > 0) {
1444
+ onCleanup(untracked(() => registerRendererBehaviors(annotation, renderers, () => untracked(this.activeToolId))));
1445
+ }
1446
+ this.registrations.update((count) => count + 1);
1447
+ });
1448
+ }
1449
+ /**
1450
+ * The baked pictures, loaded again when the page's appearance epoch or the bake scale changes;
1451
+ * the ones shown stay valid until the next are shown.
1452
+ */
1453
+ loadAppearances() {
1454
+ effect((onCleanup) => {
1455
+ const annotation = this.annotation.capability();
1456
+ const scale = this.bakeScale();
1457
+ this.appearanceEpoch();
1458
+ if (!annotation || !scale)
1459
+ return;
1460
+ const page = this.page.ref;
1461
+ onCleanup(untracked(() => loadAppearanceUrls(this.shown, (abort) => annotation.renderAppearances(page, scale, abort), annotationKey, (urls) => this.urls.set(urls))));
1462
+ });
1463
+ }
1464
+ /**
1465
+ * The armed stamp's ghost picture, for its size class. The previous class's picture stays up
1466
+ * until this one is there, so a zoom across a class boundary never flickers; a disarm drops it.
1467
+ */
1468
+ loadGhost() {
1469
+ effect((onCleanup) => {
1470
+ const annotation = this.annotation.capability();
1471
+ const bucket = this.ghostBucket();
1472
+ this.armedStamp();
1473
+ if (!annotation || !bucket) {
1474
+ this.ghostUrl.set(null);
1475
+ return;
1476
+ }
1477
+ onCleanup(untracked(() => loadObjectUrl(() => annotation.renderArmedStampPreview(bucket), (url) => this.ghostUrl.set(url))));
1478
+ });
1479
+ }
1480
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.2.4", ngImport: i0, type: EpdfAnnotationLayer, deps: [], target: i0.ɵɵFactoryTarget.Component });
1481
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "20.2.4", type: EpdfAnnotationLayer, isStandalone: true, selector: "epdf-annotation-layer", host: { styleAttribute: "position: absolute; inset: 0; display: block; pointer-events: none" }, queries: [{ propertyName: "looks", predicate: EpdfAnnotationTemplate, descendants: true, isSignal: true }, { propertyName: "behaviors", predicate: EpdfAnnotationBehaviorTemplate, descendants: true, isSignal: true }, { propertyName: "handleTemplate", first: true, predicate: EpdfHandleTemplate, descendants: true, isSignal: true }, { propertyName: "rotationHandleTemplate", first: true, predicate: EpdfRotationHandleTemplate, descendants: true, isSignal: true }], ngImport: i0, template: `
1482
+ @for (drawn of drawings(); track drawn.item.id) {
1483
+ <!-- The layer's own drawing of it, which a look may keep: its \`native\`. -->
1484
+ <ng-template #native>
1485
+ <epdf-annotation-native [item]="drawn.item" [shows]="drawn.shows" />
1486
+ </ng-template>
1487
+ @if (drawn.owner; as owner) {
1488
+ <!-- A sibling plugin's controls place themselves in the layer and take the input;
1489
+ the annotation's own drawing keeps its frame. -->
1490
+ <ng-template #framedNative>
1491
+ <div [epdfAnnotationFrame]="drawn.frame" [frameBlend]="drawn.item.blend">
1492
+ <ng-container [ngTemplateOutlet]="native" />
1493
+ </div>
1494
+ </ng-template>
1495
+ <ng-container
1496
+ [ngTemplateOutlet]="owner.template.template"
1497
+ [ngTemplateOutletContext]="{
1498
+ $implicit: owner.annotation,
1499
+ annotation: owner.annotation,
1500
+ item: drawn.item,
1501
+ page: page,
1502
+ native: framedNative,
1503
+ hovered: drawn.item.hovered ?? false,
1504
+ selected: drawn.item.selected,
1505
+ interactive: true
1506
+ }"
1507
+ />
1508
+ } @else {
1509
+ @if (drawn.look; as look) {
1510
+ <div
1511
+ [epdfAnnotationFrame]="drawn.frame"
1512
+ [frameBlend]="drawn.item.blend"
1513
+ [frameInteractive]="drawn.interactive"
1514
+ [frameInert]="drawn.inert"
1515
+ >
1516
+ <ng-template #lookTemplate>
1517
+ <ng-container
1518
+ [ngTemplateOutlet]="look.template.template"
1519
+ [ngTemplateOutletInjector]="look.injector"
1520
+ [ngTemplateOutletContext]="{
1521
+ $implicit: look.annotation,
1522
+ annotation: look.annotation,
1523
+ frame: look.frame,
1524
+ native: native,
1525
+ appearance: drawn.appearance,
1526
+ hovered: drawn.item.hovered ?? false,
1527
+ selected: drawn.item.selected,
1528
+ interactive: drawn.interactive
1529
+ }"
1530
+ />
1531
+ </ng-template>
1532
+ @if (look.scaled) {
1533
+ <!-- Drawn at the annotation's 100% size, scaled with the page as a whole. -->
1534
+ <div
1535
+ style="position: absolute; left: 0; top: 0; transform-origin: 0 0"
1536
+ [style.width.px]="drawn.frame.design.width"
1537
+ [style.height.px]="drawn.frame.design.height"
1538
+ [style.transform]="'scale(' + drawn.frame.scale + ')'"
1539
+ >
1540
+ <ng-container [ngTemplateOutlet]="lookTemplate" />
1541
+ </div>
1542
+ } @else {
1543
+ <ng-container [ngTemplateOutlet]="lookTemplate" />
1544
+ }
1545
+ </div>
1546
+ } @else {
1547
+ <!-- Inert when an engaged behavior has no template of yours: its plugin owns the
1548
+ input (a link's anchor takes the click), and the annotation keeps its own look. -->
1549
+ <div
1550
+ [epdfAnnotationFrame]="drawn.frame"
1551
+ [frameBlend]="drawn.item.blend"
1552
+ [frameInert]="drawn.inert"
1553
+ >
1554
+ <ng-container [ngTemplateOutlet]="native" />
1555
+ </div>
1556
+ }
1557
+ }
1558
+ }
1559
+ @for (text of texts(); track text.id) {
1560
+ <div [epdfFreeTextBox]="text" [outline]="textOutline()"></div>
1561
+ }
1562
+ @if (ghost(); as ghost) {
1563
+ <!-- The armed stamp, see-through, in the box a click would place it in. -->
1564
+ <img
1565
+ alt=""
1566
+ draggable="false"
1567
+ style="position: absolute; max-width: none; max-height: none; pointer-events: none; transform-origin: center"
1568
+ [attr.src]="ghost.url"
1569
+ [style.left.px]="ghost.box.left"
1570
+ [style.top.px]="ghost.box.top"
1571
+ [style.width.px]="ghost.box.width"
1572
+ [style.height.px]="ghost.box.height"
1573
+ [style.opacity]="ghost.opacity"
1574
+ [style.transform]="ghost.rotation ? 'rotate(' + ghost.rotation + 'deg)' : null"
1575
+ />
1576
+ }
1577
+ <epdf-annotation-chrome
1578
+ [handle]="handleTemplate()?.template ?? null"
1579
+ [rotationHandle]="rotationHandleTemplate()?.template ?? null"
1580
+ />
1581
+ `, isInline: true, dependencies: [{ kind: "directive", type: NgTemplateOutlet, selector: "[ngTemplateOutlet]", inputs: ["ngTemplateOutletContext", "ngTemplateOutlet", "ngTemplateOutletInjector"] }, { kind: "directive", type: EpdfAnnotationFrame, selector: "[epdfAnnotationFrame]", inputs: ["epdfAnnotationFrame", "frameBlend", "frameInteractive", "frameInert"] }, { kind: "component", type: EpdfAnnotationNative, selector: "epdf-annotation-native", inputs: ["item", "shows"] }, { kind: "component", type: EpdfAnnotationChrome, selector: "epdf-annotation-chrome", inputs: ["handle", "rotationHandle"] }, { kind: "directive", type: EpdfFreeTextBox, selector: "[epdfFreeTextBox]", inputs: ["epdfFreeTextBox", "outline"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
1582
+ }
1583
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.2.4", ngImport: i0, type: EpdfAnnotationLayer, decorators: [{
1584
+ type: Component,
1585
+ args: [{
1586
+ selector: 'epdf-annotation-layer',
1587
+ imports: [
1588
+ NgTemplateOutlet,
1589
+ EpdfAnnotationFrame,
1590
+ EpdfAnnotationNative,
1591
+ EpdfAnnotationChrome,
1592
+ EpdfFreeTextBox,
1593
+ ],
1594
+ changeDetection: ChangeDetectionStrategy.OnPush,
1595
+ host: { style: 'position: absolute; inset: 0; display: block; pointer-events: none' },
1596
+ template: `
1597
+ @for (drawn of drawings(); track drawn.item.id) {
1598
+ <!-- The layer's own drawing of it, which a look may keep: its \`native\`. -->
1599
+ <ng-template #native>
1600
+ <epdf-annotation-native [item]="drawn.item" [shows]="drawn.shows" />
1601
+ </ng-template>
1602
+ @if (drawn.owner; as owner) {
1603
+ <!-- A sibling plugin's controls place themselves in the layer and take the input;
1604
+ the annotation's own drawing keeps its frame. -->
1605
+ <ng-template #framedNative>
1606
+ <div [epdfAnnotationFrame]="drawn.frame" [frameBlend]="drawn.item.blend">
1607
+ <ng-container [ngTemplateOutlet]="native" />
1608
+ </div>
1609
+ </ng-template>
1610
+ <ng-container
1611
+ [ngTemplateOutlet]="owner.template.template"
1612
+ [ngTemplateOutletContext]="{
1613
+ $implicit: owner.annotation,
1614
+ annotation: owner.annotation,
1615
+ item: drawn.item,
1616
+ page: page,
1617
+ native: framedNative,
1618
+ hovered: drawn.item.hovered ?? false,
1619
+ selected: drawn.item.selected,
1620
+ interactive: true
1621
+ }"
1622
+ />
1623
+ } @else {
1624
+ @if (drawn.look; as look) {
1625
+ <div
1626
+ [epdfAnnotationFrame]="drawn.frame"
1627
+ [frameBlend]="drawn.item.blend"
1628
+ [frameInteractive]="drawn.interactive"
1629
+ [frameInert]="drawn.inert"
1630
+ >
1631
+ <ng-template #lookTemplate>
1632
+ <ng-container
1633
+ [ngTemplateOutlet]="look.template.template"
1634
+ [ngTemplateOutletInjector]="look.injector"
1635
+ [ngTemplateOutletContext]="{
1636
+ $implicit: look.annotation,
1637
+ annotation: look.annotation,
1638
+ frame: look.frame,
1639
+ native: native,
1640
+ appearance: drawn.appearance,
1641
+ hovered: drawn.item.hovered ?? false,
1642
+ selected: drawn.item.selected,
1643
+ interactive: drawn.interactive
1644
+ }"
1645
+ />
1646
+ </ng-template>
1647
+ @if (look.scaled) {
1648
+ <!-- Drawn at the annotation's 100% size, scaled with the page as a whole. -->
1649
+ <div
1650
+ style="position: absolute; left: 0; top: 0; transform-origin: 0 0"
1651
+ [style.width.px]="drawn.frame.design.width"
1652
+ [style.height.px]="drawn.frame.design.height"
1653
+ [style.transform]="'scale(' + drawn.frame.scale + ')'"
1654
+ >
1655
+ <ng-container [ngTemplateOutlet]="lookTemplate" />
1656
+ </div>
1657
+ } @else {
1658
+ <ng-container [ngTemplateOutlet]="lookTemplate" />
1659
+ }
1660
+ </div>
1661
+ } @else {
1662
+ <!-- Inert when an engaged behavior has no template of yours: its plugin owns the
1663
+ input (a link's anchor takes the click), and the annotation keeps its own look. -->
1664
+ <div
1665
+ [epdfAnnotationFrame]="drawn.frame"
1666
+ [frameBlend]="drawn.item.blend"
1667
+ [frameInert]="drawn.inert"
1668
+ >
1669
+ <ng-container [ngTemplateOutlet]="native" />
1670
+ </div>
1671
+ }
1672
+ }
1673
+ }
1674
+ @for (text of texts(); track text.id) {
1675
+ <div [epdfFreeTextBox]="text" [outline]="textOutline()"></div>
1676
+ }
1677
+ @if (ghost(); as ghost) {
1678
+ <!-- The armed stamp, see-through, in the box a click would place it in. -->
1679
+ <img
1680
+ alt=""
1681
+ draggable="false"
1682
+ style="position: absolute; max-width: none; max-height: none; pointer-events: none; transform-origin: center"
1683
+ [attr.src]="ghost.url"
1684
+ [style.left.px]="ghost.box.left"
1685
+ [style.top.px]="ghost.box.top"
1686
+ [style.width.px]="ghost.box.width"
1687
+ [style.height.px]="ghost.box.height"
1688
+ [style.opacity]="ghost.opacity"
1689
+ [style.transform]="ghost.rotation ? 'rotate(' + ghost.rotation + 'deg)' : null"
1690
+ />
1691
+ }
1692
+ <epdf-annotation-chrome
1693
+ [handle]="handleTemplate()?.template ?? null"
1694
+ [rotationHandle]="rotationHandleTemplate()?.template ?? null"
1695
+ />
1696
+ `,
1697
+ }]
1698
+ }], ctorParameters: () => [] });
1699
+
1700
+ /**
1701
+ * Your UI next to annotations, inside `<epdf-stage>`, each on `<epdf-anchored>`, so it moves
1702
+ * with the pages in the same frame and a click in it never reaches the page:
1703
+ *
1704
+ * <epdf-annotation-menu> your menu next to the selection
1705
+ * <epdf-annotation-draft-menu #menu> buttons over a polygon being drawn (`menu.draft()`)
1706
+ * <epdf-annotation-rotation-badge> the angle next to the pointer while turning
1707
+ *
1708
+ * They only place things: what's in them is yours, built from `inject(EpdfAnnotation)`.
1709
+ */
1710
+ /** The annotation plugin of the document this part of the template talks to. */
1711
+ function annotationInScope(what) {
1712
+ return new CapabilityBinding(injectKernelHost(what), () => AnnotationToken$1, injectDocumentScope());
1713
+ }
1714
+ /**
1715
+ * Your menu next to the selection: one menu, also for a selection over several pages, clear of
1716
+ * the rotation handle. It hides while the selection is dragged, resized or turned. Put it inside
1717
+ * `<epdf-stage>`, next to the page template, with what the menu holds between its tags:
1718
+ *
1719
+ * <epdf-annotation-menu placement="bottom">
1720
+ * <button (click)="annotation.selection.delete()">Delete</button>
1721
+ * </epdf-annotation-menu>
1722
+ */
1723
+ class EpdfAnnotationMenu {
1724
+ /** Which side of the selection: `'top'` (the default), `'bottom'`, `'left'` or `'right'`, or a corner. */
1725
+ placement = input('top', ...(ngDevMode ? [{ debugName: "placement" }] : []));
1726
+ /** Screen pixels between the selection and the menu. */
1727
+ gap = input(15, ...(ngDevMode ? [{ debugName: "gap" }] : []));
1728
+ annotation = annotationInScope('<epdf-annotation-menu>');
1729
+ projection = inject(EPDF_PROJECTOR, { optional: true });
1730
+ /**
1731
+ * The selection's anchor as its page is shown: the rotation handle sits a fixed number of
1732
+ * pixels off the box, so where it is on the page depends on the zoom. Read again with the
1733
+ * projection, in the same pass as the pages.
1734
+ */
1735
+ selection = this.annotation.select((annotation) => {
1736
+ const anchor = annotation.selection.getAnchor();
1737
+ if (!anchor)
1738
+ return null;
1739
+ this.projection?.revision();
1740
+ const view = this.projection?.projector()?.viewEnv(anchor.page);
1741
+ return view ? annotation.getSelectionAnchorIn(view) : anchor;
1742
+ }, null, sameSelectionAnchor);
1743
+ anchor = computed(() => {
1744
+ const anchor = this.selection();
1745
+ if (!anchor)
1746
+ return null;
1747
+ return {
1748
+ page: anchor.page,
1749
+ bounds: anchor.bounds,
1750
+ ...(anchor.rotationHandle ? { avoid: [anchor.rotationHandle] } : {}),
1751
+ };
1752
+ }, ...(ngDevMode ? [{ debugName: "anchor" }] : []));
1753
+ constructor() {
1754
+ if (!this.projection)
1755
+ throw outsideStageError('<epdf-annotation-menu>');
1756
+ }
1757
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.2.4", ngImport: i0, type: EpdfAnnotationMenu, deps: [], target: i0.ɵɵFactoryTarget.Component });
1758
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.1.0", version: "20.2.4", type: EpdfAnnotationMenu, isStandalone: true, selector: "epdf-annotation-menu", inputs: { placement: { classPropertyName: "placement", publicName: "placement", isSignal: true, isRequired: false, transformFunction: null }, gap: { classPropertyName: "gap", publicName: "gap", isSignal: true, isRequired: false, transformFunction: null } }, ngImport: i0, template: `
1759
+ <epdf-anchored [anchor]="anchor()" [placement]="placement()" [gap]="gap()">
1760
+ <ng-content />
1761
+ </epdf-anchored>
1762
+ `, isInline: true, dependencies: [{ kind: "component", type: EpdfAnchored, selector: "epdf-anchored", inputs: ["anchor", "placement", "gap", "pinned"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
1763
+ }
1764
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.2.4", ngImport: i0, type: EpdfAnnotationMenu, decorators: [{
1765
+ type: Component,
1766
+ args: [{
1767
+ selector: 'epdf-annotation-menu',
1768
+ imports: [EpdfAnchored],
1769
+ changeDetection: ChangeDetectionStrategy.OnPush,
1770
+ template: `
1771
+ <epdf-anchored [anchor]="anchor()" [placement]="placement()" [gap]="gap()">
1772
+ <ng-content />
1773
+ </epdf-anchored>
1774
+ `,
1775
+ }]
1776
+ }], ctorParameters: () => [] });
1777
+ /**
1778
+ * Buttons over a polygon or polyline being drawn, which helps on touch screens. `draft()` is the
1779
+ * shape being drawn (`pointCount`, `minPoints`, `canFinish`, …), or null; the verbs are the
1780
+ * annotation service's `draft.finish()` and `draft.cancel()`:
1781
+ *
1782
+ * <epdf-annotation-draft-menu #menu="epdfAnnotationDraftMenu">
1783
+ * <button [disabled]="!menu.draft()?.canFinish" (click)="annotation.draft.finish()">Done</button>
1784
+ * </epdf-annotation-draft-menu>
1785
+ */
1786
+ class EpdfAnnotationDraftMenu {
1787
+ /** Which side of the shape being drawn. */
1788
+ placement = input('top', ...(ngDevMode ? [{ debugName: "placement" }] : []));
1789
+ /** Screen pixels between the shape and the menu. */
1790
+ gap = input(8, ...(ngDevMode ? [{ debugName: "gap" }] : []));
1791
+ annotation = annotationInScope('<epdf-annotation-draft-menu>');
1792
+ /** The shape being drawn: where it is, its points so far and whether it can end; or null. */
1793
+ draft = this.annotation.select((annotation) => annotation.draft.get(), null, sameCreationDraftAnchor);
1794
+ constructor() {
1795
+ if (!inject(EPDF_PROJECTOR, { optional: true })) {
1796
+ throw outsideStageError('<epdf-annotation-draft-menu>');
1797
+ }
1798
+ }
1799
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.2.4", ngImport: i0, type: EpdfAnnotationDraftMenu, deps: [], target: i0.ɵɵFactoryTarget.Component });
1800
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.1.0", version: "20.2.4", type: EpdfAnnotationDraftMenu, isStandalone: true, selector: "epdf-annotation-draft-menu", inputs: { placement: { classPropertyName: "placement", publicName: "placement", isSignal: true, isRequired: false, transformFunction: null }, gap: { classPropertyName: "gap", publicName: "gap", isSignal: true, isRequired: false, transformFunction: null } }, exportAs: ["epdfAnnotationDraftMenu"], ngImport: i0, template: `
1801
+ <epdf-anchored [anchor]="draft()" [placement]="placement()" [gap]="gap()">
1802
+ <ng-content />
1803
+ </epdf-anchored>
1804
+ `, isInline: true, dependencies: [{ kind: "component", type: EpdfAnchored, selector: "epdf-anchored", inputs: ["anchor", "placement", "gap", "pinned"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
1805
+ }
1806
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.2.4", ngImport: i0, type: EpdfAnnotationDraftMenu, decorators: [{
1807
+ type: Component,
1808
+ args: [{
1809
+ selector: 'epdf-annotation-draft-menu',
1810
+ exportAs: 'epdfAnnotationDraftMenu',
1811
+ imports: [EpdfAnchored],
1812
+ changeDetection: ChangeDetectionStrategy.OnPush,
1813
+ template: `
1814
+ <epdf-anchored [anchor]="draft()" [placement]="placement()" [gap]="gap()">
1815
+ <ng-content />
1816
+ </epdf-anchored>
1817
+ `,
1818
+ }]
1819
+ }], ctorParameters: () => [] });
1820
+ /**
1821
+ * The angle next to the pointer while a selection is turned, upright however the page is shown.
1822
+ * It shows the angle in the `chrome.readout` colors when `chrome.readout.enabled`; give it an
1823
+ * `<ng-template epdfRotationBadge let-rotation>` to draw it your way. `rotation()` is the turn
1824
+ * in progress (`page`, `at`, `angle`), or null.
1825
+ */
1826
+ class EpdfAnnotationRotationBadge {
1827
+ /** Which side of the pointer. */
1828
+ placement = input('right', ...(ngDevMode ? [{ debugName: "placement" }] : []));
1829
+ /** Screen pixels between the pointer and the badge. */
1830
+ gap = input(16, ...(ngDevMode ? [{ debugName: "gap" }] : []));
1831
+ annotation = annotationInScope('<epdf-annotation-rotation-badge>');
1832
+ settings = injectChromeSettings();
1833
+ custom = contentChild(EpdfRotationBadgeTemplate, ...(ngDevMode ? [{ debugName: "custom" }] : []));
1834
+ /** The turn in progress: its page, the pointer's point and the angle so far; or null. */
1835
+ rotation = this.annotation.select((annotation) => annotation.selection.getRotationAnchor(), null, sameRotationAnchor);
1836
+ /** Shown while turning, with a template of yours or with the readout on. */
1837
+ shown = computed(() => {
1838
+ const rotation = this.rotation();
1839
+ return rotation && (this.custom() || this.settings.chrome().readout.enabled) ? rotation : null;
1840
+ }, ...(ngDevMode ? [{ debugName: "shown" }] : []));
1841
+ /** A box of no size at the pointer. */
1842
+ anchor = computed(() => {
1843
+ const rotation = this.rotation();
1844
+ return rotation
1845
+ ? { page: rotation.page, bounds: { ...rotation.at, width: 0, height: 0 } }
1846
+ : null;
1847
+ }, ...(ngDevMode ? [{ debugName: "anchor" }] : []));
1848
+ fontFamily = paintDefault('font-mono');
1849
+ background = computed(() => paint('readout-background', this.settings.chrome().readout.background), ...(ngDevMode ? [{ debugName: "background" }] : []));
1850
+ color = computed(() => paint('readout-color', this.settings.chrome().readout.color), ...(ngDevMode ? [{ debugName: "color" }] : []));
1851
+ constructor() {
1852
+ if (!inject(EPDF_PROJECTOR, { optional: true })) {
1853
+ throw outsideStageError('<epdf-annotation-rotation-badge>');
1854
+ }
1855
+ }
1856
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.2.4", ngImport: i0, type: EpdfAnnotationRotationBadge, deps: [], target: i0.ɵɵFactoryTarget.Component });
1857
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "20.2.4", type: EpdfAnnotationRotationBadge, isStandalone: true, selector: "epdf-annotation-rotation-badge", inputs: { placement: { classPropertyName: "placement", publicName: "placement", isSignal: true, isRequired: false, transformFunction: null }, gap: { classPropertyName: "gap", publicName: "gap", isSignal: true, isRequired: false, transformFunction: null } }, queries: [{ propertyName: "custom", first: true, predicate: EpdfRotationBadgeTemplate, descendants: true, isSignal: true }], exportAs: ["epdfAnnotationRotationBadge"], ngImport: i0, template: `
1858
+ @if (shown(); as rotation) {
1859
+ <epdf-anchored [anchor]="anchor()" [placement]="placement()" [gap]="gap()">
1860
+ @if (custom(); as custom) {
1861
+ <ng-container
1862
+ [ngTemplateOutlet]="custom.template"
1863
+ [ngTemplateOutletContext]="{ $implicit: rotation }"
1864
+ />
1865
+ } @else {
1866
+ <div
1867
+ style="pointer-events: none; white-space: nowrap; border-radius: 4px; padding: 2px 6px; font-size: 12px"
1868
+ [style.font-family]="fontFamily"
1869
+ [style.background]="background()"
1870
+ [style.color]="color()"
1871
+ >
1872
+ {{ rotation.angle }}°
1873
+ </div>
1874
+ }
1875
+ </epdf-anchored>
1876
+ }
1877
+ `, isInline: true, dependencies: [{ kind: "component", type: EpdfAnchored, selector: "epdf-anchored", inputs: ["anchor", "placement", "gap", "pinned"] }, { kind: "directive", type: NgTemplateOutlet, selector: "[ngTemplateOutlet]", inputs: ["ngTemplateOutletContext", "ngTemplateOutlet", "ngTemplateOutletInjector"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
1878
+ }
1879
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.2.4", ngImport: i0, type: EpdfAnnotationRotationBadge, decorators: [{
1880
+ type: Component,
1881
+ args: [{
1882
+ selector: 'epdf-annotation-rotation-badge',
1883
+ exportAs: 'epdfAnnotationRotationBadge',
1884
+ imports: [EpdfAnchored, NgTemplateOutlet],
1885
+ changeDetection: ChangeDetectionStrategy.OnPush,
1886
+ template: `
1887
+ @if (shown(); as rotation) {
1888
+ <epdf-anchored [anchor]="anchor()" [placement]="placement()" [gap]="gap()">
1889
+ @if (custom(); as custom) {
1890
+ <ng-container
1891
+ [ngTemplateOutlet]="custom.template"
1892
+ [ngTemplateOutletContext]="{ $implicit: rotation }"
1893
+ />
1894
+ } @else {
1895
+ <div
1896
+ style="pointer-events: none; white-space: nowrap; border-radius: 4px; padding: 2px 6px; font-size: 12px"
1897
+ [style.font-family]="fontFamily"
1898
+ [style.background]="background()"
1899
+ [style.color]="color()"
1900
+ >
1901
+ {{ rotation.angle }}°
1902
+ </div>
1903
+ }
1904
+ </epdf-anchored>
1905
+ }
1906
+ `,
1907
+ }]
1908
+ }], ctorParameters: () => [] });
1909
+
1910
+ /**
1911
+ * @embedpdf/angular/annotation: drawing, selecting, changing and saving annotations.
1912
+ *
1913
+ * withAnnotation(options) the plugin, for provideEmbedPdf() (with withInteraction())
1914
+ * withFilePicker(provider?) the file dialog behind the stamp and attachment tools
1915
+ * inject(EpdfAnnotation) the API, the State table as signals, the events as
1916
+ * streams, and `watch()`, `anchorOf()`, `tools.defaultsOf()`
1917
+ * inject(EpdfComments) threads and replies: `threads()`, `threadOf(ref)`, `reply()`, …
1918
+ * <epdf-annotation-layer> the annotations, the selection and the text boxes, in each page
1919
+ * <ng-template [epdfAnnotation]> your own look for some annotations
1920
+ * <ng-template epdfHandle>, epdfRotationHandle your own handles
1921
+ * [epdfRichTextEditor] your element as a text box's editor, in a look
1922
+ * <epdf-annotation-menu> your menu next to the selection, inside <epdf-stage>
1923
+ * <epdf-annotation-draft-menu> buttons over a polygon being drawn
1924
+ * <epdf-annotation-rotation-badge> the angle while a selection is turned
1925
+ */
1926
+ // The plugin's types and helpers, so app code has one import for the feature.
1927
+
1928
+ /**
1929
+ * Generated bundle index. Do not edit.
1930
+ */
1931
+
1932
+ export { EpdfAnnotation, EpdfAnnotationBehaviorTemplate, EpdfAnnotationDraftMenu, EpdfAnnotationLayer, EpdfAnnotationMenu, EpdfAnnotationRotationBadge, EpdfAnnotationTemplate, EpdfComments, EpdfHandleTemplate, EpdfRichTextEditor, EpdfRotationBadgeTemplate, EpdfRotationHandleTemplate, filePickerProvider, withAnnotation, withFilePicker };
1933
+ //# sourceMappingURL=embedpdf-angular-annotation.mjs.map