@vaadin-component-factory/vcf-pdf-viewer 4.0.2 → 4.2.0

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.
@@ -1,1072 +1,1214 @@
1
- import { LitElement, html, css } from "lit";
2
- import { ElementMixin } from "@vaadin/component-base/src/element-mixin";
3
- import { ThemeDetectionMixin } from "@vaadin/vaadin-themable-mixin/vaadin-theme-detection-mixin";
4
- import { ResizeMixin, SlotStylesMixin } from "@vaadin/component-base/";
5
- import { ThemableMixin } from "@vaadin/vaadin-themable-mixin";
6
-
7
- import "@vaadin/text-field";
8
- import "@vaadin/select";
9
- import "@vaadin/item";
10
- import "@vaadin/button";
11
- import "@vaadin/icon";
12
- import "@vaadin/icons";
13
- import "@vaadin/tooltip";
14
-
15
- import * as pdfjsLib from "../pdfjs/dist/pdf";
16
- import * as pdfjsViewer from "../pdfjs/dist/pdf_viewer";
17
- import * as pdfUtils from "../pdfjs/dist/ui_utils";
18
- import * as pdfjsLinkService from "../pdfjs/dist/pdf_link_service";
19
- import * as pdfjsThumbnailViewer from "../pdfjs/dist/pdf_thumbnail_viewer";
20
- import * as pdfjsRenderingQueue from "../pdfjs/dist/pdf_rendering_queue";
21
- import { NullL10n } from "../pdfjs/dist/l10n_utils";
22
- import * as pdfjsWorker from "../pdfjs/dist/worker";
23
-
24
- pdfjsLib.GlobalWorkerOptions.workerSrc = pdfjsWorker;
25
-
26
- /**
27
- * `<vcf-pdf-viewer>` is a Web Component for rendering PDF files without
28
- * the need of plugins. You can provide a pdf file to it through the src
29
- * attribute.
30
- *
31
- * ```
32
- * <vcf-pdf-viewer src="myfile.pdf"></vcf-pdf-viewer>
33
- * ```
34
- *
35
- * @memberof Vaadin
36
- * @mixes Vaadin.ElementMixin
37
- * @mixes Vaadin.ThemableMixin
38
- * @demo demo/index.html
39
- */
40
- export class PdfViewerElement extends ResizeMixin(
41
- SlotStylesMixin(ThemeDetectionMixin(ThemableMixin(ElementMixin(LitElement)))),
42
- ) {
43
- static get styles() {
44
- return css`
45
- :host {
46
- display: flex;
47
- flex-direction: column;
48
- width: 100%;
49
- height: 500px;
50
- }
51
-
52
- :host([hidden]) {
53
- display: none !important;
54
- }
55
-
56
- [part~="toolbar"] #currentPage,
57
- [part~="toolbar"] #pageSeparator,
58
- [part~="toolbar"] #totalPages,
59
- [part~="toolbar"] #previousPage,
60
- [part~="toolbar"] #nextPage,
61
- [part~="toolbar"] #zoom,
62
- [part~="toolbar"] #sidebarToggle {
63
- display: none;
64
- }
65
-
66
- [part~="toolbar"].ready #currentPage,
67
- [part~="toolbar"].ready #pageSeparator,
68
- [part~="toolbar"].ready #totalPages,
69
- [part~="toolbar"].ready #previousPage,
70
- [part~="toolbar"].ready #nextPage,
71
- [part~="toolbar"].ready #zoom,
72
- [part~="toolbar"].ready #sidebarToggle {
73
- display: inherit;
74
- }
75
-
76
- [part~="outer-container"] {
77
- width: 100%;
78
- height: 100%;
79
- }
80
-
81
- [part~="main-container"] {
82
- position: absolute;
83
- top: 0;
84
- right: 0;
85
- bottom: 0;
86
- left: 0;
87
- min-width: 320px;
88
- }
89
-
90
- [part~="viewer-container"] {
91
- position: absolute;
92
- flex: 1;
93
- overflow: auto;
94
- width: 100%;
95
- height: -moz-calc(100% - 45px); /* Firefox */
96
- height: -webkit-calc(100% - 45px); /* Chrome, Safari */
97
- height: calc(100% - 45px); /*all other browsers */
98
- }
99
-
100
- [part~="sidebar-container"] {
101
- position: absolute;
102
- width: 200px;
103
- top: 45px;
104
- bottom: 0;
105
- visibility: hidden;
106
- height: -moz-calc(100% - 45px); /* Firefox */
107
- height: -webkit-calc(100% - 45px); /* Chrome, Safari */
108
- height: calc(100% - 45px); /*all other browsers */
109
- z-index: 100;
110
- }
111
-
112
- [part~="sidebar-content"] {
113
- position: absolute;
114
- top: 0;
115
- bottom: 0;
116
- overflow: auto;
117
- width: 100%;
118
- background-color: rgba(0, 0, 0, 0.1);
119
- }
120
-
121
- [part~="thumbnail-view"] {
122
- position: absolute;
123
- width: calc(100% - 60px);
124
- top: 0;
125
- bottom: 0;
126
- padding: 10px 30px 0;
127
- overflow: auto;
128
- }
129
-
130
- [part~="toolbar"] {
131
- height: 44px;
132
- }
133
-
134
- .page {
135
- position: relative;
136
- margin: 0 auto;
137
- }
138
-
139
- .textLayer {
140
- position: absolute;
141
- left: 0;
142
- top: 0;
143
- right: 0;
144
- bottom: 0;
145
- overflow: hidden;
146
- line-height: 1;
147
- }
148
-
149
- .textLayer > span {
150
- color: transparent;
151
- position: absolute;
152
- white-space: pre;
153
- cursor: text;
154
- -webkit-transform-origin: 0% 0%;
155
- transform-origin: 0% 0%;
156
- }
157
-
158
- .textLayer .highlight {
159
- margin: -1px;
160
- padding: 1px;
161
- }
162
-
163
- .textLayer .highlight.begin {
164
- border-radius: 4px 0 0 4px;
165
- }
166
-
167
- .textLayer .highlight.end {
168
- border-radius: 0 4px 4px 0;
169
- }
170
-
171
- .textLayer .highlight.middle {
172
- border-radius: 0;
173
- }
174
-
175
- .textLayer .endOfContent {
176
- display: block;
177
- position: absolute;
178
- left: 0;
179
- top: 100%;
180
- right: 0;
181
- bottom: 0;
182
- z-index: -1;
183
- cursor: default;
184
- -webkit-user-select: none;
185
- -moz-user-select: none;
186
- -ms-user-select: none;
187
- user-select: none;
188
- }
189
-
190
- .textLayer .endOfContent.active {
191
- top: 0;
192
- }
193
-
194
- #header {
195
- display: flex;
196
- flex-direction: row;
197
- align-items: baseline;
198
- }
199
-
200
- ::slotted(#currentPage) {
201
- align-self: baseline;
202
- }
203
-
204
- #outerContainer.sidebarOpen #viewerContainer {
205
- transition-property: left;
206
- left: 200px;
207
- width: -moz-calc(100% - 200px); /* Firefox */
208
- width: -webkit-calc(100% - 200px); /* Chrome, Safari */
209
- width: calc(100% - 200px); /*all other browsers */
210
- }
211
-
212
- #outerContainer.sidebarOpen #sidebarContainer {
213
- visibility: visible;
214
- }
215
-
216
- .thumbnail {
217
- margin: 0 10px 5px;
218
- }
219
-
220
- .thumbnailImage {
221
- border: 1px solid rgba(0, 0, 0, 0);
222
- box-shadow:
223
- 0 0 0 1px rgba(0, 0, 0, 0.5),
224
- 0 2px 8px rgba(0, 0, 0, 0.3);
225
- opacity: 1;
226
- z-index: 99;
227
- background-color: rgba(255, 255, 255, 1);
228
- background-clip: content-box;
229
- }
230
-
231
- .thumbnailSelectionRing {
232
- border-radius: 2px;
233
- padding: 7px;
234
- }
235
-
236
- .thumbnail.selected > .thumbnailSelectionRing {
237
- background-color: rgba(0, 0, 0, 0.15);
238
- }
239
-
240
- [part~="toolbar"].ready ::slotted(.toolbar-zoom.hide-zoom) {
241
- display: none;
242
- }
243
- `;
244
- }
245
-
246
- get slotStyles() {
247
- const tag = "vcf-pdf-viewer";
248
- const lumo = '[data-application-theme="lumo"]';
249
-
250
- /**
251
- * These rules target a <vaadin-text-field> element with a child element
252
- * having the 'toggle-button' class name. We can't use `::slotted()` as
253
- * the toggle button is not a direct child of the month picker element.
254
- * Also for `vaadin-popover` we can't use `::part()` after `::slotted()`.
255
- */
256
- return [
257
- `
258
- ${tag}:not(${lumo}) vaadin-button.toolbar-button vaadin-icon {
259
- display: none;
260
- }
261
-
262
- ${tag}:not(${lumo}) vaadin-button.toolbar-button {
263
- height: var(--vaadin-icon-size, 1.5lh);
264
- width: var(--vaadin-icon-size, 1.5lh);
265
- padding: 0;
266
- justify-content: flex-start;
267
- box-sizing: content-box;
268
- }
269
-
270
- ${tag}:not(${lumo}) vaadin-button.toolbar-button::before {
271
- content: '';
272
- flex: none;
273
- background: var(--vaadin-input-field-button-text-color, var(--vaadin-text-color-secondary));
274
- cursor: var(--vaadin-clickable-cursor);
275
- touch-action: manipulation;
276
- -webkit-tap-highlight-color: transparent;
277
- -webkit-user-select: none;
278
- user-select: none;
279
- height: var(--vaadin-icon-size, 1.5lh);
280
- width: var(--vaadin-icon-size, 1.5lh);
281
- mask-size: var(--vaadin-icon-visual-size, 100%);
282
- mask-position: 50%;
283
- mask-repeat: no-repeat;
284
- }
285
-
286
- ${tag}:not(${lumo}) vaadin-button.toolbar-button#sidebarToggle::before {
287
- mask-image: var(--pdf-viewer-toggle-button-icon-closed);
288
- }
289
-
290
- ${tag}:not(${lumo}) vaadin-button.toolbar-button#sidebarToggle:has(vaadin-icon.sidebarOpen)::before {
291
- mask-image: var(--pdf-viewer-toggle-button-icon-open);
292
- }
293
-
294
- ${tag}:not(${lumo}) vaadin-button.toolbar-button#previousPage::before {
295
- mask-image: var(--pdf-viewer-previous-page-button-icon);
296
- }
297
-
298
- ${tag}:not(${lumo}) vaadin-button.toolbar-button#nextPage::before {
299
- mask-image: var(--pdf-viewer-next-page-button-icon);
300
- }
301
-
302
- ${tag}${lumo} vaadin-button[theme~="icon"] {
303
- padding-left: 6px;
304
- padding-right: 6px;
305
- }
306
-
307
- ${tag}${lumo} vaadin-button.toolbar-button#sidebarToggle {
308
- border: 2px solid;
309
- border-color: rgba(0, 0, 0, 0.5);
310
- }
311
- `,
312
- ];
313
- }
314
-
315
- render() {
316
- return html`
317
- <div id="outerContainer" part="outer-container">
318
- <div id="sidebarContainer" part="sidebar-container">
319
- <div id="sidebarContent" part="sidebar-content">
320
- <div id="thumbnailView" part="thumbnail-view"></div>
321
- </div>
322
- </div>
323
- <div id="mainContainer" part="main-container">
324
- <div id="toolbar" part="toolbar">
325
- <slot name="sidebar-toggle-button-slot"></slot>
326
- <span id="title" part="toolbar-text toolbar-title"
327
- >${this.__title}</span
328
- >
329
- <slot name="toolbar-zoom-slot"></slot>
330
- <div part="toolbar-pages">
331
- <slot name="toolbar-current-page-slot"></slot>
332
- <span
333
- id="pageSeparator"
334
- part="toolbar-text toolbar-page-separator"
335
- >/</span
336
- >
337
- <span id="totalPages" part="toolbar-text toolbar-total-pages"
338
- >${this.__totalPages}</span
339
- >
340
- <slot name="previous-page-button-slot"></slot>
341
- <slot name="next-page-button-slot"></slot>
342
- </div>
343
- <slot></slot>
344
- </div>
345
-
346
- <div id="viewerContainer" part="viewer-container">
347
- <div id="viewer" part="viewer"></div>
348
- </div>
349
- </div>
350
- </div>
351
- `;
352
- }
353
-
354
- static get is() {
355
- return "vcf-pdf-viewer";
356
- }
357
-
358
- static get version() {
359
- return "4.0.2";
360
- }
361
-
362
- static get properties() {
363
- return {
364
- /**
365
- * You can set a pdf file that you want to render with src. Note that regular cross
366
- * site scripting (XSS) rules apply. This means that the file should be on the same
367
- * server as where the component is run, or that the server where the file is on should
368
- * be configured to allow loading files from other sites.
369
- */
370
- src: { type: String },
371
-
372
- /**
373
- * The viewer, which takes care of rendering content into a DOM element.
374
- */
375
- __viewer: Object,
376
-
377
- /**
378
- * The viewer for thumbnails.
379
- */
380
- __thumbnailViewer: Object,
381
-
382
- /**
383
- * The link service.
384
- */
385
- __linkService: Object,
386
-
387
- /**
388
- * A represenentation of a document that has been read in.
389
- */
390
- __document: Object,
391
- /**
392
- * The title for the PDF shown in the toolbar of component. It uses both the file name and
393
- * the title in the PDF metadata if available.
394
- */
395
- __title: { type: String },
396
- /**
397
- * Relative filename
398
- */
399
- __filename: String,
400
- /**
401
- * The pdf metadata title
402
- */
403
- __pdfTitle: String,
404
- /**
405
- * The level of zoom on the document.
406
- * Allowed values are
407
- * - Number, for zoom percentage. Eg. 1.5 means 150% zoom
408
- * - 'auto', default value
409
- * - 'page-fit', fit a full page into component
410
- */
411
- zoom: { type: String },
412
- /**
413
- * The current page visible viewed right now
414
- */
415
- currentPage: { type: String },
416
- /**
417
- * Total amount of pages in an opened document
418
- */
419
- __totalPages: Number,
420
-
421
- /**
422
- * Loading state
423
- */
424
- __loading: { type: Boolean },
425
-
426
- /**
427
- * Whether sidebar is open after loading or not
428
- */
429
- __sidebarOpen: { type: Boolean },
430
-
431
- /**
432
- * Flag to indicate if toolbar should only show filename as title
433
- */
434
- toolbarOnlyFilename: { type: Boolean },
435
-
436
- /**
437
- * Property to define auto zoom label
438
- */
439
- autoZoomOptionLabel: { type: String },
440
-
441
- /**
442
- * Property to define page fit zoom label
443
- */
444
- fitZoomOptionLabel: { type: String },
445
-
446
- /**
447
- * Property to define a custom title for the viewer
448
- */
449
- customTitle: { type: String },
450
-
451
- /**
452
- * Renders interactive form elements in the annotation layer (html) if true,
453
- * renders values of form elements directly onto the canvas if false
454
- */
455
- renderInteractiveForms: { type: Boolean },
456
-
457
- /**
458
- * Allows to hide the zoom dropdown. By default it's always shown.
459
- */
460
- hideZoom: { type: Boolean },
461
-
462
- /**
463
- * Property to define a custom tooltip for the sidebar toggle button
464
- */
465
- sidebarToggleTooltip: { type: String },
466
-
467
- /**
468
- * Property to define a custom tooltip for the previous page button
469
- */
470
- previousPageTooltip: { type: String },
471
-
472
- /**
473
- * Property to define a custom tooltip for the next page button
474
- */
475
- nextPageTooltip: { type: String },
476
- };
477
- }
478
-
479
- get __zoomItems() {
480
- return this.__computeZoomItems(
481
- this.autoZoomOptionLabel,
482
- this.fitZoomOptionLabel,
483
- );
484
- }
485
-
486
- constructor() {
487
- super();
488
- this.__title = "PDF";
489
- this.zoom = "auto";
490
- this.currentPage = "1";
491
- this.__loading = true;
492
- this.__sidebarOpen = false;
493
- this.toolbarOnlyFilename = false;
494
- this.autoZoomOptionLabel = "Automatic zoom";
495
- this.fitZoomOptionLabel = "Page fit";
496
- this.customTitle = "";
497
- this.renderInteractiveForms = true;
498
- this.hideZoom = false;
499
- this.sidebarToggleTooltip = "";
500
- this.previousPageTooltip = "";
501
- this.nextPageTooltip = "";
502
- }
503
-
504
- willUpdate(changedProperties) {
505
- super.willUpdate(changedProperties);
506
-
507
- // Compute filename if src changes
508
- if (changedProperties.has("src")) {
509
- this.__setFilename(this.src);
510
- }
511
-
512
- // Compute title before rendering to avoid setting reactive properties during update
513
- if (
514
- changedProperties.has("src") ||
515
- changedProperties.has("customTitle") ||
516
- changedProperties.has("toolbarOnlyFilename")
517
- ) {
518
- this.__setTitle(this.__pdfTitle, this.__filename);
519
- }
520
- }
521
-
522
- updated(changedProperties) {
523
- super.updated(changedProperties);
524
-
525
- // Defer property synchronization to avoid "update during update" warning
526
- // This ensures these calls happen after the current update cycle completes
527
- Promise.resolve().then(() => {
528
- if (changedProperties.has("src")) {
529
- this.__srcChanged(this.src);
530
- }
531
- if (changedProperties.has("zoom")) {
532
- const zoomSelect = this.querySelector("#zoom");
533
- if (zoomSelect && zoomSelect.value !== this.zoom) {
534
- zoomSelect.value = this.zoom;
535
- }
536
- this.__zoomChanged(this.zoom);
537
- }
538
- if (changedProperties.has("currentPage")) {
539
- const currentPageInput = this.querySelector("#currentPage");
540
- if (currentPageInput && currentPageInput.value !== this.currentPage) {
541
- currentPageInput.value = this.currentPage;
542
- this.__pageChange();
543
- }
544
- }
545
- });
546
- }
547
-
548
- __createToolbarButton() {
549
- const icon = document.createElement("vaadin-icon");
550
- icon.setAttribute("slot", "prefix");
551
-
552
- const tooltip = document.createElement("vaadin-tooltip");
553
- tooltip.setAttribute("slot", "tooltip");
554
-
555
- const button = document.createElement("vaadin-button");
556
- button.classList.add("toolbar-button");
557
- button.setAttribute("theme", "icon tertiary contrast");
558
-
559
- button.appendChild(icon);
560
- button.appendChild(tooltip);
561
- return button;
562
- }
563
-
564
- /**
565
- * Adds toggle button to the toolbar slot named "sidebar-toggle-button-slot".
566
- */
567
- _createSideBarToggleButton() {
568
- const button = this.__createToolbarButton();
569
- const icon = button.querySelector("vaadin-icon");
570
- icon.classList.add("toggle-button-icon");
571
- button
572
- .querySelector("vaadin-tooltip")
573
- .setAttribute("text", this.sidebarToggleTooltip);
574
- button.setAttribute("slot", "sidebar-toggle-button-slot");
575
- button.setAttribute("id", "sidebarToggle");
576
- button.setAttribute("aria-label", "Sidebar toggle");
577
- button.addEventListener("click", () => {
578
- this.__toogleSidebar();
579
- if (this._outerContainer.classList.contains("sidebarOpen")) {
580
- icon.classList.add("sidebarOpen");
581
- } else {
582
- icon.classList.remove("sidebarOpen");
583
- }
584
- });
585
- this.appendChild(button);
586
- }
587
-
588
- /**
589
- * Adds previous page button to the toolbar slot named "previous-page-button-slot".
590
- */
591
- _createPreviousPageButton() {
592
- const button = this.__createToolbarButton();
593
- button
594
- .querySelector("vaadin-icon")
595
- .classList.add("previous-page-button-icon");
596
- button
597
- .querySelector("vaadin-tooltip")
598
- .setAttribute("text", this.previousPageTooltip);
599
- button.setAttribute("slot", "previous-page-button-slot");
600
- button.setAttribute("id", "previousPage");
601
- button.setAttribute("aria-label", "Previous page");
602
- button.addEventListener("click", () => this.__previousPage());
603
- this.appendChild(button);
604
- }
605
-
606
- /**
607
- * Adds next page button to the toolbar slot named "next-page-button-slot".
608
- */
609
- _createNextPageButton() {
610
- const button = this.__createToolbarButton();
611
- button.querySelector("vaadin-icon").classList.add("next-page-button-icon");
612
- button
613
- .querySelector("vaadin-tooltip")
614
- .setAttribute("text", this.nextPageTooltip);
615
- button.setAttribute("slot", "next-page-button-slot");
616
- button.setAttribute("id", "nextPage");
617
- button.setAttribute("aria-label", "Next page");
618
- button.addEventListener("click", () => this.__nextPage());
619
- this.appendChild(button);
620
- }
621
-
622
- /**
623
- * Adds current page text field to the toolbar slot named "toolbar-current-page-slot".
624
- */
625
- _createCurrentPageTextField() {
626
- const textField = document.createElement("vaadin-text-field");
627
- textField.setAttribute("slot", "toolbar-current-page-slot");
628
- textField.setAttribute("id", "currentPage");
629
- textField.classList.add("toolbar-current-page");
630
- textField.setAttribute("value", this.currentPage);
631
- textField.addEventListener("change", () => this.__pageChange());
632
- this.appendChild(textField);
633
- }
634
-
635
- /**
636
- * Adds zoom select to the toolbar slot named "toolbar-zoom-slot".
637
- */
638
- _createZoomSelect() {
639
- const select = document.createElement("vaadin-select");
640
- select.setAttribute("slot", "toolbar-zoom-slot");
641
- select.setAttribute("id", "zoom");
642
- select.classList.add("toolbar-zoom");
643
- select.setAttribute("value", this.zoom);
644
- select.items = this.__zoomItems;
645
- select.addEventListener("value-changed", (e) => {
646
- if (e.detail.value !== this.zoom) {
647
- this.__zoomChanged(e.detail.value);
648
- }
649
- });
650
- if (this.hideZoom) {
651
- select.classList.add("hide-zoom");
652
- } else {
653
- select.classList.remove("hide-zoom");
654
- }
655
- this.appendChild(select);
656
- }
657
-
658
- __computeZoomItems(autoZoomOptionLabel, fitZoomOptionLabel) {
659
- return [
660
- { label: autoZoomOptionLabel, value: "auto" },
661
- { label: fitZoomOptionLabel, value: "page-fit" },
662
- { label: "50%", value: "0.5" },
663
- { label: "75%", value: "0.75" },
664
- { label: "100%", value: "1.0" },
665
- { label: "125%", value: "1.25" },
666
- { label: "150%", value: "1.5" },
667
- { label: "200%", value: "2.0" },
668
- { label: "300%", value: "3.0" },
669
- { label: "400%", value: "4.0" },
670
- ];
671
- }
672
-
673
- __setTitle(pdfTitle, filename) {
674
- if (this.customTitle) {
675
- this.__title = this.customTitle;
676
- } else if (this.__viewer && this.toolbarOnlyFilename && filename) {
677
- this.__title = filename;
678
- } else if (pdfTitle && filename) {
679
- this.__title = pdfTitle + " - " + filename;
680
- } else if (pdfTitle) {
681
- this.__title = pdfTitle;
682
- } else if (filename) {
683
- this.__title = filename;
684
- } else {
685
- this.__title = "PDF";
686
- }
687
- }
688
-
689
- _addToolbarButtons() {
690
- this._createSideBarToggleButton();
691
- this._createZoomSelect();
692
- this._createCurrentPageTextField();
693
- this._createPreviousPageButton();
694
- this._createNextPageButton();
695
- }
696
-
697
- firstUpdated() {
698
- this._toolbar = this.shadowRoot.querySelector("#toolbar");
699
- this._viewerContainer = this.shadowRoot.querySelector("#viewerContainer");
700
- this._outerContainer = this.shadowRoot.querySelector("#outerContainer");
701
- this._viewer = this.shadowRoot.querySelector("#viewer");
702
- this._thumbnailView = this.shadowRoot.querySelector("#thumbnailView");
703
-
704
- this._addToolbarButtons();
705
-
706
- this._viewerContainer.addEventListener(
707
- "focus",
708
- (e) => this.__setFocused(true),
709
- true,
710
- );
711
- this._viewerContainer.addEventListener(
712
- "blur",
713
- (e) => this.__setFocused(false),
714
- true,
715
- );
716
- this._viewerContainer.addEventListener("mousedown", (e) => {
717
- this._mousedown = true;
718
- const mouseUpListener = () => {
719
- this._mousedown = false;
720
- document.removeEventListener("mouseup", mouseUpListener);
721
- };
722
- document.addEventListener("mouseup", mouseUpListener);
723
- });
724
-
725
- // options
726
- const eventBus = new pdfUtils.EventBus();
727
-
728
- // Defer initialization of reactive controller properties to avoid immediate update request
729
- // that triggers "update scheduled after update" warning.
730
- Promise.resolve().then(() => {
731
- this.__linkService = new pdfjsLinkService.PDFLinkService({
732
- eventBus,
733
- });
734
- var pdfRenderingQueue = new pdfjsRenderingQueue.PDFRenderingQueue();
735
- var l10n = NullL10n;
736
-
737
- // pdfViewer
738
- this.__viewer = new pdfjsViewer.PDFViewer({
739
- container: this._viewerContainer,
740
- textLayerMode: 2,
741
- viewer: this._viewer,
742
- eventBus: eventBus,
743
- linkService: this.__linkService,
744
- renderingQueue: pdfRenderingQueue,
745
- l10n: l10n,
746
- renderInteractiveForms: this.renderInteractiveForms,
747
- });
748
-
749
- this.__linkService.setViewer(this.__viewer);
750
- pdfRenderingQueue.setViewer(this.__viewer);
751
-
752
- // thumbnailViewer
753
- this.__thumbnailViewer = new pdfjsThumbnailViewer.PDFThumbnailViewer({
754
- container: this._thumbnailView,
755
- eventBus: eventBus,
756
- linkService: this.__linkService,
757
- renderingQueue: pdfRenderingQueue,
758
- l10n: l10n,
759
- });
760
-
761
- pdfRenderingQueue.setThumbnailViewer(this.__thumbnailViewer);
762
-
763
- // Initialize listeners after viewer creation
764
- this.__initListeners(eventBus);
765
- });
766
- }
767
-
768
- __initListeners(eventBus) {
769
- // listeners
770
- eventBus.on("pagesinit", () => {
771
- this.__viewer.currentScaleValue = this.zoom;
772
- this.__loading = false;
773
- this.__updateThumbnailViewer();
774
- if (this.__sidebarOpen) {
775
- this.__openSidebar();
776
- } else {
777
- this.__closeSidebar();
778
- }
779
- this.__viewer.currentPage = this.setCurrentPage();
780
- });
781
- eventBus.on("pagechanging", (event) => {
782
- this.__updateCurrentPageValue(event.pageNumber);
783
- this.__updatePageNumberStates();
784
- if (
785
- this.__thumbnailViewer &&
786
- this.__thumbnailViewer.renderingQueue.isThumbnailViewEnabled
787
- ) {
788
- this.__thumbnailViewer.scrollThumbnailIntoView(this.currentPage);
789
- }
790
- const currentPageInput = this.querySelector("#currentPage");
791
- if (currentPageInput) {
792
- currentPageInput.value = this.currentPage;
793
- }
794
- });
795
- }
796
-
797
- __updateCurrentPageValue(pageNumber) {
798
- this.currentPage = "" + pageNumber;
799
- this.dispatchEvent(new CustomEvent("currentPage-changed"));
800
- }
801
-
802
- __setFocused(focused) {
803
- if (focused) {
804
- this._viewerContainer.setAttribute("focused", "");
805
- if (!this._mousedown) {
806
- this._viewerContainer.setAttribute("focus-ring", "");
807
- }
808
- } else {
809
- this._viewerContainer.removeAttribute("focused");
810
- this._viewerContainer.removeAttribute("focus-ring");
811
- }
812
- }
813
-
814
- __open(src) {
815
- // Is there already a document loaded?
816
- if (this.__document) {
817
- // We need to close the current document
818
- return this.__close().then(() => {
819
- // and start over with opening the new one
820
- return this.__open(src);
821
- });
822
- }
823
- if (!src) {
824
- // No file given, show nothing.
825
- return;
826
- }
827
- this.__setFilename(src);
828
- this.__document = pdfjsLib.getDocument(new URL(src, document.baseURI).href);
829
- return this.__document.promise.then(
830
- (pdfDocument) => {
831
- // Document loaded, specifying document for the viewer.
832
- this.__thumbnailViewer.setDocument(pdfDocument);
833
- this.__viewer.setDocument(pdfDocument);
834
- this.__linkService.setDocument(pdfDocument);
835
-
836
- this._toolbar.classList.add("ready");
837
- this.__totalPages = pdfDocument.numPages;
838
- this.__updatePageNumberStates();
839
- this.__setPdfTitleFromMetadata(pdfDocument).then(() => {
840
- this.dispatchEvent(
841
- new CustomEvent("document-loaded", {
842
- detail: {
843
- document: pdfDocument,
844
- },
845
- }),
846
- );
847
- });
848
- },
849
- function (exception) {
850
- console.error(exception && exception.message);
851
- },
852
- );
853
- }
854
-
855
- __srcChanged(newSrc) {
856
- this.__open(newSrc);
857
- }
858
-
859
- /**
860
- * Closes opened PDF document.
861
- * @returns {Promise} - Returns the promise, which is resolved when all
862
- * destruction is completed.
863
- */
864
- __close() {
865
- this._toolbar.classList.remove("ready");
866
- this.__filename = "PDF";
867
- if (!this.__document) {
868
- return Promise.resolve();
869
- }
870
-
871
- var promise = this.__document.destroy();
872
- if (this.__document) {
873
- this.__document = null;
874
- this.__viewer.setDocument(null);
875
- this.__thumbnailViewer.setDocument(null);
876
- this.__linkService.setDocument(null);
877
- }
878
- return promise;
879
- }
880
-
881
- __setFilename(src) {
882
- let filename = pdfjsLib.getFilenameFromUrl(src) || src;
883
- try {
884
- filename = decodeURIComponent(filename);
885
- } catch (e) {
886
- // decodeURIComponent may throw URIError,
887
- // fall back to using the unprocessed url in that case
888
- }
889
- this.__filename = filename;
890
- }
891
-
892
- __setPdfTitleFromMetadata(pdfDocument) {
893
- return pdfDocument.getMetadata().then((data) => {
894
- let pdfTitle;
895
- const metadata = data.metadata;
896
- if (metadata && metadata.has("dc:title")) {
897
- const title = metadata.get("dc:title");
898
- // Ghostscript sometimes returns 'Untitled', so prevent setting the
899
- // title to 'Untitled'.
900
- if (title !== "Untitled") {
901
- pdfTitle = title;
902
- }
903
- }
904
-
905
- const info = data.info;
906
- if (!pdfTitle && info && info["Title"]) {
907
- pdfTitle = info["Title"];
908
- }
909
- this.__pdfTitle = pdfTitle;
910
- });
911
- }
912
-
913
- __updatePageNumberStates() {
914
- this.querySelector("#previousPage").disabled = this.currentPage === "1";
915
- this.querySelector("#nextPage").disabled =
916
- this.currentPage === "" + this.__totalPages;
917
- }
918
-
919
- __zoomChanged(value) {
920
- if (!this.__viewer || this.__loading) {
921
- return;
922
- }
923
- // This logs error 'TextLayerBuilder._bindEvents: `this.cancel()` should have
924
- // been called when the page was reset, or rendering cancelled.'
925
- //
926
- // There is a problem deep inside pdfjs viewer that causes an console.error()
927
- // to be logged, but the component still works. It seems to be due to
928
- // webcomponents/shadow dom messing with
929
- // TODO: Fix the issue so that we get rid of the error in log
930
- this.__viewer.currentScaleValue = value;
931
- this.__viewer.forceRendering();
932
- }
933
-
934
- __pageChange(event) {
935
- const currentPageInput = this.querySelector("#currentPage");
936
- if (!currentPageInput) {
937
- return; // Component not yet rendered, skip
938
- }
939
- const currentPageValue = currentPageInput.value;
940
- let pageNumber = parseInt(currentPageValue, 10);
941
- if (isNaN(pageNumber)) {
942
- pageNumber = this.__viewer.currentPageNumber;
943
- currentPageInput.value = "" + pageNumber;
944
- }
945
- if (pageNumber < 1) {
946
- pageNumber = 1;
947
- }
948
- if (pageNumber > this.__totalPages) {
949
- pageNumber = this.__totalPages;
950
- }
951
- this.__viewer.currentPageNumber = pageNumber;
952
- }
953
-
954
- setCurrentPage(value) {
955
- if (value != undefined) {
956
- this.currentPage = "" + value;
957
- }
958
- this.__pageChange();
959
- }
960
-
961
- _getPage() {
962
- return this.__viewer.currentPageNumber;
963
- }
964
-
965
- __previousPage() {
966
- this.__viewer.currentPageNumber--;
967
- }
968
-
969
- __nextPage() {
970
- this.__viewer.currentPageNumber++;
971
- }
972
-
973
- __toogleSidebar() {
974
- if (this._outerContainer.classList.length == 0) {
975
- this.__openSidebar();
976
- } else {
977
- this.__closeSidebar();
978
- }
979
- }
980
-
981
- __openSidebar() {
982
- if (!this.__thumbnailViewer || this.__loading) {
983
- this.__sidebarOpen = true;
984
- } else {
985
- this.__thumbnailViewer.renderingQueue.isThumbnailViewEnabled = true;
986
- this.__updateThumbnailViewer();
987
- this._outerContainer.classList.add("sidebarOpen");
988
- }
989
- }
990
-
991
- __closeSidebar() {
992
- if (!this.__thumbnailViewer || this.__loading) {
993
- this.__sidebarOpen = false;
994
- } else {
995
- this.__thumbnailViewer.renderingQueue.isThumbnailViewEnabled = false;
996
- this._outerContainer.classList.remove("sidebarOpen");
997
- }
998
- }
999
-
1000
- __updateThumbnailViewer() {
1001
- const pagesCount = this.__totalPages;
1002
- for (let i = 0; i < pagesCount; i++) {
1003
- const pageView = this.__viewer.getPageView(i);
1004
- if (
1005
- pageView.renderingState === pdfjsRenderingQueue.RenderingStates.FINISHED
1006
- ) {
1007
- const thumbnailView = this.__thumbnailViewer.getThumbnail(i);
1008
- thumbnailView.setImage(pageView);
1009
- } else {
1010
- this.__thumbnailViewer.renderingQueue.renderHighestPriority();
1011
- }
1012
- }
1013
- var component = this;
1014
- for (let i = 0; i < this.__thumbnailViewer._thumbnails.length; i++) {
1015
- const thumbnailView = this.__thumbnailViewer.getThumbnail(i);
1016
- thumbnailView.anchor.onclick = function () {
1017
- const id = thumbnailView.id;
1018
- thumbnailView.linkService.goToPage(id);
1019
- component.dispatchEvent(
1020
- new CustomEvent("thumbnail-clicked", {
1021
- detail: {
1022
- source: component,
1023
- pageNumber: id,
1024
- },
1025
- }),
1026
- );
1027
- return false;
1028
- };
1029
- }
1030
- if (
1031
- this.__thumbnailViewer &&
1032
- this.__thumbnailViewer.renderingQueue.isThumbnailViewEnabled
1033
- ) {
1034
- this.__thumbnailViewer.scrollThumbnailIntoView(this.currentPage);
1035
- }
1036
- }
1037
-
1038
- _onResize() {
1039
- if (this.offsetWidth < 600) {
1040
- this.classList.add("small-size");
1041
- this._toolbar.classList.add("small-size");
1042
- } else {
1043
- this.classList.remove("small-size");
1044
- this._toolbar.classList.remove("small-size");
1045
- }
1046
- }
1047
-
1048
- /**
1049
- * Rotates document clockwise.
1050
- */
1051
- rotateCw() {
1052
- let delta = 90;
1053
- this.__viewer.pagesRotation += delta;
1054
- this.__viewer.forceRendering();
1055
- }
1056
-
1057
- /**
1058
- * Rotates document counterclockwise.
1059
- */
1060
- rotateCcw() {
1061
- let delta = -90;
1062
- this.__viewer.pagesRotation += delta;
1063
- this.__viewer.forceRendering();
1064
- }
1065
- }
1066
-
1067
- customElements.define(PdfViewerElement.is, PdfViewerElement);
1068
-
1069
- /**
1070
- * @namespace Vaadin
1071
- */
1072
- window.Vaadin.PdfViewerElement = PdfViewerElement;
1
+ import { LitElement, html, css } from "lit";
2
+ import { ElementMixin } from "@vaadin/component-base/src/element-mixin";
3
+ import { ThemeDetectionMixin } from "@vaadin/vaadin-themable-mixin/vaadin-theme-detection-mixin";
4
+ import { ResizeMixin, SlotStylesMixin } from "@vaadin/component-base/";
5
+ import { ThemableMixin } from "@vaadin/vaadin-themable-mixin";
6
+
7
+ import "@vaadin/text-field";
8
+ import "@vaadin/select";
9
+ import "@vaadin/item";
10
+ import "@vaadin/button";
11
+ import "@vaadin/icon";
12
+ import "@vaadin/icons";
13
+ import "@vaadin/tooltip";
14
+
15
+ import * as pdfjsLib from "../pdfjs/dist/pdf";
16
+ import * as pdfjsViewer from "../pdfjs/dist/pdf_viewer";
17
+ import * as pdfUtils from "../pdfjs/dist/ui_utils";
18
+ import { EventBus } from "../pdfjs/dist/event_utils";
19
+ import * as pdfjsLinkService from "../pdfjs/dist/pdf_link_service";
20
+ import * as pdfjsThumbnailViewer from "../pdfjs/dist/pdf_thumbnail_viewer";
21
+ import * as pdfjsRenderingQueue from "../pdfjs/dist/pdf_rendering_queue";
22
+ import { GenericL10n } from "../pdfjs/dist/genericl10n";
23
+
24
+ pdfjsLib.GlobalWorkerOptions.workerSrc = "../pdfjs/dist/worker.js";
25
+
26
+ /**
27
+ * `<vcf-pdf-viewer>` is a Web Component for rendering PDF files without
28
+ * the need of plugins. You can provide a pdf file to it through the src
29
+ * attribute.
30
+ *
31
+ * ```
32
+ * <vcf-pdf-viewer src="myfile.pdf"></vcf-pdf-viewer>
33
+ * ```
34
+ *
35
+ * @tagname vcf-pdf-viewer
36
+ * @fires {CustomEvent} currentPage-changed - Fired after the element itself updated `currentPage`.
37
+ * @fires {CustomEvent} document-loaded - Fired once the document behind `src` finished loading, with the pdf.js document in `detail.document`.
38
+ * @fires {CustomEvent} thumbnail-clicked - Fired when a sidebar thumbnail is clicked, after navigating to its page. Dispatched on the viewer, which is also passed as `detail.source`, together with `detail.pageNumber`.
39
+ * @memberof Vaadin
40
+ * @mixes Vaadin.ElementMixin
41
+ * @mixes Vaadin.ThemableMixin
42
+ * @demo demo/index.html
43
+ */
44
+ export class PdfViewerElement extends ResizeMixin(
45
+ SlotStylesMixin(ThemeDetectionMixin(ThemableMixin(ElementMixin(LitElement)))),
46
+ ) {
47
+ static get styles() {
48
+ return css`
49
+ :host {
50
+ display: flex;
51
+ flex-direction: column;
52
+ width: 100%;
53
+ height: 500px;
54
+ --pdf-viewer-link-hover-color: rgb(255 255 0);
55
+ }
56
+
57
+ :host([hidden]) {
58
+ display: none !important;
59
+ }
60
+
61
+ [part~="toolbar"] #currentPage,
62
+ [part~="toolbar"] #pageSeparator,
63
+ [part~="toolbar"] #totalPages,
64
+ [part~="toolbar"] #previousPage,
65
+ [part~="toolbar"] #nextPage,
66
+ [part~="toolbar"] #zoom,
67
+ [part~="toolbar"] #sidebarToggle {
68
+ display: none;
69
+ }
70
+
71
+ [part~="toolbar"].ready #currentPage,
72
+ [part~="toolbar"].ready #pageSeparator,
73
+ [part~="toolbar"].ready #totalPages,
74
+ [part~="toolbar"].ready #previousPage,
75
+ [part~="toolbar"].ready #nextPage,
76
+ [part~="toolbar"].ready #zoom,
77
+ [part~="toolbar"].ready #sidebarToggle {
78
+ display: inherit;
79
+ }
80
+
81
+ [part~="outer-container"] {
82
+ width: 100%;
83
+ height: 100%;
84
+ }
85
+
86
+ [part~="main-container"] {
87
+ position: absolute;
88
+ top: 0;
89
+ right: 0;
90
+ bottom: 0;
91
+ left: 0;
92
+ min-width: 320px;
93
+ }
94
+
95
+ [part~="viewer-container"] {
96
+ position: absolute;
97
+ flex: 1;
98
+ overflow: auto;
99
+ width: 100%;
100
+ height: -moz-calc(100% - 45px); /* Firefox */
101
+ height: -webkit-calc(100% - 45px); /* Chrome, Safari */
102
+ height: calc(100% - 45px); /*all other browsers */
103
+ }
104
+
105
+ [part~="sidebar-container"] {
106
+ position: absolute;
107
+ width: 200px;
108
+ top: 45px;
109
+ bottom: 0;
110
+ visibility: hidden;
111
+ height: -moz-calc(100% - 45px); /* Firefox */
112
+ height: -webkit-calc(100% - 45px); /* Chrome, Safari */
113
+ height: calc(100% - 45px); /*all other browsers */
114
+ z-index: 100;
115
+ }
116
+
117
+ [part~="sidebar-content"] {
118
+ position: absolute;
119
+ top: 0;
120
+ bottom: 0;
121
+ overflow: auto;
122
+ width: 100%;
123
+ background-color: rgba(0, 0, 0, 0.1);
124
+ }
125
+
126
+ [part~="thumbnail-view"] {
127
+ position: absolute;
128
+ top: 0;
129
+ bottom: 0;
130
+ padding: 10px 30px 0;
131
+ overflow: auto;
132
+ }
133
+
134
+ [part~="toolbar"] {
135
+ height: 44px;
136
+ }
137
+
138
+ .page {
139
+ position: relative;
140
+ margin: 0 auto;
141
+ }
142
+
143
+ .canvasWrapper {
144
+ position: absolute;
145
+ top: 0;
146
+ left: 0;
147
+ width: 100%;
148
+ height: 100%;
149
+ }
150
+
151
+ .textLayer {
152
+ position: absolute;
153
+ left: 0;
154
+ top: 0;
155
+ right: 0;
156
+ bottom: 0;
157
+ overflow: hidden;
158
+ line-height: 1;
159
+ }
160
+
161
+ .textLayer > span {
162
+ color: transparent;
163
+ position: absolute;
164
+ white-space: pre;
165
+ cursor: text;
166
+ -webkit-transform-origin: 0% 0%;
167
+ transform-origin: 0% 0%;
168
+ }
169
+
170
+ .textLayer .highlight {
171
+ margin: -1px;
172
+ padding: 1px;
173
+ }
174
+
175
+ .textLayer .highlight.begin {
176
+ border-radius: 4px 0 0 4px;
177
+ }
178
+
179
+ .textLayer .highlight.end {
180
+ border-radius: 0 4px 4px 0;
181
+ }
182
+
183
+ .textLayer .highlight.middle {
184
+ border-radius: 0;
185
+ }
186
+
187
+ .textLayer .endOfContent {
188
+ display: block;
189
+ position: absolute;
190
+ left: 0;
191
+ top: 100%;
192
+ right: 0;
193
+ bottom: 0;
194
+ z-index: -1;
195
+ cursor: default;
196
+ -webkit-user-select: none;
197
+ -moz-user-select: none;
198
+ -ms-user-select: none;
199
+ user-select: none;
200
+ }
201
+
202
+ .textLayer .endOfContent.active {
203
+ top: 0;
204
+ }
205
+
206
+ #header {
207
+ display: flex;
208
+ flex-direction: row;
209
+ align-items: baseline;
210
+ }
211
+
212
+ ::slotted(#currentPage) {
213
+ align-self: baseline;
214
+ }
215
+
216
+ #outerContainer.sidebarOpen #viewerContainer {
217
+ transition-property: left;
218
+ left: 200px;
219
+ width: -moz-calc(100% - 200px); /* Firefox */
220
+ width: -webkit-calc(100% - 200px); /* Chrome, Safari */
221
+ width: calc(100% - 200px); /*all other browsers */
222
+ }
223
+
224
+ #outerContainer.sidebarOpen #sidebarContainer {
225
+ visibility: visible;
226
+ }
227
+
228
+ .thumbnail {
229
+ margin: 5px;
230
+ padding: 7px 7px 0px 7px;
231
+ }
232
+
233
+ .thumbnailImage {
234
+ border: 1px solid rgba(0, 0, 0, 0);
235
+ box-shadow:
236
+ 0 0 0 1px rgba(0, 0, 0, 0.5),
237
+ 0 2px 8px rgba(0, 0, 0, 0.3);
238
+ opacity: 1;
239
+ z-index: 99;
240
+ background-color: rgba(255, 255, 255, 1);
241
+ background-clip: content-box;
242
+ }
243
+
244
+ .thumbnail.selected {
245
+ background-color: rgba(0, 0, 0, 0.15);
246
+ }
247
+
248
+ [part~="toolbar"].ready ::slotted(.toolbar-zoom.hide-zoom) {
249
+ display: none;
250
+ }
251
+
252
+ /* Annotation layer */
253
+
254
+ .annotationLayer {
255
+ --annotation-unfocused-field-background: url("data:image/svg+xml;charset=UTF-8,<svg width='1px' height='1px' xmlns='http://www.w3.org/2000/svg'><rect width='100%' height='100%' style='fill:rgba(0, 54, 255, 0.13);'/></svg>");
256
+ --input-focus-border-color: Highlight;
257
+ --input-focus-outline: 1px solid Canvas;
258
+ --input-unfocused-border-color: transparent;
259
+ --input-disabled-border-color: transparent;
260
+ --input-hover-border-color: black;
261
+ --link-outline: none;
262
+ position: absolute;
263
+ top: 0;
264
+ left: 0;
265
+ pointer-events: none;
266
+ transform-origin: 0 0;
267
+ }
268
+
269
+ .annotationLayer section {
270
+ position: absolute;
271
+ text-align: initial;
272
+ pointer-events: auto;
273
+ box-sizing: border-box;
274
+ transform-origin: 0 0;
275
+ }
276
+
277
+ .annotationLayer :is(.linkAnnotation, .buttonWidgetAnnotation.pushButton) > a {
278
+ position: absolute;
279
+ font-size: 1em;
280
+ top: 0;
281
+ left: 0;
282
+ width: 100%;
283
+ height: 100%;
284
+ }
285
+
286
+ .annotationLayer :is(.linkAnnotation, .buttonWidgetAnnotation.pushButton):not(.hasBorder) > a:hover {
287
+ opacity: 0.2;
288
+ background-color: var(--pdf-viewer-link-hover-color);
289
+ box-shadow: 0 2px 10px var(--pdf-viewer-link-hover-color);
290
+ }
291
+
292
+ .annotationLayer .linkAnnotation.hasBorder:hover {
293
+ background-color: color-mix(in srgb, var(--pdf-viewer-link-hover-color) 20%, transparent);
294
+ }
295
+
296
+ .annotationLayer .hasBorder {
297
+ background-size: 100% 100%;
298
+ }
299
+
300
+ .annotationLayer .textWidgetAnnotation :is(input, textarea),
301
+ .annotationLayer .choiceWidgetAnnotation select,
302
+ .annotationLayer .buttonWidgetAnnotation:is(.checkBox, .radioButton) input {
303
+ background-image: var(--annotation-unfocused-field-background);
304
+ border: 2px solid var(--input-unfocused-border-color);
305
+ box-sizing: border-box;
306
+ font: calc(9px * var(--scale-factor)) sans-serif;
307
+ height: 100%;
308
+ margin: 0;
309
+ vertical-align: top;
310
+ width: 100%;
311
+ }
312
+
313
+ .annotationLayer .textWidgetAnnotation :is(input, textarea):hover,
314
+ .annotationLayer .choiceWidgetAnnotation select:hover,
315
+ .annotationLayer .buttonWidgetAnnotation:is(.checkBox, .radioButton) input:hover {
316
+ border: 2px solid var(--input-hover-border-color);
317
+ }
318
+
319
+ .annotationLayer .textWidgetAnnotation :is(input, textarea):focus,
320
+ .annotationLayer .choiceWidgetAnnotation select:focus {
321
+ background: none;
322
+ border: 2px solid var(--input-focus-border-color);
323
+ border-radius: 2px;
324
+ outline: var(--input-focus-outline);
325
+ }
326
+
327
+ .annotationLayer .buttonWidgetAnnotation.radioButton input {
328
+ border-radius: 50%;
329
+ }
330
+
331
+ .annotationLayer .textWidgetAnnotation textarea {
332
+ resize: none;
333
+ }
334
+
335
+ .annotationLayer .buttonWidgetAnnotation:is(.checkBox, .radioButton) input {
336
+ appearance: none;
337
+ }
338
+
339
+ .annotationLayer .buttonWidgetAnnotation.checkBox input:checked::before,
340
+ .annotationLayer .buttonWidgetAnnotation.checkBox input:checked::after,
341
+ .annotationLayer .buttonWidgetAnnotation.radioButton input:checked::before {
342
+ background-color: CanvasText;
343
+ content: "";
344
+ display: block;
345
+ position: absolute;
346
+ }
347
+
348
+ .annotationLayer .buttonWidgetAnnotation.checkBox input:checked::before,
349
+ .annotationLayer .buttonWidgetAnnotation.checkBox input:checked::after {
350
+ height: 80%;
351
+ left: 45%;
352
+ width: 1px;
353
+ }
354
+
355
+ .annotationLayer .buttonWidgetAnnotation.checkBox input:checked::before {
356
+ transform: rotate(45deg);
357
+ }
358
+
359
+ .annotationLayer .buttonWidgetAnnotation.checkBox input:checked::after {
360
+ transform: rotate(-45deg);
361
+ }
362
+
363
+ .annotationLayer .buttonWidgetAnnotation.radioButton input:checked::before {
364
+ border-radius: 50%;
365
+ height: 50%;
366
+ left: 25%;
367
+ top: 25%;
368
+ width: 50%;
369
+ }
370
+
371
+ .annotationLayer .textWidgetAnnotation :is(input, textarea)[disabled],
372
+ .annotationLayer .choiceWidgetAnnotation select[disabled],
373
+ .annotationLayer .buttonWidgetAnnotation:is(.checkBox, .radioButton) input[disabled] {
374
+ background: none;
375
+ border: 2px solid var(--input-disabled-border-color);
376
+ cursor: not-allowed;
377
+ }
378
+ `;
379
+ }
380
+
381
+ /** @protected */
382
+ get slotStyles() {
383
+ const tag = "vcf-pdf-viewer";
384
+ const lumo = '[data-application-theme="lumo"]';
385
+
386
+ /**
387
+ * These rules target a <vaadin-text-field> element with a child element
388
+ * having the 'toggle-button' class name. We can't use `::slotted()` as
389
+ * the toggle button is not a direct child of the month picker element.
390
+ * Also for `vaadin-popover` we can't use `::part()` after `::slotted()`.
391
+ */
392
+ return [
393
+ `
394
+ ${tag}:not(${lumo}) vaadin-button.toolbar-button vaadin-icon {
395
+ display: none;
396
+ }
397
+
398
+ ${tag}:not(${lumo}) vaadin-button.toolbar-button {
399
+ height: var(--vaadin-icon-size, 1.5lh);
400
+ width: var(--vaadin-icon-size, 1.5lh);
401
+ padding: 0;
402
+ justify-content: flex-start;
403
+ box-sizing: content-box;
404
+ }
405
+
406
+ ${tag}:not(${lumo}) vaadin-button.toolbar-button::before {
407
+ content: '';
408
+ flex: none;
409
+ background: var(--vaadin-input-field-button-text-color, var(--vaadin-text-color-secondary));
410
+ cursor: var(--vaadin-clickable-cursor);
411
+ touch-action: manipulation;
412
+ -webkit-tap-highlight-color: transparent;
413
+ -webkit-user-select: none;
414
+ user-select: none;
415
+ height: var(--vaadin-icon-size, 1.5lh);
416
+ width: var(--vaadin-icon-size, 1.5lh);
417
+ mask-size: var(--vaadin-icon-visual-size, 100%);
418
+ mask-position: 50%;
419
+ mask-repeat: no-repeat;
420
+ }
421
+
422
+ ${tag}:not(${lumo}) vaadin-button.toolbar-button#sidebarToggle::before {
423
+ mask-image: var(--pdf-viewer-toggle-button-icon-closed);
424
+ }
425
+
426
+ ${tag}:not(${lumo}) vaadin-button.toolbar-button#sidebarToggle:has(vaadin-icon.sidebarOpen)::before {
427
+ mask-image: var(--pdf-viewer-toggle-button-icon-open);
428
+ }
429
+
430
+ ${tag}:not(${lumo}) vaadin-button.toolbar-button#previousPage::before {
431
+ mask-image: var(--pdf-viewer-previous-page-button-icon);
432
+ }
433
+
434
+ ${tag}:not(${lumo}) vaadin-button.toolbar-button#nextPage::before {
435
+ mask-image: var(--pdf-viewer-next-page-button-icon);
436
+ }
437
+
438
+ ${tag}${lumo} vaadin-button[theme~="icon"] {
439
+ padding-left: 6px;
440
+ padding-right: 6px;
441
+ }
442
+
443
+ ${tag}${lumo} vaadin-button.toolbar-button#sidebarToggle {
444
+ border: 2px solid;
445
+ border-color: rgba(0, 0, 0, 0.5);
446
+ }
447
+ `,
448
+ ];
449
+ }
450
+
451
+ render() {
452
+ return html`
453
+ <div id="outerContainer" part="outer-container">
454
+ <div id="sidebarContainer" part="sidebar-container">
455
+ <div id="sidebarContent" part="sidebar-content">
456
+ <div id="thumbnailView" part="thumbnail-view"></div>
457
+ </div>
458
+ </div>
459
+ <div id="mainContainer" part="main-container">
460
+ <div id="toolbar" part="toolbar">
461
+ <slot name="sidebar-toggle-button-slot"></slot>
462
+ <span id="title" part="toolbar-text toolbar-title"
463
+ >${this.__title}</span
464
+ >
465
+ <slot name="toolbar-zoom-slot"></slot>
466
+ <div part="toolbar-pages">
467
+ <slot name="toolbar-current-page-slot"></slot>
468
+ <span
469
+ id="pageSeparator"
470
+ part="toolbar-text toolbar-page-separator"
471
+ >/</span
472
+ >
473
+ <span id="totalPages" part="toolbar-text toolbar-total-pages"
474
+ >${this.__totalPages}</span
475
+ >
476
+ <slot name="previous-page-button-slot"></slot>
477
+ <slot name="next-page-button-slot"></slot>
478
+ </div>
479
+ <slot></slot>
480
+ </div>
481
+
482
+ <div id="viewerContainer" part="viewer-container">
483
+ <div id="viewer" part="viewer"></div>
484
+ </div>
485
+ </div>
486
+ </div>
487
+ `;
488
+ }
489
+
490
+ static get is() {
491
+ return "vcf-pdf-viewer";
492
+ }
493
+
494
+ static get version() {
495
+ return "4.1.0";
496
+ }
497
+
498
+ static get properties() {
499
+ return {
500
+ /**
501
+ * You can set a pdf file that you want to render with src. Note that regular cross
502
+ * site scripting (XSS) rules apply. This means that the file should be on the same
503
+ * server as where the component is run, or that the server where the file is on should
504
+ * be configured to allow loading files from other sites.
505
+ */
506
+ src: { type: String },
507
+
508
+ /**
509
+ * The viewer, which takes care of rendering content into a DOM element.
510
+ */
511
+ __viewer: Object,
512
+
513
+ /**
514
+ * The viewer for thumbnails.
515
+ */
516
+ __thumbnailViewer: Object,
517
+
518
+ /**
519
+ * The link service.
520
+ */
521
+ __linkService: Object,
522
+
523
+ /**
524
+ * A represenentation of a document that has been read in.
525
+ */
526
+ __document: Object,
527
+ /**
528
+ * The title for the PDF shown in the toolbar of component. It uses both the file name and
529
+ * the title in the PDF metadata if available.
530
+ */
531
+ __title: { type: String },
532
+ /**
533
+ * Relative filename
534
+ */
535
+ __filename: String,
536
+ /**
537
+ * The pdf metadata title
538
+ */
539
+ __pdfTitle: String,
540
+ /**
541
+ * The level of zoom on the document.
542
+ * Allowed values are
543
+ * - Number, for zoom percentage. Eg. 1.5 means 150% zoom
544
+ * - 'auto', default value
545
+ * - 'page-fit', fit a full page into component
546
+ */
547
+ zoom: { type: String },
548
+ /**
549
+ * The current page visible viewed right now
550
+ */
551
+ currentPage: { type: String },
552
+ /**
553
+ * Total amount of pages in an opened document
554
+ */
555
+ __totalPages: Number,
556
+
557
+ /**
558
+ * Loading state
559
+ */
560
+ __loading: { type: Boolean },
561
+
562
+ /**
563
+ * Whether sidebar is open after loading or not
564
+ */
565
+ __sidebarOpen: { type: Boolean },
566
+
567
+ /**
568
+ * Flag to indicate if toolbar should only show filename as title
569
+ */
570
+ toolbarOnlyFilename: { type: Boolean },
571
+
572
+ /**
573
+ * Property to define auto zoom label
574
+ */
575
+ autoZoomOptionLabel: { type: String },
576
+
577
+ /**
578
+ * Property to define page fit zoom label
579
+ */
580
+ fitZoomOptionLabel: { type: String },
581
+
582
+ /**
583
+ * Property to define a custom title for the viewer
584
+ */
585
+ customTitle: { type: String },
586
+
587
+ /**
588
+ * Renders interactive form elements in the annotation layer (html) if true,
589
+ * renders values of form elements directly onto the canvas if false
590
+ */
591
+ renderInteractiveForms: { type: Boolean },
592
+
593
+ /**
594
+ * Allows to hide the zoom dropdown. By default it's always shown.
595
+ */
596
+ hideZoom: { type: Boolean },
597
+
598
+ /**
599
+ * Property to define a custom tooltip for the sidebar toggle button
600
+ */
601
+ sidebarToggleTooltip: { type: String },
602
+
603
+ /**
604
+ * Property to define a custom tooltip for the previous page button
605
+ */
606
+ previousPageTooltip: { type: String },
607
+
608
+ /**
609
+ * Property to define a custom tooltip for the next page button
610
+ */
611
+ nextPageTooltip: { type: String },
612
+ };
613
+ }
614
+
615
+ get __zoomItems() {
616
+ return this.__computeZoomItems(
617
+ this.autoZoomOptionLabel,
618
+ this.fitZoomOptionLabel,
619
+ );
620
+ }
621
+
622
+ constructor() {
623
+ super();
624
+ this.__title = "PDF";
625
+ this.zoom = "auto";
626
+ this.currentPage = "1";
627
+ this.__loading = true;
628
+ this.__sidebarOpen = false;
629
+ this.toolbarOnlyFilename = false;
630
+ this.autoZoomOptionLabel = "Automatic zoom";
631
+ this.fitZoomOptionLabel = "Page fit";
632
+ this.customTitle = "";
633
+ this.renderInteractiveForms = true;
634
+ this.hideZoom = false;
635
+ this.sidebarToggleTooltip = "";
636
+ this.previousPageTooltip = "";
637
+ this.nextPageTooltip = "";
638
+ }
639
+
640
+ willUpdate(changedProperties) {
641
+ super.willUpdate(changedProperties);
642
+
643
+ // Compute filename if src changes
644
+ if (changedProperties.has("src")) {
645
+ this.__setFilename(this.src);
646
+ }
647
+
648
+ // Compute title before rendering to avoid setting reactive properties during update
649
+ if (
650
+ changedProperties.has("src") ||
651
+ changedProperties.has("customTitle") ||
652
+ changedProperties.has("toolbarOnlyFilename")
653
+ ) {
654
+ this.__setTitle(this.__pdfTitle, this.__filename);
655
+ }
656
+ }
657
+
658
+ updated(changedProperties) {
659
+ super.updated(changedProperties);
660
+
661
+ // Defer property synchronization to avoid "update during update" warning
662
+ // This ensures these calls happen after the current update cycle completes
663
+ Promise.resolve().then(() => {
664
+ if (changedProperties.has("src")) {
665
+ this.__srcChanged(this.src);
666
+ }
667
+ if (changedProperties.has("zoom")) {
668
+ const zoomSelect = this.querySelector("#zoom");
669
+ if (zoomSelect && zoomSelect.value !== this.zoom) {
670
+ zoomSelect.value = this.zoom;
671
+ }
672
+ this.__zoomChanged(this.zoom);
673
+ }
674
+ if (changedProperties.has("currentPage")) {
675
+ const currentPageInput = this.querySelector("#currentPage");
676
+ if (currentPageInput && currentPageInput.value !== this.currentPage) {
677
+ currentPageInput.value = this.currentPage;
678
+ this.__pageChange();
679
+ }
680
+ }
681
+ });
682
+ }
683
+
684
+ __createToolbarButton() {
685
+ const icon = document.createElement("vaadin-icon");
686
+ icon.setAttribute("slot", "prefix");
687
+
688
+ const tooltip = document.createElement("vaadin-tooltip");
689
+ tooltip.setAttribute("slot", "tooltip");
690
+
691
+ const button = document.createElement("vaadin-button");
692
+ button.classList.add("toolbar-button");
693
+ button.setAttribute("theme", "icon tertiary contrast");
694
+
695
+ button.appendChild(icon);
696
+ button.appendChild(tooltip);
697
+ return button;
698
+ }
699
+
700
+ /**
701
+ * Adds toggle button to the toolbar slot named "sidebar-toggle-button-slot".
702
+ */
703
+ _createSideBarToggleButton() {
704
+ const button = this.__createToolbarButton();
705
+ const icon = button.querySelector("vaadin-icon");
706
+ icon.classList.add("toggle-button-icon");
707
+ button
708
+ .querySelector("vaadin-tooltip")
709
+ .setAttribute("text", this.sidebarToggleTooltip);
710
+ button.setAttribute("slot", "sidebar-toggle-button-slot");
711
+ button.setAttribute("id", "sidebarToggle");
712
+ button.setAttribute("aria-label", "Sidebar toggle");
713
+ button.addEventListener("click", () => {
714
+ this.__toogleSidebar();
715
+ if (this._outerContainer.classList.contains("sidebarOpen")) {
716
+ icon.classList.add("sidebarOpen");
717
+ } else {
718
+ icon.classList.remove("sidebarOpen");
719
+ }
720
+ });
721
+ this.appendChild(button);
722
+ }
723
+
724
+ /**
725
+ * Adds previous page button to the toolbar slot named "previous-page-button-slot".
726
+ */
727
+ _createPreviousPageButton() {
728
+ const button = this.__createToolbarButton();
729
+ button
730
+ .querySelector("vaadin-icon")
731
+ .classList.add("previous-page-button-icon");
732
+ button
733
+ .querySelector("vaadin-tooltip")
734
+ .setAttribute("text", this.previousPageTooltip);
735
+ button.setAttribute("slot", "previous-page-button-slot");
736
+ button.setAttribute("id", "previousPage");
737
+ button.setAttribute("aria-label", "Previous page");
738
+ button.addEventListener("click", () => this.__previousPage());
739
+ this.appendChild(button);
740
+ }
741
+
742
+ /**
743
+ * Adds next page button to the toolbar slot named "next-page-button-slot".
744
+ */
745
+ _createNextPageButton() {
746
+ const button = this.__createToolbarButton();
747
+ button.querySelector("vaadin-icon").classList.add("next-page-button-icon");
748
+ button
749
+ .querySelector("vaadin-tooltip")
750
+ .setAttribute("text", this.nextPageTooltip);
751
+ button.setAttribute("slot", "next-page-button-slot");
752
+ button.setAttribute("id", "nextPage");
753
+ button.setAttribute("aria-label", "Next page");
754
+ button.addEventListener("click", () => this.__nextPage());
755
+ this.appendChild(button);
756
+ }
757
+
758
+ /**
759
+ * Adds current page text field to the toolbar slot named "toolbar-current-page-slot".
760
+ */
761
+ _createCurrentPageTextField() {
762
+ const textField = document.createElement("vaadin-text-field");
763
+ textField.setAttribute("slot", "toolbar-current-page-slot");
764
+ textField.setAttribute("id", "currentPage");
765
+ textField.classList.add("toolbar-current-page");
766
+ textField.setAttribute("value", this.currentPage);
767
+ textField.addEventListener("change", () => this.__pageChange());
768
+ this.appendChild(textField);
769
+ }
770
+
771
+ /**
772
+ * Adds zoom select to the toolbar slot named "toolbar-zoom-slot".
773
+ */
774
+ _createZoomSelect() {
775
+ const select = document.createElement("vaadin-select");
776
+ select.setAttribute("slot", "toolbar-zoom-slot");
777
+ select.setAttribute("id", "zoom");
778
+ select.classList.add("toolbar-zoom");
779
+ select.setAttribute("value", this.zoom);
780
+ select.items = this.__zoomItems;
781
+ select.addEventListener("value-changed", (e) => {
782
+ if (e.detail.value !== this.zoom) {
783
+ this.__zoomChanged(e.detail.value);
784
+ }
785
+ });
786
+ if (this.hideZoom) {
787
+ select.classList.add("hide-zoom");
788
+ } else {
789
+ select.classList.remove("hide-zoom");
790
+ }
791
+ this.appendChild(select);
792
+ }
793
+
794
+ __computeZoomItems(autoZoomOptionLabel, fitZoomOptionLabel) {
795
+ return [
796
+ { label: autoZoomOptionLabel, value: "auto" },
797
+ { label: fitZoomOptionLabel, value: "page-fit" },
798
+ { label: "50%", value: "0.5" },
799
+ { label: "75%", value: "0.75" },
800
+ { label: "100%", value: "1.0" },
801
+ { label: "125%", value: "1.25" },
802
+ { label: "150%", value: "1.5" },
803
+ { label: "200%", value: "2.0" },
804
+ { label: "300%", value: "3.0" },
805
+ { label: "400%", value: "4.0" },
806
+ ];
807
+ }
808
+
809
+ __setTitle(pdfTitle, filename) {
810
+ if (this.customTitle) {
811
+ this.__title = this.customTitle;
812
+ } else if (this.__viewer && this.toolbarOnlyFilename && filename) {
813
+ this.__title = filename;
814
+ } else if (pdfTitle && filename) {
815
+ this.__title = pdfTitle + " - " + filename;
816
+ } else if (pdfTitle) {
817
+ this.__title = pdfTitle;
818
+ } else if (filename) {
819
+ this.__title = filename;
820
+ } else {
821
+ this.__title = "PDF";
822
+ }
823
+ }
824
+
825
+ _addToolbarButtons() {
826
+ this._createSideBarToggleButton();
827
+ this._createZoomSelect();
828
+ this._createCurrentPageTextField();
829
+ this._createPreviousPageButton();
830
+ this._createNextPageButton();
831
+ }
832
+
833
+ firstUpdated() {
834
+ // Required by PDF.js annotation editor: --freetext-line-height must match
835
+ // the LINE_FACTOR constant (1.35) defined in src/shared/util.js
836
+ document.documentElement.style.setProperty('--freetext-line-height', '1.35');
837
+
838
+ this._toolbar = this.shadowRoot.querySelector("#toolbar");
839
+ this._viewerContainer = this.shadowRoot.querySelector("#viewerContainer");
840
+ this._outerContainer = this.shadowRoot.querySelector("#outerContainer");
841
+ this._viewer = this.shadowRoot.querySelector("#viewer");
842
+ this._thumbnailView = this.shadowRoot.querySelector("#thumbnailView");
843
+
844
+ this._addToolbarButtons();
845
+
846
+ this._viewerContainer.addEventListener(
847
+ "focus",
848
+ (e) => this.__setFocused(true),
849
+ true,
850
+ );
851
+ this._viewerContainer.addEventListener(
852
+ "blur",
853
+ (e) => this.__setFocused(false),
854
+ true,
855
+ );
856
+ this._viewerContainer.addEventListener("mousedown", (e) => {
857
+ this._mousedown = true;
858
+ const mouseUpListener = () => {
859
+ this._mousedown = false;
860
+ document.removeEventListener("mouseup", mouseUpListener);
861
+ };
862
+ document.addEventListener("mouseup", mouseUpListener);
863
+ });
864
+
865
+ // options
866
+ const eventBus = new EventBus();
867
+
868
+ // Defer initialization of reactive controller properties to avoid immediate update request
869
+ // that triggers "update scheduled after update" warning.
870
+ Promise.resolve().then(() => {
871
+ this.__linkService = new pdfjsLinkService.PDFLinkService({
872
+ eventBus,
873
+ });
874
+ var pdfRenderingQueue = new pdfjsRenderingQueue.PDFRenderingQueue();
875
+ var l10n = new GenericL10n(navigator.language || 'en-US');
876
+
877
+ // pdfViewer
878
+ this.__viewer = new pdfjsViewer.PDFViewer({
879
+ container: this._viewerContainer,
880
+ textLayerMode: 2,
881
+ viewer: this._viewer,
882
+ eventBus: eventBus,
883
+ linkService: this.__linkService,
884
+ renderingQueue: pdfRenderingQueue,
885
+ l10n: l10n,
886
+ annotationMode: this.renderInteractiveForms
887
+ ? pdfjsLib.AnnotationMode.ENABLE_FORMS
888
+ : pdfjsLib.AnnotationMode.ENABLE,
889
+ });
890
+
891
+ this.__linkService.setViewer(this.__viewer);
892
+ pdfRenderingQueue.setViewer(this.__viewer);
893
+
894
+ // thumbnailViewer
895
+ this.__thumbnailViewer = new pdfjsThumbnailViewer.PDFThumbnailViewer({
896
+ container: this._thumbnailView,
897
+ eventBus: eventBus,
898
+ linkService: this.__linkService,
899
+ renderingQueue: pdfRenderingQueue,
900
+ l10n: l10n,
901
+ });
902
+
903
+ pdfRenderingQueue.setThumbnailViewer(this.__thumbnailViewer);
904
+
905
+ // Initialize listeners after viewer creation
906
+ this.__initListeners(eventBus);
907
+ });
908
+ }
909
+
910
+ __initListeners(eventBus) {
911
+ // listeners
912
+ eventBus.on("pagesinit", () => {
913
+ this.__viewer.currentScaleValue = this.zoom;
914
+ this.__loading = false;
915
+ this.__updateThumbnailViewer();
916
+ if (this.__sidebarOpen) {
917
+ this.__openSidebar();
918
+ } else {
919
+ this.__closeSidebar();
920
+ }
921
+ this.__viewer.currentPage = this.setCurrentPage();
922
+ });
923
+ eventBus.on("pagechanging", (event) => {
924
+ this.__updateCurrentPageValue(event.pageNumber);
925
+ this.__updatePageNumberStates();
926
+ if (
927
+ this.__thumbnailViewer &&
928
+ this.__thumbnailViewer.renderingQueue.isThumbnailViewEnabled
929
+ ) {
930
+ this.__thumbnailViewer.scrollThumbnailIntoView(this.currentPage);
931
+ }
932
+ const currentPageInput = this.querySelector("#currentPage");
933
+ if (currentPageInput) {
934
+ currentPageInput.value = this.currentPage;
935
+ }
936
+ });
937
+ }
938
+
939
+ __updateCurrentPageValue(pageNumber) {
940
+ this.currentPage = "" + pageNumber;
941
+ this.dispatchEvent(new CustomEvent("currentPage-changed"));
942
+ }
943
+
944
+ __setFocused(focused) {
945
+ if (focused) {
946
+ this._viewerContainer.setAttribute("focused", "");
947
+ if (!this._mousedown) {
948
+ this._viewerContainer.setAttribute("focus-ring", "");
949
+ }
950
+ } else {
951
+ this._viewerContainer.removeAttribute("focused");
952
+ this._viewerContainer.removeAttribute("focus-ring");
953
+ }
954
+ }
955
+
956
+ __open(src) {
957
+ // Is there already a document loaded?
958
+ if (this.__document) {
959
+ // We need to close the current document
960
+ return this.__close().then(() => {
961
+ // and start over with opening the new one
962
+ return this.__open(src);
963
+ });
964
+ }
965
+ if (!src) {
966
+ // No file given, show nothing.
967
+ return;
968
+ }
969
+ this.__setFilename(src);
970
+ this.__document = pdfjsLib.getDocument(new URL(src, document.baseURI).href);
971
+ return this.__document.promise.then(
972
+ (pdfDocument) => {
973
+ // Document loaded, specifying document for the viewer.
974
+ this.__thumbnailViewer.setDocument(pdfDocument);
975
+ this.__viewer.setDocument(pdfDocument);
976
+ this.__linkService.setDocument(pdfDocument);
977
+
978
+ this._toolbar.classList.add("ready");
979
+ this.__totalPages = pdfDocument.numPages;
980
+ this.__updatePageNumberStates();
981
+ this.__setPdfTitleFromMetadata(pdfDocument).then(() => {
982
+ this.dispatchEvent(
983
+ new CustomEvent("document-loaded", {
984
+ detail: {
985
+ document: pdfDocument,
986
+ },
987
+ }),
988
+ );
989
+ });
990
+ },
991
+ function (exception) {
992
+ console.error(exception && exception.message);
993
+ },
994
+ );
995
+ }
996
+
997
+ __srcChanged(newSrc) {
998
+ this.__open(newSrc);
999
+ }
1000
+
1001
+ /**
1002
+ * Closes opened PDF document.
1003
+ * @returns {Promise} - Returns the promise, which is resolved when all
1004
+ * destruction is completed.
1005
+ */
1006
+ __close() {
1007
+ this._toolbar.classList.remove("ready");
1008
+ this.__filename = "PDF";
1009
+ if (!this.__document) {
1010
+ return Promise.resolve();
1011
+ }
1012
+
1013
+ var promise = this.__document.destroy();
1014
+ if (this.__document) {
1015
+ this.__document = null;
1016
+ this.__viewer.setDocument(null);
1017
+ this.__thumbnailViewer.setDocument(null);
1018
+ this.__linkService.setDocument(null);
1019
+ }
1020
+ return promise;
1021
+ }
1022
+
1023
+ __setFilename(src) {
1024
+ let filename = pdfjsLib.getFilenameFromUrl(src) || src;
1025
+ try {
1026
+ filename = decodeURIComponent(filename);
1027
+ } catch (e) {
1028
+ // decodeURIComponent may throw URIError,
1029
+ // fall back to using the unprocessed url in that case
1030
+ }
1031
+ this.__filename = filename;
1032
+ }
1033
+
1034
+ __setPdfTitleFromMetadata(pdfDocument) {
1035
+ return pdfDocument.getMetadata().then((data) => {
1036
+ let pdfTitle;
1037
+ const metadata = data.metadata;
1038
+ if (metadata && metadata.has("dc:title")) {
1039
+ const title = metadata.get("dc:title");
1040
+ // Ghostscript sometimes returns 'Untitled', so prevent setting the
1041
+ // title to 'Untitled'.
1042
+ if (title !== "Untitled") {
1043
+ pdfTitle = title;
1044
+ }
1045
+ }
1046
+
1047
+ const info = data.info;
1048
+ if (!pdfTitle && info && info["Title"]) {
1049
+ pdfTitle = info["Title"];
1050
+ }
1051
+ this.__pdfTitle = pdfTitle;
1052
+ });
1053
+ }
1054
+
1055
+ __updatePageNumberStates() {
1056
+ this.querySelector("#previousPage").disabled = this.currentPage === "1";
1057
+ this.querySelector("#nextPage").disabled =
1058
+ this.currentPage === "" + this.__totalPages;
1059
+ }
1060
+
1061
+ __zoomChanged(value) {
1062
+ if (!this.__viewer || this.__loading) {
1063
+ return;
1064
+ }
1065
+ // This logs error 'TextLayerBuilder._bindEvents: `this.cancel()` should have
1066
+ // been called when the page was reset, or rendering cancelled.'
1067
+ //
1068
+ // There is a problem deep inside pdfjs viewer that causes an console.error()
1069
+ // to be logged, but the component still works. It seems to be due to
1070
+ // webcomponents/shadow dom messing with
1071
+ // TODO: Fix the issue so that we get rid of the error in log
1072
+ this.__viewer.currentScaleValue = value;
1073
+ this.__viewer.forceRendering();
1074
+ }
1075
+
1076
+ __pageChange(event) {
1077
+ const currentPageInput = this.querySelector("#currentPage");
1078
+ if (!currentPageInput) {
1079
+ return; // Component not yet rendered, skip
1080
+ }
1081
+ const currentPageValue = currentPageInput.value;
1082
+ let pageNumber = parseInt(currentPageValue, 10);
1083
+ if (isNaN(pageNumber)) {
1084
+ pageNumber = this.__viewer.currentPageNumber;
1085
+ currentPageInput.value = "" + pageNumber;
1086
+ }
1087
+ if (pageNumber < 1) {
1088
+ pageNumber = 1;
1089
+ }
1090
+ if (pageNumber > this.__totalPages) {
1091
+ pageNumber = this.__totalPages;
1092
+ }
1093
+ this.__viewer.currentPageNumber = pageNumber;
1094
+ }
1095
+
1096
+ setCurrentPage(value) {
1097
+ if (value != undefined) {
1098
+ this.currentPage = "" + value;
1099
+ }
1100
+ this.__pageChange();
1101
+ }
1102
+
1103
+ _getPage() {
1104
+ return this.__viewer.currentPageNumber;
1105
+ }
1106
+
1107
+ __previousPage() {
1108
+ this.__viewer.currentPageNumber--;
1109
+ }
1110
+
1111
+ __nextPage() {
1112
+ this.__viewer.currentPageNumber++;
1113
+ }
1114
+
1115
+ __toogleSidebar() {
1116
+ if (this._outerContainer.classList.length == 0) {
1117
+ this.__openSidebar();
1118
+ } else {
1119
+ this.__closeSidebar();
1120
+ }
1121
+ }
1122
+
1123
+ __openSidebar() {
1124
+ if (!this.__thumbnailViewer || this.__loading) {
1125
+ this.__sidebarOpen = true;
1126
+ } else {
1127
+ this.__thumbnailViewer.renderingQueue.isThumbnailViewEnabled = true;
1128
+ this.__updateThumbnailViewer();
1129
+ this._outerContainer.classList.add("sidebarOpen");
1130
+ }
1131
+ }
1132
+
1133
+ __closeSidebar() {
1134
+ if (!this.__thumbnailViewer || this.__loading) {
1135
+ this.__sidebarOpen = false;
1136
+ } else {
1137
+ this.__thumbnailViewer.renderingQueue.isThumbnailViewEnabled = false;
1138
+ this._outerContainer.classList.remove("sidebarOpen");
1139
+ }
1140
+ }
1141
+
1142
+ __updateThumbnailViewer() {
1143
+ const pagesCount = this.__totalPages;
1144
+ for (let i = 0; i < pagesCount; i++) {
1145
+ const pageView = this.__viewer.getPageView(i);
1146
+ if (
1147
+ pageView.renderingState === pdfUtils.RenderingStates.FINISHED
1148
+ ) {
1149
+ const thumbnailView = this.__thumbnailViewer.getThumbnail(i);
1150
+ thumbnailView.setImage(pageView);
1151
+ } else {
1152
+ this.__thumbnailViewer.renderingQueue.renderHighestPriority();
1153
+ }
1154
+ }
1155
+ var component = this;
1156
+ for (let i = 0; i < this.__thumbnailViewer._thumbnails.length; i++) {
1157
+ const thumbnailView = this.__thumbnailViewer.getThumbnail(i);
1158
+ thumbnailView.anchor.onclick = function () {
1159
+ const id = thumbnailView.id;
1160
+ thumbnailView.linkService.goToPage(id);
1161
+ component.dispatchEvent(
1162
+ new CustomEvent("thumbnail-clicked", {
1163
+ detail: {
1164
+ source: component,
1165
+ pageNumber: id,
1166
+ },
1167
+ }),
1168
+ );
1169
+ return false;
1170
+ };
1171
+ }
1172
+ if (
1173
+ this.__thumbnailViewer &&
1174
+ this.__thumbnailViewer.renderingQueue.isThumbnailViewEnabled
1175
+ ) {
1176
+ this.__thumbnailViewer.scrollThumbnailIntoView(this.currentPage);
1177
+ }
1178
+ }
1179
+
1180
+ _onResize() {
1181
+ if (this.offsetWidth < 600) {
1182
+ this.classList.add("small-size");
1183
+ this._toolbar.classList.add("small-size");
1184
+ } else {
1185
+ this.classList.remove("small-size");
1186
+ this._toolbar.classList.remove("small-size");
1187
+ }
1188
+ }
1189
+
1190
+ /**
1191
+ * Rotates document clockwise.
1192
+ */
1193
+ rotateCw() {
1194
+ let delta = 90;
1195
+ this.__viewer.pagesRotation += delta;
1196
+ this.__viewer.forceRendering();
1197
+ }
1198
+
1199
+ /**
1200
+ * Rotates document counterclockwise.
1201
+ */
1202
+ rotateCcw() {
1203
+ let delta = -90;
1204
+ this.__viewer.pagesRotation += delta;
1205
+ this.__viewer.forceRendering();
1206
+ }
1207
+ }
1208
+
1209
+ customElements.define(PdfViewerElement.is, PdfViewerElement);
1210
+
1211
+ /**
1212
+ * @namespace Vaadin
1213
+ */
1214
+ window.Vaadin.PdfViewerElement = PdfViewerElement;