@embedpdf/angular 3.0.0-next.13 → 3.0.0-next.15

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 (81) 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 +599 -0
  6. package/dist/commands/index.d.ts +109 -0
  7. package/dist/fesm2022/embedpdf-angular-actions.mjs +117 -36
  8. package/dist/fesm2022/embedpdf-angular-actions.mjs.map +1 -1
  9. package/dist/fesm2022/embedpdf-angular-anchored.mjs +148 -0
  10. package/dist/fesm2022/embedpdf-angular-anchored.mjs.map +1 -0
  11. package/dist/fesm2022/embedpdf-angular-annotation.mjs +1960 -0
  12. package/dist/fesm2022/embedpdf-angular-annotation.mjs.map +1 -0
  13. package/dist/fesm2022/embedpdf-angular-commands.mjs +235 -0
  14. package/dist/fesm2022/embedpdf-angular-commands.mjs.map +1 -0
  15. package/dist/fesm2022/embedpdf-angular-form.mjs +1074 -0
  16. package/dist/fesm2022/embedpdf-angular-form.mjs.map +1 -0
  17. package/dist/fesm2022/embedpdf-angular-history.mjs +54 -0
  18. package/dist/fesm2022/embedpdf-angular-history.mjs.map +1 -0
  19. package/dist/fesm2022/embedpdf-angular-i18n.mjs +100 -0
  20. package/dist/fesm2022/embedpdf-angular-i18n.mjs.map +1 -0
  21. package/dist/fesm2022/embedpdf-angular-interaction.mjs +157 -0
  22. package/dist/fesm2022/embedpdf-angular-interaction.mjs.map +1 -0
  23. package/dist/fesm2022/embedpdf-angular-link.mjs +306 -0
  24. package/dist/fesm2022/embedpdf-angular-link.mjs.map +1 -0
  25. package/dist/fesm2022/embedpdf-angular-measurement.mjs +106 -0
  26. package/dist/fesm2022/embedpdf-angular-measurement.mjs.map +1 -0
  27. package/dist/fesm2022/embedpdf-angular-metadata.mjs +77 -0
  28. package/dist/fesm2022/embedpdf-angular-metadata.mjs.map +1 -0
  29. package/dist/fesm2022/embedpdf-angular-page-edit.mjs +65 -0
  30. package/dist/fesm2022/embedpdf-angular-page-edit.mjs.map +1 -0
  31. package/dist/fesm2022/embedpdf-angular-page-view.mjs +297 -0
  32. package/dist/fesm2022/embedpdf-angular-page-view.mjs.map +1 -0
  33. package/dist/fesm2022/embedpdf-angular-redaction.mjs +93 -0
  34. package/dist/fesm2022/embedpdf-angular-redaction.mjs.map +1 -0
  35. package/dist/fesm2022/embedpdf-angular-render.mjs +331 -69
  36. package/dist/fesm2022/embedpdf-angular-render.mjs.map +1 -1
  37. package/dist/fesm2022/embedpdf-angular-runtime.mjs +1147 -390
  38. package/dist/fesm2022/embedpdf-angular-runtime.mjs.map +1 -1
  39. package/dist/fesm2022/embedpdf-angular-search.mjs +251 -0
  40. package/dist/fesm2022/embedpdf-angular-search.mjs.map +1 -0
  41. package/dist/fesm2022/embedpdf-angular-selection.mjs +477 -0
  42. package/dist/fesm2022/embedpdf-angular-selection.mjs.map +1 -0
  43. package/dist/fesm2022/embedpdf-angular-shell.mjs +177 -0
  44. package/dist/fesm2022/embedpdf-angular-shell.mjs.map +1 -0
  45. package/dist/fesm2022/embedpdf-angular-signature.mjs +120 -0
  46. package/dist/fesm2022/embedpdf-angular-signature.mjs.map +1 -0
  47. package/dist/fesm2022/embedpdf-angular-stage.mjs +688 -288
  48. package/dist/fesm2022/embedpdf-angular-stage.mjs.map +1 -1
  49. package/dist/fesm2022/embedpdf-angular-stamp.mjs +169 -0
  50. package/dist/fesm2022/embedpdf-angular-stamp.mjs.map +1 -0
  51. package/dist/fesm2022/embedpdf-angular-testing.mjs +89 -0
  52. package/dist/fesm2022/embedpdf-angular-testing.mjs.map +1 -0
  53. package/dist/fesm2022/embedpdf-angular-toolbar.mjs +1123 -0
  54. package/dist/fesm2022/embedpdf-angular-toolbar.mjs.map +1 -0
  55. package/dist/fesm2022/embedpdf-angular-view-manager.mjs +69 -0
  56. package/dist/fesm2022/embedpdf-angular-view-manager.mjs.map +1 -0
  57. package/dist/fesm2022/embedpdf-angular.mjs +23 -3
  58. package/dist/fesm2022/embedpdf-angular.mjs.map +1 -1
  59. package/dist/form/index.d.ts +79 -0
  60. package/dist/history/index.d.ts +28 -0
  61. package/dist/i18n/index.d.ts +52 -0
  62. package/dist/index.d.ts +20 -0
  63. package/dist/interaction/index.d.ts +58 -0
  64. package/dist/link/index.d.ts +77 -0
  65. package/dist/measurement/index.d.ts +46 -0
  66. package/dist/metadata/index.d.ts +51 -0
  67. package/dist/page-edit/index.d.ts +22 -0
  68. package/dist/page-view/index.d.ts +94 -0
  69. package/dist/redaction/index.d.ts +38 -0
  70. package/dist/render/index.d.ts +45 -6
  71. package/dist/runtime/index.d.ts +579 -199
  72. package/dist/search/index.d.ts +71 -0
  73. package/dist/selection/index.d.ts +141 -0
  74. package/dist/shell/index.d.ts +88 -0
  75. package/dist/signature/index.d.ts +45 -0
  76. package/dist/stage/index.d.ts +242 -141
  77. package/dist/stamp/index.d.ts +58 -0
  78. package/dist/testing/index.d.ts +53 -0
  79. package/dist/toolbar/index.d.ts +257 -0
  80. package/dist/view-manager/index.d.ts +23 -0
  81. package/package.json +135 -14
