@aprova.ch/ngx-next-pdf-viewer 20.0.8

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,744 @@
1
+ import * as i0 from '@angular/core';
2
+ import { InjectionToken, EventEmitter, HostListener, ViewChild, Output, Input, Inject, Component } from '@angular/core';
3
+ import * as i1 from '@angular/common';
4
+ import { CommonModule } from '@angular/common';
5
+ import * as i2 from '@angular/forms';
6
+ import { FormsModule } from '@angular/forms';
7
+ import * as i3 from '@angular/material/icon';
8
+ import { MatIconModule } from '@angular/material/icon';
9
+ import * as i4 from '@angular/material/button';
10
+ import { MatButtonModule } from '@angular/material/button';
11
+ import * as i5 from '@angular/material/tooltip';
12
+ import { MatTooltipModule } from '@angular/material/tooltip';
13
+ import { MatInputModule } from '@angular/material/input';
14
+ import { MatFormFieldModule } from '@angular/material/form-field';
15
+ import * as pdfjsLib from 'pdfjs-dist';
16
+
17
+ /**
18
+ * URL to the PDF.js worker script.
19
+ *
20
+ * Default: `'assets/pdf.worker.min.mjs'`
21
+ *
22
+ * Override in your application by providing this token:
23
+ * ```ts
24
+ * providers: [
25
+ * { provide: NGX_PDF_WORKER_SRC, useValue: 'assets/pdf.worker.min.mjs' }
26
+ * ]
27
+ * ```
28
+ */
29
+ const NGX_PDF_WORKER_SRC = new InjectionToken('NGX_PDF_WORKER_SRC', { factory: () => 'assets/pdf.worker.min.mjs' });
30
+
31
+ // ──────────────────────────────────────────────
32
+ // Component
33
+ // ──────────────────────────────────────────────
34
+ class NgxNextPdfViewerComponent {
35
+ static { this.DEFAULT_LABELS = {
36
+ zoomOut: 'Zoom out',
37
+ zoomIn: 'Zoom in',
38
+ rotateLeft: 'Rotate left',
39
+ rotateRight: 'Rotate right',
40
+ previousPage: 'Previous page',
41
+ nextPage: 'Next page',
42
+ search: 'Search',
43
+ previousResult: 'Previous result',
44
+ nextResult: 'Next result',
45
+ previousAnnotation: 'Previous annotation',
46
+ nextAnnotation: 'Next annotation',
47
+ selectArea: 'Select area',
48
+ selectText: 'Select & copy text',
49
+ searchPlaceholder: 'Search in PDF…',
50
+ noResults: 'No results',
51
+ loading: 'Loading PDF…',
52
+ noDocument: 'No PDF loaded',
53
+ loadError: 'Failed to load PDF',
54
+ }; }
55
+ get l() {
56
+ return { ...NgxNextPdfViewerComponent.DEFAULT_LABELS, ...this.labels };
57
+ }
58
+ constructor(cdr, zone, el, workerSrc) {
59
+ this.cdr = cdr;
60
+ this.zone = zone;
61
+ this.el = el;
62
+ /** Bounding-box annotations to overlay */
63
+ this.annotations = [];
64
+ /** Items shown in the context menu after drag-selecting boxes */
65
+ this.contextMenuItems = [];
66
+ /** Fill colour for a single-clicked box (CSS colour string) */
67
+ this.highlightingColor = 'rgba(255, 200, 0, 0.35)';
68
+ /** Fill colour for drag-selected boxes (CSS colour string) */
69
+ this.selectedColor = 'rgba(0, 120, 215, 0.35)';
70
+ /** Stroke colour for all annotation polygons */
71
+ this.annotationStroke = '#e97332';
72
+ /** Initial zoom: numeric level (1.0 = 100 %) or 'fit-to-page' | 'fit-to-width' | 'fit-to-height' */
73
+ this.initialZoom = 1.5;
74
+ /** Override any subset of the viewer's user-visible strings */
75
+ this.labels = {};
76
+ // ── Outputs ─────────────────────────────────
77
+ /** Fired when the user clicks a single annotation box */
78
+ this.boxClicked = new EventEmitter();
79
+ /** Fired when the user picks a context-menu item after drag-selection */
80
+ this.boxesSelected = new EventEmitter();
81
+ // ── PDF state ────────────────────────────────
82
+ this.pdfDoc = null;
83
+ this.renderedPages = [];
84
+ this.currentPage = 1;
85
+ this.totalPages = 0;
86
+ this.zoom = 1.5;
87
+ this.userRotation = 0;
88
+ // ── Annotation state ─────────────────────────
89
+ this.clickedBoxKey = null;
90
+ this.multiSelectedKeys = new Set();
91
+ // ── Drag-select state ────────────────────────
92
+ this.drag = { active: false, pageNum: 0, startX: 0, startY: 0, currentX: 0, currentY: 0 };
93
+ // ── Context-menu state ───────────────────────
94
+ this.ctxVisible = false;
95
+ this.ctxX = 0;
96
+ this.ctxY = 0;
97
+ // ── Search state ─────────────────────────────
98
+ this.searchVisible = false;
99
+ this.searchQuery = '';
100
+ this.searchMatches = [];
101
+ this.searchIndex = -1;
102
+ // ── Selection mode ───────────────────────────
103
+ this.selectionMode = 'select-area';
104
+ // ── Loading ──────────────────────────────────
105
+ this.loading = false;
106
+ this.loadError = false;
107
+ this.renderTasks = new Map();
108
+ this.textLayers = new Map();
109
+ pdfjsLib.GlobalWorkerOptions.workerSrc = workerSrc;
110
+ }
111
+ ngAfterViewInit() {
112
+ if (typeof this.initialZoom === 'number') {
113
+ this.zoom = this.initialZoom;
114
+ }
115
+ // Ctrl+scroll zooming – requires passive:false to call preventDefault()
116
+ this.zone.runOutsideAngular(() => {
117
+ this.wheelListener = (e) => {
118
+ if (!e.ctrlKey)
119
+ return;
120
+ e.preventDefault();
121
+ this.zone.run(() => e.deltaY < 0 ? this.zoomIn() : this.zoomOut());
122
+ };
123
+ this.el.nativeElement.addEventListener('wheel', this.wheelListener, { passive: false });
124
+ });
125
+ }
126
+ ngOnChanges(changes) {
127
+ if (changes['blob'] && this.blob) {
128
+ console.log('load new file');
129
+ this.loadPdf(this.blob);
130
+ }
131
+ if (changes['annotations'] && !changes['blob']) {
132
+ console.log('set new annotations', this.annotations.length);
133
+ this.cdr.detectChanges();
134
+ if (this.annotations.length === 1) {
135
+ this.scrollToBox(this.annotations[0]);
136
+ }
137
+ }
138
+ }
139
+ ngOnDestroy() {
140
+ if (this.wheelListener) {
141
+ this.el.nativeElement.removeEventListener('wheel', this.wheelListener);
142
+ }
143
+ this.removeAllTextLayers();
144
+ this.pdfDoc?.destroy();
145
+ }
146
+ // ─────────────────────────────────────────────
147
+ // PDF loading & rendering
148
+ // ─────────────────────────────────────────────
149
+ async loadPdf(blob) {
150
+ this.loading = true;
151
+ this.loadError = false;
152
+ this.renderedPages = [];
153
+ this.clickedBoxKey = null;
154
+ this.multiSelectedKeys.clear();
155
+ this.searchMatches = [];
156
+ this.searchIndex = -1;
157
+ this.removeAllTextLayers();
158
+ this.cdr.detectChanges();
159
+ try {
160
+ this.pdfDoc?.destroy();
161
+ const buf = await blob.arrayBuffer();
162
+ this.pdfDoc = await pdfjsLib.getDocument({ data: buf }).promise;
163
+ this.totalPages = this.pdfDoc.numPages;
164
+ this.currentPage = 1;
165
+ if (typeof this.initialZoom !== 'number') {
166
+ this.zoom = await this.computeFitZoom(this.initialZoom);
167
+ }
168
+ this.loading = false;
169
+ this.cdr.detectChanges();
170
+ for (let p = 1; p <= this.totalPages; p++) {
171
+ await this.renderPage(p);
172
+ }
173
+ if (this.annotations.length === 1) {
174
+ this.scrollToBox(this.annotations[0]);
175
+ }
176
+ }
177
+ catch {
178
+ this.loading = false;
179
+ this.loadError = true;
180
+ }
181
+ this.cdr.detectChanges();
182
+ }
183
+ async computeFitZoom(mode) {
184
+ if (!this.pdfDoc)
185
+ return 1;
186
+ const page = await this.pdfDoc.getPage(1);
187
+ const rotation = (page.rotate + this.userRotation) % 360;
188
+ const vp = page.getViewport({ scale: 1, rotation });
189
+ page.cleanup();
190
+ const container = this.pdfContainer?.nativeElement;
191
+ if (!container)
192
+ return 1;
193
+ // Subtract the 16 px padding on each side defined in npv-scroll-inner
194
+ const availW = container.clientWidth - 32;
195
+ const availH = container.clientHeight - 32;
196
+ const scaleW = availW / vp.width;
197
+ const scaleH = availH / vp.height;
198
+ switch (mode) {
199
+ case 'fit-to-width': return scaleW;
200
+ case 'fit-to-height': return scaleH;
201
+ case 'fit-to-page': return Math.min(scaleW, scaleH);
202
+ }
203
+ }
204
+ async renderPage(pageNum) {
205
+ if (!this.pdfDoc)
206
+ return;
207
+ const page = await this.pdfDoc.getPage(pageNum);
208
+ const totalRotation = (page.rotate + this.userRotation) % 360;
209
+ const viewport = page.getViewport({ scale: this.zoom, rotation: totalRotation });
210
+ // HiDPI: render at physical pixel density, display at CSS size
211
+ const dpr = window.devicePixelRatio || 1;
212
+ const hiResViewport = page.getViewport({ scale: this.zoom * dpr, rotation: totalRotation });
213
+ const canvas = document.getElementById(`npv-canvas-${pageNum}`);
214
+ if (!canvas)
215
+ return;
216
+ canvas.width = hiResViewport.width;
217
+ canvas.height = hiResViewport.height;
218
+ canvas.style.width = `${viewport.width}px`;
219
+ canvas.style.height = `${viewport.height}px`;
220
+ if (this.renderTasks.has(pageNum)) {
221
+ try {
222
+ this.renderTasks.get(pageNum).cancel();
223
+ }
224
+ catch (_) { }
225
+ }
226
+ const ctx = canvas.getContext('2d');
227
+ const task = page.render({ canvasContext: ctx, viewport: hiResViewport });
228
+ this.renderTasks.set(pageNum, task);
229
+ try {
230
+ await task.promise;
231
+ }
232
+ catch (e) {
233
+ if (e?.name !== 'RenderingCancelledException')
234
+ console.error(e);
235
+ return;
236
+ }
237
+ this.renderTasks.delete(pageNum);
238
+ const existing = this.renderedPages.find(r => r.pageNum === pageNum);
239
+ if (existing) {
240
+ existing.width = viewport.width;
241
+ existing.height = viewport.height;
242
+ existing.rotation = totalRotation;
243
+ }
244
+ else {
245
+ this.renderedPages.push({ pageNum, width: viewport.width, height: viewport.height, rotation: totalRotation });
246
+ }
247
+ this.cdr.detectChanges();
248
+ page.cleanup();
249
+ }
250
+ async rerenderAll() {
251
+ if (!this.pdfDoc)
252
+ return;
253
+ if (this.selectionMode === 'select-text') {
254
+ this.removeAllTextLayers();
255
+ }
256
+ for (let p = 1; p <= this.totalPages; p++) {
257
+ await this.renderPage(p);
258
+ if (this.selectionMode === 'select-text') {
259
+ await this.renderTextLayer(p);
260
+ }
261
+ }
262
+ this.cdr.detectChanges();
263
+ }
264
+ // ─────────────────────────────────────────────
265
+ // Coordinate transformation
266
+ // ─────────────────────────────────────────────
267
+ /** Converts a polygon (y=0 at top, any consistent unit) to SVG canvas coordinates */
268
+ transformPolygon(polygon, pdfWidth, pdfHeight, canvasWidth, canvasHeight, rotation) {
269
+ const norm = ((rotation % 360) + 360) % 360;
270
+ const result = [];
271
+ for (let i = 0; i < polygon.length; i += 2) {
272
+ const px = polygon[i];
273
+ const py = polygon[i + 1];
274
+ let cx, cy;
275
+ switch (norm) {
276
+ case 90:
277
+ cx = (pdfHeight - py) * canvasWidth / pdfHeight;
278
+ cy = px * canvasHeight / pdfWidth;
279
+ break;
280
+ case 180:
281
+ cx = (pdfWidth - px) * canvasWidth / pdfWidth;
282
+ cy = (pdfHeight - py) * canvasHeight / pdfHeight;
283
+ break;
284
+ case 270:
285
+ cx = py * canvasWidth / pdfHeight;
286
+ cy = (pdfWidth - px) * canvasHeight / pdfWidth;
287
+ break;
288
+ default:
289
+ cx = px * canvasWidth / pdfWidth;
290
+ cy = py * canvasHeight / pdfHeight;
291
+ }
292
+ result.push(cx, cy);
293
+ }
294
+ return result;
295
+ }
296
+ getSvgPoints(box, pi) {
297
+ const pts = this.transformPolygon(box.polygon, box.width, box.height, pi.width, pi.height, pi.rotation);
298
+ const pairs = [];
299
+ for (let i = 0; i < pts.length; i += 2) {
300
+ pairs.push(`${pts[i].toFixed(2)},${pts[i + 1].toFixed(2)}`);
301
+ }
302
+ return pairs.join(' ');
303
+ }
304
+ polygonBBox(pts) {
305
+ let minX = Infinity, minY = Infinity, maxX = -Infinity, maxY = -Infinity;
306
+ for (let i = 0; i < pts.length; i += 2) {
307
+ minX = Math.min(minX, pts[i]);
308
+ maxX = Math.max(maxX, pts[i]);
309
+ minY = Math.min(minY, pts[i + 1]);
310
+ maxY = Math.max(maxY, pts[i + 1]);
311
+ }
312
+ return { x: minX, y: minY, w: maxX - minX, h: maxY - minY };
313
+ }
314
+ boxKey(box) {
315
+ return JSON.stringify({ page: box.page, polygon: box.polygon });
316
+ }
317
+ // ─────────────────────────────────────────────
318
+ // Template helpers
319
+ // ─────────────────────────────────────────────
320
+ getPageInfo(pageNum) {
321
+ return this.renderedPages.find(r => r.pageNum === pageNum);
322
+ }
323
+ getBoxesForPage(pageNum) {
324
+ return this.annotations.filter(b => b.page === pageNum);
325
+ }
326
+ isClicked(box) {
327
+ return this.clickedBoxKey === this.boxKey(box);
328
+ }
329
+ isMultiSelected(box) {
330
+ return this.multiSelectedKeys.has(this.boxKey(box));
331
+ }
332
+ pageRange() {
333
+ return Array.from({ length: this.totalPages }, (_, i) => i + 1);
334
+ }
335
+ getDragRect(pageNum) {
336
+ if (!this.drag.active || this.drag.pageNum !== pageNum)
337
+ return null;
338
+ return {
339
+ x: Math.min(this.drag.startX, this.drag.currentX),
340
+ y: Math.min(this.drag.startY, this.drag.currentY),
341
+ w: Math.abs(this.drag.currentX - this.drag.startX),
342
+ h: Math.abs(this.drag.currentY - this.drag.startY)
343
+ };
344
+ }
345
+ getSearchMatchesForPage(pageNum) {
346
+ return this.searchMatches.filter(m => m.pageNum === pageNum);
347
+ }
348
+ getSearchIndexOffset(pageNum) {
349
+ return this.searchMatches.findIndex(m => m.pageNum === pageNum);
350
+ }
351
+ isCurrentSearchMatch(pageNum, localIndex) {
352
+ const offset = this.getSearchIndexOffset(pageNum);
353
+ return offset >= 0 && offset + localIndex === this.searchIndex;
354
+ }
355
+ searchMatchPoints(m) {
356
+ const pairs = [];
357
+ for (let i = 0; i < m.polygon.length; i += 2) {
358
+ pairs.push(`${m.polygon[i].toFixed(2)},${m.polygon[i + 1].toFixed(2)}`);
359
+ }
360
+ return pairs.join(' ');
361
+ }
362
+ // ─────────────────────────────────────────────
363
+ // Box interaction
364
+ // ─────────────────────────────────────────────
365
+ onBoxClick(event, box) {
366
+ event.stopPropagation();
367
+ this.ctxVisible = false;
368
+ this.multiSelectedKeys.clear();
369
+ const key = this.boxKey(box);
370
+ const wasSelected = this.clickedBoxKey === key;
371
+ this.clickedBoxKey = wasSelected ? null : key;
372
+ this.cdr.detectChanges();
373
+ this.boxClicked.emit({ box, selected: !wasSelected });
374
+ }
375
+ // ─────────────────────────────────────────────
376
+ // Drag-select
377
+ // ─────────────────────────────────────────────
378
+ onSvgMouseDown(event, pageNum) {
379
+ if (this.selectionMode === 'select-text')
380
+ return;
381
+ if (event.button !== 0)
382
+ return;
383
+ const tag = event.target.tagName?.toLowerCase();
384
+ if (tag === 'polygon' || tag === 'title')
385
+ return;
386
+ this.ctxVisible = false;
387
+ this.clickedBoxKey = null;
388
+ const pt = this.svgPoint(event, pageNum);
389
+ if (!pt)
390
+ return;
391
+ this.drag = { active: true, pageNum, startX: pt.x, startY: pt.y, currentX: pt.x, currentY: pt.y };
392
+ }
393
+ onDocMouseMove(event) {
394
+ if (!this.drag.active)
395
+ return;
396
+ const pt = this.svgPoint(event, this.drag.pageNum);
397
+ if (!pt)
398
+ return;
399
+ this.drag.currentX = pt.x;
400
+ this.drag.currentY = pt.y;
401
+ this.cdr.detectChanges();
402
+ }
403
+ onDocMouseUp(event) {
404
+ if (!this.drag.active)
405
+ return;
406
+ const rect = this.getDragRect(this.drag.pageNum);
407
+ this.drag.active = false;
408
+ if (!rect || (rect.w < 5 && rect.h < 5)) {
409
+ this.cdr.detectChanges();
410
+ return;
411
+ }
412
+ const pageInfo = this.getPageInfo(this.drag.pageNum);
413
+ if (!pageInfo) {
414
+ this.cdr.detectChanges();
415
+ return;
416
+ }
417
+ const hits = [];
418
+ for (const box of this.annotations) {
419
+ const pts = this.transformPolygon(box.polygon, box.width, box.height, pageInfo.width, pageInfo.height, pageInfo.rotation);
420
+ const bb = this.polygonBBox(pts);
421
+ if (this.rectsIntersect(rect, bb))
422
+ hits.push(box);
423
+ }
424
+ this.multiSelectedKeys = new Set(hits.map(b => this.boxKey(b)));
425
+ if (hits.length > 0 && this.contextMenuItems.length > 0) {
426
+ this.ctxX = event.clientX;
427
+ this.ctxY = event.clientY;
428
+ this.ctxVisible = true;
429
+ }
430
+ this.cdr.detectChanges();
431
+ }
432
+ svgPoint(event, pageNum) {
433
+ const svg = document.getElementById(`npv-svg-${pageNum}`);
434
+ if (!svg)
435
+ return null;
436
+ const r = svg.getBoundingClientRect();
437
+ return { x: event.clientX - r.left, y: event.clientY - r.top };
438
+ }
439
+ rectsIntersect(a, b) {
440
+ return !(a.x + a.w < b.x || b.x + b.w < a.x || a.y + a.h < b.y || b.y + b.h < a.y);
441
+ }
442
+ // ─────────────────────────────────────────────
443
+ // Context menu
444
+ // ─────────────────────────────────────────────
445
+ onContextMenuAction(item) {
446
+ this.ctxVisible = false;
447
+ const selected = this.annotations.filter(b => this.multiSelectedKeys.has(this.boxKey(b)));
448
+ this.boxesSelected.emit({ boxes: selected, action: item.action });
449
+ this.multiSelectedKeys.clear();
450
+ this.cdr.detectChanges();
451
+ }
452
+ onDocClick() {
453
+ if (this.ctxVisible) {
454
+ this.ctxVisible = false;
455
+ this.cdr.detectChanges();
456
+ }
457
+ }
458
+ // ─────────────────────────────────────────────
459
+ // Toolbar – zoom
460
+ // ─────────────────────────────────────────────
461
+ zoomIn() { this.zoom = Math.min(this.zoom + 0.25, 5); this.rerenderAll(); }
462
+ zoomOut() { this.zoom = Math.max(this.zoom - 0.25, 0.25); this.rerenderAll(); }
463
+ get zoomPercent() { return Math.round(this.zoom * 100); }
464
+ // ─────────────────────────────────────────────
465
+ // Toolbar – rotation
466
+ // ─────────────────────────────────────────────
467
+ rotateLeft() { this.userRotation = (this.userRotation + 270) % 360; this.rerenderAll(); }
468
+ rotateRight() { this.userRotation = (this.userRotation + 90) % 360; this.rerenderAll(); }
469
+ // ─────────────────────────────────────────────
470
+ // Toolbar – page navigation
471
+ // ─────────────────────────────────────────────
472
+ goToPrevPage() { if (this.currentPage > 1)
473
+ this.scrollToPageNum(this.currentPage - 1); }
474
+ goToNextPage() { if (this.currentPage < this.totalPages)
475
+ this.scrollToPageNum(this.currentPage + 1); }
476
+ scrollToPageNum(pageNum) {
477
+ this.currentPage = pageNum;
478
+ document.getElementById(`npv-page-${pageNum}`)?.scrollIntoView({ behavior: 'smooth', block: 'start' });
479
+ }
480
+ scrollToBox(box) {
481
+ this.currentPage = box.page;
482
+ console.log('scroll to page', this.currentPage);
483
+ const pageEl = document.getElementById(`npv-page-${box.page}`);
484
+ if (!pageEl) {
485
+ console.log('no page element found, stop scrolling');
486
+ return;
487
+ }
488
+ const pi = this.getPageInfo(box.page);
489
+ if (!pi) {
490
+ pageEl.scrollIntoView({ behavior: 'smooth', block: 'start' });
491
+ console.log('scroll into view done');
492
+ return;
493
+ }
494
+ const pts = this.transformPolygon(box.polygon, box.width, box.height, pi.width, pi.height, pi.rotation);
495
+ const bb = this.polygonBBox(pts);
496
+ const top = pageEl.offsetTop + bb.y - window.innerHeight / 2;
497
+ this.pdfContainer?.nativeElement.scrollTo({ top, behavior: 'smooth' });
498
+ console.log('scrolling to element done');
499
+ }
500
+ // ─────────────────────────────────────────────
501
+ // Toolbar – search
502
+ // ─────────────────────────────────────────────
503
+ toggleSearch() {
504
+ this.searchVisible = !this.searchVisible;
505
+ if (!this.searchVisible) {
506
+ this.searchQuery = '';
507
+ this.searchMatches = [];
508
+ this.searchIndex = -1;
509
+ this.cdr.detectChanges();
510
+ }
511
+ }
512
+ async onSearchInput() {
513
+ const q = this.searchQuery.trim().toLowerCase();
514
+ this.searchMatches = [];
515
+ this.searchIndex = -1;
516
+ if (!q || !this.pdfDoc) {
517
+ this.cdr.detectChanges();
518
+ return;
519
+ }
520
+ for (let p = 1; p <= this.totalPages; p++) {
521
+ const pi = this.getPageInfo(p);
522
+ if (!pi)
523
+ continue;
524
+ const page = await this.pdfDoc.getPage(p);
525
+ const text = await page.getTextContent();
526
+ const vp = page.getViewport({ scale: this.zoom, rotation: pi.rotation });
527
+ for (const item of text.items) {
528
+ if (!item.str)
529
+ continue;
530
+ const idx = item.str.toLowerCase().indexOf(q);
531
+ if (idx < 0)
532
+ continue;
533
+ const charW = (item.width / item.str.length) || 6;
534
+ const itemH = Math.abs(item.transform[3]) || 10;
535
+ const x0 = item.transform[4] + idx * charW;
536
+ const y0 = item.transform[5];
537
+ const t = vp.transform;
538
+ const applyT = (x, y) => [t[0] * x + t[2] * y + t[4], t[1] * x + t[3] * y + t[5]];
539
+ const [cx0, cy0] = applyT(x0, y0 + itemH);
540
+ const [cx1, cy1] = applyT(x0 + q.length * charW, y0);
541
+ this.searchMatches.push({
542
+ pageNum: p,
543
+ polygon: [cx0, cy0, cx1, cy0, cx1, cy1, cx0, cy1]
544
+ });
545
+ }
546
+ page.cleanup();
547
+ }
548
+ if (this.searchMatches.length > 0) {
549
+ this.searchIndex = 0;
550
+ this.scrollToSearchMatch(0);
551
+ }
552
+ this.cdr.detectChanges();
553
+ }
554
+ searchNext() {
555
+ if (!this.searchMatches.length)
556
+ return;
557
+ this.searchIndex = (this.searchIndex + 1) % this.searchMatches.length;
558
+ this.scrollToSearchMatch(this.searchIndex);
559
+ this.cdr.detectChanges();
560
+ }
561
+ searchPrev() {
562
+ if (!this.searchMatches.length)
563
+ return;
564
+ this.searchIndex = (this.searchIndex - 1 + this.searchMatches.length) % this.searchMatches.length;
565
+ this.scrollToSearchMatch(this.searchIndex);
566
+ this.cdr.detectChanges();
567
+ }
568
+ scrollToSearchMatch(idx) {
569
+ const m = this.searchMatches[idx];
570
+ if (!m)
571
+ return;
572
+ document.getElementById(`npv-page-${m.pageNum}`)?.scrollIntoView({ behavior: 'smooth', block: 'center' });
573
+ }
574
+ // ─────────────────────────────────────────────
575
+ // Toolbar – annotation navigation
576
+ // ─────────────────────────────────────────────
577
+ get annotationIndex() {
578
+ if (!this.clickedBoxKey)
579
+ return -1;
580
+ return this.annotations.findIndex(b => this.boxKey(b) === this.clickedBoxKey);
581
+ }
582
+ goToPrevAnnotation() {
583
+ if (!this.annotations.length)
584
+ return;
585
+ const prev = (this.annotationIndex - 1 + this.annotations.length) % this.annotations.length;
586
+ this.selectAnnotation(prev);
587
+ }
588
+ goToNextAnnotation() {
589
+ if (!this.annotations.length)
590
+ return;
591
+ const next = (this.annotationIndex + 1) % this.annotations.length;
592
+ this.selectAnnotation(next);
593
+ }
594
+ selectAnnotation(index) {
595
+ const box = this.annotations[index];
596
+ if (!box)
597
+ return;
598
+ this.multiSelectedKeys.clear();
599
+ this.ctxVisible = false;
600
+ this.clickedBoxKey = this.boxKey(box);
601
+ this.cdr.detectChanges();
602
+ this.scrollToBox(box);
603
+ this.boxClicked.emit({ box, selected: true });
604
+ }
605
+ // ─────────────────────────────────────────────
606
+ // Toolbar – selection mode
607
+ // ─────────────────────────────────────────────
608
+ toggleSelectionMode() {
609
+ this.selectionMode = this.selectionMode === 'select-area' ? 'select-text' : 'select-area';
610
+ if (this.selectionMode === 'select-text') {
611
+ this.renderedPages.forEach(rp => this.renderTextLayer(rp.pageNum));
612
+ }
613
+ else {
614
+ this.removeAllTextLayers();
615
+ }
616
+ this.cdr.detectChanges();
617
+ }
618
+ async renderTextLayer(pageNum) {
619
+ if (!this.pdfDoc)
620
+ return;
621
+ const pi = this.getPageInfo(pageNum);
622
+ if (!pi)
623
+ return;
624
+ const wrapper = document.getElementById(`npv-page-${pageNum}`);
625
+ if (!wrapper)
626
+ return;
627
+ this.removeTextLayer(pageNum);
628
+ const page = await this.pdfDoc.getPage(pageNum);
629
+ const totalRotation = (page.rotate + this.userRotation) % 360;
630
+ const viewport = page.getViewport({ scale: this.zoom, rotation: totalRotation });
631
+ const div = document.createElement('div');
632
+ div.className = 'npv-text-layer';
633
+ div.style.width = `${pi.width}px`;
634
+ div.style.height = `${pi.height}px`;
635
+ wrapper.appendChild(div);
636
+ const textLayer = new pdfjsLib.TextLayer({
637
+ textContentSource: page.streamTextContent(),
638
+ container: div,
639
+ viewport,
640
+ });
641
+ this.textLayers.set(pageNum, textLayer);
642
+ try {
643
+ await textLayer.render();
644
+ }
645
+ catch (_) { }
646
+ page.cleanup();
647
+ }
648
+ removeTextLayer(pageNum) {
649
+ const tl = this.textLayers.get(pageNum);
650
+ if (tl) {
651
+ try {
652
+ tl.cancel();
653
+ }
654
+ catch (_) { }
655
+ this.textLayers.delete(pageNum);
656
+ }
657
+ const wrapper = document.getElementById(`npv-page-${pageNum}`);
658
+ wrapper?.querySelector('.npv-text-layer')?.remove();
659
+ }
660
+ removeAllTextLayers() {
661
+ for (const pageNum of Array.from(this.textLayers.keys())) {
662
+ this.removeTextLayer(pageNum);
663
+ }
664
+ }
665
+ // ─────────────────────────────────────────────
666
+ // Scroll tracking
667
+ // ─────────────────────────────────────────────
668
+ onContainerScroll() {
669
+ if (!this.pdfContainer)
670
+ return;
671
+ const top = this.pdfContainer.nativeElement.scrollTop;
672
+ let closest = 1, minDist = Infinity;
673
+ for (let p = 1; p <= this.totalPages; p++) {
674
+ const el = document.getElementById(`npv-page-${p}`);
675
+ if (!el)
676
+ continue;
677
+ const dist = Math.abs(el.offsetTop - top);
678
+ if (dist < minDist) {
679
+ minDist = dist;
680
+ closest = p;
681
+ }
682
+ }
683
+ if (this.currentPage !== closest) {
684
+ this.currentPage = closest;
685
+ this.cdr.detectChanges();
686
+ }
687
+ }
688
+ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.16", ngImport: i0, type: NgxNextPdfViewerComponent, deps: [{ token: i0.ChangeDetectorRef }, { token: i0.NgZone }, { token: i0.ElementRef }, { token: NGX_PDF_WORKER_SRC }], target: i0.ɵɵFactoryTarget.Component }); }
689
+ static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "20.3.16", type: NgxNextPdfViewerComponent, isStandalone: true, selector: "ngx-next-pdf-viewer", inputs: { blob: "blob", annotations: "annotations", contextMenuItems: "contextMenuItems", highlightingColor: "highlightingColor", selectedColor: "selectedColor", annotationStroke: "annotationStroke", initialZoom: "initialZoom", labels: "labels" }, outputs: { boxClicked: "boxClicked", boxesSelected: "boxesSelected" }, host: { listeners: { "mousemove": "onDocMouseMove($event)", "mouseup": "onDocMouseUp($event)", "document:click": "onDocClick()" } }, viewQueries: [{ propertyName: "pdfContainer", first: true, predicate: ["pdfContainer"], descendants: true }], usesOnChanges: true, ngImport: i0, template: "<div class=\"npv-root\">\n\n <!-- \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 Toolbar \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 -->\n <div class=\"npv-toolbar\">\n\n <button mat-icon-button [matTooltip]=\"l.zoomOut\" (click)=\"zoomOut()\" [disabled]=\"zoom <= 0.25\">\n <mat-icon>zoom_out</mat-icon>\n </button>\n <span class=\"npv-label\">{{ zoomPercent }}%</span>\n <button mat-icon-button [matTooltip]=\"l.zoomIn\" (click)=\"zoomIn()\" [disabled]=\"zoom >= 5\">\n <mat-icon>zoom_in</mat-icon>\n </button>\n\n <div class=\"npv-sep\"></div>\n\n <button mat-icon-button [matTooltip]=\"l.rotateLeft\" (click)=\"rotateLeft()\">\n <mat-icon>rotate_left</mat-icon>\n </button>\n <button mat-icon-button [matTooltip]=\"l.rotateRight\" (click)=\"rotateRight()\">\n <mat-icon>rotate_right</mat-icon>\n </button>\n\n <div class=\"npv-sep\"></div>\n\n <button mat-icon-button [matTooltip]=\"l.previousPage\" (click)=\"goToPrevPage()\" [disabled]=\"currentPage <= 1\">\n <mat-icon>chevron_left</mat-icon>\n </button>\n <span class=\"npv-label\">{{ currentPage }} / {{ totalPages }}</span>\n <button mat-icon-button [matTooltip]=\"l.nextPage\" (click)=\"goToNextPage()\" [disabled]=\"currentPage >= totalPages\">\n <mat-icon>chevron_right</mat-icon>\n </button>\n\n <ng-container *ngIf=\"annotations.length\">\n <div class=\"npv-sep\"></div>\n\n <button mat-icon-button [matTooltip]=\"l.previousAnnotation\" (click)=\"goToPrevAnnotation()\">\n <mat-icon>navigate_before</mat-icon>\n </button>\n <span class=\"npv-label\">\n {{ annotationIndex >= 0 ? annotationIndex + 1 : '\u2013' }} / {{ annotations.length }}\n </span>\n <button mat-icon-button [matTooltip]=\"l.nextAnnotation\" (click)=\"goToNextAnnotation()\">\n <mat-icon>navigate_next</mat-icon>\n </button>\n </ng-container>\n\n <div class=\"npv-sep\"></div>\n\n <button mat-icon-button\n [matTooltip]=\"selectionMode === 'select-area' ? l.selectText : l.selectArea\"\n (click)=\"toggleSelectionMode()\"\n [class.npv-active]=\"selectionMode === 'select-text'\">\n <mat-icon>text_fields</mat-icon>\n </button>\n\n <div class=\"npv-sep\"></div>\n\n <button mat-icon-button [matTooltip]=\"l.search\" (click)=\"toggleSearch()\" [class.npv-active]=\"searchVisible\">\n <mat-icon>search</mat-icon>\n </button>\n\n <ng-container *ngIf=\"searchVisible\">\n <input\n class=\"npv-search-input\"\n type=\"text\"\n [placeholder]=\"l.searchPlaceholder\"\n [(ngModel)]=\"searchQuery\"\n (input)=\"onSearchInput()\"\n (keydown.enter)=\"searchNext()\"\n (keydown.shift.enter)=\"searchPrev()\"\n autofocus\n />\n <button mat-icon-button [matTooltip]=\"l.previousResult\"\n (click)=\"searchPrev()\" [disabled]=\"!searchMatches.length\">\n <mat-icon>expand_less</mat-icon>\n </button>\n <button mat-icon-button [matTooltip]=\"l.nextResult\"\n (click)=\"searchNext()\" [disabled]=\"!searchMatches.length\">\n <mat-icon>expand_more</mat-icon>\n </button>\n <span class=\"npv-search-count\" *ngIf=\"searchMatches.length\">\n {{ searchIndex + 1 }} / {{ searchMatches.length }}\n </span>\n <span class=\"npv-search-count npv-no-match\" *ngIf=\"searchQuery && !searchMatches.length\">\n {{ l.noResults }}\n </span>\n </ng-container>\n\n </div>\n\n <!-- \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 Loading \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 -->\n <div class=\"npv-loading\" *ngIf=\"loading\">\n <span class=\"npv-spinner\"></span> {{ l.loading }}\n </div>\n\n <!-- \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 Scroll area \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 -->\n <div class=\"npv-scroll-area\" #pdfContainer\n (scroll)=\"onContainerScroll()\" (click)=\"ctxVisible = false\">\n <div class=\"npv-scroll-inner\">\n\n <ng-container *ngFor=\"let p of pageRange()\">\n <div class=\"npv-page-wrapper\" [id]=\"'npv-page-' + p\">\n\n <canvas [id]=\"'npv-canvas-' + p\" class=\"npv-canvas\"></canvas>\n\n <svg *ngIf=\"getPageInfo(p) as pi\"\n [id]=\"'npv-svg-' + p\"\n class=\"npv-overlay\"\n [class.npv-overlay--text-mode]=\"selectionMode === 'select-text'\"\n [attr.width]=\"pi.width\" [attr.height]=\"pi.height\"\n [attr.viewBox]=\"'0 0 ' + pi.width + ' ' + pi.height\"\n (mousedown)=\"onSvgMouseDown($event, p)\">\n\n <!-- Hit area for drag selection on empty page space -->\n <rect x=\"0\" y=\"0\" [attr.width]=\"pi.width\" [attr.height]=\"pi.height\"\n fill=\"transparent\"></rect>\n\n <!-- Annotation boxes -->\n <polygon\n *ngFor=\"let box of getBoxesForPage(p)\"\n [attr.points]=\"getSvgPoints(box, pi)\"\n class=\"npv-box\"\n [class.npv-box--clicked]=\"isClicked(box)\"\n [class.npv-box--multi]=\"isMultiSelected(box)\"\n [style.fill]=\"isClicked(box) ? highlightingColor : (isMultiSelected(box) ? selectedColor : 'transparent')\"\n [style.stroke]=\"annotationStroke\"\n (click)=\"onBoxClick($event, box)\">\n <title>{{ box.content || '' }}</title>\n </polygon>\n\n <!-- Search highlights -->\n <polygon\n *ngFor=\"let m of getSearchMatchesForPage(p); let mi = index\"\n [attr.points]=\"searchMatchPoints(m)\"\n class=\"npv-search-highlight\"\n [class.npv-search-highlight--current]=\"isCurrentSearchMatch(p, mi)\">\n </polygon>\n\n <!-- Drag selection rectangle -->\n <rect *ngIf=\"getDragRect(p) as dr\"\n [attr.x]=\"dr.x\" [attr.y]=\"dr.y\"\n [attr.width]=\"dr.w\" [attr.height]=\"dr.h\"\n class=\"npv-drag-rect\">\n </rect>\n\n </svg>\n </div>\n </ng-container>\n\n <div class=\"npv-placeholder npv-placeholder--error\" *ngIf=\"!loading && loadError\">\n {{ l.loadError }}\n </div>\n <div class=\"npv-placeholder\" *ngIf=\"!loading && !loadError && totalPages === 0\">\n {{ l.noDocument }}\n </div>\n\n </div><!-- /npv-scroll-inner -->\n </div>\n\n <!-- \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 Context menu \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 -->\n <div *ngIf=\"ctxVisible && contextMenuItems.length\"\n class=\"npv-context-menu\"\n [style.left.px]=\"ctxX\" [style.top.px]=\"ctxY\"\n (click)=\"$event.stopPropagation()\">\n <div *ngFor=\"let item of contextMenuItems\"\n class=\"npv-context-item\"\n (click)=\"onContextMenuAction(item)\">\n {{ item.label }}\n </div>\n </div>\n\n</div>\n", styles: [".npv-root{display:flex;flex-direction:column;height:100%;overflow:hidden;background:#e9ecee;position:relative;font-family:Roboto,sans-serif}.npv-toolbar{display:flex;align-items:center;gap:2px;padding:4px 8px;background:#fff;color:#000;min-height:48px;flex-shrink:0;z-index:10;box-shadow:0 2px 4px #0006}.npv-toolbar button.mat-icon-button{color:#505050}.npv-toolbar button.mat-icon-button:hover:not([disabled]){color:#fff;background:#ffffff1a}.npv-toolbar button.mat-icon-button[disabled]{opacity:.35}.npv-toolbar button.mat-icon-button.npv-active{color:#90caf9;background:#90caf91a}.npv-toolbar .npv-sep{width:1px;height:24px;background:#00000026;margin:0 4px}.npv-toolbar .npv-label{font-size:13px;min-width:50px;text-align:center;color:#505050;-webkit-user-select:none;user-select:none}.npv-toolbar .npv-search-input{background:#0000000f;border:1px solid rgba(0,0,0,.2);border-radius:4px;color:#000;font-size:13px;padding:4px 8px;outline:none;width:180px}.npv-toolbar .npv-search-input::placeholder{color:#0006}.npv-toolbar .npv-search-input:focus{border-color:#1976d2}.npv-toolbar .npv-search-count{font-size:12px;color:#607d8b;white-space:nowrap;min-width:60px}.npv-toolbar .npv-search-count.npv-no-match{color:#e53935}.npv-loading{display:flex;align-items:center;justify-content:center;gap:10px;padding:16px;color:#666;font-size:14px;flex-shrink:0}.npv-spinner{width:20px;height:20px;border:3px solid rgba(0,0,0,.1);border-top-color:#1976d2;border-radius:50%;animation:npv-spin .8s linear infinite}@keyframes npv-spin{to{transform:rotate(360deg)}}.npv-scroll-area{flex:1;overflow:auto}.npv-scroll-inner{display:flex;flex-direction:column;align-items:center;min-width:max-content;padding:16px;gap:16px}.npv-page-wrapper{position:relative;display:inline-block;box-shadow:0 4px 12px #00000080;line-height:0}.npv-canvas{display:block}.npv-overlay{position:absolute;top:0;left:0;pointer-events:all;overflow:visible;cursor:crosshair;user-select:none;-webkit-user-select:none}.npv-overlay.npv-overlay--text-mode{pointer-events:none;cursor:text}:host ::ng-deep .npv-text-layer{position:absolute;top:0;left:0;overflow:hidden;line-height:1;-webkit-text-size-adjust:none;text-size-adjust:none;forced-color-adjust:none}:host ::ng-deep .npv-text-layer span,:host ::ng-deep .npv-text-layer br{color:transparent;position:absolute;white-space:pre;cursor:text;transform-origin:0% 0%}:host ::ng-deep .npv-text-layer ::selection{background:#0078d740;color:transparent}.npv-box{pointer-events:all;stroke-width:2;fill:transparent;cursor:pointer;transition:fill .15s ease,stroke-width .15s ease}.npv-box:hover{stroke-width:3;filter:drop-shadow(0 0 3px rgba(233,115,50,.7))}.npv-box.npv-box--clicked{stroke-width:3}.npv-box.npv-box--multi{stroke-width:2.5}.npv-search-highlight{fill:#ffeb3b59;stroke:#ffc800cc;stroke-width:1.5;pointer-events:none}.npv-search-highlight.npv-search-highlight--current{fill:#ff980073;stroke:#ff6400e6;stroke-width:2}.npv-drag-rect{fill:#0078d71a;stroke:#0078d7b3;stroke-width:1.5;stroke-dasharray:5 3;pointer-events:none}.npv-placeholder{color:#aaa;font-size:14px;padding:40px;text-align:center}.npv-placeholder.npv-placeholder--error{color:#e53935}.npv-context-menu{position:fixed;z-index:9999;background:#fff;border:1px solid rgba(0,0,0,.12);border-radius:4px;box-shadow:0 4px 16px #0003;padding:4px 0;min-width:160px}.npv-context-item{padding:10px 16px;font-size:14px;cursor:pointer;color:#000000de;-webkit-user-select:none;user-select:none}.npv-context-item:hover{background:#0000000d}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "directive", type: i1.NgForOf, selector: "[ngFor][ngForOf]", inputs: ["ngForOf", "ngForTrackBy", "ngForTemplate"] }, { kind: "directive", type: i1.NgIf, selector: "[ngIf]", inputs: ["ngIf", "ngIfThen", "ngIfElse"] }, { kind: "ngmodule", type: FormsModule }, { kind: "directive", type: i2.DefaultValueAccessor, selector: "input:not([type=checkbox])[formControlName],textarea[formControlName],input:not([type=checkbox])[formControl],textarea[formControl],input:not([type=checkbox])[ngModel],textarea[ngModel],[ngDefaultControl]" }, { kind: "directive", type: i2.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i2.NgModel, selector: "[ngModel]:not([formControlName]):not([formControl])", inputs: ["name", "disabled", "ngModel", "ngModelOptions"], outputs: ["ngModelChange"], exportAs: ["ngModel"] }, { kind: "ngmodule", type: MatIconModule }, { kind: "component", type: i3.MatIcon, selector: "mat-icon", inputs: ["color", "inline", "svgIcon", "fontSet", "fontIcon"], exportAs: ["matIcon"] }, { kind: "ngmodule", type: MatButtonModule }, { kind: "component", type: i4.MatIconButton, selector: "button[mat-icon-button], a[mat-icon-button], button[matIconButton], a[matIconButton]", exportAs: ["matButton", "matAnchor"] }, { kind: "ngmodule", type: MatTooltipModule }, { kind: "directive", type: i5.MatTooltip, selector: "[matTooltip]", inputs: ["matTooltipPosition", "matTooltipPositionAtOrigin", "matTooltipDisabled", "matTooltipShowDelay", "matTooltipHideDelay", "matTooltipTouchGestures", "matTooltip", "matTooltipClass"], exportAs: ["matTooltip"] }, { kind: "ngmodule", type: MatInputModule }, { kind: "ngmodule", type: MatFormFieldModule }] }); }
690
+ }
691
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.16", ngImport: i0, type: NgxNextPdfViewerComponent, decorators: [{
692
+ type: Component,
693
+ args: [{ selector: 'ngx-next-pdf-viewer', standalone: true, imports: [
694
+ CommonModule,
695
+ FormsModule,
696
+ MatIconModule,
697
+ MatButtonModule,
698
+ MatTooltipModule,
699
+ MatInputModule,
700
+ MatFormFieldModule
701
+ ], template: "<div class=\"npv-root\">\n\n <!-- \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 Toolbar \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 -->\n <div class=\"npv-toolbar\">\n\n <button mat-icon-button [matTooltip]=\"l.zoomOut\" (click)=\"zoomOut()\" [disabled]=\"zoom <= 0.25\">\n <mat-icon>zoom_out</mat-icon>\n </button>\n <span class=\"npv-label\">{{ zoomPercent }}%</span>\n <button mat-icon-button [matTooltip]=\"l.zoomIn\" (click)=\"zoomIn()\" [disabled]=\"zoom >= 5\">\n <mat-icon>zoom_in</mat-icon>\n </button>\n\n <div class=\"npv-sep\"></div>\n\n <button mat-icon-button [matTooltip]=\"l.rotateLeft\" (click)=\"rotateLeft()\">\n <mat-icon>rotate_left</mat-icon>\n </button>\n <button mat-icon-button [matTooltip]=\"l.rotateRight\" (click)=\"rotateRight()\">\n <mat-icon>rotate_right</mat-icon>\n </button>\n\n <div class=\"npv-sep\"></div>\n\n <button mat-icon-button [matTooltip]=\"l.previousPage\" (click)=\"goToPrevPage()\" [disabled]=\"currentPage <= 1\">\n <mat-icon>chevron_left</mat-icon>\n </button>\n <span class=\"npv-label\">{{ currentPage }} / {{ totalPages }}</span>\n <button mat-icon-button [matTooltip]=\"l.nextPage\" (click)=\"goToNextPage()\" [disabled]=\"currentPage >= totalPages\">\n <mat-icon>chevron_right</mat-icon>\n </button>\n\n <ng-container *ngIf=\"annotations.length\">\n <div class=\"npv-sep\"></div>\n\n <button mat-icon-button [matTooltip]=\"l.previousAnnotation\" (click)=\"goToPrevAnnotation()\">\n <mat-icon>navigate_before</mat-icon>\n </button>\n <span class=\"npv-label\">\n {{ annotationIndex >= 0 ? annotationIndex + 1 : '\u2013' }} / {{ annotations.length }}\n </span>\n <button mat-icon-button [matTooltip]=\"l.nextAnnotation\" (click)=\"goToNextAnnotation()\">\n <mat-icon>navigate_next</mat-icon>\n </button>\n </ng-container>\n\n <div class=\"npv-sep\"></div>\n\n <button mat-icon-button\n [matTooltip]=\"selectionMode === 'select-area' ? l.selectText : l.selectArea\"\n (click)=\"toggleSelectionMode()\"\n [class.npv-active]=\"selectionMode === 'select-text'\">\n <mat-icon>text_fields</mat-icon>\n </button>\n\n <div class=\"npv-sep\"></div>\n\n <button mat-icon-button [matTooltip]=\"l.search\" (click)=\"toggleSearch()\" [class.npv-active]=\"searchVisible\">\n <mat-icon>search</mat-icon>\n </button>\n\n <ng-container *ngIf=\"searchVisible\">\n <input\n class=\"npv-search-input\"\n type=\"text\"\n [placeholder]=\"l.searchPlaceholder\"\n [(ngModel)]=\"searchQuery\"\n (input)=\"onSearchInput()\"\n (keydown.enter)=\"searchNext()\"\n (keydown.shift.enter)=\"searchPrev()\"\n autofocus\n />\n <button mat-icon-button [matTooltip]=\"l.previousResult\"\n (click)=\"searchPrev()\" [disabled]=\"!searchMatches.length\">\n <mat-icon>expand_less</mat-icon>\n </button>\n <button mat-icon-button [matTooltip]=\"l.nextResult\"\n (click)=\"searchNext()\" [disabled]=\"!searchMatches.length\">\n <mat-icon>expand_more</mat-icon>\n </button>\n <span class=\"npv-search-count\" *ngIf=\"searchMatches.length\">\n {{ searchIndex + 1 }} / {{ searchMatches.length }}\n </span>\n <span class=\"npv-search-count npv-no-match\" *ngIf=\"searchQuery && !searchMatches.length\">\n {{ l.noResults }}\n </span>\n </ng-container>\n\n </div>\n\n <!-- \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 Loading \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 -->\n <div class=\"npv-loading\" *ngIf=\"loading\">\n <span class=\"npv-spinner\"></span> {{ l.loading }}\n </div>\n\n <!-- \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 Scroll area \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 -->\n <div class=\"npv-scroll-area\" #pdfContainer\n (scroll)=\"onContainerScroll()\" (click)=\"ctxVisible = false\">\n <div class=\"npv-scroll-inner\">\n\n <ng-container *ngFor=\"let p of pageRange()\">\n <div class=\"npv-page-wrapper\" [id]=\"'npv-page-' + p\">\n\n <canvas [id]=\"'npv-canvas-' + p\" class=\"npv-canvas\"></canvas>\n\n <svg *ngIf=\"getPageInfo(p) as pi\"\n [id]=\"'npv-svg-' + p\"\n class=\"npv-overlay\"\n [class.npv-overlay--text-mode]=\"selectionMode === 'select-text'\"\n [attr.width]=\"pi.width\" [attr.height]=\"pi.height\"\n [attr.viewBox]=\"'0 0 ' + pi.width + ' ' + pi.height\"\n (mousedown)=\"onSvgMouseDown($event, p)\">\n\n <!-- Hit area for drag selection on empty page space -->\n <rect x=\"0\" y=\"0\" [attr.width]=\"pi.width\" [attr.height]=\"pi.height\"\n fill=\"transparent\"></rect>\n\n <!-- Annotation boxes -->\n <polygon\n *ngFor=\"let box of getBoxesForPage(p)\"\n [attr.points]=\"getSvgPoints(box, pi)\"\n class=\"npv-box\"\n [class.npv-box--clicked]=\"isClicked(box)\"\n [class.npv-box--multi]=\"isMultiSelected(box)\"\n [style.fill]=\"isClicked(box) ? highlightingColor : (isMultiSelected(box) ? selectedColor : 'transparent')\"\n [style.stroke]=\"annotationStroke\"\n (click)=\"onBoxClick($event, box)\">\n <title>{{ box.content || '' }}</title>\n </polygon>\n\n <!-- Search highlights -->\n <polygon\n *ngFor=\"let m of getSearchMatchesForPage(p); let mi = index\"\n [attr.points]=\"searchMatchPoints(m)\"\n class=\"npv-search-highlight\"\n [class.npv-search-highlight--current]=\"isCurrentSearchMatch(p, mi)\">\n </polygon>\n\n <!-- Drag selection rectangle -->\n <rect *ngIf=\"getDragRect(p) as dr\"\n [attr.x]=\"dr.x\" [attr.y]=\"dr.y\"\n [attr.width]=\"dr.w\" [attr.height]=\"dr.h\"\n class=\"npv-drag-rect\">\n </rect>\n\n </svg>\n </div>\n </ng-container>\n\n <div class=\"npv-placeholder npv-placeholder--error\" *ngIf=\"!loading && loadError\">\n {{ l.loadError }}\n </div>\n <div class=\"npv-placeholder\" *ngIf=\"!loading && !loadError && totalPages === 0\">\n {{ l.noDocument }}\n </div>\n\n </div><!-- /npv-scroll-inner -->\n </div>\n\n <!-- \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 Context menu \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 -->\n <div *ngIf=\"ctxVisible && contextMenuItems.length\"\n class=\"npv-context-menu\"\n [style.left.px]=\"ctxX\" [style.top.px]=\"ctxY\"\n (click)=\"$event.stopPropagation()\">\n <div *ngFor=\"let item of contextMenuItems\"\n class=\"npv-context-item\"\n (click)=\"onContextMenuAction(item)\">\n {{ item.label }}\n </div>\n </div>\n\n</div>\n", styles: [".npv-root{display:flex;flex-direction:column;height:100%;overflow:hidden;background:#e9ecee;position:relative;font-family:Roboto,sans-serif}.npv-toolbar{display:flex;align-items:center;gap:2px;padding:4px 8px;background:#fff;color:#000;min-height:48px;flex-shrink:0;z-index:10;box-shadow:0 2px 4px #0006}.npv-toolbar button.mat-icon-button{color:#505050}.npv-toolbar button.mat-icon-button:hover:not([disabled]){color:#fff;background:#ffffff1a}.npv-toolbar button.mat-icon-button[disabled]{opacity:.35}.npv-toolbar button.mat-icon-button.npv-active{color:#90caf9;background:#90caf91a}.npv-toolbar .npv-sep{width:1px;height:24px;background:#00000026;margin:0 4px}.npv-toolbar .npv-label{font-size:13px;min-width:50px;text-align:center;color:#505050;-webkit-user-select:none;user-select:none}.npv-toolbar .npv-search-input{background:#0000000f;border:1px solid rgba(0,0,0,.2);border-radius:4px;color:#000;font-size:13px;padding:4px 8px;outline:none;width:180px}.npv-toolbar .npv-search-input::placeholder{color:#0006}.npv-toolbar .npv-search-input:focus{border-color:#1976d2}.npv-toolbar .npv-search-count{font-size:12px;color:#607d8b;white-space:nowrap;min-width:60px}.npv-toolbar .npv-search-count.npv-no-match{color:#e53935}.npv-loading{display:flex;align-items:center;justify-content:center;gap:10px;padding:16px;color:#666;font-size:14px;flex-shrink:0}.npv-spinner{width:20px;height:20px;border:3px solid rgba(0,0,0,.1);border-top-color:#1976d2;border-radius:50%;animation:npv-spin .8s linear infinite}@keyframes npv-spin{to{transform:rotate(360deg)}}.npv-scroll-area{flex:1;overflow:auto}.npv-scroll-inner{display:flex;flex-direction:column;align-items:center;min-width:max-content;padding:16px;gap:16px}.npv-page-wrapper{position:relative;display:inline-block;box-shadow:0 4px 12px #00000080;line-height:0}.npv-canvas{display:block}.npv-overlay{position:absolute;top:0;left:0;pointer-events:all;overflow:visible;cursor:crosshair;user-select:none;-webkit-user-select:none}.npv-overlay.npv-overlay--text-mode{pointer-events:none;cursor:text}:host ::ng-deep .npv-text-layer{position:absolute;top:0;left:0;overflow:hidden;line-height:1;-webkit-text-size-adjust:none;text-size-adjust:none;forced-color-adjust:none}:host ::ng-deep .npv-text-layer span,:host ::ng-deep .npv-text-layer br{color:transparent;position:absolute;white-space:pre;cursor:text;transform-origin:0% 0%}:host ::ng-deep .npv-text-layer ::selection{background:#0078d740;color:transparent}.npv-box{pointer-events:all;stroke-width:2;fill:transparent;cursor:pointer;transition:fill .15s ease,stroke-width .15s ease}.npv-box:hover{stroke-width:3;filter:drop-shadow(0 0 3px rgba(233,115,50,.7))}.npv-box.npv-box--clicked{stroke-width:3}.npv-box.npv-box--multi{stroke-width:2.5}.npv-search-highlight{fill:#ffeb3b59;stroke:#ffc800cc;stroke-width:1.5;pointer-events:none}.npv-search-highlight.npv-search-highlight--current{fill:#ff980073;stroke:#ff6400e6;stroke-width:2}.npv-drag-rect{fill:#0078d71a;stroke:#0078d7b3;stroke-width:1.5;stroke-dasharray:5 3;pointer-events:none}.npv-placeholder{color:#aaa;font-size:14px;padding:40px;text-align:center}.npv-placeholder.npv-placeholder--error{color:#e53935}.npv-context-menu{position:fixed;z-index:9999;background:#fff;border:1px solid rgba(0,0,0,.12);border-radius:4px;box-shadow:0 4px 16px #0003;padding:4px 0;min-width:160px}.npv-context-item{padding:10px 16px;font-size:14px;cursor:pointer;color:#000000de;-webkit-user-select:none;user-select:none}.npv-context-item:hover{background:#0000000d}\n"] }]
702
+ }], ctorParameters: () => [{ type: i0.ChangeDetectorRef }, { type: i0.NgZone }, { type: i0.ElementRef }, { type: undefined, decorators: [{
703
+ type: Inject,
704
+ args: [NGX_PDF_WORKER_SRC]
705
+ }] }], propDecorators: { blob: [{
706
+ type: Input
707
+ }], annotations: [{
708
+ type: Input
709
+ }], contextMenuItems: [{
710
+ type: Input
711
+ }], highlightingColor: [{
712
+ type: Input
713
+ }], selectedColor: [{
714
+ type: Input
715
+ }], annotationStroke: [{
716
+ type: Input
717
+ }], initialZoom: [{
718
+ type: Input
719
+ }], labels: [{
720
+ type: Input
721
+ }], boxClicked: [{
722
+ type: Output
723
+ }], boxesSelected: [{
724
+ type: Output
725
+ }], pdfContainer: [{
726
+ type: ViewChild,
727
+ args: ['pdfContainer']
728
+ }], onDocMouseMove: [{
729
+ type: HostListener,
730
+ args: ['mousemove', ['$event']]
731
+ }], onDocMouseUp: [{
732
+ type: HostListener,
733
+ args: ['mouseup', ['$event']]
734
+ }], onDocClick: [{
735
+ type: HostListener,
736
+ args: ['document:click']
737
+ }] } });
738
+
739
+ /**
740
+ * Generated bundle index. Do not edit.
741
+ */
742
+
743
+ export { NGX_PDF_WORKER_SRC, NgxNextPdfViewerComponent };
744
+ //# sourceMappingURL=aprova.ch-ngx-next-pdf-viewer.mjs.map