@@ -0,0 +1,1960 @@
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, effect, untracked, ElementRef, Injector, contentChildren, contentChild, signal, PLATFORM_ID } 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, textPlateInPixels, textBoxStyleOf, textBoxEditorScaleOf, 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
+ function bindTextBoxEditor(element, annotation, item, scale) {
1133
+ const follower = createTextBoxEditorFollower();
1134
+ effect(() => {
1135
+ const host = annotation();
1136
+ const next = item();
1137
+ const at = scale();
1138
+ untracked(() => follower.follow(element, host, next, at));
1139
+ });
1140
+ inject(DestroyRef).onDestroy(() => follower.detach());
1141
+ }
1142
+ /**
1143
+ * The layer's own text box for one free text: the engine's text plate (the box inset by its
1144
+ * padding), styled like the box's body, with no padding of its own so its edge is the plate's.
1145
+ * The box's fill and border are the vector scene's, under it. It takes the pointer only while
1146
+ * someone types in it; otherwise clicks fall through to the shapes (select, move, resize).
1147
+ */
1148
+ class EpdfFreeTextBox {
1149
+ /** The text box, as the plugin lists it for the page. */
1150
+ item = input.required(...(ngDevMode ? [{ debugName: "item", alias: 'epdfFreeTextBox' }] : [{ alias: 'epdfFreeTextBox' }]));
1151
+ /** The outline color while someone types: the chrome's `textOutline`, painted. */
1152
+ outline = input.required(...(ngDevMode ? [{ debugName: "outline" }] : []));
1153
+ page = injectPage('[epdfFreeTextBox]');
1154
+ pixels = pagePixelsOf(this.page);
1155
+ plate = computed(() => textPlateInPixels(this.item(), this.pixels()), ...(ngDevMode ? [{ debugName: "plate" }] : []));
1156
+ look = computed(() => textBoxStyleOf(this.item().css, this.plate().scale), ...(ngDevMode ? [{ debugName: "look" }] : []));
1157
+ constructor() {
1158
+ const annotation = annotationHostOf(this.page, '[epdfFreeTextBox]').capability;
1159
+ bindTextBoxEditor(inject(ElementRef).nativeElement, annotation, this.item, computed(() => this.plate().scale));
1160
+ }
1161
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.2.4", ngImport: i0, type: EpdfFreeTextBox, deps: [], target: i0.ɵɵFactoryTarget.Directive });
1162
+ 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.left.px": "plate().left", "style.top.px": "plate().top", "style.width.px": "plate().width", "style.height.px": "plate().height", "style.font-family": "look().fontFamily", "style.font-size": "look().fontSize", "style.color": "look().color", "style.font-weight": "look().fontWeight", "style.font-style": "look().fontStyle", "style.text-decoration": "look().textDecoration", "style.text-align": "look().textAlign", "style.overflow-y": "item().editing ? 'auto' : 'hidden'", "style.outline": "item().editing ? '1px solid ' + outline() : 'none'", "style.cursor": "item().editing ? 'text' : 'default'", "style.transform": "item().rot ? 'rotate(' + item().rot + 'deg)' : null", "style.pointer-events": "item().editing ? 'auto' : 'none'" }, styleAttribute: "position: absolute; box-sizing: border-box; background: transparent; white-space: pre-wrap; overflow-wrap: break-word; overflow-x: hidden; transform-origin: center" }, ngImport: i0 });
1163
+ }
1164
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.2.4", ngImport: i0, type: EpdfFreeTextBox, decorators: [{
1165
+ type: Directive,
1166
+ args: [{
1167
+ selector: '[epdfFreeTextBox]',
1168
+ host: {
1169
+ style: 'position: absolute; box-sizing: border-box; background: transparent; ' +
1170
+ 'white-space: pre-wrap; overflow-wrap: break-word; overflow-x: hidden; transform-origin: center',
1171
+ '[style.left.px]': 'plate().left',
1172
+ '[style.top.px]': 'plate().top',
1173
+ '[style.width.px]': 'plate().width',
1174
+ // Fixed to the plate: the box never grows with its text; it scrolls while typing and clips
1175
+ // otherwise, where the baked appearance clips.
1176
+ '[style.height.px]': 'plate().height',
1177
+ '[style.font-family]': 'look().fontFamily',
1178
+ '[style.font-size]': 'look().fontSize',
1179
+ '[style.color]': 'look().color',
1180
+ '[style.font-weight]': 'look().fontWeight',
1181
+ '[style.font-style]': 'look().fontStyle',
1182
+ '[style.text-decoration]': 'look().textDecoration',
1183
+ '[style.text-align]': 'look().textAlign',
1184
+ '[style.overflow-y]': "item().editing ? 'auto' : 'hidden'",
1185
+ '[style.outline]': "item().editing ? '1px solid ' + outline() : 'none'",
1186
+ '[style.cursor]': "item().editing ? 'text' : 'default'",
1187
+ // A plain text box turns about its centre, as the baked appearance does.
1188
+ '[style.transform]': "item().rot ? 'rotate(' + item().rot + 'deg)' : null",
1189
+ '[style.pointer-events]': "item().editing ? 'auto' : 'none'",
1190
+ },
1191
+ }]
1192
+ }], ctorParameters: () => [] });
1193
+ /**
1194
+ * Your own element as the editor of a text box you draw yourself, in a look
1195
+ * (`<ng-template [epdfAnnotation]="isTextBox" let-annotation>`):
1196
+ *
1197
+ * <div [epdfRichTextEditor]="annotation" #editor="epdfRichTextEditor"
1198
+ * class="text-box" [class.editing]="editor.editing()"></div>
1199
+ *
1200
+ * It draws the runs, turns typing back into runs, keeps the caret when the style changes, and
1201
+ * handles pasting, input methods and the format shortcuts; `text.toggleFormat()` and
1202
+ * `selection.update()` work on it as on the layer's own box. It gives the element the box's
1203
+ * body style (font, size, color and alignment), and while someone types it takes the pointer.
1204
+ * Leave the element empty: the editor fills it.
1205
+ */
1206
+ class EpdfRichTextEditor {
1207
+ /** The free text annotation this element edits: the template's `let-annotation`. */
1208
+ annotation = input.required(...(ngDevMode ? [{ debugName: "annotation", alias: 'epdfRichTextEditor' }] : [{ alias: 'epdfRichTextEditor' }]));
1209
+ page = injectPage('[epdfRichTextEditor]');
1210
+ host = annotationHostOf(this.page, '[epdfRichTextEditor]');
1211
+ lookScale = inject(EPDF_LOOK_SCALE, { optional: true });
1212
+ view = pageViewOf(this.page);
1213
+ pixels = pagePixelsOf(this.page);
1214
+ /** The annotation's text box on the page, or null while there is none. */
1215
+ item = this.host.select((annotation) => {
1216
+ const key = annotationKey(this.annotation().ref);
1217
+ return (annotation
1218
+ .listTextItems(this.page.ref, this.view())
1219
+ .find((text) => text.ref !== null && annotationKey(text.ref) === key) ?? null);
1220
+ }, null);
1221
+ /** Pixels per point where the element is: in a scaled look, at the annotation's 100% size. */
1222
+ scale = computed(() => textBoxEditorScaleOf(this.item(), this.pixels(), this.lookScale?.() ?? 1), ...(ngDevMode ? [{ debugName: "scale" }] : []));
1223
+ /** The box's body style: font, size, color and alignment, or null while there is no box. */
1224
+ look = computed(() => {
1225
+ const item = this.item();
1226
+ return item ? textBoxStyleOf(item.css, this.scale()) : null;
1227
+ }, ...(ngDevMode ? [{ debugName: "look" }] : []));
1228
+ /** True while someone types in the box. */
1229
+ editing = computed(() => this.item()?.editing ?? false, ...(ngDevMode ? [{ debugName: "editing" }] : []));
1230
+ constructor() {
1231
+ bindTextBoxEditor(inject(ElementRef).nativeElement, this.host.capability, this.item, this.scale);
1232
+ }
1233
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.2.4", ngImport: i0, type: EpdfRichTextEditor, deps: [], target: i0.ɵɵFactoryTarget.Directive });
1234
+ 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.font-family": "look()?.fontFamily ?? null", "style.font-size": "look()?.fontSize ?? null", "style.color": "look()?.color ?? null", "style.font-weight": "look()?.fontWeight ?? null", "style.font-style": "look()?.fontStyle ?? null", "style.text-decoration": "look()?.textDecoration ?? null", "style.text-align": "look()?.textAlign ?? null", "style.pointer-events": "editing() ? 'auto' : null" } }, exportAs: ["epdfRichTextEditor"], ngImport: i0 });
1235
+ }
1236
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.2.4", ngImport: i0, type: EpdfRichTextEditor, decorators: [{
1237
+ type: Directive,
1238
+ args: [{
1239
+ selector: '[epdfRichTextEditor]',
1240
+ exportAs: 'epdfRichTextEditor',
1241
+ host: {
1242
+ '[style.font-family]': 'look()?.fontFamily ?? null',
1243
+ '[style.font-size]': 'look()?.fontSize ?? null',
1244
+ '[style.color]': 'look()?.color ?? null',
1245
+ '[style.font-weight]': 'look()?.fontWeight ?? null',
1246
+ '[style.font-style]': 'look()?.fontStyle ?? null',
1247
+ '[style.text-decoration]': 'look()?.textDecoration ?? null',
1248
+ '[style.text-align]': 'look()?.textAlign ?? null',
1249
+ // While typing, the element takes the pointer (the caret, a drag over words).
1250
+ '[style.pointer-events]': "editing() ? 'auto' : null",
1251
+ },
1252
+ }]
1253
+ }], ctorParameters: () => [] });
1254
+
1255
+ /**
1256
+ * `<epdf-annotation-layer>`: a page's annotations, the selection's outline and handles, the
1257
+ * tool's preview, and the text boxes being typed in. Put it in each page, above the rendered
1258
+ * page; while it's there, the render layer leaves the annotations out of the page's picture:
1259
+ *
1260
+ * <ng-template epdfPage>
1261
+ * <epdf-render-layer />
1262
+ * <epdf-annotation-layer />
1263
+ * </ng-template>
1264
+ *
1265
+ * Pure paint: it reads the plugin's items and chrome for the page and draws them. The pointer
1266
+ * reaches the plugin through the interaction hub (the Stage forwards it), and so does the
1267
+ * cursor. Each annotation is one drawing: its vector scene, the engine's baked picture, or a
1268
+ * look of yours from an `<ng-template [epdfAnnotation]>` inside the layer, which may keep the
1269
+ * layer's own drawing (`native`) and add to it. `<ng-template epdfHandle>` and
1270
+ * `<ng-template epdfRotationHandle>` draw the handles your way.
1271
+ *
1272
+ * What isn't Angular is `@embedpdf/web`'s, the same for every framework: which drawing each
1273
+ * annotation gets and the behaviors interactive looks register, the frames and chrome in
1274
+ * pixels, the scene as SVG elements, the text box editor, and the object URLs of baked pictures
1275
+ * and the stamp ghost.
1276
+ */
1277
+ const NO_ITEMS = Object.freeze([]);
1278
+ const NO_TEXTS = Object.freeze([]);
1279
+ class EpdfAnnotationLayer {
1280
+ page = injectPage('<epdf-annotation-layer>');
1281
+ injector = inject(Injector);
1282
+ annotation = annotationHostOf(this.page, '<epdf-annotation-layer>');
1283
+ interaction = new CapabilityBinding(injectKernelHost('<epdf-annotation-layer>'), () => InteractionToken, () => this.page.documentId);
1284
+ render = new CapabilityBinding(injectKernelHost('<epdf-annotation-layer>'), () => RenderToken, () => this.page.documentId);
1285
+ chrome = injectChromeSettings();
1286
+ // ── your templates ───────────────────────────────────────────────────────────
1287
+ looks = contentChildren(EpdfAnnotationTemplate, ...(ngDevMode ? [{ debugName: "looks", descendants: true }] : [{ descendants: true }]));
1288
+ behaviors = contentChildren(EpdfAnnotationBehaviorTemplate, ...(ngDevMode ? [{ debugName: "behaviors", descendants: true }] : [{
1289
+ descendants: true,
1290
+ }]));
1291
+ handleTemplate = contentChild(EpdfHandleTemplate, ...(ngDevMode ? [{ debugName: "handleTemplate", descendants: true }] : [{ descendants: true }]));
1292
+ rotationHandleTemplate = contentChild(EpdfRotationHandleTemplate, ...(ngDevMode ? [{ debugName: "rotationHandleTemplate", descendants: true }] : [{
1293
+ descendants: true,
1294
+ }]));
1295
+ /**
1296
+ * The renderer entries, in the order the templates are written: the first look that matches
1297
+ * wins. The same array while every entry stays the same.
1298
+ */
1299
+ renderers = computed(() => [
1300
+ ...this.behaviors().map((template) => template.renderer()),
1301
+ ...this.looks().map((template) => template.renderer()),
1302
+ ].filter((entry) => entry !== null), ...(ngDevMode ? [{ debugName: "renderers", equal: (left, right) => left.length === right.length && left.every((entry, index) => entry === right[index]) }] : [{
1303
+ equal: (left, right) => left.length === right.length && left.every((entry, index) => entry === right[index]),
1304
+ }]));
1305
+ /** Goes up when the behaviors register again, so each drawing is decided again. */
1306
+ registrations = signal(0, ...(ngDevMode ? [{ debugName: "registrations" }] : []));
1307
+ // ── what the page shows ──────────────────────────────────────────────────────
1308
+ view = pageViewOf(this.page);
1309
+ pixels = pagePixelsOf(this.page);
1310
+ items = this.annotation.select((annotation) => annotation.listPageItems(this.page.ref, this.view()), NO_ITEMS);
1311
+ allTexts = this.annotation.select((annotation) => annotation.listTextItems(this.page.ref, this.view()), NO_TEXTS);
1312
+ /** The active tool: an interactive look's function is asked about it, so a tool change redraws. */
1313
+ activeToolId = this.interaction.select((interaction) => interaction.getActiveToolId() ?? '', '');
1314
+ /** The pictures' URLs, by annotation and state; `null` until the page's first ones arrive. */
1315
+ urls = signal(null, ...(ngDevMode ? [{ debugName: "urls" }] : []));
1316
+ /** The pictures shown stay valid until the next ones are shown. */
1317
+ shown = createShownUrls();
1318
+ // A move or a turn leaves the epoch as it is (the same pixels, placed elsewhere), and live
1319
+ // gestures don't touch it: nothing loads mid-drag.
1320
+ appearanceEpoch = this.annotation.select((annotation) => annotation.getAppearanceEpoch(this.page.ref), '');
1321
+ // Zoom steps inside one rung of the document's appearance lattice bake nothing again.
1322
+ // Without the render plugin, only this session's pictures load.
1323
+ appearanceScale = this.render.select((render) => render.getAppearanceScale(this.page.transform().renderScale), 0);
1324
+ bakeScale = computed(() => this.render.capability() ? this.appearanceScale() : this.page.transform().renderScale, ...(ngDevMode ? [{ debugName: "bakeScale" }] : []));
1325
+ /** The text box being typed in, by key: while it is, a look's editor takes the keys. */
1326
+ editingKey = computed(() => editingTextKeyOf(this.allTexts(), annotationKey), ...(ngDevMode ? [{ debugName: "editingKey" }] : []));
1327
+ /** One injector per look, by item: kept while the item is drawn, so the look is never made again. */
1328
+ lookInjectors = new Map();
1329
+ drawings = computed(() => {
1330
+ this.activeToolId();
1331
+ this.registrations();
1332
+ const annotation = this.annotation.capability();
1333
+ const renderers = this.renderers();
1334
+ const looks = this.looks();
1335
+ const behaviors = this.behaviors();
1336
+ const pixels = this.pixels();
1337
+ const urls = this.urls();
1338
+ const editingKey = this.editingKey();
1339
+ const drawings = this.items().map((item) => {
1340
+ const frame = frameInPixels(item.frame, pixels);
1341
+ const state = item.annotation?.appearanceState ?? null;
1342
+ const drawn = {
1343
+ item,
1344
+ frame,
1345
+ shows: pictureOrPainting({
1346
+ pending: item.source !== 'baked',
1347
+ picture: urls && shownAppearance(urls, item.id, state),
1348
+ version: item.pictureVersion ?? 0,
1349
+ loaded: urls !== null,
1350
+ paint: () => (hasLiveDrawing(item) ? item : null),
1351
+ }),
1352
+ appearance: urls && bakedAppearanceOf(urls, item.id, state),
1353
+ look: null,
1354
+ owner: null,
1355
+ interactive: false,
1356
+ inert: false,
1357
+ };
1358
+ const record = item.annotation;
1359
+ if (!record || !annotation)
1360
+ return drawn;
1361
+ // Ownership beats looks: an engaged behavior's template is authoritative (a form
1362
+ // plugin's controls own their DOM); looks apply only to what the layer owns, and draw
1363
+ // without the pointer. While its text box is typed in, a look's editor takes the keys.
1364
+ const typing = editingKey !== null && annotationKey(record.ref) === editingKey;
1365
+ const drawing = annotationDrawingOf(record, annotation, renderers, typing);
1366
+ switch (drawing.kind) {
1367
+ case 'native':
1368
+ return { ...drawn, inert: drawing.inert };
1369
+ case 'owned': {
1370
+ const template = behaviors.find((behavior) => behavior.renderer() === drawing.entry);
1371
+ return template ? { ...drawn, owner: { template, annotation: record } } : drawn;
1372
+ }
1373
+ case 'look': {
1374
+ const template = looks.find((look) => look.renderer() === drawing.entry);
1375
+ if (!template)
1376
+ return drawn;
1377
+ const scaled = drawing.entry.scale !== false;
1378
+ return {
1379
+ ...drawn,
1380
+ look: {
1381
+ template,
1382
+ annotation: record,
1383
+ frame: lookFrameOf(frame, scaled),
1384
+ scaled,
1385
+ injector: this.lookInjectorOf(item.id),
1386
+ },
1387
+ interactive: drawing.interactive,
1388
+ inert: drawing.inert,
1389
+ };
1390
+ }
1391
+ }
1392
+ });
1393
+ // Forget the injectors of looks no longer drawn: a look drawn again starts afresh.
1394
+ const drawnLooks = new Set(drawings.flatMap((drawn) => (drawn.look ? [drawn.item.id] : [])));
1395
+ for (const id of this.lookInjectors.keys()) {
1396
+ if (!drawnLooks.has(id))
1397
+ this.lookInjectors.delete(id);
1398
+ }
1399
+ return drawings;
1400
+ }, ...(ngDevMode ? [{ debugName: "drawings" }] : []));
1401
+ /** How much the layer scales each look, by item: 1 for a look drawn at its size on screen. */
1402
+ lookScales = computed(() => {
1403
+ const scales = new Map();
1404
+ for (const drawn of this.drawings()) {
1405
+ if (drawn.look)
1406
+ scales.set(drawn.item.id, drawn.look.scaled ? drawn.frame.scale : 1);
1407
+ }
1408
+ return scales;
1409
+ }, ...(ngDevMode ? [{ debugName: "lookScales" }] : []));
1410
+ /** The text boxes the layer draws: not the ones a look of yours draws, which edits them there. */
1411
+ texts = computed(() => layerTextBoxesOf(this.allTexts(), this.items(), this.renderers(), annotationKey), ...(ngDevMode ? [{ debugName: "texts" }] : []));
1412
+ textOutline = computed(() => annotationChromePaint(this.chrome.chrome(), this.chrome.accent()).textOutline, ...(ngDevMode ? [{ debugName: "textOutline" }] : []));
1413
+ // ── the armed stamp's ghost ──────────────────────────────────────────────────
1414
+ imageGhost = this.annotation.select((annotation) => annotation.getImageGhost(this.page.ref), null);
1415
+ armedStamp = this.annotation.select((annotation) => annotation.getArmedStamp(), null, Object.is);
1416
+ /**
1417
+ * The ghost is a picture of vector artwork, sharp at one size: the size class that covers the
1418
+ * box's device width. The plugin keeps one picture per class, so a zoom never renders per frame.
1419
+ */
1420
+ ghostBucket = computed(() => {
1421
+ const ghost = this.imageGhost();
1422
+ return ghost ? previewBucket(ghost.box.width * this.page.transform().renderScale) : 0;
1423
+ }, ...(ngDevMode ? [{ debugName: "ghostBucket" }] : []));
1424
+ ghostUrl = signal(null, ...(ngDevMode ? [{ debugName: "ghostUrl" }] : []));
1425
+ ghost = computed(() => {
1426
+ const ghost = this.imageGhost();
1427
+ const url = this.ghostUrl();
1428
+ if (!ghost || !url)
1429
+ return null;
1430
+ return {
1431
+ url,
1432
+ box: rectInPixels(ghost.box, this.pixels()),
1433
+ opacity: ghostOpacity(ghost.opacity),
1434
+ rotation: ghost.rot,
1435
+ };
1436
+ }, ...(ngDevMode ? [{ debugName: "ghost" }] : []));
1437
+ constructor() {
1438
+ // While it's here, the page's picture leaves the annotations to it.
1439
+ paintsPagePart(() => this.page.ref, 'annotations');
1440
+ this.registerBehaviors();
1441
+ if (!isPlatformBrowser(inject(PLATFORM_ID)))
1442
+ return;
1443
+ inject(DestroyRef).onDestroy(() => this.shown.release());
1444
+ this.loadAppearances();
1445
+ this.loadGhost();
1446
+ }
1447
+ /** The injector a look's template gets for one item: the look's scale, followed live. */
1448
+ lookInjectorOf(id) {
1449
+ let injector = this.lookInjectors.get(id);
1450
+ if (!injector) {
1451
+ const scale = computed(() => this.lookScales().get(id) ?? 1, ...(ngDevMode ? [{ debugName: "scale" }] : []));
1452
+ injector = Injector.create({
1453
+ providers: [{ provide: EPDF_LOOK_SCALE, useValue: scale }],
1454
+ parent: this.injector,
1455
+ });
1456
+ this.lookInjectors.set(id, injector);
1457
+ }
1458
+ return injector;
1459
+ }
1460
+ /**
1461
+ * An interactive look takes the pointer through a behavior the plugin knows, registered once
1462
+ * per document and look however many pages show the layer (`@embedpdf/web` counts them). The
1463
+ * looks are the same entries on every page, so keep the functions you give `epdfAnnotation`
1464
+ * and `epdfAnnotationInteractive` in fields: a function made anew registers again.
1465
+ */
1466
+ registerBehaviors() {
1467
+ effect((onCleanup) => {
1468
+ const annotation = this.annotation.capability();
1469
+ const renderers = this.renderers();
1470
+ if (annotation && renderers.length > 0) {
1471
+ onCleanup(untracked(() => registerRendererBehaviors(annotation, renderers, () => untracked(this.activeToolId))));
1472
+ }
1473
+ this.registrations.update((count) => count + 1);
1474
+ });
1475
+ }
1476
+ /**
1477
+ * The baked pictures, loaded again when the page's appearance epoch or the bake scale changes;
1478
+ * the ones shown stay valid until the next are shown.
1479
+ */
1480
+ loadAppearances() {
1481
+ effect((onCleanup) => {
1482
+ const annotation = this.annotation.capability();
1483
+ const scale = this.bakeScale();
1484
+ this.appearanceEpoch();
1485
+ if (!annotation || !scale)
1486
+ return;
1487
+ const page = this.page.ref;
1488
+ onCleanup(untracked(() => loadAppearanceUrls(this.shown, (abort) => annotation.renderAppearances(page, scale, abort), annotationKey, (urls) => this.urls.set(urls))));
1489
+ });
1490
+ }
1491
+ /**
1492
+ * The armed stamp's ghost picture, for its size class. The previous class's picture stays up
1493
+ * until this one is there, so a zoom across a class boundary never flickers; a disarm drops it.
1494
+ */
1495
+ loadGhost() {
1496
+ effect((onCleanup) => {
1497
+ const annotation = this.annotation.capability();
1498
+ const bucket = this.ghostBucket();
1499
+ this.armedStamp();
1500
+ if (!annotation || !bucket) {
1501
+ this.ghostUrl.set(null);
1502
+ return;
1503
+ }
1504
+ onCleanup(untracked(() => loadObjectUrl(() => annotation.renderArmedStampPreview(bucket), (url) => this.ghostUrl.set(url))));
1505
+ });
1506
+ }
1507
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.2.4", ngImport: i0, type: EpdfAnnotationLayer, deps: [], target: i0.ɵɵFactoryTarget.Component });
1508
+ 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: `
1509
+ @for (drawn of drawings(); track drawn.item.id) {
1510
+ <!-- The layer's own drawing of it, which a look may keep: its \`native\`. -->
1511
+ <ng-template #native>
1512
+ <epdf-annotation-native [item]="drawn.item" [shows]="drawn.shows" />
1513
+ </ng-template>
1514
+ @if (drawn.owner; as owner) {
1515
+ <!-- A sibling plugin's controls place themselves in the layer and take the input;
1516
+ the annotation's own drawing keeps its frame. -->
1517
+ <ng-template #framedNative>
1518
+ <div [epdfAnnotationFrame]="drawn.frame" [frameBlend]="drawn.item.blend">
1519
+ <ng-container [ngTemplateOutlet]="native" />
1520
+ </div>
1521
+ </ng-template>
1522
+ <ng-container
1523
+ [ngTemplateOutlet]="owner.template.template"
1524
+ [ngTemplateOutletContext]="{
1525
+ $implicit: owner.annotation,
1526
+ annotation: owner.annotation,
1527
+ item: drawn.item,
1528
+ page: page,
1529
+ native: framedNative,
1530
+ hovered: drawn.item.hovered ?? false,
1531
+ selected: drawn.item.selected,
1532
+ interactive: true
1533
+ }"
1534
+ />
1535
+ } @else {
1536
+ @if (drawn.look; as look) {
1537
+ <div
1538
+ [epdfAnnotationFrame]="drawn.frame"
1539
+ [frameBlend]="drawn.item.blend"
1540
+ [frameInteractive]="drawn.interactive"
1541
+ [frameInert]="drawn.inert"
1542
+ >
1543
+ <ng-template #lookTemplate>
1544
+ <ng-container
1545
+ [ngTemplateOutlet]="look.template.template"
1546
+ [ngTemplateOutletInjector]="look.injector"
1547
+ [ngTemplateOutletContext]="{
1548
+ $implicit: look.annotation,
1549
+ annotation: look.annotation,
1550
+ frame: look.frame,
1551
+ native: native,
1552
+ appearance: drawn.appearance,
1553
+ hovered: drawn.item.hovered ?? false,
1554
+ selected: drawn.item.selected,
1555
+ interactive: drawn.interactive
1556
+ }"
1557
+ />
1558
+ </ng-template>
1559
+ @if (look.scaled) {
1560
+ <!-- Drawn at the annotation's 100% size, scaled with the page as a whole. -->
1561
+ <div
1562
+ style="position: absolute; left: 0; top: 0; transform-origin: 0 0"
1563
+ [style.width.px]="drawn.frame.design.width"
1564
+ [style.height.px]="drawn.frame.design.height"
1565
+ [style.transform]="'scale(' + drawn.frame.scale + ')'"
1566
+ >
1567
+ <ng-container [ngTemplateOutlet]="lookTemplate" />
1568
+ </div>
1569
+ } @else {
1570
+ <ng-container [ngTemplateOutlet]="lookTemplate" />
1571
+ }
1572
+ </div>
1573
+ } @else {
1574
+ <!-- Inert when an engaged behavior has no template of yours: its plugin owns the
1575
+ input (a link's anchor takes the click), and the annotation keeps its own look. -->
1576
+ <div
1577
+ [epdfAnnotationFrame]="drawn.frame"
1578
+ [frameBlend]="drawn.item.blend"
1579
+ [frameInert]="drawn.inert"
1580
+ >
1581
+ <ng-container [ngTemplateOutlet]="native" />
1582
+ </div>
1583
+ }
1584
+ }
1585
+ }
1586
+ @for (text of texts(); track text.id) {
1587
+ <div [epdfFreeTextBox]="text" [outline]="textOutline()"></div>
1588
+ }
1589
+ @if (ghost(); as ghost) {
1590
+ <!-- The armed stamp, see-through, in the box a click would place it in. -->
1591
+ <img
1592
+ alt=""
1593
+ draggable="false"
1594
+ style="position: absolute; max-width: none; max-height: none; pointer-events: none; transform-origin: center"
1595
+ [attr.src]="ghost.url"
1596
+ [style.left.px]="ghost.box.left"
1597
+ [style.top.px]="ghost.box.top"
1598
+ [style.width.px]="ghost.box.width"
1599
+ [style.height.px]="ghost.box.height"
1600
+ [style.opacity]="ghost.opacity"
1601
+ [style.transform]="ghost.rotation ? 'rotate(' + ghost.rotation + 'deg)' : null"
1602
+ />
1603
+ }
1604
+ <epdf-annotation-chrome
1605
+ [handle]="handleTemplate()?.template ?? null"
1606
+ [rotationHandle]="rotationHandleTemplate()?.template ?? null"
1607
+ />
1608
+ `, 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 });
1609
+ }
1610
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.2.4", ngImport: i0, type: EpdfAnnotationLayer, decorators: [{
1611
+ type: Component,
1612
+ args: [{
1613
+ selector: 'epdf-annotation-layer',
1614
+ imports: [
1615
+ NgTemplateOutlet,
1616
+ EpdfAnnotationFrame,
1617
+ EpdfAnnotationNative,
1618
+ EpdfAnnotationChrome,
1619
+ EpdfFreeTextBox,
1620
+ ],
1621
+ changeDetection: ChangeDetectionStrategy.OnPush,
1622
+ host: { style: 'position: absolute; inset: 0; display: block; pointer-events: none' },
1623
+ template: `
1624
+ @for (drawn of drawings(); track drawn.item.id) {
1625
+ <!-- The layer's own drawing of it, which a look may keep: its \`native\`. -->
1626
+ <ng-template #native>
1627
+ <epdf-annotation-native [item]="drawn.item" [shows]="drawn.shows" />
1628
+ </ng-template>
1629
+ @if (drawn.owner; as owner) {
1630
+ <!-- A sibling plugin's controls place themselves in the layer and take the input;
1631
+ the annotation's own drawing keeps its frame. -->
1632
+ <ng-template #framedNative>
1633
+ <div [epdfAnnotationFrame]="drawn.frame" [frameBlend]="drawn.item.blend">
1634
+ <ng-container [ngTemplateOutlet]="native" />
1635
+ </div>
1636
+ </ng-template>
1637
+ <ng-container
1638
+ [ngTemplateOutlet]="owner.template.template"
1639
+ [ngTemplateOutletContext]="{
1640
+ $implicit: owner.annotation,
1641
+ annotation: owner.annotation,
1642
+ item: drawn.item,
1643
+ page: page,
1644
+ native: framedNative,
1645
+ hovered: drawn.item.hovered ?? false,
1646
+ selected: drawn.item.selected,
1647
+ interactive: true
1648
+ }"
1649
+ />
1650
+ } @else {
1651
+ @if (drawn.look; as look) {
1652
+ <div
1653
+ [epdfAnnotationFrame]="drawn.frame"
1654
+ [frameBlend]="drawn.item.blend"
1655
+ [frameInteractive]="drawn.interactive"
1656
+ [frameInert]="drawn.inert"
1657
+ >
1658
+ <ng-template #lookTemplate>
1659
+ <ng-container
1660
+ [ngTemplateOutlet]="look.template.template"
1661
+ [ngTemplateOutletInjector]="look.injector"
1662
+ [ngTemplateOutletContext]="{
1663
+ $implicit: look.annotation,
1664
+ annotation: look.annotation,
1665
+ frame: look.frame,
1666
+ native: native,
1667
+ appearance: drawn.appearance,
1668
+ hovered: drawn.item.hovered ?? false,
1669
+ selected: drawn.item.selected,
1670
+ interactive: drawn.interactive
1671
+ }"
1672
+ />
1673
+ </ng-template>
1674
+ @if (look.scaled) {
1675
+ <!-- Drawn at the annotation's 100% size, scaled with the page as a whole. -->
1676
+ <div
1677
+ style="position: absolute; left: 0; top: 0; transform-origin: 0 0"
1678
+ [style.width.px]="drawn.frame.design.width"
1679
+ [style.height.px]="drawn.frame.design.height"
1680
+ [style.transform]="'scale(' + drawn.frame.scale + ')'"
1681
+ >
1682
+ <ng-container [ngTemplateOutlet]="lookTemplate" />
1683
+ </div>
1684
+ } @else {
1685
+ <ng-container [ngTemplateOutlet]="lookTemplate" />
1686
+ }
1687
+ </div>
1688
+ } @else {
1689
+ <!-- Inert when an engaged behavior has no template of yours: its plugin owns the
1690
+ input (a link's anchor takes the click), and the annotation keeps its own look. -->
1691
+ <div
1692
+ [epdfAnnotationFrame]="drawn.frame"
1693
+ [frameBlend]="drawn.item.blend"
1694
+ [frameInert]="drawn.inert"
1695
+ >
1696
+ <ng-container [ngTemplateOutlet]="native" />
1697
+ </div>
1698
+ }
1699
+ }
1700
+ }
1701
+ @for (text of texts(); track text.id) {
1702
+ <div [epdfFreeTextBox]="text" [outline]="textOutline()"></div>
1703
+ }
1704
+ @if (ghost(); as ghost) {
1705
+ <!-- The armed stamp, see-through, in the box a click would place it in. -->
1706
+ <img
1707
+ alt=""
1708
+ draggable="false"
1709
+ style="position: absolute; max-width: none; max-height: none; pointer-events: none; transform-origin: center"
1710
+ [attr.src]="ghost.url"
1711
+ [style.left.px]="ghost.box.left"
1712
+ [style.top.px]="ghost.box.top"
1713
+ [style.width.px]="ghost.box.width"
1714
+ [style.height.px]="ghost.box.height"
1715
+ [style.opacity]="ghost.opacity"
1716
+ [style.transform]="ghost.rotation ? 'rotate(' + ghost.rotation + 'deg)' : null"
1717
+ />
1718
+ }
1719
+ <epdf-annotation-chrome
1720
+ [handle]="handleTemplate()?.template ?? null"
1721
+ [rotationHandle]="rotationHandleTemplate()?.template ?? null"
1722
+ />
1723
+ `,
1724
+ }]
1725
+ }], ctorParameters: () => [] });
1726
+
1727
+ /**
1728
+ * Your UI next to annotations, inside `<epdf-stage>`, each on `<epdf-anchored>`, so it moves
1729
+ * with the pages in the same frame and a click in it never reaches the page:
1730
+ *
1731
+ * <epdf-annotation-menu> your menu next to the selection
1732
+ * <epdf-annotation-draft-menu #menu> buttons over a polygon being drawn (`menu.draft()`)
1733
+ * <epdf-annotation-rotation-badge> the angle next to the pointer while turning
1734
+ *
1735
+ * They only place things: what's in them is yours, built from `inject(EpdfAnnotation)`.
1736
+ */
1737
+ /** The annotation plugin of the document this part of the template talks to. */
1738
+ function annotationInScope(what) {
1739
+ return new CapabilityBinding(injectKernelHost(what), () => AnnotationToken$1, injectDocumentScope());
1740
+ }
1741
+ /**
1742
+ * Your menu next to the selection: one menu, also for a selection over several pages, clear of
1743
+ * the rotation handle. It hides while the selection is dragged, resized or turned. Put it inside
1744
+ * `<epdf-stage>`, next to the page template, with what the menu holds between its tags:
1745
+ *
1746
+ * <epdf-annotation-menu placement="bottom">
1747
+ * <button (click)="annotation.selection.delete()">Delete</button>
1748
+ * </epdf-annotation-menu>
1749
+ */
1750
+ class EpdfAnnotationMenu {
1751
+ /** Which side of the selection: `'top'` (the default), `'bottom'`, `'left'` or `'right'`, or a corner. */
1752
+ placement = input('top', ...(ngDevMode ? [{ debugName: "placement" }] : []));
1753
+ /** Screen pixels between the selection and the menu. */
1754
+ gap = input(15, ...(ngDevMode ? [{ debugName: "gap" }] : []));
1755
+ annotation = annotationInScope('<epdf-annotation-menu>');
1756
+ projection = inject(EPDF_PROJECTOR, { optional: true });
1757
+ /**
1758
+ * The selection's anchor as its page is shown: the rotation handle sits a fixed number of
1759
+ * pixels off the box, so where it is on the page depends on the zoom. Read again with the
1760
+ * projection, in the same pass as the pages.
1761
+ */
1762
+ selection = this.annotation.select((annotation) => {
1763
+ const anchor = annotation.selection.getAnchor();
1764
+ if (!anchor)
1765
+ return null;
1766
+ this.projection?.revision();
1767
+ const view = this.projection?.projector()?.viewEnv(anchor.page);
1768
+ return view ? annotation.getSelectionAnchorIn(view) : anchor;
1769
+ }, null, sameSelectionAnchor);
1770
+ anchor = computed(() => {
1771
+ const anchor = this.selection();
1772
+ if (!anchor)
1773
+ return null;
1774
+ return {
1775
+ page: anchor.page,
1776
+ bounds: anchor.bounds,
1777
+ ...(anchor.rotationHandle ? { avoid: [anchor.rotationHandle] } : {}),
1778
+ };
1779
+ }, ...(ngDevMode ? [{ debugName: "anchor" }] : []));
1780
+ constructor() {
1781
+ if (!this.projection)
1782
+ throw outsideStageError('<epdf-annotation-menu>');
1783
+ }
1784
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.2.4", ngImport: i0, type: EpdfAnnotationMenu, deps: [], target: i0.ɵɵFactoryTarget.Component });
1785
+ 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: `
1786
+ <epdf-anchored [anchor]="anchor()" [placement]="placement()" [gap]="gap()">
1787
+ <ng-content />
1788
+ </epdf-anchored>
1789
+ `, isInline: true, dependencies: [{ kind: "component", type: EpdfAnchored, selector: "epdf-anchored", inputs: ["anchor", "placement", "gap", "pinned"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
1790
+ }
1791
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.2.4", ngImport: i0, type: EpdfAnnotationMenu, decorators: [{
1792
+ type: Component,
1793
+ args: [{
1794
+ selector: 'epdf-annotation-menu',
1795
+ imports: [EpdfAnchored],
1796
+ changeDetection: ChangeDetectionStrategy.OnPush,
1797
+ template: `
1798
+ <epdf-anchored [anchor]="anchor()" [placement]="placement()" [gap]="gap()">
1799
+ <ng-content />
1800
+ </epdf-anchored>
1801
+ `,
1802
+ }]
1803
+ }], ctorParameters: () => [] });
1804
+ /**
1805
+ * Buttons over a polygon or polyline being drawn, which helps on touch screens. `draft()` is the
1806
+ * shape being drawn (`pointCount`, `minPoints`, `canFinish`, …), or null; the verbs are the
1807
+ * annotation service's `draft.finish()` and `draft.cancel()`:
1808
+ *
1809
+ * <epdf-annotation-draft-menu #menu="epdfAnnotationDraftMenu">
1810
+ * <button [disabled]="!menu.draft()?.canFinish" (click)="annotation.draft.finish()">Done</button>
1811
+ * </epdf-annotation-draft-menu>
1812
+ */
1813
+ class EpdfAnnotationDraftMenu {
1814
+ /** Which side of the shape being drawn. */
1815
+ placement = input('top', ...(ngDevMode ? [{ debugName: "placement" }] : []));
1816
+ /** Screen pixels between the shape and the menu. */
1817
+ gap = input(8, ...(ngDevMode ? [{ debugName: "gap" }] : []));
1818
+ annotation = annotationInScope('<epdf-annotation-draft-menu>');
1819
+ /** The shape being drawn: where it is, its points so far and whether it can end; or null. */
1820
+ draft = this.annotation.select((annotation) => annotation.draft.get(), null, sameCreationDraftAnchor);
1821
+ constructor() {
1822
+ if (!inject(EPDF_PROJECTOR, { optional: true })) {
1823
+ throw outsideStageError('<epdf-annotation-draft-menu>');
1824
+ }
1825
+ }
1826
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.2.4", ngImport: i0, type: EpdfAnnotationDraftMenu, deps: [], target: i0.ɵɵFactoryTarget.Component });
1827
+ 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: `
1828
+ <epdf-anchored [anchor]="draft()" [placement]="placement()" [gap]="gap()">
1829
+ <ng-content />
1830
+ </epdf-anchored>
1831
+ `, isInline: true, dependencies: [{ kind: "component", type: EpdfAnchored, selector: "epdf-anchored", inputs: ["anchor", "placement", "gap", "pinned"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
1832
+ }
1833
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.2.4", ngImport: i0, type: EpdfAnnotationDraftMenu, decorators: [{
1834
+ type: Component,
1835
+ args: [{
1836
+ selector: 'epdf-annotation-draft-menu',
1837
+ exportAs: 'epdfAnnotationDraftMenu',
1838
+ imports: [EpdfAnchored],
1839
+ changeDetection: ChangeDetectionStrategy.OnPush,
1840
+ template: `
1841
+ <epdf-anchored [anchor]="draft()" [placement]="placement()" [gap]="gap()">
1842
+ <ng-content />
1843
+ </epdf-anchored>
1844
+ `,
1845
+ }]
1846
+ }], ctorParameters: () => [] });
1847
+ /**
1848
+ * The angle next to the pointer while a selection is turned, upright however the page is shown.
1849
+ * It shows the angle in the `chrome.readout` colors when `chrome.readout.enabled`; give it an
1850
+ * `<ng-template epdfRotationBadge let-rotation>` to draw it your way. `rotation()` is the turn
1851
+ * in progress (`page`, `at`, `angle`), or null.
1852
+ */
1853
+ class EpdfAnnotationRotationBadge {
1854
+ /** Which side of the pointer. */
1855
+ placement = input('right', ...(ngDevMode ? [{ debugName: "placement" }] : []));
1856
+ /** Screen pixels between the pointer and the badge. */
1857
+ gap = input(16, ...(ngDevMode ? [{ debugName: "gap" }] : []));
1858
+ annotation = annotationInScope('<epdf-annotation-rotation-badge>');
1859
+ settings = injectChromeSettings();
1860
+ custom = contentChild(EpdfRotationBadgeTemplate, ...(ngDevMode ? [{ debugName: "custom" }] : []));
1861
+ /** The turn in progress: its page, the pointer's point and the angle so far; or null. */
1862
+ rotation = this.annotation.select((annotation) => annotation.selection.getRotationAnchor(), null, sameRotationAnchor);
1863
+ /** Shown while turning, with a template of yours or with the readout on. */
1864
+ shown = computed(() => {
1865
+ const rotation = this.rotation();
1866
+ return rotation && (this.custom() || this.settings.chrome().readout.enabled) ? rotation : null;
1867
+ }, ...(ngDevMode ? [{ debugName: "shown" }] : []));
1868
+ /** A box of no size at the pointer. */
1869
+ anchor = computed(() => {
1870
+ const rotation = this.rotation();
1871
+ return rotation
1872
+ ? { page: rotation.page, bounds: { ...rotation.at, width: 0, height: 0 } }
1873
+ : null;
1874
+ }, ...(ngDevMode ? [{ debugName: "anchor" }] : []));
1875
+ fontFamily = paintDefault('font-mono');
1876
+ background = computed(() => paint('readout-background', this.settings.chrome().readout.background), ...(ngDevMode ? [{ debugName: "background" }] : []));
1877
+ color = computed(() => paint('readout-color', this.settings.chrome().readout.color), ...(ngDevMode ? [{ debugName: "color" }] : []));
1878
+ constructor() {
1879
+ if (!inject(EPDF_PROJECTOR, { optional: true })) {
1880
+ throw outsideStageError('<epdf-annotation-rotation-badge>');
1881
+ }
1882
+ }
1883
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.2.4", ngImport: i0, type: EpdfAnnotationRotationBadge, deps: [], target: i0.ɵɵFactoryTarget.Component });
1884
+ 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: `
1885
+ @if (shown(); as rotation) {
1886
+ <epdf-anchored [anchor]="anchor()" [placement]="placement()" [gap]="gap()">
1887
+ @if (custom(); as custom) {
1888
+ <ng-container
1889
+ [ngTemplateOutlet]="custom.template"
1890
+ [ngTemplateOutletContext]="{ $implicit: rotation }"
1891
+ />
1892
+ } @else {
1893
+ <div
1894
+ style="pointer-events: none; white-space: nowrap; border-radius: 4px; padding: 2px 6px; font-size: 12px"
1895
+ [style.font-family]="fontFamily"
1896
+ [style.background]="background()"
1897
+ [style.color]="color()"
1898
+ >
1899
+ {{ rotation.angle }}°
1900
+ </div>
1901
+ }
1902
+ </epdf-anchored>
1903
+ }
1904
+ `, 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 });
1905
+ }
1906
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.2.4", ngImport: i0, type: EpdfAnnotationRotationBadge, decorators: [{
1907
+ type: Component,
1908
+ args: [{
1909
+ selector: 'epdf-annotation-rotation-badge',
1910
+ exportAs: 'epdfAnnotationRotationBadge',
1911
+ imports: [EpdfAnchored, NgTemplateOutlet],
1912
+ changeDetection: ChangeDetectionStrategy.OnPush,
1913
+ template: `
1914
+ @if (shown(); as rotation) {
1915
+ <epdf-anchored [anchor]="anchor()" [placement]="placement()" [gap]="gap()">
1916
+ @if (custom(); as custom) {
1917
+ <ng-container
1918
+ [ngTemplateOutlet]="custom.template"
1919
+ [ngTemplateOutletContext]="{ $implicit: rotation }"
1920
+ />
1921
+ } @else {
1922
+ <div
1923
+ style="pointer-events: none; white-space: nowrap; border-radius: 4px; padding: 2px 6px; font-size: 12px"
1924
+ [style.font-family]="fontFamily"
1925
+ [style.background]="background()"
1926
+ [style.color]="color()"
1927
+ >
1928
+ {{ rotation.angle }}°
1929
+ </div>
1930
+ }
1931
+ </epdf-anchored>
1932
+ }
1933
+ `,
1934
+ }]
1935
+ }], ctorParameters: () => [] });
1936
+
1937
+ /**
1938
+ * @embedpdf/angular/annotation: drawing, selecting, changing and saving annotations.
1939
+ *
1940
+ * withAnnotation(options) the plugin, for provideEmbedPdf() (with withInteraction())
1941
+ * withFilePicker(provider?) the file dialog behind the stamp and attachment tools
1942
+ * inject(EpdfAnnotation) the API, the State table as signals, the events as
1943
+ * streams, and `watch()`, `anchorOf()`, `tools.defaultsOf()`
1944
+ * inject(EpdfComments) threads and replies: `threads()`, `threadOf(ref)`, `reply()`, …
1945
+ * <epdf-annotation-layer> the annotations, the selection and the text boxes, in each page
1946
+ * <ng-template [epdfAnnotation]> your own look for some annotations
1947
+ * <ng-template epdfHandle>, epdfRotationHandle your own handles
1948
+ * [epdfRichTextEditor] your element as a text box's editor, in a look
1949
+ * <epdf-annotation-menu> your menu next to the selection, inside <epdf-stage>
1950
+ * <epdf-annotation-draft-menu> buttons over a polygon being drawn
1951
+ * <epdf-annotation-rotation-badge> the angle while a selection is turned
1952
+ */
1953
+ // The plugin's types and helpers, so app code has one import for the feature.
1954
+
1955
+ /**
1956
+ * Generated bundle index. Do not edit.
1957
+ */
1958
+
1959
+ export { EpdfAnnotation, EpdfAnnotationBehaviorTemplate, EpdfAnnotationDraftMenu, EpdfAnnotationLayer, EpdfAnnotationMenu, EpdfAnnotationRotationBadge, EpdfAnnotationTemplate, EpdfComments, EpdfHandleTemplate, EpdfRichTextEditor, EpdfRotationBadgeTemplate, EpdfRotationHandleTemplate, filePickerProvider, withAnnotation, withFilePicker };
1960
+ //# sourceMappingURL=embedpdf-angular-annotation.mjs.map