@ngstarter-ui/components 21.0.32 → 21.0.34
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/ai/component-registry.json +121 -2
- package/fesm2022/ngstarter-ui-components-form-field.mjs +2 -2
- package/fesm2022/ngstarter-ui-components-form-field.mjs.map +1 -1
- package/fesm2022/ngstarter-ui-components-icon.mjs +118 -7
- package/fesm2022/ngstarter-ui-components-icon.mjs.map +1 -1
- package/fesm2022/ngstarter-ui-components-pdf-viewer.mjs +1873 -0
- package/fesm2022/ngstarter-ui-components-pdf-viewer.mjs.map +1 -0
- package/package.json +8 -1
- package/types/ngstarter-ui-components-icon.d.ts +27 -6
- package/types/ngstarter-ui-components-pdf-viewer.d.ts +393 -0
|
@@ -0,0 +1,1873 @@
|
|
|
1
|
+
import { NgTemplateOutlet, DOCUMENT, isPlatformBrowser } from '@angular/common';
|
|
2
|
+
import * as i0 from '@angular/core';
|
|
3
|
+
import { input, output, signal, computed, ChangeDetectionStrategy, Component, Directive, Injectable, effect, untracked, inject, DestroyRef, ElementRef, PLATFORM_ID, viewChild, contentChildren, numberAttribute, booleanAttribute } from '@angular/core';
|
|
4
|
+
import { MatchFlag } from '@embedpdf/models';
|
|
5
|
+
import { BlockLoader } from '@ngstarter-ui/components/block-loader';
|
|
6
|
+
import { Button } from '@ngstarter-ui/components/button';
|
|
7
|
+
import { Divider } from '@ngstarter-ui/components/divider';
|
|
8
|
+
import { Icon } from '@ngstarter-ui/components/icon';
|
|
9
|
+
import { ImagePlaceholder } from '@ngstarter-ui/components/image-placeholder';
|
|
10
|
+
import { Menu, MenuDivider, MenuHeading, MenuItem, MenuTrigger } from '@ngstarter-ui/components/menu';
|
|
11
|
+
import { Panel, PanelContent, PanelHeader, PanelAside, PanelSidebar } from '@ngstarter-ui/components/panel';
|
|
12
|
+
import { Toolbar, ToolbarSpacer, ToolbarTitle, ToolbarItem } from '@ngstarter-ui/components/toolbar';
|
|
13
|
+
import { isObservable } from 'rxjs';
|
|
14
|
+
import { Avatar } from '@ngstarter-ui/components/avatar';
|
|
15
|
+
import { Card, CardActions, CardAside, CardAvatar, CardContent, CardHeader, CardSubtitle, CardTitle } from '@ngstarter-ui/components/card';
|
|
16
|
+
import { Chip } from '@ngstarter-ui/components/chips';
|
|
17
|
+
import { FormField, IconButtonSuffix, IconPrefix } from '@ngstarter-ui/components/form-field';
|
|
18
|
+
import { Input } from '@ngstarter-ui/components/input';
|
|
19
|
+
import { PluginRegistry } from '@embedpdf/core';
|
|
20
|
+
import { Checkbox } from '@ngstarter-ui/components/checkbox';
|
|
21
|
+
|
|
22
|
+
class PdfViewerAnnotations {
|
|
23
|
+
annotations = input([], ...(ngDevMode ? [{ debugName: "annotations" }] : /* istanbul ignore next */ []));
|
|
24
|
+
annotationDefs = input([], ...(ngDevMode ? [{ debugName: "annotationDefs" }] : /* istanbul ignore next */ []));
|
|
25
|
+
annotationTypeProperty = input('type', ...(ngDevMode ? [{ debugName: "annotationTypeProperty" }] : /* istanbul ignore next */ []));
|
|
26
|
+
closed = output();
|
|
27
|
+
pageSelected = output();
|
|
28
|
+
filterQuery = signal('', ...(ngDevMode ? [{ debugName: "filterQuery" }] : /* istanbul ignore next */ []));
|
|
29
|
+
filteredAnnotations = computed(() => {
|
|
30
|
+
const query = this.filterQuery().trim().toLocaleLowerCase();
|
|
31
|
+
const annotations = this.annotations();
|
|
32
|
+
if (!query) {
|
|
33
|
+
return annotations;
|
|
34
|
+
}
|
|
35
|
+
return annotations.filter((annotation) => this.annotationMatchesFilter(annotation, query));
|
|
36
|
+
}, ...(ngDevMode ? [{ debugName: "filteredAnnotations" }] : /* istanbul ignore next */ []));
|
|
37
|
+
setFilterQuery(event) {
|
|
38
|
+
this.filterQuery.set(event.target.value);
|
|
39
|
+
}
|
|
40
|
+
clearFilterQuery() {
|
|
41
|
+
this.filterQuery.set('');
|
|
42
|
+
}
|
|
43
|
+
getAnnotationTemplate(annotation, index) {
|
|
44
|
+
const annotationDefs = this.annotationDefs();
|
|
45
|
+
const typeProperty = this.annotationTypeProperty();
|
|
46
|
+
const matchingDef = annotationDefs.find((def) => def.matches(annotation, index, typeProperty))
|
|
47
|
+
?? annotationDefs.find((def) => !def.hasWhen());
|
|
48
|
+
return matchingDef?.template ?? null;
|
|
49
|
+
}
|
|
50
|
+
getAnnotationTemplateContext(annotation, index) {
|
|
51
|
+
return {
|
|
52
|
+
$implicit: annotation,
|
|
53
|
+
annotation,
|
|
54
|
+
index,
|
|
55
|
+
goToPage: (pageNumber) => this.pageSelected.emit(pageNumber),
|
|
56
|
+
};
|
|
57
|
+
}
|
|
58
|
+
getAnnotationTypeLabel(annotation) {
|
|
59
|
+
const label = annotation.label ?? annotation.type;
|
|
60
|
+
return typeof label === 'string' && label.trim().length > 0 ? label : 'Comment';
|
|
61
|
+
}
|
|
62
|
+
getAvatarLabel(annotation) {
|
|
63
|
+
const explicitLabel = annotation.avatarLabel;
|
|
64
|
+
if (typeof explicitLabel === 'string' && explicitLabel.trim().length > 0) {
|
|
65
|
+
return explicitLabel.trim();
|
|
66
|
+
}
|
|
67
|
+
return annotation.author
|
|
68
|
+
.split(/\s+/)
|
|
69
|
+
.filter(Boolean)
|
|
70
|
+
.slice(0, 2)
|
|
71
|
+
.map((part) => part[0]?.toUpperCase())
|
|
72
|
+
.join('');
|
|
73
|
+
}
|
|
74
|
+
getAvatarImage(annotation) {
|
|
75
|
+
return typeof annotation.avatarUrl === 'string' ? annotation.avatarUrl : '';
|
|
76
|
+
}
|
|
77
|
+
getReplyLabel(annotation) {
|
|
78
|
+
return typeof annotation.replyLabel === 'string' && annotation.replyLabel.trim().length > 0
|
|
79
|
+
? annotation.replyLabel
|
|
80
|
+
: 'Reply';
|
|
81
|
+
}
|
|
82
|
+
annotationMatchesFilter(annotation, query) {
|
|
83
|
+
return [
|
|
84
|
+
annotation.author,
|
|
85
|
+
annotation.time,
|
|
86
|
+
annotation.text,
|
|
87
|
+
annotation.type,
|
|
88
|
+
annotation.label,
|
|
89
|
+
`page ${annotation.pageNumber}`,
|
|
90
|
+
]
|
|
91
|
+
.filter((value) => typeof value === 'string')
|
|
92
|
+
.some((value) => value.toLocaleLowerCase().includes(query));
|
|
93
|
+
}
|
|
94
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.4", ngImport: i0, type: PdfViewerAnnotations, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
95
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.4", type: PdfViewerAnnotations, isStandalone: true, selector: "ngs-pdf-viewer-annotations", inputs: { annotations: { classPropertyName: "annotations", publicName: "annotations", isSignal: true, isRequired: false, transformFunction: null }, annotationDefs: { classPropertyName: "annotationDefs", publicName: "annotationDefs", isSignal: true, isRequired: false, transformFunction: null }, annotationTypeProperty: { classPropertyName: "annotationTypeProperty", publicName: "annotationTypeProperty", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { closed: "closed", pageSelected: "pageSelected" }, host: { classAttribute: "ngs-pdf-viewer-annotations" }, ngImport: i0, template: "<ngs-panel class=\"pdf-viewer-annotations\" aria-label=\"PDF annotations\">\n <ngs-panel-header class=\"pdf-viewer-annotations__toolbar\">\n <h3>Annotations</h3>\n <button\n class=\"pdf-viewer-annotations__close\"\n ngsIconButton\n type=\"button\"\n aria-label=\"Close annotations panel\"\n (click)=\"closed.emit()\">\n <ngs-icon name=\"fluent:dismiss-24-regular\" />\n </button>\n </ngs-panel-header>\n\n <ngs-panel-content>\n <div class=\"pdf-viewer-annotations__content\">\n <div class=\"pdf-viewer-annotations__filters\">\n <ngs-form-field subscriptHiddenIfEmpty>\n <ngs-icon ngsIconPrefix name=\"fluent:search-24-regular\" />\n <input\n ngsInput\n type=\"text\"\n aria-label=\"Filter annotations\"\n placeholder=\"Filter annotations...\"\n [value]=\"filterQuery()\"\n (input)=\"setFilterQuery($event)\" />\n @if (filterQuery()) {\n <button\n ngsIconButton\n ngsIconButtonSuffix\n type=\"button\"\n aria-label=\"Clear annotation filter\"\n (click)=\"clearFilterQuery()\">\n <ngs-icon name=\"fluent:dismiss-24-regular\" />\n </button>\n }\n </ngs-form-field>\n\n <button\n ngsIconButton\n type=\"button\"\n aria-label=\"Annotation filters\">\n <ngs-icon name=\"fluent:filter-24-regular\" />\n </button>\n </div>\n\n <div class=\"pdf-viewer-annotations__list\">\n <ng-template #defaultAnnotationTemplate let-annotation let-goToPage=\"goToPage\">\n <ngs-card class=\"pdf-viewer-annotation-card\">\n <ngs-card-header>\n <ngs-avatar\n ngsCardAvatar\n variant=\"tonal\"\n [image]=\"getAvatarImage(annotation)\"\n [label]=\"getAvatarLabel(annotation)\"\n [alt]=\"annotation.author\" />\n\n <ngs-card-title>{{ annotation.author }}</ngs-card-title>\n @if (annotation.time) {\n <ngs-card-subtitle>{{ annotation.time }}</ngs-card-subtitle>\n }\n\n <ngs-card-aside>\n <ngs-chip appearance=\"tonal\">{{ getAnnotationTypeLabel(annotation) }}</ngs-chip>\n </ngs-card-aside>\n </ngs-card-header>\n\n <ngs-card-content>\n <p>{{ annotation.text }}</p>\n </ngs-card-content>\n\n <ngs-card-actions align=\"between\">\n <button ngsButton=\"tonal\" type=\"button\" (click)=\"goToPage(annotation.pageNumber)\">\n Page {{ annotation.pageNumber }}\n </button>\n\n <button ngsButton=\"text\" type=\"button\">\n <ngs-icon name=\"fluent:arrow-reply-24-regular\" />\n {{ getReplyLabel(annotation) }}\n </button>\n </ngs-card-actions>\n </ngs-card>\n </ng-template>\n\n @for (annotation of filteredAnnotations(); track annotation.id ?? annotation.pageNumber + '-' + $index) {\n <ng-container\n [ngTemplateOutlet]=\"getAnnotationTemplate(annotation, $index) ?? defaultAnnotationTemplate\"\n [ngTemplateOutletContext]=\"getAnnotationTemplateContext(annotation, $index)\" />\n } @empty {\n <div class=\"pdf-viewer-annotations__empty\">No annotations found.</div>\n }\n </div>\n </div>\n </ngs-panel-content>\n</ngs-panel>\n", styles: [":host{display:block;min-height:0;height:100%}:host ngs-panel.pdf-viewer-annotations{background:var(--ngs-color-surface)}:host ngs-panel.pdf-viewer-annotations .pdf-viewer-annotations__toolbar{display:flex;align-items:center;justify-content:space-between;padding:0 calc(var(--spacing, .25rem) * 4);border-bottom:1px solid var(--ngs-color-border)}:host ngs-panel.pdf-viewer-annotations .pdf-viewer-annotations__toolbar h3{margin:0;color:var(--ngs-color-on-surface);font-size:var(--ngs-font-size-sm);font-weight:600;letter-spacing:0;text-transform:uppercase}:host ngs-panel.pdf-viewer-annotations .pdf-viewer-annotations__content{display:flex;flex-direction:column;min-height:100%;background:var(--ngs-color-surface-container-lowest)}:host ngs-panel.pdf-viewer-annotations .pdf-viewer-annotations__filters{display:grid;grid-template-columns:minmax(0,1fr) auto;align-items:center;gap:calc(var(--spacing, .25rem) * 2);padding:calc(var(--spacing, .25rem) * 3) calc(var(--spacing, .25rem) * 4);border-bottom:1px solid var(--ngs-color-border)}:host ngs-panel.pdf-viewer-annotations .pdf-viewer-annotations__list{display:flex;flex:1 1 auto;flex-direction:column;gap:calc(var(--spacing, .25rem) * 4);padding:calc(var(--spacing, .25rem) * 4)}:host ngs-panel.pdf-viewer-annotations .pdf-viewer-annotations__empty{padding:calc(var(--spacing, .25rem) * 4);color:var(--ngs-color-on-surface-variant);font-size:var(--ngs-font-size-sm);text-align:center}:host ngs-card.pdf-viewer-annotation-card{--ngs-card-bg: var(--ngs-color-surface);--ngs-card-radius: var(--ngs-radius-md);--ngs-card-padding: calc(var(--spacing, .25rem) * 4)}:host ngs-card.pdf-viewer-annotation-card ngs-card-header{align-items:flex-start}:host ngs-card.pdf-viewer-annotation-card ngs-card-title{font-weight:600}:host ngs-card.pdf-viewer-annotation-card ngs-card-subtitle{color:var(--ngs-color-on-surface-variant)}:host ngs-card.pdf-viewer-annotation-card ngs-card-content p{margin:0;color:var(--ngs-color-on-surface);font-size:var(--ngs-font-size-base);line-height:1.45}:host ngs-card.pdf-viewer-annotation-card ngs-card-actions{padding:0 calc(var(--spacing, .25rem) * 3) calc(var(--spacing, .25rem) * 3)}:host ngs-card.pdf-viewer-annotation-card ngs-chip{text-transform:uppercase}\n/*! tailwindcss v4.2.2 | MIT License | https://tailwindcss.com */\n"], dependencies: [{ kind: "component", type: Avatar, selector: "ngs-avatar,[ngs-avatar]", inputs: ["image", "variant", "clickable", "label", "alt", "automaticColor", "presenceIndicator"], exportAs: ["ngsAvatar"] }, { kind: "component", type: Button, selector: " button[ngsButton], button[ngsIconButton], a[ngsButton], a[ngsIconButton] ", inputs: ["ngsButton", "ngsIconButton", "loading", "disabled", "disabledInteractive", "disableRipple", "reverse", "fullWidth", "hideTextOnMobile"], exportAs: ["ngsButton"] }, { kind: "component", type: Card, selector: "ngs-card", inputs: ["appearance"], exportAs: ["ngsCard"] }, { kind: "component", type: CardActions, selector: "ngs-card-actions, [ngs-card-actions], [ngsCardActions]", inputs: ["align"] }, { kind: "component", type: CardAside, selector: "ngs-card-aside, [ngs-card-aside], ngsCardAside" }, { kind: "directive", type: CardAvatar, selector: "ngs-card-avatar, [ngs-card-avatar], [ngsCardAvatar]" }, { kind: "component", type: CardContent, selector: "ngs-card-content, [ngs-card-content], [ngsCardContent]", inputs: ["withoutPadding"], exportAs: ["ngsCardContent"] }, { kind: "component", type: CardHeader, selector: "ngs-card-header", exportAs: ["ngsCardHeader"] }, { kind: "component", type: CardSubtitle, selector: "ngs-card-subtitle, [ngs-card-subtitle], [ngsCardSubtitle]", exportAs: ["ngsCardSubtitle"] }, { kind: "component", type: CardTitle, selector: "ngs-card-title, [ngs-card-title], [ngsCardTitle]", exportAs: ["ngsCardTitle"] }, { kind: "component", type: Chip, selector: "ngs-chip", inputs: ["appearance", "disabled", "value"], outputs: ["destroyed", "removed"], exportAs: ["ngsChip"] }, { kind: "component", type: FormField, selector: "ngs-form-field", inputs: ["subscriptHiddenIfEmpty", "sameHeightAsButton"], exportAs: ["ngsFormField"] }, { kind: "component", type: Icon, selector: "ngs-icon", inputs: ["name"], exportAs: ["ngsIcon"] }, { kind: "directive", type: IconButtonSuffix, selector: "[ngsIconButtonSuffix]" }, { kind: "directive", type: IconPrefix, selector: "[ngsIconPrefix]" }, { kind: "directive", type: Input, selector: "input[ngsInput], textarea[ngsInput]", inputs: ["id", "placeholder", "required", "disabled", "readonly", "errorStateMatcher"], exportAs: ["ngsInput"] }, { kind: "directive", type: NgTemplateOutlet, selector: "[ngTemplateOutlet]", inputs: ["ngTemplateOutletContext", "ngTemplateOutlet", "ngTemplateOutletInjector"] }, { kind: "component", type: Panel, selector: "ngs-panel", inputs: ["absolute"], exportAs: ["ngsPanel"] }, { kind: "component", type: PanelContent, selector: "ngs-panel-content", exportAs: ["ngsPanelContent"] }, { kind: "component", type: PanelHeader, selector: "ngs-panel-header", inputs: ["flex", "autoHeight"], exportAs: ["ngsPanelHeader"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
|
|
96
|
+
}
|
|
97
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.4", ngImport: i0, type: PdfViewerAnnotations, decorators: [{
|
|
98
|
+
type: Component,
|
|
99
|
+
args: [{ selector: 'ngs-pdf-viewer-annotations', standalone: true, imports: [
|
|
100
|
+
Avatar,
|
|
101
|
+
Button,
|
|
102
|
+
Card,
|
|
103
|
+
CardActions,
|
|
104
|
+
CardAside,
|
|
105
|
+
CardAvatar,
|
|
106
|
+
CardContent,
|
|
107
|
+
CardHeader,
|
|
108
|
+
CardSubtitle,
|
|
109
|
+
CardTitle,
|
|
110
|
+
Chip,
|
|
111
|
+
FormField,
|
|
112
|
+
Icon,
|
|
113
|
+
IconButtonSuffix,
|
|
114
|
+
IconPrefix,
|
|
115
|
+
Input,
|
|
116
|
+
NgTemplateOutlet,
|
|
117
|
+
Panel,
|
|
118
|
+
PanelContent,
|
|
119
|
+
PanelHeader,
|
|
120
|
+
], changeDetection: ChangeDetectionStrategy.OnPush, host: {
|
|
121
|
+
class: 'ngs-pdf-viewer-annotations',
|
|
122
|
+
}, template: "<ngs-panel class=\"pdf-viewer-annotations\" aria-label=\"PDF annotations\">\n <ngs-panel-header class=\"pdf-viewer-annotations__toolbar\">\n <h3>Annotations</h3>\n <button\n class=\"pdf-viewer-annotations__close\"\n ngsIconButton\n type=\"button\"\n aria-label=\"Close annotations panel\"\n (click)=\"closed.emit()\">\n <ngs-icon name=\"fluent:dismiss-24-regular\" />\n </button>\n </ngs-panel-header>\n\n <ngs-panel-content>\n <div class=\"pdf-viewer-annotations__content\">\n <div class=\"pdf-viewer-annotations__filters\">\n <ngs-form-field subscriptHiddenIfEmpty>\n <ngs-icon ngsIconPrefix name=\"fluent:search-24-regular\" />\n <input\n ngsInput\n type=\"text\"\n aria-label=\"Filter annotations\"\n placeholder=\"Filter annotations...\"\n [value]=\"filterQuery()\"\n (input)=\"setFilterQuery($event)\" />\n @if (filterQuery()) {\n <button\n ngsIconButton\n ngsIconButtonSuffix\n type=\"button\"\n aria-label=\"Clear annotation filter\"\n (click)=\"clearFilterQuery()\">\n <ngs-icon name=\"fluent:dismiss-24-regular\" />\n </button>\n }\n </ngs-form-field>\n\n <button\n ngsIconButton\n type=\"button\"\n aria-label=\"Annotation filters\">\n <ngs-icon name=\"fluent:filter-24-regular\" />\n </button>\n </div>\n\n <div class=\"pdf-viewer-annotations__list\">\n <ng-template #defaultAnnotationTemplate let-annotation let-goToPage=\"goToPage\">\n <ngs-card class=\"pdf-viewer-annotation-card\">\n <ngs-card-header>\n <ngs-avatar\n ngsCardAvatar\n variant=\"tonal\"\n [image]=\"getAvatarImage(annotation)\"\n [label]=\"getAvatarLabel(annotation)\"\n [alt]=\"annotation.author\" />\n\n <ngs-card-title>{{ annotation.author }}</ngs-card-title>\n @if (annotation.time) {\n <ngs-card-subtitle>{{ annotation.time }}</ngs-card-subtitle>\n }\n\n <ngs-card-aside>\n <ngs-chip appearance=\"tonal\">{{ getAnnotationTypeLabel(annotation) }}</ngs-chip>\n </ngs-card-aside>\n </ngs-card-header>\n\n <ngs-card-content>\n <p>{{ annotation.text }}</p>\n </ngs-card-content>\n\n <ngs-card-actions align=\"between\">\n <button ngsButton=\"tonal\" type=\"button\" (click)=\"goToPage(annotation.pageNumber)\">\n Page {{ annotation.pageNumber }}\n </button>\n\n <button ngsButton=\"text\" type=\"button\">\n <ngs-icon name=\"fluent:arrow-reply-24-regular\" />\n {{ getReplyLabel(annotation) }}\n </button>\n </ngs-card-actions>\n </ngs-card>\n </ng-template>\n\n @for (annotation of filteredAnnotations(); track annotation.id ?? annotation.pageNumber + '-' + $index) {\n <ng-container\n [ngTemplateOutlet]=\"getAnnotationTemplate(annotation, $index) ?? defaultAnnotationTemplate\"\n [ngTemplateOutletContext]=\"getAnnotationTemplateContext(annotation, $index)\" />\n } @empty {\n <div class=\"pdf-viewer-annotations__empty\">No annotations found.</div>\n }\n </div>\n </div>\n </ngs-panel-content>\n</ngs-panel>\n", styles: [":host{display:block;min-height:0;height:100%}:host ngs-panel.pdf-viewer-annotations{background:var(--ngs-color-surface)}:host ngs-panel.pdf-viewer-annotations .pdf-viewer-annotations__toolbar{display:flex;align-items:center;justify-content:space-between;padding:0 calc(var(--spacing, .25rem) * 4);border-bottom:1px solid var(--ngs-color-border)}:host ngs-panel.pdf-viewer-annotations .pdf-viewer-annotations__toolbar h3{margin:0;color:var(--ngs-color-on-surface);font-size:var(--ngs-font-size-sm);font-weight:600;letter-spacing:0;text-transform:uppercase}:host ngs-panel.pdf-viewer-annotations .pdf-viewer-annotations__content{display:flex;flex-direction:column;min-height:100%;background:var(--ngs-color-surface-container-lowest)}:host ngs-panel.pdf-viewer-annotations .pdf-viewer-annotations__filters{display:grid;grid-template-columns:minmax(0,1fr) auto;align-items:center;gap:calc(var(--spacing, .25rem) * 2);padding:calc(var(--spacing, .25rem) * 3) calc(var(--spacing, .25rem) * 4);border-bottom:1px solid var(--ngs-color-border)}:host ngs-panel.pdf-viewer-annotations .pdf-viewer-annotations__list{display:flex;flex:1 1 auto;flex-direction:column;gap:calc(var(--spacing, .25rem) * 4);padding:calc(var(--spacing, .25rem) * 4)}:host ngs-panel.pdf-viewer-annotations .pdf-viewer-annotations__empty{padding:calc(var(--spacing, .25rem) * 4);color:var(--ngs-color-on-surface-variant);font-size:var(--ngs-font-size-sm);text-align:center}:host ngs-card.pdf-viewer-annotation-card{--ngs-card-bg: var(--ngs-color-surface);--ngs-card-radius: var(--ngs-radius-md);--ngs-card-padding: calc(var(--spacing, .25rem) * 4)}:host ngs-card.pdf-viewer-annotation-card ngs-card-header{align-items:flex-start}:host ngs-card.pdf-viewer-annotation-card ngs-card-title{font-weight:600}:host ngs-card.pdf-viewer-annotation-card ngs-card-subtitle{color:var(--ngs-color-on-surface-variant)}:host ngs-card.pdf-viewer-annotation-card ngs-card-content p{margin:0;color:var(--ngs-color-on-surface);font-size:var(--ngs-font-size-base);line-height:1.45}:host ngs-card.pdf-viewer-annotation-card ngs-card-actions{padding:0 calc(var(--spacing, .25rem) * 3) calc(var(--spacing, .25rem) * 3)}:host ngs-card.pdf-viewer-annotation-card ngs-chip{text-transform:uppercase}\n/*! tailwindcss v4.2.2 | MIT License | https://tailwindcss.com */\n"] }]
|
|
123
|
+
}], propDecorators: { annotations: [{ type: i0.Input, args: [{ isSignal: true, alias: "annotations", required: false }] }], annotationDefs: [{ type: i0.Input, args: [{ isSignal: true, alias: "annotationDefs", required: false }] }], annotationTypeProperty: [{ type: i0.Input, args: [{ isSignal: true, alias: "annotationTypeProperty", required: false }] }], closed: [{ type: i0.Output, args: ["closed"] }], pageSelected: [{ type: i0.Output, args: ["pageSelected"] }] } });
|
|
124
|
+
|
|
125
|
+
class PdfViewerAnnotationDef {
|
|
126
|
+
template;
|
|
127
|
+
annotationWhen = input(undefined, { ...(ngDevMode ? { debugName: "annotationWhen" } : /* istanbul ignore next */ {}), alias: 'ngsPdfViewerAnnotation' });
|
|
128
|
+
defWhen = input(undefined, { ...(ngDevMode ? { debugName: "defWhen" } : /* istanbul ignore next */ {}), alias: 'ngsPdfViewerAnnotationDef' });
|
|
129
|
+
when = input(undefined, { ...(ngDevMode ? { debugName: "when" } : /* istanbul ignore next */ {}), alias: 'ngsPdfViewerAnnotationWhen' });
|
|
130
|
+
constructor(template) {
|
|
131
|
+
this.template = template;
|
|
132
|
+
}
|
|
133
|
+
matches(annotation, index, typeProperty) {
|
|
134
|
+
const whenValue = this.whenValue();
|
|
135
|
+
if (!whenValue) {
|
|
136
|
+
return false;
|
|
137
|
+
}
|
|
138
|
+
if (typeof whenValue === 'function') {
|
|
139
|
+
return whenValue(annotation, index);
|
|
140
|
+
}
|
|
141
|
+
return annotation[typeProperty] === whenValue;
|
|
142
|
+
}
|
|
143
|
+
hasWhen() {
|
|
144
|
+
return !!this.whenValue();
|
|
145
|
+
}
|
|
146
|
+
whenValue() {
|
|
147
|
+
return this.when() ?? this.defWhen() ?? this.annotationWhen();
|
|
148
|
+
}
|
|
149
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.4", ngImport: i0, type: PdfViewerAnnotationDef, deps: [{ token: i0.TemplateRef }], target: i0.ɵɵFactoryTarget.Directive });
|
|
150
|
+
static ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "17.1.0", version: "21.2.4", type: PdfViewerAnnotationDef, isStandalone: true, selector: "[ngsPdfViewerAnnotationDef], [ngsPdfViewerAnnotation]", inputs: { annotationWhen: { classPropertyName: "annotationWhen", publicName: "ngsPdfViewerAnnotation", isSignal: true, isRequired: false, transformFunction: null }, defWhen: { classPropertyName: "defWhen", publicName: "ngsPdfViewerAnnotationDef", isSignal: true, isRequired: false, transformFunction: null }, when: { classPropertyName: "when", publicName: "ngsPdfViewerAnnotationWhen", isSignal: true, isRequired: false, transformFunction: null } }, ngImport: i0 });
|
|
151
|
+
}
|
|
152
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.4", ngImport: i0, type: PdfViewerAnnotationDef, decorators: [{
|
|
153
|
+
type: Directive,
|
|
154
|
+
args: [{
|
|
155
|
+
selector: '[ngsPdfViewerAnnotationDef], [ngsPdfViewerAnnotation]',
|
|
156
|
+
standalone: true,
|
|
157
|
+
}]
|
|
158
|
+
}], ctorParameters: () => [{ type: i0.TemplateRef }], propDecorators: { annotationWhen: [{ type: i0.Input, args: [{ isSignal: true, alias: "ngsPdfViewerAnnotation", required: false }] }], defWhen: [{ type: i0.Input, args: [{ isSignal: true, alias: "ngsPdfViewerAnnotationDef", required: false }] }], when: [{ type: i0.Input, args: [{ isSignal: true, alias: "ngsPdfViewerAnnotationWhen", required: false }] }] } });
|
|
159
|
+
|
|
160
|
+
class PdfViewerEngineService {
|
|
161
|
+
engines = new Map();
|
|
162
|
+
getEngine(wasmUrl) {
|
|
163
|
+
const cachedEngine = this.engines.get(wasmUrl);
|
|
164
|
+
if (cachedEngine) {
|
|
165
|
+
return cachedEngine;
|
|
166
|
+
}
|
|
167
|
+
const engine = this.createEngine(wasmUrl);
|
|
168
|
+
this.engines.set(wasmUrl, engine);
|
|
169
|
+
return engine;
|
|
170
|
+
}
|
|
171
|
+
createRegistry(engine, defaultScale) {
|
|
172
|
+
const registry = new PluginRegistry(engine, { defaultScale });
|
|
173
|
+
return registry.initialize().then(() => registry);
|
|
174
|
+
}
|
|
175
|
+
async createEngine(wasmUrl) {
|
|
176
|
+
const { createPdfiumEngine } = await import('@embedpdf/engines/pdfium-direct-engine');
|
|
177
|
+
return createPdfiumEngine(wasmUrl, { encoderPoolSize: 0 });
|
|
178
|
+
}
|
|
179
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.4", ngImport: i0, type: PdfViewerEngineService, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
|
|
180
|
+
static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "21.2.4", ngImport: i0, type: PdfViewerEngineService, providedIn: 'root' });
|
|
181
|
+
}
|
|
182
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.4", ngImport: i0, type: PdfViewerEngineService, decorators: [{
|
|
183
|
+
type: Injectable,
|
|
184
|
+
args: [{
|
|
185
|
+
providedIn: 'root'
|
|
186
|
+
}]
|
|
187
|
+
}] });
|
|
188
|
+
|
|
189
|
+
class PdfViewerSearch {
|
|
190
|
+
results = input([], ...(ngDevMode ? [{ debugName: "results" }] : /* istanbul ignore next */ []));
|
|
191
|
+
query = input('', ...(ngDevMode ? [{ debugName: "query" }] : /* istanbul ignore next */ []));
|
|
192
|
+
closed = output();
|
|
193
|
+
resultSelected = output();
|
|
194
|
+
searchChanged = output();
|
|
195
|
+
caseSensitive = signal(false, ...(ngDevMode ? [{ debugName: "caseSensitive" }] : /* istanbul ignore next */ []));
|
|
196
|
+
wholeWord = signal(false, ...(ngDevMode ? [{ debugName: "wholeWord" }] : /* istanbul ignore next */ []));
|
|
197
|
+
activeResultIndex = signal(0, ...(ngDevMode ? [{ debugName: "activeResultIndex" }] : /* istanbul ignore next */ []));
|
|
198
|
+
queryValue = signal('', ...(ngDevMode ? [{ debugName: "queryValue" }] : /* istanbul ignore next */ []));
|
|
199
|
+
hasQuery = computed(() => this.queryValue().trim().length > 0, ...(ngDevMode ? [{ debugName: "hasQuery" }] : /* istanbul ignore next */ []));
|
|
200
|
+
visibleResultGroups = computed(() => {
|
|
201
|
+
const groups = new Map();
|
|
202
|
+
this.results().forEach((result, index) => {
|
|
203
|
+
const group = groups.get(result.pageNumber) ?? {
|
|
204
|
+
pageNumber: result.pageNumber,
|
|
205
|
+
results: [],
|
|
206
|
+
};
|
|
207
|
+
group.results.push({ result, index });
|
|
208
|
+
groups.set(result.pageNumber, group);
|
|
209
|
+
});
|
|
210
|
+
return [...groups.values()];
|
|
211
|
+
}, ...(ngDevMode ? [{ debugName: "visibleResultGroups" }] : /* istanbul ignore next */ []));
|
|
212
|
+
resultCountLabel = computed(() => {
|
|
213
|
+
const count = this.results().length;
|
|
214
|
+
return `${count} ${count === 1 ? 'result' : 'results'} found`;
|
|
215
|
+
}, ...(ngDevMode ? [{ debugName: "resultCountLabel" }] : /* istanbul ignore next */ []));
|
|
216
|
+
constructor() {
|
|
217
|
+
effect(() => {
|
|
218
|
+
const query = this.query();
|
|
219
|
+
untracked(() => {
|
|
220
|
+
this.queryValue.set(query);
|
|
221
|
+
this.activeResultIndex.set(0);
|
|
222
|
+
});
|
|
223
|
+
});
|
|
224
|
+
}
|
|
225
|
+
setQuery(event) {
|
|
226
|
+
this.queryValue.set(event.target.value);
|
|
227
|
+
this.activeResultIndex.set(0);
|
|
228
|
+
this.emitSearchChanged();
|
|
229
|
+
}
|
|
230
|
+
clearQuery() {
|
|
231
|
+
this.queryValue.set('');
|
|
232
|
+
this.activeResultIndex.set(0);
|
|
233
|
+
this.emitSearchChanged();
|
|
234
|
+
}
|
|
235
|
+
setCaseSensitive(value) {
|
|
236
|
+
this.caseSensitive.set(value);
|
|
237
|
+
this.activeResultIndex.set(0);
|
|
238
|
+
this.emitSearchChanged();
|
|
239
|
+
}
|
|
240
|
+
setWholeWord(value) {
|
|
241
|
+
this.wholeWord.set(value);
|
|
242
|
+
this.activeResultIndex.set(0);
|
|
243
|
+
this.emitSearchChanged();
|
|
244
|
+
}
|
|
245
|
+
previousResult() {
|
|
246
|
+
const count = this.results().length;
|
|
247
|
+
if (count === 0) {
|
|
248
|
+
return;
|
|
249
|
+
}
|
|
250
|
+
this.activeResultIndex.update((index) => (index - 1 + count) % count);
|
|
251
|
+
}
|
|
252
|
+
nextResult() {
|
|
253
|
+
const count = this.results().length;
|
|
254
|
+
if (count === 0) {
|
|
255
|
+
return;
|
|
256
|
+
}
|
|
257
|
+
this.activeResultIndex.update((index) => (index + 1) % count);
|
|
258
|
+
}
|
|
259
|
+
selectResult(result, index) {
|
|
260
|
+
this.activeResultIndex.set(index);
|
|
261
|
+
this.resultSelected.emit(result);
|
|
262
|
+
}
|
|
263
|
+
resultParts(result) {
|
|
264
|
+
const query = this.queryValue().trim();
|
|
265
|
+
if (!query) {
|
|
266
|
+
return [{ text: result.excerpt, highlight: false }];
|
|
267
|
+
}
|
|
268
|
+
const flags = this.caseSensitive() ? 'g' : 'gi';
|
|
269
|
+
const escapedQuery = this.escapeRegExp(query);
|
|
270
|
+
const pattern = this.wholeWord()
|
|
271
|
+
? new RegExp(`\\b${escapedQuery}\\b`, flags)
|
|
272
|
+
: new RegExp(escapedQuery, flags);
|
|
273
|
+
const parts = [];
|
|
274
|
+
let lastIndex = 0;
|
|
275
|
+
let match;
|
|
276
|
+
while ((match = pattern.exec(result.excerpt)) !== null) {
|
|
277
|
+
if (match.index > lastIndex) {
|
|
278
|
+
parts.push({ text: result.excerpt.slice(lastIndex, match.index), highlight: false });
|
|
279
|
+
}
|
|
280
|
+
parts.push({ text: match[0], highlight: true });
|
|
281
|
+
lastIndex = match.index + match[0].length;
|
|
282
|
+
if (match[0].length === 0) {
|
|
283
|
+
pattern.lastIndex++;
|
|
284
|
+
}
|
|
285
|
+
}
|
|
286
|
+
if (lastIndex < result.excerpt.length) {
|
|
287
|
+
parts.push({ text: result.excerpt.slice(lastIndex), highlight: false });
|
|
288
|
+
}
|
|
289
|
+
return parts.length > 0 ? parts : [{ text: result.excerpt, highlight: false }];
|
|
290
|
+
}
|
|
291
|
+
emitSearchChanged() {
|
|
292
|
+
this.searchChanged.emit({
|
|
293
|
+
query: this.queryValue(),
|
|
294
|
+
options: {
|
|
295
|
+
caseSensitive: this.caseSensitive(),
|
|
296
|
+
wholeWord: this.wholeWord(),
|
|
297
|
+
},
|
|
298
|
+
});
|
|
299
|
+
}
|
|
300
|
+
escapeRegExp(value) {
|
|
301
|
+
return value.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
|
|
302
|
+
}
|
|
303
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.4", ngImport: i0, type: PdfViewerSearch, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
304
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.4", type: PdfViewerSearch, isStandalone: true, selector: "ngs-pdf-viewer-search", inputs: { results: { classPropertyName: "results", publicName: "results", isSignal: true, isRequired: false, transformFunction: null }, query: { classPropertyName: "query", publicName: "query", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { closed: "closed", resultSelected: "resultSelected", searchChanged: "searchChanged" }, host: { classAttribute: "ngs-pdf-viewer-search" }, ngImport: i0, template: "<ngs-panel class=\"pdf-viewer-search\" aria-label=\"PDF search\">\n <ngs-panel-header class=\"ps-5 pe-3\">\n <ngs-toolbar>\n <ngs-toolbar-title>Search</ngs-toolbar-title>\n <ngs-toolbar-spacer />\n <button ngsIconButton type=\"button\" aria-label=\"Close search panel\" (click)=\"closed.emit()\">\n <ngs-icon name=\"fluent:dismiss-24-regular\" />\n </button>\n </ngs-toolbar>\n </ngs-panel-header>\n\n <ngs-panel-content>\n <div class=\"pdf-viewer-search__content flex min-h-full flex-col gap-5 p-6\">\n <ngs-form-field class=\"pdf-viewer-search__field\" subscriptHiddenIfEmpty>\n <ngs-icon name=\"fluent:search-24-regular\" ngsIconPrefix />\n <input\n ngsInput\n type=\"text\"\n aria-label=\"Search PDF\"\n placeholder=\"Search\"\n [value]=\"queryValue()\"\n (input)=\"setQuery($event)\" />\n @if (queryValue()) {\n <button ngsIconButtonSuffix ngsIconButton type=\"button\" aria-label=\"Clear search\" (click)=\"clearQuery()\">\n <ngs-icon name=\"fluent:dismiss-24-regular\" />\n </button>\n }\n </ngs-form-field>\n\n <div class=\"pdf-viewer-search__options grid gap-3 text-base text-on-surface\">\n <ngs-checkbox [checked]=\"caseSensitive()\" (checkedChange)=\"setCaseSensitive($event)\">\n Case sensitive\n </ngs-checkbox>\n <ngs-checkbox [checked]=\"wholeWord()\" (checkedChange)=\"setWholeWord($event)\">\n Whole word\n </ngs-checkbox>\n </div>\n\n @if (hasQuery()) {\n <ngs-divider />\n\n <div class=\"pdf-viewer-search__summary flex items-center justify-between gap-4 text-base text-on-surface-variant\">\n <span>{{ resultCountLabel() }}</span>\n <div class=\"pdf-viewer-search__nav flex items-center gap-3\">\n <button\n class=\"text-on-surface\"\n ngsIconButton\n type=\"button\"\n aria-label=\"Previous search result\"\n [disabled]=\"results().length === 0\"\n (click)=\"previousResult()\">\n <ngs-icon name=\"fluent:chevron-left-24-regular\" />\n </button>\n <button\n class=\"text-on-surface\"\n ngsIconButton\n type=\"button\"\n aria-label=\"Next search result\"\n [disabled]=\"results().length === 0\"\n (click)=\"nextResult()\">\n <ngs-icon name=\"fluent:chevron-right-24-regular\" />\n </button>\n </div>\n </div>\n\n <div class=\"pdf-viewer-search__results grid min-h-0 gap-4\">\n @for (group of visibleResultGroups(); track group.pageNumber) {\n <section class=\"pdf-viewer-search__page-group grid gap-3\">\n <h3 class=\"m-0 text-base font-medium text-on-surface-variant\">Page {{ group.pageNumber }}</h3>\n @for (item of group.results; track item.result.id ?? item.result.pageNumber + '-' + item.index) {\n <button\n class=\"pdf-viewer-search__result w-full cursor-pointer rounded border border-border p-4 text-left text-base leading-snug text-on-surface\"\n type=\"button\"\n [class.bg-primary-container]=\"item.index === activeResultIndex()\"\n [class.bg-surface]=\"item.index !== activeResultIndex()\"\n (click)=\"selectResult(item.result, item.index)\">\n @for (part of resultParts(item.result); track part.text + '-' + $index) {\n <span\n [class.font-bold]=\"part.highlight\"\n [class.text-primary]=\"part.highlight\">{{ part.text }}</span>\n }\n </button>\n }\n </section>\n } @empty {\n <div class=\"pdf-viewer-search__empty p-4 text-center text-sm text-on-surface-variant\">No results.</div>\n }\n </div>\n }\n </div>\n </ngs-panel-content>\n</ngs-panel>\n", styles: [":host{display:block;min-height:0;height:100%}\n"], dependencies: [{ kind: "component", type: Button, selector: " button[ngsButton], button[ngsIconButton], a[ngsButton], a[ngsIconButton] ", inputs: ["ngsButton", "ngsIconButton", "loading", "disabled", "disabledInteractive", "disableRipple", "reverse", "fullWidth", "hideTextOnMobile"], exportAs: ["ngsButton"] }, { kind: "component", type: Checkbox, selector: "ngs-checkbox", inputs: ["aria-label", "aria-labelledby", "aria-describedby", "aria-expanded", "aria-controls", "aria-owns", "id", "required", "labelPosition", "name", "value", "disableRipple", "tabIndex", "color", "disabledInteractive", "checked", "disabled", "indeterminate"], outputs: ["checkedChange", "disabledChange", "indeterminateChange", "change"], exportAs: ["ngsCheckbox"] }, { kind: "component", type: Divider, selector: "ngs-divider", inputs: ["vertical", "inset", "fixedHeight"], exportAs: ["ngsDivider"] }, { kind: "component", type: FormField, selector: "ngs-form-field", inputs: ["subscriptHiddenIfEmpty", "sameHeightAsButton"], exportAs: ["ngsFormField"] }, { kind: "component", type: Icon, selector: "ngs-icon", inputs: ["name"], exportAs: ["ngsIcon"] }, { kind: "directive", type: IconButtonSuffix, selector: "[ngsIconButtonSuffix]" }, { kind: "directive", type: IconPrefix, selector: "[ngsIconPrefix]" }, { kind: "directive", type: Input, selector: "input[ngsInput], textarea[ngsInput]", inputs: ["id", "placeholder", "required", "disabled", "readonly", "errorStateMatcher"], exportAs: ["ngsInput"] }, { kind: "component", type: Panel, selector: "ngs-panel", inputs: ["absolute"], exportAs: ["ngsPanel"] }, { kind: "component", type: PanelContent, selector: "ngs-panel-content", exportAs: ["ngsPanelContent"] }, { kind: "component", type: PanelHeader, selector: "ngs-panel-header", inputs: ["flex", "autoHeight"], exportAs: ["ngsPanelHeader"] }, { kind: "component", type: Toolbar, selector: "ngs-toolbar", exportAs: ["ngsToolbar"] }, { kind: "component", type: ToolbarSpacer, selector: "ngs-toolbar-spacer" }, { kind: "component", type: ToolbarTitle, selector: "ngs-toolbar-title" }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
|
|
305
|
+
}
|
|
306
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.4", ngImport: i0, type: PdfViewerSearch, decorators: [{
|
|
307
|
+
type: Component,
|
|
308
|
+
args: [{ selector: 'ngs-pdf-viewer-search', standalone: true, imports: [
|
|
309
|
+
Button,
|
|
310
|
+
Checkbox,
|
|
311
|
+
Divider,
|
|
312
|
+
FormField,
|
|
313
|
+
Icon,
|
|
314
|
+
IconButtonSuffix,
|
|
315
|
+
IconPrefix,
|
|
316
|
+
Input,
|
|
317
|
+
Panel,
|
|
318
|
+
PanelContent,
|
|
319
|
+
PanelHeader,
|
|
320
|
+
Toolbar,
|
|
321
|
+
ToolbarSpacer,
|
|
322
|
+
ToolbarTitle,
|
|
323
|
+
], changeDetection: ChangeDetectionStrategy.OnPush, host: {
|
|
324
|
+
class: 'ngs-pdf-viewer-search',
|
|
325
|
+
}, template: "<ngs-panel class=\"pdf-viewer-search\" aria-label=\"PDF search\">\n <ngs-panel-header class=\"ps-5 pe-3\">\n <ngs-toolbar>\n <ngs-toolbar-title>Search</ngs-toolbar-title>\n <ngs-toolbar-spacer />\n <button ngsIconButton type=\"button\" aria-label=\"Close search panel\" (click)=\"closed.emit()\">\n <ngs-icon name=\"fluent:dismiss-24-regular\" />\n </button>\n </ngs-toolbar>\n </ngs-panel-header>\n\n <ngs-panel-content>\n <div class=\"pdf-viewer-search__content flex min-h-full flex-col gap-5 p-6\">\n <ngs-form-field class=\"pdf-viewer-search__field\" subscriptHiddenIfEmpty>\n <ngs-icon name=\"fluent:search-24-regular\" ngsIconPrefix />\n <input\n ngsInput\n type=\"text\"\n aria-label=\"Search PDF\"\n placeholder=\"Search\"\n [value]=\"queryValue()\"\n (input)=\"setQuery($event)\" />\n @if (queryValue()) {\n <button ngsIconButtonSuffix ngsIconButton type=\"button\" aria-label=\"Clear search\" (click)=\"clearQuery()\">\n <ngs-icon name=\"fluent:dismiss-24-regular\" />\n </button>\n }\n </ngs-form-field>\n\n <div class=\"pdf-viewer-search__options grid gap-3 text-base text-on-surface\">\n <ngs-checkbox [checked]=\"caseSensitive()\" (checkedChange)=\"setCaseSensitive($event)\">\n Case sensitive\n </ngs-checkbox>\n <ngs-checkbox [checked]=\"wholeWord()\" (checkedChange)=\"setWholeWord($event)\">\n Whole word\n </ngs-checkbox>\n </div>\n\n @if (hasQuery()) {\n <ngs-divider />\n\n <div class=\"pdf-viewer-search__summary flex items-center justify-between gap-4 text-base text-on-surface-variant\">\n <span>{{ resultCountLabel() }}</span>\n <div class=\"pdf-viewer-search__nav flex items-center gap-3\">\n <button\n class=\"text-on-surface\"\n ngsIconButton\n type=\"button\"\n aria-label=\"Previous search result\"\n [disabled]=\"results().length === 0\"\n (click)=\"previousResult()\">\n <ngs-icon name=\"fluent:chevron-left-24-regular\" />\n </button>\n <button\n class=\"text-on-surface\"\n ngsIconButton\n type=\"button\"\n aria-label=\"Next search result\"\n [disabled]=\"results().length === 0\"\n (click)=\"nextResult()\">\n <ngs-icon name=\"fluent:chevron-right-24-regular\" />\n </button>\n </div>\n </div>\n\n <div class=\"pdf-viewer-search__results grid min-h-0 gap-4\">\n @for (group of visibleResultGroups(); track group.pageNumber) {\n <section class=\"pdf-viewer-search__page-group grid gap-3\">\n <h3 class=\"m-0 text-base font-medium text-on-surface-variant\">Page {{ group.pageNumber }}</h3>\n @for (item of group.results; track item.result.id ?? item.result.pageNumber + '-' + item.index) {\n <button\n class=\"pdf-viewer-search__result w-full cursor-pointer rounded border border-border p-4 text-left text-base leading-snug text-on-surface\"\n type=\"button\"\n [class.bg-primary-container]=\"item.index === activeResultIndex()\"\n [class.bg-surface]=\"item.index !== activeResultIndex()\"\n (click)=\"selectResult(item.result, item.index)\">\n @for (part of resultParts(item.result); track part.text + '-' + $index) {\n <span\n [class.font-bold]=\"part.highlight\"\n [class.text-primary]=\"part.highlight\">{{ part.text }}</span>\n }\n </button>\n }\n </section>\n } @empty {\n <div class=\"pdf-viewer-search__empty p-4 text-center text-sm text-on-surface-variant\">No results.</div>\n }\n </div>\n }\n </div>\n </ngs-panel-content>\n</ngs-panel>\n", styles: [":host{display:block;min-height:0;height:100%}\n"] }]
|
|
326
|
+
}], ctorParameters: () => [], propDecorators: { results: [{ type: i0.Input, args: [{ isSignal: true, alias: "results", required: false }] }], query: [{ type: i0.Input, args: [{ isSignal: true, alias: "query", required: false }] }], closed: [{ type: i0.Output, args: ["closed"] }], resultSelected: [{ type: i0.Output, args: ["resultSelected"] }], searchChanged: [{ type: i0.Output, args: ["searchChanged"] }] } });
|
|
327
|
+
|
|
328
|
+
class PdfViewer {
|
|
329
|
+
textSelectionHorizontalPadding = 2;
|
|
330
|
+
textSelectionLineHeight = 1.2;
|
|
331
|
+
document = inject(DOCUMENT);
|
|
332
|
+
destroyRef = inject(DestroyRef);
|
|
333
|
+
engineService = inject(PdfViewerEngineService);
|
|
334
|
+
hostElement = inject(ElementRef);
|
|
335
|
+
platformId = inject(PLATFORM_ID);
|
|
336
|
+
isBrowser = isPlatformBrowser(this.platformId);
|
|
337
|
+
viewerBody = viewChild('viewerBody', ...(ngDevMode ? [{ debugName: "viewerBody" }] : /* istanbul ignore next */ []));
|
|
338
|
+
pageList = viewChild('pageList', ...(ngDevMode ? [{ debugName: "pageList" }] : /* istanbul ignore next */ []));
|
|
339
|
+
annotationDefs = contentChildren(PdfViewerAnnotationDef, { ...(ngDevMode ? { debugName: "annotationDefs" } : /* istanbul ignore next */ {}), descendants: true });
|
|
340
|
+
src = input(null, ...(ngDevMode ? [{ debugName: "src" }] : /* istanbul ignore next */ []));
|
|
341
|
+
documentName = input(null, ...(ngDevMode ? [{ debugName: "documentName" }] : /* istanbul ignore next */ []));
|
|
342
|
+
wasmUrl = input('/assets/embedpdf/pdfium.wasm', ...(ngDevMode ? [{ debugName: "wasmUrl" }] : /* istanbul ignore next */ []));
|
|
343
|
+
page = input(1, { ...(ngDevMode ? { debugName: "page" } : /* istanbul ignore next */ {}), transform: numberAttribute });
|
|
344
|
+
scale = input(1, { ...(ngDevMode ? { debugName: "scale" } : /* istanbul ignore next */ {}), transform: numberAttribute });
|
|
345
|
+
minScale = input(0.2, { ...(ngDevMode ? { debugName: "minScale" } : /* istanbul ignore next */ {}), transform: numberAttribute });
|
|
346
|
+
maxScale = input(60, { ...(ngDevMode ? { debugName: "maxScale" } : /* istanbul ignore next */ {}), transform: numberAttribute });
|
|
347
|
+
zoomStep = input(0.1, { ...(ngDevMode ? { debugName: "zoomStep" } : /* istanbul ignore next */ {}), transform: numberAttribute });
|
|
348
|
+
maxRenderPixels = input(128_000_000, { ...(ngDevMode ? { debugName: "maxRenderPixels" } : /* istanbul ignore next */ {}), transform: numberAttribute });
|
|
349
|
+
maxRenderDimension = input(12_000, { ...(ngDevMode ? { debugName: "maxRenderDimension" } : /* istanbul ignore next */ {}), transform: numberAttribute });
|
|
350
|
+
renderAll = input(true, { ...(ngDevMode ? { debugName: "renderAll" } : /* istanbul ignore next */ {}), transform: booleanAttribute });
|
|
351
|
+
showToolbar = input(true, { ...(ngDevMode ? { debugName: "showToolbar" } : /* istanbul ignore next */ {}), transform: booleanAttribute });
|
|
352
|
+
showPageList = input(true, { ...(ngDevMode ? { debugName: "showPageList" } : /* istanbul ignore next */ {}), transform: booleanAttribute });
|
|
353
|
+
showSearchPanel = input(true, { ...(ngDevMode ? { debugName: "showSearchPanel" } : /* istanbul ignore next */ {}), transform: booleanAttribute });
|
|
354
|
+
showAnnotationsPanel = input(false, { ...(ngDevMode ? { debugName: "showAnnotationsPanel" } : /* istanbul ignore next */ {}), transform: booleanAttribute });
|
|
355
|
+
annotations = input([], ...(ngDevMode ? [{ debugName: "annotations" }] : /* istanbul ignore next */ []));
|
|
356
|
+
annotationsDataSource = input(null, ...(ngDevMode ? [{ debugName: "annotationsDataSource" }] : /* istanbul ignore next */ []));
|
|
357
|
+
annotationTypeProperty = input('type', ...(ngDevMode ? [{ debugName: "annotationTypeProperty" }] : /* istanbul ignore next */ []));
|
|
358
|
+
searchQuery = input('', ...(ngDevMode ? [{ debugName: "searchQuery" }] : /* istanbul ignore next */ []));
|
|
359
|
+
withAnnotations = input(true, { ...(ngDevMode ? { debugName: "withAnnotations" } : /* istanbul ignore next */ {}), transform: booleanAttribute });
|
|
360
|
+
withForms = input(true, { ...(ngDevMode ? { debugName: "withForms" } : /* istanbul ignore next */ {}), transform: booleanAttribute });
|
|
361
|
+
loaded = output();
|
|
362
|
+
pageChanged = output();
|
|
363
|
+
pageRendered = output();
|
|
364
|
+
error = output();
|
|
365
|
+
isLoading = signal(false, ...(ngDevMode ? [{ debugName: "isLoading" }] : /* istanbul ignore next */ []));
|
|
366
|
+
errorState = signal(null, ...(ngDevMode ? [{ debugName: "errorState" }] : /* istanbul ignore next */ []));
|
|
367
|
+
renderedPages = signal([], ...(ngDevMode ? [{ debugName: "renderedPages" }] : /* istanbul ignore next */ []));
|
|
368
|
+
thumbnailPages = signal([], ...(ngDevMode ? [{ debugName: "thumbnailPages" }] : /* istanbul ignore next */ []));
|
|
369
|
+
pageCount = signal(0, ...(ngDevMode ? [{ debugName: "pageCount" }] : /* istanbul ignore next */ []));
|
|
370
|
+
activePage = signal(1, ...(ngDevMode ? [{ debugName: "activePage" }] : /* istanbul ignore next */ []));
|
|
371
|
+
zoom = signal(1, ...(ngDevMode ? [{ debugName: "zoom" }] : /* istanbul ignore next */ []));
|
|
372
|
+
pageListVisible = signal(false, ...(ngDevMode ? [{ debugName: "pageListVisible" }] : /* istanbul ignore next */ []));
|
|
373
|
+
searchPanelVisible = signal(false, ...(ngDevMode ? [{ debugName: "searchPanelVisible" }] : /* istanbul ignore next */ []));
|
|
374
|
+
annotationsPanelVisible = signal(false, ...(ngDevMode ? [{ debugName: "annotationsPanelVisible" }] : /* istanbul ignore next */ []));
|
|
375
|
+
spreadMode = signal('single', ...(ngDevMode ? [{ debugName: "spreadMode" }] : /* istanbul ignore next */ []));
|
|
376
|
+
scrollLayout = signal('horizontal', ...(ngDevMode ? [{ debugName: "scrollLayout" }] : /* istanbul ignore next */ []));
|
|
377
|
+
zoomMode = signal('custom', ...(ngDevMode ? [{ debugName: "zoomMode" }] : /* istanbul ignore next */ []));
|
|
378
|
+
annotationItems = signal([], ...(ngDevMode ? [{ debugName: "annotationItems" }] : /* istanbul ignore next */ []));
|
|
379
|
+
activeSearchQuery = signal('', ...(ngDevMode ? [{ debugName: "activeSearchQuery" }] : /* istanbul ignore next */ []));
|
|
380
|
+
pdfSearchResults = signal([], ...(ngDevMode ? [{ debugName: "pdfSearchResults" }] : /* istanbul ignore next */ []));
|
|
381
|
+
selectionRects = signal([], ...(ngDevMode ? [{ debugName: "selectionRects" }] : /* istanbul ignore next */ []));
|
|
382
|
+
hasDocument = computed(() => this.pageCount() > 0, ...(ngDevMode ? [{ debugName: "hasDocument" }] : /* istanbul ignore next */ []));
|
|
383
|
+
isPageListVisible = computed(() => this.showPageList() && this.pageListVisible(), ...(ngDevMode ? [{ debugName: "isPageListVisible" }] : /* istanbul ignore next */ []));
|
|
384
|
+
isSearchPanelVisible = computed(() => this.showSearchPanel() && this.searchPanelVisible(), ...(ngDevMode ? [{ debugName: "isSearchPanelVisible" }] : /* istanbul ignore next */ []));
|
|
385
|
+
isAnnotationsPanelVisible = computed(() => this.showAnnotationsPanel() && !this.searchPanelVisible() && this.annotationsPanelVisible(), ...(ngDevMode ? [{ debugName: "isAnnotationsPanelVisible" }] : /* istanbul ignore next */ []));
|
|
386
|
+
thumbnailPageMap = computed(() => new Map(this.thumbnailPages().map((thumbnail) => [thumbnail.pageNumber, thumbnail])), ...(ngDevMode ? [{ debugName: "thumbnailPageMap" }] : /* istanbul ignore next */ []));
|
|
387
|
+
pageItems = computed(() => Array.from({ length: this.pageCount() }, (_, index) => {
|
|
388
|
+
const pageNumber = index + 1;
|
|
389
|
+
return {
|
|
390
|
+
pageNumber,
|
|
391
|
+
thumbnail: this.thumbnailPageMap().get(pageNumber) ?? null,
|
|
392
|
+
};
|
|
393
|
+
}), ...(ngDevMode ? [{ debugName: "pageItems" }] : /* istanbul ignore next */ []));
|
|
394
|
+
canGoPrevious = computed(() => this.activePage() > 1, ...(ngDevMode ? [{ debugName: "canGoPrevious" }] : /* istanbul ignore next */ []));
|
|
395
|
+
canGoNext = computed(() => this.activePage() < this.pageCount(), ...(ngDevMode ? [{ debugName: "canGoNext" }] : /* istanbul ignore next */ []));
|
|
396
|
+
canZoomOut = computed(() => this.zoom() > this.getScaleBounds().min, ...(ngDevMode ? [{ debugName: "canZoomOut" }] : /* istanbul ignore next */ []));
|
|
397
|
+
canZoomIn = computed(() => this.zoom() < this.getScaleBounds().max, ...(ngDevMode ? [{ debugName: "canZoomIn" }] : /* istanbul ignore next */ []));
|
|
398
|
+
zoomLabel = computed(() => `${Math.round(this.zoom() * 100)}%`, ...(ngDevMode ? [{ debugName: "zoomLabel" }] : /* istanbul ignore next */ []));
|
|
399
|
+
displayDocumentName = computed(() => this.documentName() || this.getSourceName(this.src()), ...(ngDevMode ? [{ debugName: "displayDocumentName" }] : /* istanbul ignore next */ []));
|
|
400
|
+
zoomPresets = [0.25, 0.5, 1, 1.25, 1.5, 2, 4, 8, 16];
|
|
401
|
+
engine = null;
|
|
402
|
+
pdfDocument = null;
|
|
403
|
+
registry = null;
|
|
404
|
+
pageIntersectionObserver = null;
|
|
405
|
+
visiblePageRatios = new Map();
|
|
406
|
+
loadToken = 0;
|
|
407
|
+
renderToken = 0;
|
|
408
|
+
searchToken = 0;
|
|
409
|
+
scrollSyncFrame = null;
|
|
410
|
+
programmaticScrollTargetPage = null;
|
|
411
|
+
programmaticScrollTimeout = null;
|
|
412
|
+
pageObserverFrame = null;
|
|
413
|
+
pageObserverTimeout = null;
|
|
414
|
+
visiblePageRenderFrame = null;
|
|
415
|
+
visiblePageRenderTimeout = null;
|
|
416
|
+
pageObserverRefreshAttempts = 0;
|
|
417
|
+
selectionStart = null;
|
|
418
|
+
isViewInitialized = false;
|
|
419
|
+
annotationDataSourceToken = 0;
|
|
420
|
+
annotationDataSourceCleanup = null;
|
|
421
|
+
lastZoomChangeTime = 0;
|
|
422
|
+
programmaticScrollMinDuration = 900;
|
|
423
|
+
programmaticScrollMaxDuration = 6000;
|
|
424
|
+
qualityRenderZoomIdleDelay = 160;
|
|
425
|
+
constructor() {
|
|
426
|
+
this.destroyRef.onDestroy(() => {
|
|
427
|
+
this.loadToken++;
|
|
428
|
+
this.renderToken++;
|
|
429
|
+
this.searchToken++;
|
|
430
|
+
this.annotationDataSourceToken++;
|
|
431
|
+
this.annotationDataSourceCleanup?.();
|
|
432
|
+
this.cancelScrollSyncFrame();
|
|
433
|
+
this.clearProgrammaticScrollLock();
|
|
434
|
+
this.cancelPageObserverFrame();
|
|
435
|
+
this.cancelVisiblePageRenderFrame();
|
|
436
|
+
this.disconnectPageObserver();
|
|
437
|
+
this.revokeRenderedPages();
|
|
438
|
+
this.revokeThumbnailPages();
|
|
439
|
+
void this.closeDocument();
|
|
440
|
+
void this.registry?.destroy();
|
|
441
|
+
});
|
|
442
|
+
effect((onCleanup) => {
|
|
443
|
+
const dataSource = this.annotationsDataSource();
|
|
444
|
+
const fallbackAnnotations = this.annotations();
|
|
445
|
+
const source = this.src();
|
|
446
|
+
const documentName = this.documentName() || this.getSourceName(source);
|
|
447
|
+
const pageCount = this.pageCount();
|
|
448
|
+
const token = ++this.annotationDataSourceToken;
|
|
449
|
+
const context = {
|
|
450
|
+
source,
|
|
451
|
+
documentName,
|
|
452
|
+
pageCount,
|
|
453
|
+
};
|
|
454
|
+
this.annotationDataSourceCleanup?.();
|
|
455
|
+
this.annotationDataSourceCleanup = untracked(() => this.loadAnnotationsDataSource(dataSource ?? fallbackAnnotations, context, token));
|
|
456
|
+
onCleanup(() => {
|
|
457
|
+
this.annotationDataSourceCleanup?.();
|
|
458
|
+
this.annotationDataSourceCleanup = null;
|
|
459
|
+
});
|
|
460
|
+
});
|
|
461
|
+
effect(() => {
|
|
462
|
+
const source = this.src();
|
|
463
|
+
const wasmUrl = this.wasmUrl();
|
|
464
|
+
if (!this.isViewInitialized || !this.isBrowser) {
|
|
465
|
+
return;
|
|
466
|
+
}
|
|
467
|
+
untracked(() => {
|
|
468
|
+
void this.loadDocument(source, wasmUrl);
|
|
469
|
+
});
|
|
470
|
+
});
|
|
471
|
+
effect(() => {
|
|
472
|
+
const scale = this.sanitizeScale(this.scale());
|
|
473
|
+
if (untracked(() => this.zoom()) !== scale) {
|
|
474
|
+
untracked(() => this.setZoom(scale));
|
|
475
|
+
}
|
|
476
|
+
});
|
|
477
|
+
effect(() => {
|
|
478
|
+
const requestedPage = this.sanitizePage(this.page());
|
|
479
|
+
if (requestedPage !== untracked(() => this.activePage())) {
|
|
480
|
+
this.activePage.set(requestedPage);
|
|
481
|
+
}
|
|
482
|
+
});
|
|
483
|
+
effect(() => {
|
|
484
|
+
const query = this.searchQuery();
|
|
485
|
+
if (query !== untracked(() => this.activeSearchQuery())) {
|
|
486
|
+
this.activeSearchQuery.set(query);
|
|
487
|
+
untracked(() => {
|
|
488
|
+
void this.searchPdf(query, {
|
|
489
|
+
caseSensitive: false,
|
|
490
|
+
wholeWord: false,
|
|
491
|
+
});
|
|
492
|
+
});
|
|
493
|
+
}
|
|
494
|
+
});
|
|
495
|
+
effect(() => {
|
|
496
|
+
const zoom = this.zoom();
|
|
497
|
+
const renderAll = this.renderAll();
|
|
498
|
+
const activePage = renderAll ? untracked(() => this.activePage()) : this.activePage();
|
|
499
|
+
const withAnnotations = this.withAnnotations();
|
|
500
|
+
const withForms = this.withForms();
|
|
501
|
+
if (!this.pdfDocument || !this.engine || this.isLoading()) {
|
|
502
|
+
return;
|
|
503
|
+
}
|
|
504
|
+
untracked(() => {
|
|
505
|
+
this.applyInstantZoom(zoom);
|
|
506
|
+
this.scheduleVisiblePagesRender({ zoom, activePage, renderAll, withAnnotations, withForms });
|
|
507
|
+
});
|
|
508
|
+
});
|
|
509
|
+
effect(() => {
|
|
510
|
+
const withAnnotations = this.withAnnotations();
|
|
511
|
+
const isLoading = this.isLoading();
|
|
512
|
+
const pageCount = this.pageCount();
|
|
513
|
+
if (isLoading || pageCount === 0 || !this.pdfDocument || !this.engine) {
|
|
514
|
+
return;
|
|
515
|
+
}
|
|
516
|
+
untracked(() => {
|
|
517
|
+
void this.renderThumbnails(this.loadToken, { withAnnotations });
|
|
518
|
+
});
|
|
519
|
+
});
|
|
520
|
+
}
|
|
521
|
+
ngAfterViewInit() {
|
|
522
|
+
this.isViewInitialized = true;
|
|
523
|
+
if (this.isBrowser) {
|
|
524
|
+
void this.loadDocument(this.src(), this.wasmUrl());
|
|
525
|
+
}
|
|
526
|
+
}
|
|
527
|
+
previousPage() {
|
|
528
|
+
if (!this.canGoPrevious()) {
|
|
529
|
+
return;
|
|
530
|
+
}
|
|
531
|
+
this.setPage(this.activePage() - 1);
|
|
532
|
+
}
|
|
533
|
+
nextPage() {
|
|
534
|
+
if (!this.canGoNext()) {
|
|
535
|
+
return;
|
|
536
|
+
}
|
|
537
|
+
this.setPage(this.activePage() + 1);
|
|
538
|
+
}
|
|
539
|
+
zoomIn() {
|
|
540
|
+
this.zoomMode.set('custom');
|
|
541
|
+
this.setZoom(this.roundZoom(this.zoom() + this.sanitizeZoomStep()));
|
|
542
|
+
}
|
|
543
|
+
zoomOut() {
|
|
544
|
+
this.zoomMode.set('custom');
|
|
545
|
+
this.setZoom(this.roundZoom(this.zoom() - this.sanitizeZoomStep()));
|
|
546
|
+
}
|
|
547
|
+
togglePageList() {
|
|
548
|
+
this.pageListVisible.update((isVisible) => !isVisible);
|
|
549
|
+
}
|
|
550
|
+
toggleSearchPanel() {
|
|
551
|
+
const nextVisible = !this.searchPanelVisible();
|
|
552
|
+
this.searchPanelVisible.set(nextVisible);
|
|
553
|
+
if (nextVisible) {
|
|
554
|
+
this.annotationsPanelVisible.set(false);
|
|
555
|
+
const query = this.searchQuery();
|
|
556
|
+
this.activeSearchQuery.set(query);
|
|
557
|
+
void this.searchPdf(query, {
|
|
558
|
+
caseSensitive: false,
|
|
559
|
+
wholeWord: false,
|
|
560
|
+
});
|
|
561
|
+
}
|
|
562
|
+
}
|
|
563
|
+
toggleAnnotationsPanel() {
|
|
564
|
+
const nextVisible = !this.annotationsPanelVisible();
|
|
565
|
+
this.annotationsPanelVisible.set(nextVisible);
|
|
566
|
+
if (nextVisible) {
|
|
567
|
+
this.searchPanelVisible.set(false);
|
|
568
|
+
}
|
|
569
|
+
}
|
|
570
|
+
closeAsidePanel() {
|
|
571
|
+
this.searchPanelVisible.set(false);
|
|
572
|
+
this.annotationsPanelVisible.set(false);
|
|
573
|
+
}
|
|
574
|
+
updatePdfSearch(event) {
|
|
575
|
+
this.activeSearchQuery.set(event.query);
|
|
576
|
+
return this.searchPdf(event.query, event.options);
|
|
577
|
+
}
|
|
578
|
+
selectSearchResult(result) {
|
|
579
|
+
this.setPage(result.pageNumber);
|
|
580
|
+
}
|
|
581
|
+
setZoomPreset(scale) {
|
|
582
|
+
this.zoomMode.set('custom');
|
|
583
|
+
this.setZoom(scale);
|
|
584
|
+
}
|
|
585
|
+
isZoomPresetSelected(scale) {
|
|
586
|
+
return this.zoomMode() === 'custom' && Math.abs(this.zoom() - this.sanitizeScale(scale)) < 0.0001;
|
|
587
|
+
}
|
|
588
|
+
fitToPage() {
|
|
589
|
+
const scale = this.getFitScale('fit-page');
|
|
590
|
+
if (scale === null) {
|
|
591
|
+
return;
|
|
592
|
+
}
|
|
593
|
+
this.zoomMode.set('fit-page');
|
|
594
|
+
this.setZoom(scale);
|
|
595
|
+
}
|
|
596
|
+
fitToWidth() {
|
|
597
|
+
const scale = this.getFitScale('fit-width');
|
|
598
|
+
if (scale === null) {
|
|
599
|
+
return;
|
|
600
|
+
}
|
|
601
|
+
this.zoomMode.set('fit-width');
|
|
602
|
+
this.setZoom(scale);
|
|
603
|
+
}
|
|
604
|
+
setSpreadMode(mode) {
|
|
605
|
+
this.spreadMode.set(mode);
|
|
606
|
+
}
|
|
607
|
+
setScrollLayout(layout) {
|
|
608
|
+
this.scrollLayout.set(layout);
|
|
609
|
+
}
|
|
610
|
+
rotateClockwise() {
|
|
611
|
+
this.renderedPages.update((pages) => [...pages]);
|
|
612
|
+
}
|
|
613
|
+
rotateCounterClockwise() {
|
|
614
|
+
this.renderedPages.update((pages) => [...pages]);
|
|
615
|
+
}
|
|
616
|
+
toggleFullscreen() {
|
|
617
|
+
if (!this.isBrowser) {
|
|
618
|
+
return;
|
|
619
|
+
}
|
|
620
|
+
const target = this.hostElement.nativeElement;
|
|
621
|
+
const currentFullscreenElement = this.document.fullscreenElement;
|
|
622
|
+
if (currentFullscreenElement) {
|
|
623
|
+
void this.document.exitFullscreen?.();
|
|
624
|
+
return;
|
|
625
|
+
}
|
|
626
|
+
void target.requestFullscreen?.();
|
|
627
|
+
}
|
|
628
|
+
setPage(pageNumber) {
|
|
629
|
+
const nextPage = this.clamp(pageNumber, 1, Math.max(this.pageCount(), 1));
|
|
630
|
+
const hasChanged = nextPage !== this.activePage();
|
|
631
|
+
if (!hasChanged) {
|
|
632
|
+
this.scrollToPage(nextPage);
|
|
633
|
+
this.scrollPageListToPage(nextPage);
|
|
634
|
+
return;
|
|
635
|
+
}
|
|
636
|
+
this.activePage.set(nextPage);
|
|
637
|
+
this.pageChanged.emit(nextPage);
|
|
638
|
+
this.scrollToPage(nextPage);
|
|
639
|
+
this.scrollPageListToPage(nextPage);
|
|
640
|
+
}
|
|
641
|
+
onViewerScroll() {
|
|
642
|
+
if (!this.renderAll() || !this.isBrowser || this.renderedPages().length < 2) {
|
|
643
|
+
return;
|
|
644
|
+
}
|
|
645
|
+
if (this.scrollSyncFrame !== null) {
|
|
646
|
+
return;
|
|
647
|
+
}
|
|
648
|
+
this.scrollSyncFrame = this.document.defaultView?.requestAnimationFrame(() => {
|
|
649
|
+
this.scrollSyncFrame = null;
|
|
650
|
+
if (this.isProgrammaticScrollActive()) {
|
|
651
|
+
this.completeProgrammaticScrollIfSettled();
|
|
652
|
+
return;
|
|
653
|
+
}
|
|
654
|
+
this.syncActivePageFromViewport();
|
|
655
|
+
this.scheduleCurrentVisiblePagesRender();
|
|
656
|
+
}) ?? null;
|
|
657
|
+
}
|
|
658
|
+
onViewerWheel(event) {
|
|
659
|
+
if (!event.metaKey && !event.ctrlKey) {
|
|
660
|
+
return;
|
|
661
|
+
}
|
|
662
|
+
if (!this.pdfDocument || this.isLoading()) {
|
|
663
|
+
return;
|
|
664
|
+
}
|
|
665
|
+
event.preventDefault();
|
|
666
|
+
event.stopPropagation();
|
|
667
|
+
const container = this.viewerBody()?.nativeElement;
|
|
668
|
+
const anchor = container ? this.getZoomAnchor(container, event) : null;
|
|
669
|
+
const delta = event.deltaY || event.deltaX;
|
|
670
|
+
const direction = delta < 0 ? 1 : -1;
|
|
671
|
+
const multiplier = Math.max(1, Math.min(6, Math.abs(delta) / 100));
|
|
672
|
+
const nextZoom = this.roundZoom(this.zoom() + direction * this.sanitizeZoomStep() * multiplier);
|
|
673
|
+
if (nextZoom === this.zoom()) {
|
|
674
|
+
return;
|
|
675
|
+
}
|
|
676
|
+
this.zoomMode.set('custom');
|
|
677
|
+
this.setZoom(nextZoom);
|
|
678
|
+
this.restoreZoomAnchor(anchor);
|
|
679
|
+
}
|
|
680
|
+
selectionRectsForPage(pageNumber) {
|
|
681
|
+
return this.selectionRects().filter((rect) => rect.pageNumber === pageNumber);
|
|
682
|
+
}
|
|
683
|
+
startTextSelection(event, page) {
|
|
684
|
+
if (event.button !== 0 || page.textGlyphs.length === 0) {
|
|
685
|
+
return;
|
|
686
|
+
}
|
|
687
|
+
event.preventDefault();
|
|
688
|
+
event.stopPropagation();
|
|
689
|
+
const surface = event.currentTarget;
|
|
690
|
+
if ('pointerId' in event) {
|
|
691
|
+
surface.setPointerCapture?.(event.pointerId);
|
|
692
|
+
}
|
|
693
|
+
this.selectionStart = this.getSelectionPoint(event, page, surface);
|
|
694
|
+
this.selectionRects.set([]);
|
|
695
|
+
}
|
|
696
|
+
updateTextSelection(event, page) {
|
|
697
|
+
if (!this.selectionStart || this.selectionStart.pageNumber !== page.pageNumber) {
|
|
698
|
+
return;
|
|
699
|
+
}
|
|
700
|
+
event.preventDefault();
|
|
701
|
+
const surface = event.currentTarget;
|
|
702
|
+
const currentPoint = this.getSelectionPoint(event, page, surface);
|
|
703
|
+
if (this.selectionStart.offset === currentPoint.offset) {
|
|
704
|
+
this.selectionRects.set([]);
|
|
705
|
+
return;
|
|
706
|
+
}
|
|
707
|
+
this.selectionRects.set(this.getSelectionRects(page, this.selectionStart.offset, currentPoint.offset));
|
|
708
|
+
}
|
|
709
|
+
finishTextSelection(event, page) {
|
|
710
|
+
if (!this.selectionStart) {
|
|
711
|
+
return;
|
|
712
|
+
}
|
|
713
|
+
this.updateTextSelection(event, page);
|
|
714
|
+
this.selectionStart = null;
|
|
715
|
+
const surface = event.currentTarget;
|
|
716
|
+
if ('pointerId' in event) {
|
|
717
|
+
surface.releasePointerCapture?.(event.pointerId);
|
|
718
|
+
}
|
|
719
|
+
}
|
|
720
|
+
loadAnnotationsDataSource(dataSource, context, token) {
|
|
721
|
+
if (!dataSource) {
|
|
722
|
+
this.setAnnotationItems([], token);
|
|
723
|
+
return () => { };
|
|
724
|
+
}
|
|
725
|
+
try {
|
|
726
|
+
if (Array.isArray(dataSource)) {
|
|
727
|
+
this.setAnnotationItems(dataSource, token);
|
|
728
|
+
return () => { };
|
|
729
|
+
}
|
|
730
|
+
if (this.isServerAnnotationDataSource(dataSource)) {
|
|
731
|
+
let isActive = true;
|
|
732
|
+
dataSource.getAnnotations({
|
|
733
|
+
...context,
|
|
734
|
+
successCallback: (annotations) => {
|
|
735
|
+
if (isActive) {
|
|
736
|
+
this.setAnnotationItems(annotations, token);
|
|
737
|
+
}
|
|
738
|
+
},
|
|
739
|
+
failCallback: () => {
|
|
740
|
+
if (isActive) {
|
|
741
|
+
this.setAnnotationItems([], token);
|
|
742
|
+
}
|
|
743
|
+
},
|
|
744
|
+
});
|
|
745
|
+
return () => {
|
|
746
|
+
isActive = false;
|
|
747
|
+
};
|
|
748
|
+
}
|
|
749
|
+
const result = typeof dataSource === 'function' ? dataSource(context) : dataSource;
|
|
750
|
+
return this.applyAnnotationDataSourceResult(result, token);
|
|
751
|
+
}
|
|
752
|
+
catch {
|
|
753
|
+
this.setAnnotationItems([], token);
|
|
754
|
+
return () => { };
|
|
755
|
+
}
|
|
756
|
+
}
|
|
757
|
+
applyAnnotationDataSourceResult(result, token) {
|
|
758
|
+
if (isObservable(result)) {
|
|
759
|
+
const subscription = result.subscribe({
|
|
760
|
+
next: (annotations) => this.setAnnotationItems(annotations, token),
|
|
761
|
+
error: () => this.setAnnotationItems([], token),
|
|
762
|
+
});
|
|
763
|
+
return () => subscription.unsubscribe();
|
|
764
|
+
}
|
|
765
|
+
if (this.isPromiseLike(result)) {
|
|
766
|
+
let isActive = true;
|
|
767
|
+
result
|
|
768
|
+
.then((annotations) => {
|
|
769
|
+
if (isActive) {
|
|
770
|
+
this.setAnnotationItems(annotations, token);
|
|
771
|
+
}
|
|
772
|
+
})
|
|
773
|
+
.catch(() => {
|
|
774
|
+
if (isActive) {
|
|
775
|
+
this.setAnnotationItems([], token);
|
|
776
|
+
}
|
|
777
|
+
});
|
|
778
|
+
return () => {
|
|
779
|
+
isActive = false;
|
|
780
|
+
};
|
|
781
|
+
}
|
|
782
|
+
this.setAnnotationItems(result, token);
|
|
783
|
+
return () => { };
|
|
784
|
+
}
|
|
785
|
+
setAnnotationItems(annotations, token) {
|
|
786
|
+
if (token === this.annotationDataSourceToken) {
|
|
787
|
+
this.annotationItems.set(annotations ?? []);
|
|
788
|
+
}
|
|
789
|
+
}
|
|
790
|
+
isServerAnnotationDataSource(dataSource) {
|
|
791
|
+
return typeof dataSource === 'object'
|
|
792
|
+
&& dataSource !== null
|
|
793
|
+
&& 'getAnnotations' in dataSource
|
|
794
|
+
&& typeof dataSource.getAnnotations === 'function';
|
|
795
|
+
}
|
|
796
|
+
isPromiseLike(value) {
|
|
797
|
+
return typeof value === 'object'
|
|
798
|
+
&& value !== null
|
|
799
|
+
&& 'then' in value
|
|
800
|
+
&& typeof value.then === 'function';
|
|
801
|
+
}
|
|
802
|
+
cancelTextSelection() {
|
|
803
|
+
this.selectionStart = null;
|
|
804
|
+
}
|
|
805
|
+
async searchPdf(query, options) {
|
|
806
|
+
const token = ++this.searchToken;
|
|
807
|
+
const keyword = query.trim();
|
|
808
|
+
if (!keyword || !this.engine || !this.pdfDocument) {
|
|
809
|
+
this.pdfSearchResults.set([]);
|
|
810
|
+
return;
|
|
811
|
+
}
|
|
812
|
+
const flags = [];
|
|
813
|
+
if (options.caseSensitive) {
|
|
814
|
+
flags.push(MatchFlag.MatchCase);
|
|
815
|
+
}
|
|
816
|
+
if (options.wholeWord) {
|
|
817
|
+
flags.push(MatchFlag.MatchWholeWord);
|
|
818
|
+
}
|
|
819
|
+
try {
|
|
820
|
+
const searchResult = await this.engine.searchAllPages(this.pdfDocument, keyword, { flags }).toPromise();
|
|
821
|
+
if (token !== this.searchToken) {
|
|
822
|
+
return;
|
|
823
|
+
}
|
|
824
|
+
this.pdfSearchResults.set(searchResult.results.map((result, index) => this.toSearchResultView(result, index)));
|
|
825
|
+
}
|
|
826
|
+
catch (error) {
|
|
827
|
+
if (token === this.searchToken) {
|
|
828
|
+
this.pdfSearchResults.set([]);
|
|
829
|
+
this.error.emit(error);
|
|
830
|
+
}
|
|
831
|
+
}
|
|
832
|
+
}
|
|
833
|
+
toSearchResultView(result, index) {
|
|
834
|
+
const context = result.context;
|
|
835
|
+
const excerpt = [
|
|
836
|
+
context.truncatedLeft ? '...' : '',
|
|
837
|
+
context.before,
|
|
838
|
+
context.match,
|
|
839
|
+
context.after,
|
|
840
|
+
context.truncatedRight ? '...' : '',
|
|
841
|
+
]
|
|
842
|
+
.filter((part) => part.length > 0)
|
|
843
|
+
.join(' ')
|
|
844
|
+
.replace(/\s+/g, ' ')
|
|
845
|
+
.trim();
|
|
846
|
+
return {
|
|
847
|
+
id: `${result.pageIndex}-${result.charIndex}-${result.charCount}-${index}`,
|
|
848
|
+
pageNumber: result.pageIndex + 1,
|
|
849
|
+
excerpt,
|
|
850
|
+
};
|
|
851
|
+
}
|
|
852
|
+
async loadDocument(source, wasmUrl) {
|
|
853
|
+
const token = ++this.loadToken;
|
|
854
|
+
this.renderToken++;
|
|
855
|
+
this.isLoading.set(true);
|
|
856
|
+
this.errorState.set(null);
|
|
857
|
+
this.pageCount.set(0);
|
|
858
|
+
this.searchToken++;
|
|
859
|
+
this.selectionStart = null;
|
|
860
|
+
this.selectionRects.set([]);
|
|
861
|
+
this.pdfSearchResults.set([]);
|
|
862
|
+
this.revokeRenderedPages();
|
|
863
|
+
this.revokeThumbnailPages();
|
|
864
|
+
await this.closeDocument();
|
|
865
|
+
await this.registry?.destroy();
|
|
866
|
+
this.registry = null;
|
|
867
|
+
if (!source) {
|
|
868
|
+
this.isLoading.set(false);
|
|
869
|
+
return;
|
|
870
|
+
}
|
|
871
|
+
try {
|
|
872
|
+
const engine = await this.engineService.getEngine(wasmUrl);
|
|
873
|
+
if (token !== this.loadToken) {
|
|
874
|
+
return;
|
|
875
|
+
}
|
|
876
|
+
this.engine = engine;
|
|
877
|
+
this.registry = await this.engineService.createRegistry(engine, this.zoom());
|
|
878
|
+
if (token !== this.loadToken) {
|
|
879
|
+
return;
|
|
880
|
+
}
|
|
881
|
+
const documentId = this.createDocumentId();
|
|
882
|
+
const pdfDocument = await this.openSource(engine, documentId, source);
|
|
883
|
+
if (token !== this.loadToken) {
|
|
884
|
+
await engine.closeDocument(pdfDocument).toPromise();
|
|
885
|
+
return;
|
|
886
|
+
}
|
|
887
|
+
this.pdfDocument = pdfDocument;
|
|
888
|
+
this.pageCount.set(pdfDocument.pageCount);
|
|
889
|
+
this.activePage.set(this.clamp(this.activePage(), 1, Math.max(pdfDocument.pageCount, 1)));
|
|
890
|
+
this.loaded.emit({ pageCount: pdfDocument.pageCount });
|
|
891
|
+
this.initializePageShells(pdfDocument, this.zoom(), this.renderAll(), this.activePage());
|
|
892
|
+
this.schedulePageObserverRefresh();
|
|
893
|
+
await this.searchPdf(this.activeSearchQuery() || this.searchQuery(), {
|
|
894
|
+
caseSensitive: false,
|
|
895
|
+
wholeWord: false,
|
|
896
|
+
});
|
|
897
|
+
await this.renderVisiblePages(token, ++this.renderToken, {
|
|
898
|
+
zoom: this.zoom(),
|
|
899
|
+
activePage: this.activePage(),
|
|
900
|
+
renderAll: this.renderAll(),
|
|
901
|
+
withAnnotations: this.withAnnotations(),
|
|
902
|
+
withForms: this.withForms(),
|
|
903
|
+
});
|
|
904
|
+
}
|
|
905
|
+
catch (error) {
|
|
906
|
+
if (token === this.loadToken) {
|
|
907
|
+
this.errorState.set(error);
|
|
908
|
+
this.error.emit(error);
|
|
909
|
+
}
|
|
910
|
+
}
|
|
911
|
+
finally {
|
|
912
|
+
if (token === this.loadToken) {
|
|
913
|
+
this.isLoading.set(false);
|
|
914
|
+
}
|
|
915
|
+
}
|
|
916
|
+
}
|
|
917
|
+
initializePageShells(pdfDocument, scale, renderAll, activePage) {
|
|
918
|
+
const previousPages = this.renderedPages();
|
|
919
|
+
const previousPageMap = new Map(previousPages.map((page) => [page.pageNumber, page]));
|
|
920
|
+
const pageNumbers = this.getPageNumbersToDisplay(pdfDocument, renderAll, activePage);
|
|
921
|
+
const nextScale = this.sanitizeScale(scale);
|
|
922
|
+
const nextPages = pageNumbers.map((pageNumber) => {
|
|
923
|
+
const pdfPage = pdfDocument.pages[pageNumber - 1];
|
|
924
|
+
const previousPage = previousPageMap.get(pageNumber);
|
|
925
|
+
const displaySize = this.getPageDisplaySize(pdfPage, nextScale);
|
|
926
|
+
const isFresh = previousPage?.url && Math.abs((previousPage.renderedScale ?? 0) - nextScale) < 0.0001;
|
|
927
|
+
return {
|
|
928
|
+
pageNumber,
|
|
929
|
+
url: isFresh ? previousPage.url : null,
|
|
930
|
+
scale: nextScale,
|
|
931
|
+
renderedScale: isFresh ? previousPage.renderedScale : null,
|
|
932
|
+
width: displaySize.width,
|
|
933
|
+
height: displaySize.height,
|
|
934
|
+
isRendering: false,
|
|
935
|
+
textGlyphs: isFresh ? previousPage.textGlyphs : [],
|
|
936
|
+
};
|
|
937
|
+
});
|
|
938
|
+
const nextUrlSet = new Set(nextPages.map((page) => page.url).filter((url) => !!url));
|
|
939
|
+
for (const page of previousPages) {
|
|
940
|
+
if (page.url && !nextUrlSet.has(page.url)) {
|
|
941
|
+
this.revokeObjectUrl(page.url);
|
|
942
|
+
}
|
|
943
|
+
}
|
|
944
|
+
this.selectionStart = null;
|
|
945
|
+
this.selectionRects.set([]);
|
|
946
|
+
this.disconnectPageObserver();
|
|
947
|
+
this.renderedPages.set(nextPages);
|
|
948
|
+
}
|
|
949
|
+
getPageNumbersToDisplay(pdfDocument, renderAll, activePage) {
|
|
950
|
+
if (!renderAll) {
|
|
951
|
+
return [this.clamp(activePage, 1, pdfDocument.pageCount)];
|
|
952
|
+
}
|
|
953
|
+
return Array.from({ length: pdfDocument.pageCount }, (_, index) => index + 1);
|
|
954
|
+
}
|
|
955
|
+
scheduleVisiblePagesRender(options) {
|
|
956
|
+
const targetWindow = this.document.defaultView;
|
|
957
|
+
if (!targetWindow) {
|
|
958
|
+
return;
|
|
959
|
+
}
|
|
960
|
+
this.cancelVisiblePageRenderFrame();
|
|
961
|
+
const token = this.loadToken;
|
|
962
|
+
const renderToken = ++this.renderToken;
|
|
963
|
+
const renderDelay = this.getQualityRenderDelay();
|
|
964
|
+
if (renderDelay > 0) {
|
|
965
|
+
this.visiblePageRenderTimeout = targetWindow.setTimeout(() => {
|
|
966
|
+
this.visiblePageRenderTimeout = null;
|
|
967
|
+
this.queueVisiblePageRenderFrame(token, renderToken, options);
|
|
968
|
+
}, renderDelay);
|
|
969
|
+
return;
|
|
970
|
+
}
|
|
971
|
+
this.queueVisiblePageRenderFrame(token, renderToken, options);
|
|
972
|
+
}
|
|
973
|
+
queueVisiblePageRenderFrame(token, renderToken, options) {
|
|
974
|
+
const targetWindow = this.document.defaultView;
|
|
975
|
+
if (!targetWindow) {
|
|
976
|
+
return;
|
|
977
|
+
}
|
|
978
|
+
this.visiblePageRenderFrame = targetWindow.requestAnimationFrame(() => {
|
|
979
|
+
this.visiblePageRenderFrame = null;
|
|
980
|
+
void this.renderVisiblePages(token, renderToken, options);
|
|
981
|
+
});
|
|
982
|
+
}
|
|
983
|
+
scheduleCurrentVisiblePagesRender() {
|
|
984
|
+
this.scheduleVisiblePagesRender({
|
|
985
|
+
zoom: this.zoom(),
|
|
986
|
+
activePage: this.activePage(),
|
|
987
|
+
renderAll: this.renderAll(),
|
|
988
|
+
withAnnotations: this.withAnnotations(),
|
|
989
|
+
withForms: this.withForms(),
|
|
990
|
+
});
|
|
991
|
+
}
|
|
992
|
+
async renderVisiblePages(token, renderToken, options) {
|
|
993
|
+
if (!this.engine || !this.pdfDocument) {
|
|
994
|
+
return;
|
|
995
|
+
}
|
|
996
|
+
const pdfDocument = this.pdfDocument;
|
|
997
|
+
const renderScale = this.sanitizeScale(options.zoom);
|
|
998
|
+
const pageNumbers = this.getPriorityPageNumbers(options.activePage, options.renderAll);
|
|
999
|
+
for (const pageNumber of pageNumbers) {
|
|
1000
|
+
if (!this.isRenderCurrent(token, renderToken)) {
|
|
1001
|
+
return;
|
|
1002
|
+
}
|
|
1003
|
+
const currentPage = this.renderedPages().find((page) => page.pageNumber === pageNumber);
|
|
1004
|
+
if (!currentPage || Math.abs(currentPage.scale - renderScale) > 0.0001) {
|
|
1005
|
+
continue;
|
|
1006
|
+
}
|
|
1007
|
+
if (currentPage.url && Math.abs((currentPage.renderedScale ?? 0) - renderScale) < 0.0001) {
|
|
1008
|
+
continue;
|
|
1009
|
+
}
|
|
1010
|
+
this.patchRenderedPage(pageNumber, { isRendering: !currentPage.url });
|
|
1011
|
+
const page = pdfDocument.pages[pageNumber - 1];
|
|
1012
|
+
const rasterOptions = this.getPageRasterRenderOptions(page, renderScale);
|
|
1013
|
+
const blob = await this.engine.renderPage(pdfDocument, page, {
|
|
1014
|
+
scaleFactor: rasterOptions.scaleFactor,
|
|
1015
|
+
dpr: rasterOptions.dpr,
|
|
1016
|
+
withAnnotations: options.withAnnotations,
|
|
1017
|
+
withForms: options.withForms,
|
|
1018
|
+
}).toPromise();
|
|
1019
|
+
if (!this.isRenderCurrent(token, renderToken)) {
|
|
1020
|
+
return;
|
|
1021
|
+
}
|
|
1022
|
+
const displaySize = this.getPageDisplaySize(page, renderScale);
|
|
1023
|
+
const textGlyphs = await this.getPageTextGlyphs(pdfDocument, page, renderScale);
|
|
1024
|
+
if (!this.isRenderCurrent(token, renderToken)) {
|
|
1025
|
+
return;
|
|
1026
|
+
}
|
|
1027
|
+
const url = this.document.defaultView?.URL.createObjectURL(blob) ?? URL.createObjectURL(blob);
|
|
1028
|
+
const previousUrl = this.renderedPages().find((renderedPage) => renderedPage.pageNumber === pageNumber)?.url;
|
|
1029
|
+
this.patchRenderedPage(pageNumber, {
|
|
1030
|
+
url,
|
|
1031
|
+
scale: renderScale,
|
|
1032
|
+
renderedScale: renderScale,
|
|
1033
|
+
width: displaySize.width,
|
|
1034
|
+
height: displaySize.height,
|
|
1035
|
+
isRendering: false,
|
|
1036
|
+
textGlyphs,
|
|
1037
|
+
});
|
|
1038
|
+
if (previousUrl && previousUrl !== url) {
|
|
1039
|
+
this.revokeObjectUrl(previousUrl);
|
|
1040
|
+
}
|
|
1041
|
+
this.pageRendered.emit({ pageNumber, url, width: displaySize.width, height: displaySize.height });
|
|
1042
|
+
}
|
|
1043
|
+
}
|
|
1044
|
+
setZoom(scale) {
|
|
1045
|
+
const nextZoom = this.sanitizeScale(scale);
|
|
1046
|
+
if (this.zoom() !== nextZoom) {
|
|
1047
|
+
this.lastZoomChangeTime = this.getCurrentTime();
|
|
1048
|
+
this.zoom.set(nextZoom);
|
|
1049
|
+
}
|
|
1050
|
+
this.applyInstantZoom(nextZoom);
|
|
1051
|
+
}
|
|
1052
|
+
applyInstantZoom(scale) {
|
|
1053
|
+
const nextScale = this.sanitizeScale(scale);
|
|
1054
|
+
const pdfDocument = this.pdfDocument;
|
|
1055
|
+
const pages = this.renderedPages();
|
|
1056
|
+
if (!pdfDocument || pages.length === 0) {
|
|
1057
|
+
return;
|
|
1058
|
+
}
|
|
1059
|
+
let hasChanges = false;
|
|
1060
|
+
const nextPages = pages.map((page) => {
|
|
1061
|
+
if (Math.abs(page.scale - nextScale) < 0.0001) {
|
|
1062
|
+
return page;
|
|
1063
|
+
}
|
|
1064
|
+
hasChanges = true;
|
|
1065
|
+
const pdfPage = pdfDocument.pages[page.pageNumber - 1];
|
|
1066
|
+
const displaySize = this.getPageDisplaySize(pdfPage, nextScale);
|
|
1067
|
+
return {
|
|
1068
|
+
...page,
|
|
1069
|
+
scale: nextScale,
|
|
1070
|
+
width: displaySize.width,
|
|
1071
|
+
height: displaySize.height,
|
|
1072
|
+
isRendering: false,
|
|
1073
|
+
textGlyphs: [],
|
|
1074
|
+
};
|
|
1075
|
+
});
|
|
1076
|
+
if (!hasChanges) {
|
|
1077
|
+
return;
|
|
1078
|
+
}
|
|
1079
|
+
this.selectionStart = null;
|
|
1080
|
+
this.selectionRects.set([]);
|
|
1081
|
+
this.renderedPages.set(nextPages);
|
|
1082
|
+
this.schedulePageObserverRefresh();
|
|
1083
|
+
}
|
|
1084
|
+
getPriorityPageNumbers(activePage, renderAll) {
|
|
1085
|
+
if (!this.pdfDocument) {
|
|
1086
|
+
return [];
|
|
1087
|
+
}
|
|
1088
|
+
const pageCount = this.pdfDocument.pageCount;
|
|
1089
|
+
const visiblePageNumbers = renderAll
|
|
1090
|
+
? this.getVisiblePageNumbers()
|
|
1091
|
+
: [this.clamp(activePage, 1, pageCount)];
|
|
1092
|
+
const priorityPageNumbers = new Set();
|
|
1093
|
+
for (const pageNumber of visiblePageNumbers.length > 0 ? visiblePageNumbers : [activePage]) {
|
|
1094
|
+
for (let candidate = pageNumber - 1; candidate <= pageNumber + 1; candidate++) {
|
|
1095
|
+
if (candidate >= 1 && candidate <= pageCount) {
|
|
1096
|
+
priorityPageNumbers.add(candidate);
|
|
1097
|
+
}
|
|
1098
|
+
}
|
|
1099
|
+
}
|
|
1100
|
+
priorityPageNumbers.add(this.clamp(activePage, 1, pageCount));
|
|
1101
|
+
return [...priorityPageNumbers].sort((a, b) => {
|
|
1102
|
+
const activeDistance = Math.abs(a - activePage) - Math.abs(b - activePage);
|
|
1103
|
+
return activeDistance || a - b;
|
|
1104
|
+
});
|
|
1105
|
+
}
|
|
1106
|
+
getVisiblePageNumbers() {
|
|
1107
|
+
const container = this.viewerBody()?.nativeElement;
|
|
1108
|
+
if (!container) {
|
|
1109
|
+
return [];
|
|
1110
|
+
}
|
|
1111
|
+
const containerRect = container.getBoundingClientRect();
|
|
1112
|
+
const prefetchMargin = containerRect.height;
|
|
1113
|
+
const pages = Array.from(container.querySelectorAll('[data-ngs-pdf-page]'));
|
|
1114
|
+
const visiblePageNumbers = [];
|
|
1115
|
+
for (const page of pages) {
|
|
1116
|
+
const rect = page.getBoundingClientRect();
|
|
1117
|
+
const pageNumber = Number(page.dataset['ngsPdfPage']);
|
|
1118
|
+
if (Number.isFinite(pageNumber) &&
|
|
1119
|
+
rect.bottom >= containerRect.top - prefetchMargin &&
|
|
1120
|
+
rect.top <= containerRect.bottom + prefetchMargin) {
|
|
1121
|
+
visiblePageNumbers.push(pageNumber);
|
|
1122
|
+
}
|
|
1123
|
+
}
|
|
1124
|
+
return visiblePageNumbers;
|
|
1125
|
+
}
|
|
1126
|
+
patchRenderedPage(pageNumber, patch) {
|
|
1127
|
+
this.renderedPages.update((pages) => pages.map((page) => page.pageNumber === pageNumber ? { ...page, ...patch } : page));
|
|
1128
|
+
}
|
|
1129
|
+
async renderThumbnails(token, options) {
|
|
1130
|
+
if (!this.engine || !this.pdfDocument) {
|
|
1131
|
+
return;
|
|
1132
|
+
}
|
|
1133
|
+
const pdfDocument = this.pdfDocument;
|
|
1134
|
+
const thumbnailScale = 0.18;
|
|
1135
|
+
const nextThumbnails = [];
|
|
1136
|
+
const previousThumbnails = this.thumbnailPages();
|
|
1137
|
+
for (const page of pdfDocument.pages) {
|
|
1138
|
+
const blob = await this.engine.renderThumbnail(pdfDocument, page, {
|
|
1139
|
+
scaleFactor: thumbnailScale,
|
|
1140
|
+
dpr: this.getDevicePixelRatio(),
|
|
1141
|
+
withAnnotations: options.withAnnotations,
|
|
1142
|
+
}).toPromise();
|
|
1143
|
+
if (token !== this.loadToken) {
|
|
1144
|
+
const staleUrl = this.document.defaultView?.URL.createObjectURL(blob) ?? URL.createObjectURL(blob);
|
|
1145
|
+
this.revokeObjectUrl(staleUrl);
|
|
1146
|
+
return;
|
|
1147
|
+
}
|
|
1148
|
+
const url = this.document.defaultView?.URL.createObjectURL(blob) ?? URL.createObjectURL(blob);
|
|
1149
|
+
const pageNumber = page.index + 1;
|
|
1150
|
+
const displaySize = this.getPageDisplaySize(page, thumbnailScale);
|
|
1151
|
+
nextThumbnails.push({ pageNumber, url, width: displaySize.width, height: displaySize.height });
|
|
1152
|
+
}
|
|
1153
|
+
if (token !== this.loadToken) {
|
|
1154
|
+
for (const thumbnail of nextThumbnails) {
|
|
1155
|
+
this.revokeObjectUrl(thumbnail.url);
|
|
1156
|
+
}
|
|
1157
|
+
return;
|
|
1158
|
+
}
|
|
1159
|
+
this.thumbnailPages.set(nextThumbnails);
|
|
1160
|
+
for (const thumbnail of previousThumbnails) {
|
|
1161
|
+
this.revokeObjectUrl(thumbnail.url);
|
|
1162
|
+
}
|
|
1163
|
+
}
|
|
1164
|
+
async getPageTextGlyphs(pdfDocument, page, scale) {
|
|
1165
|
+
if (!this.engine) {
|
|
1166
|
+
return [];
|
|
1167
|
+
}
|
|
1168
|
+
try {
|
|
1169
|
+
const geometry = await this.engine.getPageGeometry(pdfDocument, page).toPromise();
|
|
1170
|
+
const displaySize = this.getPageDisplaySize(page, scale);
|
|
1171
|
+
const glyphs = [];
|
|
1172
|
+
for (const run of geometry.runs) {
|
|
1173
|
+
for (const glyph of run.glyphs) {
|
|
1174
|
+
if ((glyph.flags & 2) === 2 || glyph.width <= 0 || glyph.height <= 0) {
|
|
1175
|
+
continue;
|
|
1176
|
+
}
|
|
1177
|
+
const glyphLeft = glyph.x * scale;
|
|
1178
|
+
const glyphTop = glyph.y * scale;
|
|
1179
|
+
const glyphWidth = glyph.width * scale;
|
|
1180
|
+
const glyphHeight = glyph.height * scale;
|
|
1181
|
+
const lineHeight = Math.max(glyphHeight, (run.fontSize ?? glyph.height) * this.textSelectionLineHeight * scale);
|
|
1182
|
+
const lineTop = this.clamp(glyphTop - (lineHeight - glyphHeight) / 2, 0, displaySize.height);
|
|
1183
|
+
const lineBottom = this.clamp(lineTop + lineHeight, 0, displaySize.height);
|
|
1184
|
+
const left = this.clamp(glyphLeft, 0, displaySize.width);
|
|
1185
|
+
const right = this.clamp(glyphLeft + glyphWidth, 0, displaySize.width);
|
|
1186
|
+
glyphs.push({
|
|
1187
|
+
index: glyphs.length,
|
|
1188
|
+
left,
|
|
1189
|
+
top: this.clamp(glyphTop, 0, displaySize.height),
|
|
1190
|
+
width: Math.max(0, right - left),
|
|
1191
|
+
height: Math.max(0, glyphHeight),
|
|
1192
|
+
lineTop,
|
|
1193
|
+
lineHeight: Math.max(0, lineBottom - lineTop),
|
|
1194
|
+
lineId: -1,
|
|
1195
|
+
});
|
|
1196
|
+
}
|
|
1197
|
+
}
|
|
1198
|
+
return this.assignTextGlyphLines(glyphs).map((glyph) => ({
|
|
1199
|
+
...glyph,
|
|
1200
|
+
left: this.roundCssPixel(glyph.left),
|
|
1201
|
+
top: this.roundCssPixel(glyph.top),
|
|
1202
|
+
width: this.roundCssPixel(glyph.width),
|
|
1203
|
+
height: this.roundCssPixel(glyph.height),
|
|
1204
|
+
lineTop: this.roundCssPixel(glyph.lineTop),
|
|
1205
|
+
lineHeight: this.roundCssPixel(glyph.lineHeight),
|
|
1206
|
+
}));
|
|
1207
|
+
}
|
|
1208
|
+
catch {
|
|
1209
|
+
return [];
|
|
1210
|
+
}
|
|
1211
|
+
}
|
|
1212
|
+
assignTextGlyphLines(glyphs) {
|
|
1213
|
+
const lines = [];
|
|
1214
|
+
for (const glyph of [...glyphs].sort((a, b) => a.lineTop - b.lineTop || a.left - b.left)) {
|
|
1215
|
+
const glyphBottom = glyph.lineTop + glyph.lineHeight;
|
|
1216
|
+
const glyphCenter = glyph.lineTop + glyph.lineHeight / 2;
|
|
1217
|
+
let line = lines.find((candidate) => this.rangesOverlap(candidate.top, candidate.bottom, glyph.lineTop, glyphBottom) ||
|
|
1218
|
+
Math.abs(candidate.center - glyphCenter) <= Math.max(2, glyph.lineHeight * 0.4));
|
|
1219
|
+
if (!line) {
|
|
1220
|
+
line = {
|
|
1221
|
+
lineId: lines.length,
|
|
1222
|
+
glyphs: [],
|
|
1223
|
+
top: glyph.lineTop,
|
|
1224
|
+
bottom: glyphBottom,
|
|
1225
|
+
left: glyph.left,
|
|
1226
|
+
right: glyph.left + glyph.width,
|
|
1227
|
+
center: glyphCenter,
|
|
1228
|
+
};
|
|
1229
|
+
lines.push(line);
|
|
1230
|
+
}
|
|
1231
|
+
glyph.lineId = line.lineId;
|
|
1232
|
+
line.glyphs.push(glyph);
|
|
1233
|
+
line.top = Math.min(line.top, glyph.lineTop);
|
|
1234
|
+
line.bottom = Math.max(line.bottom, glyphBottom);
|
|
1235
|
+
line.left = Math.min(line.left, glyph.left);
|
|
1236
|
+
line.right = Math.max(line.right, glyph.left + glyph.width);
|
|
1237
|
+
line.center = line.top + (line.bottom - line.top) / 2;
|
|
1238
|
+
}
|
|
1239
|
+
return glyphs;
|
|
1240
|
+
}
|
|
1241
|
+
getTextLines(glyphs) {
|
|
1242
|
+
const lines = new Map();
|
|
1243
|
+
for (const glyph of glyphs) {
|
|
1244
|
+
const glyphBottom = glyph.lineTop + glyph.lineHeight;
|
|
1245
|
+
const glyphRight = glyph.left + glyph.width;
|
|
1246
|
+
const existing = lines.get(glyph.lineId);
|
|
1247
|
+
if (!existing) {
|
|
1248
|
+
lines.set(glyph.lineId, {
|
|
1249
|
+
lineId: glyph.lineId,
|
|
1250
|
+
glyphs: [glyph],
|
|
1251
|
+
top: glyph.lineTop,
|
|
1252
|
+
bottom: glyphBottom,
|
|
1253
|
+
left: glyph.left,
|
|
1254
|
+
right: glyphRight,
|
|
1255
|
+
center: glyph.lineTop + glyph.lineHeight / 2,
|
|
1256
|
+
});
|
|
1257
|
+
continue;
|
|
1258
|
+
}
|
|
1259
|
+
existing.glyphs.push(glyph);
|
|
1260
|
+
existing.top = Math.min(existing.top, glyph.lineTop);
|
|
1261
|
+
existing.bottom = Math.max(existing.bottom, glyphBottom);
|
|
1262
|
+
existing.left = Math.min(existing.left, glyph.left);
|
|
1263
|
+
existing.right = Math.max(existing.right, glyphRight);
|
|
1264
|
+
existing.center = existing.top + (existing.bottom - existing.top) / 2;
|
|
1265
|
+
}
|
|
1266
|
+
return [...lines.values()]
|
|
1267
|
+
.map((line) => ({
|
|
1268
|
+
...line,
|
|
1269
|
+
glyphs: [...line.glyphs].sort((a, b) => a.left - b.left || a.index - b.index),
|
|
1270
|
+
}))
|
|
1271
|
+
.sort((a, b) => a.top - b.top || a.left - b.left);
|
|
1272
|
+
}
|
|
1273
|
+
rangesOverlap(aStart, aEnd, bStart, bEnd) {
|
|
1274
|
+
return aStart < bEnd && aEnd > bStart;
|
|
1275
|
+
}
|
|
1276
|
+
getCaretOffsetAtPoint(page, point) {
|
|
1277
|
+
const lines = this.getTextLines(page.textGlyphs);
|
|
1278
|
+
if (lines.length === 0) {
|
|
1279
|
+
return 0;
|
|
1280
|
+
}
|
|
1281
|
+
const line = lines.find((candidate) => point.y >= candidate.top && point.y <= candidate.bottom) ??
|
|
1282
|
+
lines.reduce((closest, candidate) => Math.abs(candidate.center - point.y) < Math.abs(closest.center - point.y) ? candidate : closest);
|
|
1283
|
+
const glyphs = line.glyphs;
|
|
1284
|
+
if (glyphs.length === 0) {
|
|
1285
|
+
return 0;
|
|
1286
|
+
}
|
|
1287
|
+
for (const glyph of glyphs) {
|
|
1288
|
+
if (point.x < glyph.left + glyph.width / 2) {
|
|
1289
|
+
return glyph.index;
|
|
1290
|
+
}
|
|
1291
|
+
}
|
|
1292
|
+
return glyphs[glyphs.length - 1].index + 1;
|
|
1293
|
+
}
|
|
1294
|
+
getSelectionPoint(event, page, surface) {
|
|
1295
|
+
const rect = surface.getBoundingClientRect();
|
|
1296
|
+
const x = this.clamp(event.clientX - rect.left, 0, page.width);
|
|
1297
|
+
const y = this.clamp(event.clientY - rect.top, 0, page.height);
|
|
1298
|
+
return {
|
|
1299
|
+
pageNumber: page.pageNumber,
|
|
1300
|
+
x,
|
|
1301
|
+
y,
|
|
1302
|
+
offset: this.getCaretOffsetAtPoint(page, { x, y }),
|
|
1303
|
+
};
|
|
1304
|
+
}
|
|
1305
|
+
getSelectionRects(page, startOffset, endOffset) {
|
|
1306
|
+
const rangeStart = Math.min(startOffset, endOffset);
|
|
1307
|
+
const rangeEnd = Math.max(startOffset, endOffset);
|
|
1308
|
+
const selectedGlyphs = page.textGlyphs.filter((glyph) => glyph.index >= rangeStart && glyph.index < rangeEnd);
|
|
1309
|
+
return this.getTextLines(selectedGlyphs).map((line) => {
|
|
1310
|
+
const left = this.clamp(line.left - this.textSelectionHorizontalPadding, 0, page.width);
|
|
1311
|
+
const top = this.clamp(line.top, 0, page.height);
|
|
1312
|
+
const right = this.clamp(line.right + this.textSelectionHorizontalPadding, 0, page.width);
|
|
1313
|
+
const bottom = this.clamp(line.bottom, 0, page.height);
|
|
1314
|
+
return {
|
|
1315
|
+
text: '',
|
|
1316
|
+
pageNumber: page.pageNumber,
|
|
1317
|
+
left: this.roundCssPixel(left),
|
|
1318
|
+
top: this.roundCssPixel(top),
|
|
1319
|
+
width: this.roundCssPixel(Math.max(0, right - left)),
|
|
1320
|
+
height: this.roundCssPixel(Math.max(0, bottom - top)),
|
|
1321
|
+
};
|
|
1322
|
+
});
|
|
1323
|
+
}
|
|
1324
|
+
async openSource(engine, documentId, source) {
|
|
1325
|
+
if (typeof source === 'string') {
|
|
1326
|
+
return engine.openDocumentUrl({ id: documentId, url: source }).toPromise();
|
|
1327
|
+
}
|
|
1328
|
+
if (source instanceof Blob) {
|
|
1329
|
+
return engine.openDocumentBuffer({ id: documentId, content: await source.arrayBuffer() }).toPromise();
|
|
1330
|
+
}
|
|
1331
|
+
if (source instanceof Uint8Array) {
|
|
1332
|
+
const content = new Uint8Array(source).buffer;
|
|
1333
|
+
return engine.openDocumentBuffer({ id: documentId, content }).toPromise();
|
|
1334
|
+
}
|
|
1335
|
+
return engine.openDocumentBuffer({ id: documentId, content: source }).toPromise();
|
|
1336
|
+
}
|
|
1337
|
+
async closeDocument() {
|
|
1338
|
+
if (!this.engine || !this.pdfDocument) {
|
|
1339
|
+
this.pdfDocument = null;
|
|
1340
|
+
return;
|
|
1341
|
+
}
|
|
1342
|
+
const documentToClose = this.pdfDocument;
|
|
1343
|
+
this.pdfDocument = null;
|
|
1344
|
+
try {
|
|
1345
|
+
await this.engine.closeDocument(documentToClose).toPromise();
|
|
1346
|
+
}
|
|
1347
|
+
catch {
|
|
1348
|
+
// Best-effort cleanup; the viewer should not surface cleanup failures as user-facing load errors.
|
|
1349
|
+
}
|
|
1350
|
+
}
|
|
1351
|
+
revokeRenderedPages() {
|
|
1352
|
+
this.disconnectPageObserver();
|
|
1353
|
+
this.cancelVisiblePageRenderFrame();
|
|
1354
|
+
for (const page of this.renderedPages()) {
|
|
1355
|
+
if (page.url) {
|
|
1356
|
+
this.revokeObjectUrl(page.url);
|
|
1357
|
+
}
|
|
1358
|
+
}
|
|
1359
|
+
this.renderedPages.set([]);
|
|
1360
|
+
}
|
|
1361
|
+
revokeThumbnailPages() {
|
|
1362
|
+
for (const thumbnail of this.thumbnailPages()) {
|
|
1363
|
+
this.revokeObjectUrl(thumbnail.url);
|
|
1364
|
+
}
|
|
1365
|
+
this.thumbnailPages.set([]);
|
|
1366
|
+
}
|
|
1367
|
+
revokeObjectUrl(url) {
|
|
1368
|
+
const targetWindow = this.document.defaultView;
|
|
1369
|
+
if (targetWindow) {
|
|
1370
|
+
targetWindow.URL.revokeObjectURL(url);
|
|
1371
|
+
return;
|
|
1372
|
+
}
|
|
1373
|
+
URL.revokeObjectURL(url);
|
|
1374
|
+
}
|
|
1375
|
+
getSourceName(source) {
|
|
1376
|
+
if (typeof File !== 'undefined' && source instanceof File && source.name) {
|
|
1377
|
+
return source.name;
|
|
1378
|
+
}
|
|
1379
|
+
if (typeof source !== 'string' || source.trim().length === 0) {
|
|
1380
|
+
return 'Document.pdf';
|
|
1381
|
+
}
|
|
1382
|
+
try {
|
|
1383
|
+
const url = new URL(source, this.document.baseURI);
|
|
1384
|
+
const pathName = url.pathname.split('/').filter(Boolean).pop();
|
|
1385
|
+
return pathName ? decodeURIComponent(pathName) : 'Document.pdf';
|
|
1386
|
+
}
|
|
1387
|
+
catch {
|
|
1388
|
+
const pathName = source.split('?')[0]?.split('#')[0]?.split('/').filter(Boolean).pop();
|
|
1389
|
+
return pathName ? decodeURIComponent(pathName) : 'Document.pdf';
|
|
1390
|
+
}
|
|
1391
|
+
}
|
|
1392
|
+
sanitizePage(pageNumber) {
|
|
1393
|
+
return this.clamp(Math.trunc(Number.isFinite(pageNumber) ? pageNumber : 1), 1, Math.max(this.pageCount(), 1));
|
|
1394
|
+
}
|
|
1395
|
+
sanitizeScale(scale) {
|
|
1396
|
+
const bounds = this.getScaleBounds();
|
|
1397
|
+
return this.clamp(Number.isFinite(scale) ? scale : 1, bounds.min, bounds.max);
|
|
1398
|
+
}
|
|
1399
|
+
roundZoom(value) {
|
|
1400
|
+
return this.sanitizeScale(Math.round(this.sanitizeScale(value) * 100) / 100);
|
|
1401
|
+
}
|
|
1402
|
+
floorFitZoom(value) {
|
|
1403
|
+
return this.sanitizeScale(Math.floor(this.sanitizeScale(value) * 100) / 100);
|
|
1404
|
+
}
|
|
1405
|
+
clamp(value, min, max) {
|
|
1406
|
+
return Math.min(Math.max(value, min), max);
|
|
1407
|
+
}
|
|
1408
|
+
roundCssPixel(value) {
|
|
1409
|
+
return Math.round(value * 100) / 100;
|
|
1410
|
+
}
|
|
1411
|
+
parseCssPixel(value) {
|
|
1412
|
+
const parsed = Number.parseFloat(value);
|
|
1413
|
+
return Number.isFinite(parsed) ? parsed : 0;
|
|
1414
|
+
}
|
|
1415
|
+
getDevicePixelRatio() {
|
|
1416
|
+
return this.document.defaultView?.devicePixelRatio || 1;
|
|
1417
|
+
}
|
|
1418
|
+
getPageRasterRenderOptions(page, scale) {
|
|
1419
|
+
const devicePixelRatio = Math.max(1, this.getDevicePixelRatio());
|
|
1420
|
+
const pageSize = this.getPageBaseSize(page);
|
|
1421
|
+
const maxRenderPixels = this.sanitizePositiveNumber(this.maxRenderPixels(), 128_000_000);
|
|
1422
|
+
const maxRenderDimension = this.sanitizePositiveNumber(this.maxRenderDimension(), 12_000);
|
|
1423
|
+
const targetEffectiveScale = scale * devicePixelRatio;
|
|
1424
|
+
const dimensionEffectiveScale = maxRenderDimension / Math.max(pageSize.width, pageSize.height);
|
|
1425
|
+
const pixelEffectiveScale = Math.sqrt(maxRenderPixels / (pageSize.width * pageSize.height));
|
|
1426
|
+
const effectiveScale = this.clamp(Math.min(targetEffectiveScale, dimensionEffectiveScale, pixelEffectiveScale), 0.05, targetEffectiveScale);
|
|
1427
|
+
const dprAtLayoutScale = effectiveScale / scale;
|
|
1428
|
+
if (dprAtLayoutScale >= 1) {
|
|
1429
|
+
return {
|
|
1430
|
+
scaleFactor: scale,
|
|
1431
|
+
dpr: this.clamp(dprAtLayoutScale, 1, devicePixelRatio),
|
|
1432
|
+
};
|
|
1433
|
+
}
|
|
1434
|
+
return {
|
|
1435
|
+
scaleFactor: effectiveScale,
|
|
1436
|
+
dpr: 1,
|
|
1437
|
+
};
|
|
1438
|
+
}
|
|
1439
|
+
createDocumentId() {
|
|
1440
|
+
return `ngs-pdf-${Date.now()}-${Math.random().toString(36).slice(2)}`;
|
|
1441
|
+
}
|
|
1442
|
+
isRenderCurrent(loadToken, renderToken) {
|
|
1443
|
+
return loadToken === this.loadToken && renderToken === this.renderToken;
|
|
1444
|
+
}
|
|
1445
|
+
cancelScrollSyncFrame() {
|
|
1446
|
+
if (this.scrollSyncFrame === null) {
|
|
1447
|
+
return;
|
|
1448
|
+
}
|
|
1449
|
+
this.document.defaultView?.cancelAnimationFrame(this.scrollSyncFrame);
|
|
1450
|
+
this.scrollSyncFrame = null;
|
|
1451
|
+
}
|
|
1452
|
+
cancelPageObserverFrame() {
|
|
1453
|
+
if (this.pageObserverTimeout !== null) {
|
|
1454
|
+
this.document.defaultView?.clearTimeout(this.pageObserverTimeout);
|
|
1455
|
+
this.pageObserverTimeout = null;
|
|
1456
|
+
}
|
|
1457
|
+
if (this.pageObserverFrame === null) {
|
|
1458
|
+
return;
|
|
1459
|
+
}
|
|
1460
|
+
this.document.defaultView?.cancelAnimationFrame(this.pageObserverFrame);
|
|
1461
|
+
this.pageObserverFrame = null;
|
|
1462
|
+
}
|
|
1463
|
+
cancelVisiblePageRenderFrame() {
|
|
1464
|
+
if (this.visiblePageRenderTimeout !== null) {
|
|
1465
|
+
this.document.defaultView?.clearTimeout(this.visiblePageRenderTimeout);
|
|
1466
|
+
this.visiblePageRenderTimeout = null;
|
|
1467
|
+
}
|
|
1468
|
+
if (this.visiblePageRenderFrame === null) {
|
|
1469
|
+
return;
|
|
1470
|
+
}
|
|
1471
|
+
this.document.defaultView?.cancelAnimationFrame(this.visiblePageRenderFrame);
|
|
1472
|
+
this.visiblePageRenderFrame = null;
|
|
1473
|
+
}
|
|
1474
|
+
getCurrentTime() {
|
|
1475
|
+
return this.document.defaultView?.performance?.now() ?? Date.now();
|
|
1476
|
+
}
|
|
1477
|
+
getQualityRenderDelay() {
|
|
1478
|
+
const elapsed = this.getCurrentTime() - this.lastZoomChangeTime;
|
|
1479
|
+
if (elapsed >= this.qualityRenderZoomIdleDelay) {
|
|
1480
|
+
return 0;
|
|
1481
|
+
}
|
|
1482
|
+
return Math.max(0, this.qualityRenderZoomIdleDelay - elapsed);
|
|
1483
|
+
}
|
|
1484
|
+
getZoomAnchor(container, event) {
|
|
1485
|
+
if (!Number.isFinite(event.clientX) || !Number.isFinite(event.clientY)) {
|
|
1486
|
+
return null;
|
|
1487
|
+
}
|
|
1488
|
+
const targetDocument = container.ownerDocument;
|
|
1489
|
+
const targetElement = targetDocument.elementFromPoint(event.clientX, event.clientY);
|
|
1490
|
+
const pageElement = targetElement?.closest('[data-ngs-pdf-page]');
|
|
1491
|
+
if (!pageElement || !container.contains(pageElement)) {
|
|
1492
|
+
return null;
|
|
1493
|
+
}
|
|
1494
|
+
const containerRect = container.getBoundingClientRect();
|
|
1495
|
+
const pageRect = pageElement.getBoundingClientRect();
|
|
1496
|
+
const pageNumber = Number(pageElement.dataset['ngsPdfPage']);
|
|
1497
|
+
if (!Number.isFinite(pageNumber) || pageRect.width <= 0 || pageRect.height <= 0) {
|
|
1498
|
+
return null;
|
|
1499
|
+
}
|
|
1500
|
+
return {
|
|
1501
|
+
container,
|
|
1502
|
+
pageNumber,
|
|
1503
|
+
relativeX: this.clamp((event.clientX - pageRect.left) / pageRect.width, 0, 1),
|
|
1504
|
+
relativeY: this.clamp((event.clientY - pageRect.top) / pageRect.height, 0, 1),
|
|
1505
|
+
viewportX: event.clientX - containerRect.left,
|
|
1506
|
+
viewportY: event.clientY - containerRect.top,
|
|
1507
|
+
};
|
|
1508
|
+
}
|
|
1509
|
+
restoreZoomAnchor(anchor) {
|
|
1510
|
+
if (!anchor) {
|
|
1511
|
+
return;
|
|
1512
|
+
}
|
|
1513
|
+
const targetWindow = this.document.defaultView;
|
|
1514
|
+
const restore = () => {
|
|
1515
|
+
const pageElement = anchor.container.querySelector(`[data-ngs-pdf-page="${anchor.pageNumber}"]`);
|
|
1516
|
+
if (!pageElement) {
|
|
1517
|
+
return;
|
|
1518
|
+
}
|
|
1519
|
+
const containerRect = anchor.container.getBoundingClientRect();
|
|
1520
|
+
const pageRect = pageElement.getBoundingClientRect();
|
|
1521
|
+
const targetLeft = anchor.container.scrollLeft +
|
|
1522
|
+
pageRect.left -
|
|
1523
|
+
containerRect.left +
|
|
1524
|
+
pageRect.width * anchor.relativeX -
|
|
1525
|
+
anchor.viewportX;
|
|
1526
|
+
const targetTop = anchor.container.scrollTop +
|
|
1527
|
+
pageRect.top -
|
|
1528
|
+
containerRect.top +
|
|
1529
|
+
pageRect.height * anchor.relativeY -
|
|
1530
|
+
anchor.viewportY;
|
|
1531
|
+
anchor.container.scrollLeft = this.clamp(targetLeft, 0, Math.max(0, anchor.container.scrollWidth - anchor.container.clientWidth));
|
|
1532
|
+
anchor.container.scrollTop = this.clamp(targetTop, 0, Math.max(0, anchor.container.scrollHeight - anchor.container.clientHeight));
|
|
1533
|
+
};
|
|
1534
|
+
if (!targetWindow) {
|
|
1535
|
+
restore();
|
|
1536
|
+
return;
|
|
1537
|
+
}
|
|
1538
|
+
targetWindow.requestAnimationFrame(restore);
|
|
1539
|
+
}
|
|
1540
|
+
disconnectPageObserver() {
|
|
1541
|
+
this.cancelPageObserverFrame();
|
|
1542
|
+
this.pageIntersectionObserver?.disconnect();
|
|
1543
|
+
this.pageIntersectionObserver = null;
|
|
1544
|
+
this.visiblePageRatios.clear();
|
|
1545
|
+
}
|
|
1546
|
+
schedulePageObserverRefresh() {
|
|
1547
|
+
const targetWindow = this.document.defaultView;
|
|
1548
|
+
if (!this.isBrowser || !targetWindow) {
|
|
1549
|
+
return;
|
|
1550
|
+
}
|
|
1551
|
+
this.cancelPageObserverFrame();
|
|
1552
|
+
this.pageObserverRefreshAttempts = 0;
|
|
1553
|
+
this.scheduleNextPageObserverRefresh();
|
|
1554
|
+
}
|
|
1555
|
+
scheduleNextPageObserverRefresh() {
|
|
1556
|
+
const targetWindow = this.document.defaultView;
|
|
1557
|
+
if (!targetWindow) {
|
|
1558
|
+
return;
|
|
1559
|
+
}
|
|
1560
|
+
this.pageObserverTimeout = targetWindow.setTimeout(() => {
|
|
1561
|
+
this.pageObserverTimeout = null;
|
|
1562
|
+
this.pageObserverFrame = targetWindow.requestAnimationFrame(() => {
|
|
1563
|
+
this.pageObserverFrame = null;
|
|
1564
|
+
const isReady = this.setupPageIntersectionObserver();
|
|
1565
|
+
if (!isReady && this.pageObserverRefreshAttempts < 10) {
|
|
1566
|
+
this.pageObserverRefreshAttempts++;
|
|
1567
|
+
this.scheduleNextPageObserverRefresh();
|
|
1568
|
+
return;
|
|
1569
|
+
}
|
|
1570
|
+
this.syncActivePageFromViewport();
|
|
1571
|
+
});
|
|
1572
|
+
}, 0);
|
|
1573
|
+
}
|
|
1574
|
+
setupPageIntersectionObserver() {
|
|
1575
|
+
const targetWindow = this.document.defaultView;
|
|
1576
|
+
const container = this.viewerBody()?.nativeElement;
|
|
1577
|
+
if (!targetWindow || !container || !('IntersectionObserver' in targetWindow)) {
|
|
1578
|
+
return false;
|
|
1579
|
+
}
|
|
1580
|
+
const pages = Array.from(container.querySelectorAll('[data-ngs-pdf-page]'));
|
|
1581
|
+
if (pages.length === 0) {
|
|
1582
|
+
return false;
|
|
1583
|
+
}
|
|
1584
|
+
this.pageIntersectionObserver?.disconnect();
|
|
1585
|
+
this.visiblePageRatios.clear();
|
|
1586
|
+
this.pageIntersectionObserver = new targetWindow.IntersectionObserver((entries) => {
|
|
1587
|
+
if (this.isProgrammaticScrollActive()) {
|
|
1588
|
+
this.scheduleCurrentVisiblePagesRender();
|
|
1589
|
+
return;
|
|
1590
|
+
}
|
|
1591
|
+
for (const entry of entries) {
|
|
1592
|
+
const pageNumber = Number(entry.target.getAttribute('data-ngs-pdf-page'));
|
|
1593
|
+
if (!Number.isFinite(pageNumber)) {
|
|
1594
|
+
continue;
|
|
1595
|
+
}
|
|
1596
|
+
this.visiblePageRatios.set(pageNumber, entry.intersectionRatio);
|
|
1597
|
+
}
|
|
1598
|
+
this.syncActivePageFromIntersections();
|
|
1599
|
+
this.scheduleCurrentVisiblePagesRender();
|
|
1600
|
+
}, {
|
|
1601
|
+
root: container,
|
|
1602
|
+
threshold: [0, 0.1, 0.25, 0.5, 0.75, 1],
|
|
1603
|
+
});
|
|
1604
|
+
for (const page of pages) {
|
|
1605
|
+
this.pageIntersectionObserver.observe(page);
|
|
1606
|
+
}
|
|
1607
|
+
return true;
|
|
1608
|
+
}
|
|
1609
|
+
getFitScale(mode) {
|
|
1610
|
+
const container = this.viewerBody()?.nativeElement;
|
|
1611
|
+
const page = this.pdfDocument?.pages[this.clamp(this.activePage(), 1, Math.max(this.pageCount(), 1)) - 1];
|
|
1612
|
+
if (!container || !page) {
|
|
1613
|
+
return null;
|
|
1614
|
+
}
|
|
1615
|
+
const pageSize = this.getPageBaseSize(page);
|
|
1616
|
+
const pagesElement = container.querySelector('.pdf-viewer-pages');
|
|
1617
|
+
const computedStyle = pagesElement && this.document.defaultView
|
|
1618
|
+
? this.document.defaultView.getComputedStyle(pagesElement)
|
|
1619
|
+
: null;
|
|
1620
|
+
const horizontalPadding = computedStyle
|
|
1621
|
+
? this.parseCssPixel(computedStyle.paddingLeft) + this.parseCssPixel(computedStyle.paddingRight)
|
|
1622
|
+
: 0;
|
|
1623
|
+
const verticalPadding = computedStyle
|
|
1624
|
+
? this.parseCssPixel(computedStyle.paddingTop) + this.parseCssPixel(computedStyle.paddingBottom)
|
|
1625
|
+
: 0;
|
|
1626
|
+
const fitAllowance = 1;
|
|
1627
|
+
const availableWidth = Math.max(1, container.clientWidth - horizontalPadding - fitAllowance);
|
|
1628
|
+
const availableHeight = Math.max(1, container.clientHeight - verticalPadding - fitAllowance);
|
|
1629
|
+
const widthScale = availableWidth / pageSize.width;
|
|
1630
|
+
if (mode === 'fit-width') {
|
|
1631
|
+
return this.floorFitZoom(widthScale);
|
|
1632
|
+
}
|
|
1633
|
+
return this.floorFitZoom(Math.min(widthScale, availableHeight / pageSize.height));
|
|
1634
|
+
}
|
|
1635
|
+
getScaleBounds() {
|
|
1636
|
+
const min = this.sanitizePositiveNumber(this.minScale(), 0.2);
|
|
1637
|
+
const max = this.sanitizePositiveNumber(this.maxScale(), 60);
|
|
1638
|
+
return min <= max ? { min, max } : { min: max, max: min };
|
|
1639
|
+
}
|
|
1640
|
+
sanitizeZoomStep() {
|
|
1641
|
+
return this.sanitizePositiveNumber(this.zoomStep(), 0.1);
|
|
1642
|
+
}
|
|
1643
|
+
sanitizePositiveNumber(value, fallback) {
|
|
1644
|
+
return Number.isFinite(value) && value > 0 ? value : fallback;
|
|
1645
|
+
}
|
|
1646
|
+
getPageDisplaySize(page, scale) {
|
|
1647
|
+
const pageSize = this.getPageBaseSize(page);
|
|
1648
|
+
return {
|
|
1649
|
+
width: Math.max(1, Math.round(pageSize.width * scale)),
|
|
1650
|
+
height: Math.max(1, Math.round(pageSize.height * scale)),
|
|
1651
|
+
};
|
|
1652
|
+
}
|
|
1653
|
+
getPageBaseSize(page) {
|
|
1654
|
+
const isRotatedSideways = page.rotation === 1 || page.rotation === 3;
|
|
1655
|
+
const width = isRotatedSideways ? page.size.height : page.size.width;
|
|
1656
|
+
const height = isRotatedSideways ? page.size.width : page.size.height;
|
|
1657
|
+
return {
|
|
1658
|
+
width: Math.max(1, width),
|
|
1659
|
+
height: Math.max(1, height),
|
|
1660
|
+
};
|
|
1661
|
+
}
|
|
1662
|
+
scrollToPage(pageNumber) {
|
|
1663
|
+
if (!this.renderAll()) {
|
|
1664
|
+
return;
|
|
1665
|
+
}
|
|
1666
|
+
const container = this.viewerBody()?.nativeElement;
|
|
1667
|
+
const target = container?.querySelector(`[data-ngs-pdf-page="${pageNumber}"]`);
|
|
1668
|
+
if (!container || !target) {
|
|
1669
|
+
return;
|
|
1670
|
+
}
|
|
1671
|
+
const nextScrollTop = this.getPageScrollTop(container, target);
|
|
1672
|
+
const scrollDistance = Math.abs(container.scrollTop - nextScrollTop);
|
|
1673
|
+
this.startProgrammaticScrollLock(pageNumber, scrollDistance);
|
|
1674
|
+
container.scrollTo({
|
|
1675
|
+
top: nextScrollTop,
|
|
1676
|
+
left: this.getPageScrollLeft(container, target),
|
|
1677
|
+
behavior: 'smooth',
|
|
1678
|
+
});
|
|
1679
|
+
}
|
|
1680
|
+
syncActivePageFromViewport() {
|
|
1681
|
+
const pageNumber = this.findActivePageInViewport();
|
|
1682
|
+
this.activatePageFromScroll(pageNumber);
|
|
1683
|
+
}
|
|
1684
|
+
syncActivePageFromIntersections() {
|
|
1685
|
+
let activePage = null;
|
|
1686
|
+
let activeRatio = 0;
|
|
1687
|
+
for (const [pageNumber, ratio] of this.visiblePageRatios) {
|
|
1688
|
+
if (ratio > activeRatio) {
|
|
1689
|
+
activeRatio = ratio;
|
|
1690
|
+
activePage = pageNumber;
|
|
1691
|
+
}
|
|
1692
|
+
}
|
|
1693
|
+
if (!activePage) {
|
|
1694
|
+
activePage = this.findActivePageInViewport();
|
|
1695
|
+
}
|
|
1696
|
+
this.activatePageFromScroll(activePage);
|
|
1697
|
+
}
|
|
1698
|
+
activatePageFromScroll(pageNumber) {
|
|
1699
|
+
if (this.isProgrammaticScrollActive()) {
|
|
1700
|
+
return;
|
|
1701
|
+
}
|
|
1702
|
+
if (!pageNumber || pageNumber === this.activePage()) {
|
|
1703
|
+
return;
|
|
1704
|
+
}
|
|
1705
|
+
this.activePage.set(pageNumber);
|
|
1706
|
+
this.pageChanged.emit(pageNumber);
|
|
1707
|
+
this.scrollPageListToPage(pageNumber);
|
|
1708
|
+
this.scheduleCurrentVisiblePagesRender();
|
|
1709
|
+
}
|
|
1710
|
+
findActivePageInViewport() {
|
|
1711
|
+
const container = this.viewerBody()?.nativeElement;
|
|
1712
|
+
if (!container) {
|
|
1713
|
+
return null;
|
|
1714
|
+
}
|
|
1715
|
+
const containerRect = container.getBoundingClientRect();
|
|
1716
|
+
const viewportCenter = containerRect.top + containerRect.height / 2;
|
|
1717
|
+
const pages = Array.from(container.querySelectorAll('[data-ngs-pdf-page]'));
|
|
1718
|
+
let closestPage = null;
|
|
1719
|
+
let closestDistance = Number.POSITIVE_INFINITY;
|
|
1720
|
+
let mostVisiblePage = null;
|
|
1721
|
+
let mostVisibleHeight = 0;
|
|
1722
|
+
for (const page of pages) {
|
|
1723
|
+
const rect = page.getBoundingClientRect();
|
|
1724
|
+
const pageNumber = Number(page.dataset['ngsPdfPage']);
|
|
1725
|
+
if (!Number.isFinite(pageNumber)) {
|
|
1726
|
+
continue;
|
|
1727
|
+
}
|
|
1728
|
+
if (rect.top <= viewportCenter && rect.bottom >= viewportCenter) {
|
|
1729
|
+
return pageNumber;
|
|
1730
|
+
}
|
|
1731
|
+
const visibleHeight = Math.max(0, Math.min(rect.bottom, containerRect.bottom) - Math.max(rect.top, containerRect.top));
|
|
1732
|
+
if (visibleHeight > mostVisibleHeight) {
|
|
1733
|
+
mostVisibleHeight = visibleHeight;
|
|
1734
|
+
mostVisiblePage = pageNumber;
|
|
1735
|
+
}
|
|
1736
|
+
const distance = Math.min(Math.abs(rect.top - viewportCenter), Math.abs(rect.bottom - viewportCenter));
|
|
1737
|
+
if (distance < closestDistance) {
|
|
1738
|
+
closestDistance = distance;
|
|
1739
|
+
closestPage = pageNumber;
|
|
1740
|
+
}
|
|
1741
|
+
}
|
|
1742
|
+
return mostVisiblePage ?? closestPage;
|
|
1743
|
+
}
|
|
1744
|
+
scrollPageListToPage(pageNumber) {
|
|
1745
|
+
const container = this.pageList()?.nativeElement;
|
|
1746
|
+
const target = container?.querySelector(`[data-ngs-pdf-page-button="${pageNumber}"]`);
|
|
1747
|
+
if (!container || !target) {
|
|
1748
|
+
return;
|
|
1749
|
+
}
|
|
1750
|
+
const containerRect = container.getBoundingClientRect();
|
|
1751
|
+
const targetRect = target.getBoundingClientRect();
|
|
1752
|
+
const isAbove = targetRect.top < containerRect.top;
|
|
1753
|
+
const isBelow = targetRect.bottom > containerRect.bottom;
|
|
1754
|
+
if (!isAbove && !isBelow) {
|
|
1755
|
+
return;
|
|
1756
|
+
}
|
|
1757
|
+
container.scrollTo({
|
|
1758
|
+
top: this.getElementScrollTop(container, target, 'center'),
|
|
1759
|
+
behavior: 'smooth',
|
|
1760
|
+
});
|
|
1761
|
+
}
|
|
1762
|
+
getPageScrollTop(container, target) {
|
|
1763
|
+
return this.getElementScrollTop(container, target, 'start');
|
|
1764
|
+
}
|
|
1765
|
+
getPageScrollLeft(container, target) {
|
|
1766
|
+
const containerRect = container.getBoundingClientRect();
|
|
1767
|
+
const targetRect = target.getBoundingClientRect();
|
|
1768
|
+
const targetLeft = container.scrollLeft + targetRect.left - containerRect.left;
|
|
1769
|
+
return this.clamp(targetLeft, 0, Math.max(0, container.scrollWidth - container.clientWidth));
|
|
1770
|
+
}
|
|
1771
|
+
getElementScrollTop(container, target, align) {
|
|
1772
|
+
const containerRect = container.getBoundingClientRect();
|
|
1773
|
+
const targetRect = target.getBoundingClientRect();
|
|
1774
|
+
const targetTop = container.scrollTop + targetRect.top - containerRect.top;
|
|
1775
|
+
const alignedTop = align === 'center'
|
|
1776
|
+
? targetTop - container.clientHeight / 2 + targetRect.height / 2
|
|
1777
|
+
: targetTop;
|
|
1778
|
+
return this.clamp(alignedTop, 0, Math.max(0, container.scrollHeight - container.clientHeight));
|
|
1779
|
+
}
|
|
1780
|
+
getProgrammaticScrollTimeout(distance) {
|
|
1781
|
+
return this.clamp(this.programmaticScrollMinDuration + distance * 0.45, this.programmaticScrollMinDuration, this.programmaticScrollMaxDuration);
|
|
1782
|
+
}
|
|
1783
|
+
startProgrammaticScrollLock(pageNumber, distance) {
|
|
1784
|
+
const targetWindow = this.document.defaultView;
|
|
1785
|
+
if (!targetWindow) {
|
|
1786
|
+
return;
|
|
1787
|
+
}
|
|
1788
|
+
this.clearProgrammaticScrollLock();
|
|
1789
|
+
this.programmaticScrollTargetPage = pageNumber;
|
|
1790
|
+
this.programmaticScrollTimeout = targetWindow.setTimeout(() => {
|
|
1791
|
+
this.programmaticScrollTimeout = null;
|
|
1792
|
+
this.completeProgrammaticScroll();
|
|
1793
|
+
}, this.getProgrammaticScrollTimeout(distance));
|
|
1794
|
+
}
|
|
1795
|
+
isProgrammaticScrollActive() {
|
|
1796
|
+
return this.programmaticScrollTargetPage !== null;
|
|
1797
|
+
}
|
|
1798
|
+
completeProgrammaticScrollIfSettled() {
|
|
1799
|
+
const pageNumber = this.programmaticScrollTargetPage;
|
|
1800
|
+
if (pageNumber === null) {
|
|
1801
|
+
return;
|
|
1802
|
+
}
|
|
1803
|
+
const container = this.viewerBody()?.nativeElement;
|
|
1804
|
+
const target = container?.querySelector(`[data-ngs-pdf-page="${pageNumber}"]`);
|
|
1805
|
+
if (!container || !target) {
|
|
1806
|
+
this.completeProgrammaticScroll();
|
|
1807
|
+
return;
|
|
1808
|
+
}
|
|
1809
|
+
if (Math.abs(container.scrollTop - this.getPageScrollTop(container, target)) <= 2) {
|
|
1810
|
+
this.completeProgrammaticScroll();
|
|
1811
|
+
}
|
|
1812
|
+
}
|
|
1813
|
+
completeProgrammaticScroll() {
|
|
1814
|
+
const pageNumber = this.programmaticScrollTargetPage;
|
|
1815
|
+
this.clearProgrammaticScrollLock();
|
|
1816
|
+
if (pageNumber === null) {
|
|
1817
|
+
return;
|
|
1818
|
+
}
|
|
1819
|
+
if (pageNumber !== this.activePage()) {
|
|
1820
|
+
this.activePage.set(pageNumber);
|
|
1821
|
+
this.pageChanged.emit(pageNumber);
|
|
1822
|
+
}
|
|
1823
|
+
this.scrollPageListToPage(pageNumber);
|
|
1824
|
+
}
|
|
1825
|
+
clearProgrammaticScrollLock() {
|
|
1826
|
+
if (this.programmaticScrollTimeout !== null) {
|
|
1827
|
+
this.document.defaultView?.clearTimeout(this.programmaticScrollTimeout);
|
|
1828
|
+
this.programmaticScrollTimeout = null;
|
|
1829
|
+
}
|
|
1830
|
+
this.programmaticScrollTargetPage = null;
|
|
1831
|
+
}
|
|
1832
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.4", ngImport: i0, type: PdfViewer, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
1833
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.4", type: PdfViewer, isStandalone: true, selector: "ngs-pdf-viewer", inputs: { src: { classPropertyName: "src", publicName: "src", isSignal: true, isRequired: false, transformFunction: null }, documentName: { classPropertyName: "documentName", publicName: "documentName", isSignal: true, isRequired: false, transformFunction: null }, wasmUrl: { classPropertyName: "wasmUrl", publicName: "wasmUrl", isSignal: true, isRequired: false, transformFunction: null }, page: { classPropertyName: "page", publicName: "page", isSignal: true, isRequired: false, transformFunction: null }, scale: { classPropertyName: "scale", publicName: "scale", isSignal: true, isRequired: false, transformFunction: null }, minScale: { classPropertyName: "minScale", publicName: "minScale", isSignal: true, isRequired: false, transformFunction: null }, maxScale: { classPropertyName: "maxScale", publicName: "maxScale", isSignal: true, isRequired: false, transformFunction: null }, zoomStep: { classPropertyName: "zoomStep", publicName: "zoomStep", isSignal: true, isRequired: false, transformFunction: null }, maxRenderPixels: { classPropertyName: "maxRenderPixels", publicName: "maxRenderPixels", isSignal: true, isRequired: false, transformFunction: null }, maxRenderDimension: { classPropertyName: "maxRenderDimension", publicName: "maxRenderDimension", isSignal: true, isRequired: false, transformFunction: null }, renderAll: { classPropertyName: "renderAll", publicName: "renderAll", isSignal: true, isRequired: false, transformFunction: null }, showToolbar: { classPropertyName: "showToolbar", publicName: "showToolbar", isSignal: true, isRequired: false, transformFunction: null }, showPageList: { classPropertyName: "showPageList", publicName: "showPageList", isSignal: true, isRequired: false, transformFunction: null }, showSearchPanel: { classPropertyName: "showSearchPanel", publicName: "showSearchPanel", isSignal: true, isRequired: false, transformFunction: null }, showAnnotationsPanel: { classPropertyName: "showAnnotationsPanel", publicName: "showAnnotationsPanel", isSignal: true, isRequired: false, transformFunction: null }, annotations: { classPropertyName: "annotations", publicName: "annotations", isSignal: true, isRequired: false, transformFunction: null }, annotationsDataSource: { classPropertyName: "annotationsDataSource", publicName: "annotationsDataSource", isSignal: true, isRequired: false, transformFunction: null }, annotationTypeProperty: { classPropertyName: "annotationTypeProperty", publicName: "annotationTypeProperty", isSignal: true, isRequired: false, transformFunction: null }, searchQuery: { classPropertyName: "searchQuery", publicName: "searchQuery", isSignal: true, isRequired: false, transformFunction: null }, withAnnotations: { classPropertyName: "withAnnotations", publicName: "withAnnotations", isSignal: true, isRequired: false, transformFunction: null }, withForms: { classPropertyName: "withForms", publicName: "withForms", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { loaded: "loaded", pageChanged: "pageChanged", pageRendered: "pageRendered", error: "error" }, host: { properties: { "class.is-loading": "isLoading()", "class.has-toolbar": "showToolbar()", "class.has-page-list": "isPageListVisible()", "class.has-error": "errorState()" }, classAttribute: "ngs-pdf-viewer not-prose" }, queries: [{ propertyName: "annotationDefs", predicate: PdfViewerAnnotationDef, descendants: true, isSignal: true }], viewQueries: [{ propertyName: "viewerBody", first: true, predicate: ["viewerBody"], descendants: true, isSignal: true }, { propertyName: "pageList", first: true, predicate: ["pageList"], descendants: true, isSignal: true }], exportAs: ["ngsPdfViewer"], ngImport: i0, template: "<ngs-panel class=\"pdf-viewer-panel h-full overflow-hidden rounded-lg border border-border bg-surface-container-low text-on-surface\">\n @if (showToolbar()) {\n <ngs-panel-header class=\"min-w-0 border-b border-border bg-surface\" flex>\n <ngs-toolbar class=\"pdf-viewer-toolbar flex h-14 w-full min-w-0 items-center gap-4 overflow-visible px-2 text-on-surface-variant\" aria-label=\"PDF viewer controls\">\n <ngs-toolbar-item class=\"pdf-viewer-toolbar__start flex min-w-0 flex-none items-center gap-2\">\n @if (showPageList()) {\n <button\n class=\"pdf-viewer-toolbar__button size-10 text-on-surface-variant hover:bg-surface-container-low hover:text-on-surface\"\n ngsIconButton\n type=\"button\"\n [attr.aria-label]=\"pageListVisible() ? 'Hide pages panel' : 'Show pages panel'\"\n [attr.aria-pressed]=\"pageListVisible()\"\n (click)=\"togglePageList()\">\n <ngs-icon\n class=\"text-2xl\"\n [name]=\"pageListVisible() ? 'fluent:panel-left-contract-24-regular' : 'fluent:panel-left-expand-24-regular'\" />\n </button>\n }\n <ngs-divider vertical />\n </ngs-toolbar-item>\n <ngs-toolbar-title class=\"min-w-0 flex-1 truncate text-sm! font-semibold! text-on-surface-variant\" [title]=\"displayDocumentName()\">\n {{ displayDocumentName() }}\n </ngs-toolbar-title>\n\n <ngs-toolbar-item class=\"pdf-viewer-toolbar__center flex flex-none items-center gap-5\">\n <div class=\"pdf-viewer-toolbar__cluster flex min-w-0 items-center gap-2\" aria-label=\"Zoom controls\">\n <button\n class=\"pdf-viewer-toolbar__button size-10 text-on-surface-variant hover:bg-surface-container-low hover:text-on-surface\"\n ngsIconButton\n type=\"button\"\n aria-label=\"Zoom out\"\n [disabled]=\"!canZoomOut()\"\n (click)=\"zoomOut()\">\n <ngs-icon class=\"text-2xl\" name=\"fluent:zoom-out-24-regular\" />\n </button>\n <button\n class=\"pdf-viewer-toolbar__zoom min-w-16 cursor-pointer border-0 bg-transparent p-0 text-center text-sm font-bold text-on-surface hover:text-primary\"\n type=\"button\"\n aria-label=\"Zoom presets\"\n [ngsMenuTriggerFor]=\"zoomMenu\">\n {{ zoomLabel() }}\n </button>\n <button\n class=\"pdf-viewer-toolbar__button size-10 text-on-surface-variant hover:bg-surface-container-low hover:text-on-surface\"\n ngsIconButton\n type=\"button\"\n aria-label=\"Zoom in\"\n [disabled]=\"!canZoomIn()\"\n (click)=\"zoomIn()\">\n <ngs-icon class=\"text-2xl\" name=\"fluent:zoom-in-24-regular\" />\n </button>\n </div>\n\n <ngs-divider vertical />\n\n <div class=\"pdf-viewer-toolbar__cluster flex min-w-0 items-center gap-2\" aria-label=\"Page navigation\">\n <button\n class=\"pdf-viewer-toolbar__button size-10 text-on-surface-variant hover:bg-surface-container-low hover:text-on-surface\"\n ngsIconButton\n type=\"button\"\n aria-label=\"Previous page\"\n [disabled]=\"!canGoPrevious()\"\n (click)=\"previousPage()\">\n <ngs-icon class=\"text-2xl\" name=\"fluent:chevron-left-24-regular\" />\n </button>\n <span class=\"pdf-viewer-toolbar__page inline-flex h-9 min-w-24 items-center justify-center gap-2 rounded bg-surface-container-low text-sm font-bold text-on-surface\">\n <span>{{ activePage() }}</span>\n <span>/</span>\n <span>{{ pageCount() || 1 }}</span>\n </span>\n <button\n class=\"pdf-viewer-toolbar__button size-10 text-on-surface-variant hover:bg-surface-container-low hover:text-on-surface\"\n ngsIconButton\n type=\"button\"\n aria-label=\"Next page\"\n [disabled]=\"!canGoNext()\"\n (click)=\"nextPage()\">\n <ngs-icon class=\"text-2xl\" name=\"fluent:chevron-right-24-regular\" />\n </button>\n </div>\n </ngs-toolbar-item>\n\n <ngs-toolbar-spacer />\n\n <ngs-toolbar-item class=\"pdf-viewer-toolbar__end flex min-w-0 flex-1 items-center justify-end gap-2\" aria-label=\"PDF tools\">\n @if (showSearchPanel()) {\n <button\n class=\"pdf-viewer-toolbar__button size-10 text-on-surface-variant hover:bg-surface-container-low hover:text-on-surface\"\n ngsIconButton\n type=\"button\"\n [attr.aria-pressed]=\"isSearchPanelVisible()\"\n aria-label=\"Search\"\n (click)=\"toggleSearchPanel()\">\n <ngs-icon class=\"text-2xl\" name=\"fluent:search-24-regular\" />\n </button>\n }\n <button\n class=\"pdf-viewer-toolbar__button size-10 text-on-surface-variant hover:bg-surface-container-low hover:text-on-surface\"\n ngsIconButton\n type=\"button\"\n aria-label=\"PDF viewer settings\"\n [ngsMenuTriggerFor]=\"settingsMenu\">\n <ngs-icon class=\"text-2xl\" name=\"fluent:settings-24-regular\" />\n </button>\n @if (showAnnotationsPanel()) {\n <button\n class=\"pdf-viewer-toolbar__button size-10 text-on-surface-variant hover:bg-surface-container-low hover:text-on-surface\"\n ngsIconButton\n type=\"button\"\n [attr.aria-pressed]=\"isAnnotationsPanelVisible()\"\n aria-label=\"Toggle annotations panel\"\n (click)=\"toggleAnnotationsPanel()\">\n <ngs-icon class=\"text-2xl\" name=\"fluent:text-bullet-list-square-edit-24-regular\" />\n </button>\n }\n </ngs-toolbar-item>\n </ngs-toolbar>\n\n <ngs-menu #zoomMenu=\"ngsMenu\">\n @for (zoomPreset of zoomPresets; track zoomPreset) {\n <button\n ngs-menu-item\n type=\"button\"\n [selected]=\"isZoomPresetSelected(zoomPreset)\"\n (click)=\"setZoomPreset(zoomPreset)\">\n <span>{{ zoomPreset * 100 }}%</span>\n </button>\n }\n\n <ngs-menu-divider />\n <button\n ngs-menu-item\n type=\"button\"\n [selected]=\"zoomMode() === 'fit-page'\"\n (click)=\"fitToPage()\">\n <ngs-icon name=\"fluent:resize-large-24-regular\" />\n <span>Fit to Page</span>\n </button>\n <button\n ngs-menu-item\n type=\"button\"\n [selected]=\"zoomMode() === 'fit-width'\"\n (click)=\"fitToWidth()\">\n <ngs-icon name=\"fluent:resize-24-regular\" />\n <span>Fit to Width</span>\n </button>\n </ngs-menu>\n\n <ngs-menu #settingsMenu=\"ngsMenu\" xPosition=\"before\">\n <ngs-menu-heading>SPREAD MODE</ngs-menu-heading>\n <button\n ngs-menu-item\n type=\"button\"\n [selected]=\"spreadMode() === 'single'\"\n (click)=\"setSpreadMode('single')\">\n <ngs-icon name=\"fluent:document-24-regular\" />\n <span>Single Page</span>\n </button>\n <button\n ngs-menu-item\n type=\"button\"\n [selected]=\"spreadMode() === 'two-odd'\"\n (click)=\"setSpreadMode('two-odd')\">\n <ngs-icon name=\"fluent:book-open-24-regular\" />\n <span>Two Page (Odd)</span>\n </button>\n <button\n ngs-menu-item\n type=\"button\"\n [selected]=\"spreadMode() === 'two-even'\"\n (click)=\"setSpreadMode('two-even')\">\n <ngs-icon name=\"fluent:document-text-24-regular\" />\n <span>Two Page (Even)</span>\n </button>\n\n <ngs-menu-divider />\n <ngs-menu-heading>SCROLL LAYOUT</ngs-menu-heading>\n <button\n ngs-menu-item\n type=\"button\"\n [selected]=\"scrollLayout() === 'vertical'\"\n (click)=\"setScrollLayout('vertical')\">\n <ngs-icon name=\"fluent:arrow-sort-24-regular\" />\n <span>Vertical</span>\n </button>\n <button\n ngs-menu-item\n type=\"button\"\n [selected]=\"scrollLayout() === 'horizontal'\"\n (click)=\"setScrollLayout('horizontal')\">\n <ngs-icon name=\"fluent:arrow-swap-24-regular\" />\n <span>Horizontal</span>\n </button>\n\n <ngs-menu-divider />\n <ngs-menu-heading>PAGE ROTATION</ngs-menu-heading>\n <button ngs-menu-item type=\"button\" (click)=\"rotateClockwise()\">\n <ngs-icon name=\"fluent:arrow-clockwise-24-regular\" />\n <span>Rotate Clockwise</span>\n </button>\n <button ngs-menu-item type=\"button\" (click)=\"rotateCounterClockwise()\">\n <ngs-icon name=\"fluent:arrow-counterclockwise-24-regular\" />\n <span>Rotate Counter-Clockwise</span>\n </button>\n\n <ngs-menu-divider />\n <button ngs-menu-item type=\"button\" (click)=\"toggleFullscreen()\">\n <ngs-icon name=\"fluent:arrow-maximize-24-regular\" />\n <span>Fullscreen</span>\n </button>\n </ngs-menu>\n </ngs-panel-header>\n }\n\n @if (isPageListVisible()) {\n <ngs-panel-sidebar class=\"w-[var(--ngs-pdf-viewer-sidebar-width)] min-w-[var(--ngs-pdf-viewer-sidebar-width)] border-r border-border bg-surface\">\n <div class=\"pdf-viewer-sidebar flex h-full min-h-0 flex-col gap-2 overflow-auto p-3\" #pageList>\n <div class=\"pdf-viewer-sidebar__title px-1 text-xs font-semibold text-on-surface-variant\">Pages</div>\n <div class=\"pdf-viewer-sidebar__pages flex min-h-0 flex-col gap-2\" aria-label=\"PDF pages\">\n @for (pageItem of pageItems(); track pageItem.pageNumber) {\n <button\n class=\"pdf-viewer-sidebar__page group flex w-full cursor-pointer flex-col items-stretch gap-1 rounded border border-transparent bg-transparent p-1 text-left text-on-surface-variant focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-primary [&.is-active]:text-on-surface\"\n type=\"button\"\n [class.is-active]=\"pageItem.pageNumber === activePage()\"\n [attr.aria-current]=\"pageItem.pageNumber === activePage() ? 'page' : null\"\n [attr.aria-label]=\"'Open page ' + pageItem.pageNumber\"\n [attr.data-ngs-pdf-page-button]=\"pageItem.pageNumber\"\n (click)=\"setPage(pageItem.pageNumber)\">\n <span class=\"pdf-viewer-sidebar__thumb flex aspect-[3/4] w-full items-center justify-center overflow-hidden rounded border border-border bg-surface shadow-xs group-hover:border-border group-[.is-active]:border-border group-[.is-active]:bg-primary-container group-[.is-active]:shadow-xs group-[.is-active]:ring-2 group-[.is-active]:ring-primary-container\" aria-hidden=\"true\">\n @if (pageItem.thumbnail; as thumbnail) {\n <img\n class=\"block h-full w-full select-none object-contain\"\n [src]=\"thumbnail.url\"\n [attr.width]=\"thumbnail.width\"\n [attr.height]=\"thumbnail.height\"\n alt=\"\"\n draggable=\"false\" />\n } @else {\n <ngs-image-placeholder class=\"h-full w-full\" />\n }\n </span>\n <span class=\"pdf-viewer-sidebar__label block truncate text-center text-xs font-medium leading-tight\">Page {{ pageItem.pageNumber }}</span>\n </button>\n }\n </div>\n </div>\n </ngs-panel-sidebar>\n }\n\n <ngs-panel-content class=\"min-h-0 min-w-0 overflow-hidden bg-surface-container-low\">\n <div class=\"pdf-viewer-body absolute inset-0 overflow-auto\" #viewerBody (scroll)=\"onViewerScroll()\" (wheel)=\"onViewerWheel($event)\">\n <ngs-block-loader [loading]=\"isLoading()\">Loading PDF...</ngs-block-loader>\n @if (errorState()) {\n <div class=\"pdf-viewer-state pdf-viewer-state--error absolute inset-0 flex flex-col items-center justify-center gap-3 p-6 text-center text-danger\">\n <ngs-icon class=\"text-3xl\" name=\"fluent:error-circle-24-regular\" />\n <span>PDF could not be loaded.</span>\n </div>\n } @else if (!src()) {\n <div class=\"pdf-viewer-state absolute inset-0 flex flex-col items-center justify-center gap-3 p-6 text-center text-on-surface-variant\">\n <ngs-icon class=\"text-3xl\" name=\"fluent:document-pdf-24-regular\" />\n <span>No PDF selected.</span>\n </div>\n } @else {\n <div class=\"pdf-viewer-pages grid min-h-full min-w-full w-max content-start grid-flow-row box-border justify-items-center gap-6 p-6\">\n @for (pdfPage of renderedPages(); track pdfPage.pageNumber) {\n <article\n class=\"pdf-viewer-page w-max max-w-none\"\n [attr.aria-label]=\"'PDF page ' + pdfPage.pageNumber\"\n [attr.data-ngs-pdf-page]=\"pdfPage.pageNumber\">\n <div\n class=\"pdf-viewer-page__surface relative cursor-text overflow-hidden rounded bg-surface shadow-md select-none touch-none\"\n [style.width.px]=\"pdfPage.width\"\n [style.height.px]=\"pdfPage.height\"\n (pointerdown)=\"startTextSelection($event, pdfPage)\"\n (pointermove)=\"updateTextSelection($event, pdfPage)\"\n (pointerup)=\"finishTextSelection($event, pdfPage)\"\n (pointercancel)=\"cancelTextSelection()\"\n (mousedown)=\"startTextSelection($event, pdfPage)\"\n (mousemove)=\"updateTextSelection($event, pdfPage)\"\n (mouseup)=\"finishTextSelection($event, pdfPage)\">\n @if (pdfPage.url; as pageUrl) {\n <img\n class=\"block h-full w-full max-w-none select-none pointer-events-none\"\n [src]=\"pageUrl\"\n [alt]=\"'PDF page ' + pdfPage.pageNumber\"\n [attr.width]=\"pdfPage.width\"\n [attr.height]=\"pdfPage.height\"\n draggable=\"false\" />\n } @else {\n <div class=\"pdf-viewer-page__placeholder pointer-events-none absolute inset-0 flex items-center justify-center bg-surface\" aria-hidden=\"true\">\n @if (pdfPage.isRendering) {\n <span class=\"size-8 animate-spin rounded-full border-2 border-border border-t-primary\"></span>\n }\n </div>\n }\n @if (selectionRectsForPage(pdfPage.pageNumber); as selectionRects) {\n @if (selectionRects.length > 0) {\n <div class=\"pdf-viewer-page__selection-layer pointer-events-none absolute inset-0 z-[1] isolate overflow-hidden mix-blend-multiply\" aria-hidden=\"true\">\n @for (selectionRect of selectionRects; track selectionRect.left + '-' + selectionRect.top + '-' + $index) {\n <div\n class=\"pdf-viewer-page__selection-rect pointer-events-none absolute bg-primary/30\"\n [style.left.px]=\"selectionRect.left\"\n [style.top.px]=\"selectionRect.top\"\n [style.width.px]=\"selectionRect.width\"\n [style.height.px]=\"selectionRect.height\"></div>\n }\n </div>\n }\n }\n </div>\n </article>\n }\n </div>\n }\n </div>\n </ngs-panel-content>\n\n @if (isSearchPanelVisible() || isAnnotationsPanelVisible()) {\n <ngs-panel-aside class=\"w-[var(--ngs-pdf-viewer-aside-width)] min-w-[var(--ngs-pdf-viewer-aside-width)] border-l border-border bg-surface\">\n @if (isSearchPanelVisible()) {\n <ngs-pdf-viewer-search\n [query]=\"activeSearchQuery()\"\n [results]=\"pdfSearchResults()\"\n (closed)=\"closeAsidePanel()\"\n (searchChanged)=\"updatePdfSearch($event)\"\n (resultSelected)=\"selectSearchResult($event)\" />\n } @else {\n <ngs-pdf-viewer-annotations\n [annotations]=\"annotationItems()\"\n [annotationDefs]=\"annotationDefs()\"\n [annotationTypeProperty]=\"annotationTypeProperty()\"\n (closed)=\"closeAsidePanel()\"\n (pageSelected)=\"setPage($event)\" />\n }\n </ngs-panel-aside>\n }\n</ngs-panel>\n", styles: [":host{--ngs-pdf-viewer-height: 720px;--ngs-pdf-viewer-min-height: 720px;--ngs-pdf-viewer-sidebar-width: 152px;--ngs-pdf-viewer-aside-width: 360px;display:block;height:var(--ngs-pdf-viewer-height);min-height:var(--ngs-pdf-viewer-min-height);overflow:hidden}:host .pdf-viewer-page__selection-layer{forced-color-adjust:none}\n"], dependencies: [{ kind: "component", type: BlockLoader, selector: "ngs-block-loader", inputs: ["loading", "spinnerDiameter", "spinnerStrokeWidth"], exportAs: ["ngsBlockLoader"] }, { kind: "component", type: Button, selector: " button[ngsButton], button[ngsIconButton], a[ngsButton], a[ngsIconButton] ", inputs: ["ngsButton", "ngsIconButton", "loading", "disabled", "disabledInteractive", "disableRipple", "reverse", "fullWidth", "hideTextOnMobile"], exportAs: ["ngsButton"] }, { kind: "component", type: Divider, selector: "ngs-divider", inputs: ["vertical", "inset", "fixedHeight"], exportAs: ["ngsDivider"] }, { kind: "component", type: Icon, selector: "ngs-icon", inputs: ["name"], exportAs: ["ngsIcon"] }, { kind: "component", type: ImagePlaceholder, selector: "ngs-image-placeholder", exportAs: ["ngsImagePlaceholder"] }, { kind: "component", type: Menu, selector: "ngs-menu", inputs: ["role", "classList", "xPosition", "yPosition"], outputs: ["closed"], exportAs: ["ngsMenu"] }, { kind: "component", type: MenuDivider, selector: "ngs-menu-divider" }, { kind: "component", type: MenuHeading, selector: "ngs-menu-heading" }, { kind: "component", type: MenuItem, selector: "ngs-menu-item, [ngs-menu-item]", inputs: ["disabled", "role", "selected"], outputs: ["_triggered"], exportAs: ["ngsMenuItem"] }, { kind: "directive", type: MenuTrigger, selector: "[ngsMenuTriggerFor]", inputs: ["ngsMenuTriggerFor", "ngsMenuTriggerData", "ngsMenuDisabled", "xPosition", "yPosition", "ngsMenuTriggerRestoreFocus"], outputs: ["menuOpened", "menuClosed"], exportAs: ["ngsMenuTrigger"] }, { kind: "component", type: Panel, selector: "ngs-panel", inputs: ["absolute"], exportAs: ["ngsPanel"] }, { kind: "component", type: PanelAside, selector: "ngs-panel-aside" }, { kind: "component", type: PanelContent, selector: "ngs-panel-content", exportAs: ["ngsPanelContent"] }, { kind: "component", type: PanelHeader, selector: "ngs-panel-header", inputs: ["flex", "autoHeight"], exportAs: ["ngsPanelHeader"] }, { kind: "component", type: PanelSidebar, selector: "ngs-panel-sidebar" }, { kind: "component", type: Toolbar, selector: "ngs-toolbar", exportAs: ["ngsToolbar"] }, { kind: "component", type: ToolbarItem, selector: "ngs-toolbar-item", inputs: ["hidden"], outputs: ["hiddenChange"] }, { kind: "component", type: ToolbarSpacer, selector: "ngs-toolbar-spacer" }, { kind: "component", type: ToolbarTitle, selector: "ngs-toolbar-title" }, { kind: "component", type: PdfViewerAnnotations, selector: "ngs-pdf-viewer-annotations", inputs: ["annotations", "annotationDefs", "annotationTypeProperty"], outputs: ["closed", "pageSelected"] }, { kind: "component", type: PdfViewerSearch, selector: "ngs-pdf-viewer-search", inputs: ["results", "query"], outputs: ["closed", "resultSelected", "searchChanged"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
|
|
1834
|
+
}
|
|
1835
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.4", ngImport: i0, type: PdfViewer, decorators: [{
|
|
1836
|
+
type: Component,
|
|
1837
|
+
args: [{ selector: 'ngs-pdf-viewer', exportAs: 'ngsPdfViewer', imports: [
|
|
1838
|
+
BlockLoader,
|
|
1839
|
+
Button,
|
|
1840
|
+
Divider,
|
|
1841
|
+
Icon,
|
|
1842
|
+
ImagePlaceholder,
|
|
1843
|
+
Menu,
|
|
1844
|
+
MenuDivider,
|
|
1845
|
+
MenuHeading,
|
|
1846
|
+
MenuItem,
|
|
1847
|
+
MenuTrigger,
|
|
1848
|
+
Panel,
|
|
1849
|
+
PanelAside,
|
|
1850
|
+
PanelContent,
|
|
1851
|
+
PanelHeader,
|
|
1852
|
+
PanelSidebar,
|
|
1853
|
+
Toolbar,
|
|
1854
|
+
ToolbarItem,
|
|
1855
|
+
ToolbarSpacer,
|
|
1856
|
+
ToolbarTitle,
|
|
1857
|
+
PdfViewerAnnotations,
|
|
1858
|
+
PdfViewerSearch,
|
|
1859
|
+
], changeDetection: ChangeDetectionStrategy.OnPush, host: {
|
|
1860
|
+
class: 'ngs-pdf-viewer not-prose',
|
|
1861
|
+
'[class.is-loading]': 'isLoading()',
|
|
1862
|
+
'[class.has-toolbar]': 'showToolbar()',
|
|
1863
|
+
'[class.has-page-list]': 'isPageListVisible()',
|
|
1864
|
+
'[class.has-error]': 'errorState()',
|
|
1865
|
+
}, template: "<ngs-panel class=\"pdf-viewer-panel h-full overflow-hidden rounded-lg border border-border bg-surface-container-low text-on-surface\">\n @if (showToolbar()) {\n <ngs-panel-header class=\"min-w-0 border-b border-border bg-surface\" flex>\n <ngs-toolbar class=\"pdf-viewer-toolbar flex h-14 w-full min-w-0 items-center gap-4 overflow-visible px-2 text-on-surface-variant\" aria-label=\"PDF viewer controls\">\n <ngs-toolbar-item class=\"pdf-viewer-toolbar__start flex min-w-0 flex-none items-center gap-2\">\n @if (showPageList()) {\n <button\n class=\"pdf-viewer-toolbar__button size-10 text-on-surface-variant hover:bg-surface-container-low hover:text-on-surface\"\n ngsIconButton\n type=\"button\"\n [attr.aria-label]=\"pageListVisible() ? 'Hide pages panel' : 'Show pages panel'\"\n [attr.aria-pressed]=\"pageListVisible()\"\n (click)=\"togglePageList()\">\n <ngs-icon\n class=\"text-2xl\"\n [name]=\"pageListVisible() ? 'fluent:panel-left-contract-24-regular' : 'fluent:panel-left-expand-24-regular'\" />\n </button>\n }\n <ngs-divider vertical />\n </ngs-toolbar-item>\n <ngs-toolbar-title class=\"min-w-0 flex-1 truncate text-sm! font-semibold! text-on-surface-variant\" [title]=\"displayDocumentName()\">\n {{ displayDocumentName() }}\n </ngs-toolbar-title>\n\n <ngs-toolbar-item class=\"pdf-viewer-toolbar__center flex flex-none items-center gap-5\">\n <div class=\"pdf-viewer-toolbar__cluster flex min-w-0 items-center gap-2\" aria-label=\"Zoom controls\">\n <button\n class=\"pdf-viewer-toolbar__button size-10 text-on-surface-variant hover:bg-surface-container-low hover:text-on-surface\"\n ngsIconButton\n type=\"button\"\n aria-label=\"Zoom out\"\n [disabled]=\"!canZoomOut()\"\n (click)=\"zoomOut()\">\n <ngs-icon class=\"text-2xl\" name=\"fluent:zoom-out-24-regular\" />\n </button>\n <button\n class=\"pdf-viewer-toolbar__zoom min-w-16 cursor-pointer border-0 bg-transparent p-0 text-center text-sm font-bold text-on-surface hover:text-primary\"\n type=\"button\"\n aria-label=\"Zoom presets\"\n [ngsMenuTriggerFor]=\"zoomMenu\">\n {{ zoomLabel() }}\n </button>\n <button\n class=\"pdf-viewer-toolbar__button size-10 text-on-surface-variant hover:bg-surface-container-low hover:text-on-surface\"\n ngsIconButton\n type=\"button\"\n aria-label=\"Zoom in\"\n [disabled]=\"!canZoomIn()\"\n (click)=\"zoomIn()\">\n <ngs-icon class=\"text-2xl\" name=\"fluent:zoom-in-24-regular\" />\n </button>\n </div>\n\n <ngs-divider vertical />\n\n <div class=\"pdf-viewer-toolbar__cluster flex min-w-0 items-center gap-2\" aria-label=\"Page navigation\">\n <button\n class=\"pdf-viewer-toolbar__button size-10 text-on-surface-variant hover:bg-surface-container-low hover:text-on-surface\"\n ngsIconButton\n type=\"button\"\n aria-label=\"Previous page\"\n [disabled]=\"!canGoPrevious()\"\n (click)=\"previousPage()\">\n <ngs-icon class=\"text-2xl\" name=\"fluent:chevron-left-24-regular\" />\n </button>\n <span class=\"pdf-viewer-toolbar__page inline-flex h-9 min-w-24 items-center justify-center gap-2 rounded bg-surface-container-low text-sm font-bold text-on-surface\">\n <span>{{ activePage() }}</span>\n <span>/</span>\n <span>{{ pageCount() || 1 }}</span>\n </span>\n <button\n class=\"pdf-viewer-toolbar__button size-10 text-on-surface-variant hover:bg-surface-container-low hover:text-on-surface\"\n ngsIconButton\n type=\"button\"\n aria-label=\"Next page\"\n [disabled]=\"!canGoNext()\"\n (click)=\"nextPage()\">\n <ngs-icon class=\"text-2xl\" name=\"fluent:chevron-right-24-regular\" />\n </button>\n </div>\n </ngs-toolbar-item>\n\n <ngs-toolbar-spacer />\n\n <ngs-toolbar-item class=\"pdf-viewer-toolbar__end flex min-w-0 flex-1 items-center justify-end gap-2\" aria-label=\"PDF tools\">\n @if (showSearchPanel()) {\n <button\n class=\"pdf-viewer-toolbar__button size-10 text-on-surface-variant hover:bg-surface-container-low hover:text-on-surface\"\n ngsIconButton\n type=\"button\"\n [attr.aria-pressed]=\"isSearchPanelVisible()\"\n aria-label=\"Search\"\n (click)=\"toggleSearchPanel()\">\n <ngs-icon class=\"text-2xl\" name=\"fluent:search-24-regular\" />\n </button>\n }\n <button\n class=\"pdf-viewer-toolbar__button size-10 text-on-surface-variant hover:bg-surface-container-low hover:text-on-surface\"\n ngsIconButton\n type=\"button\"\n aria-label=\"PDF viewer settings\"\n [ngsMenuTriggerFor]=\"settingsMenu\">\n <ngs-icon class=\"text-2xl\" name=\"fluent:settings-24-regular\" />\n </button>\n @if (showAnnotationsPanel()) {\n <button\n class=\"pdf-viewer-toolbar__button size-10 text-on-surface-variant hover:bg-surface-container-low hover:text-on-surface\"\n ngsIconButton\n type=\"button\"\n [attr.aria-pressed]=\"isAnnotationsPanelVisible()\"\n aria-label=\"Toggle annotations panel\"\n (click)=\"toggleAnnotationsPanel()\">\n <ngs-icon class=\"text-2xl\" name=\"fluent:text-bullet-list-square-edit-24-regular\" />\n </button>\n }\n </ngs-toolbar-item>\n </ngs-toolbar>\n\n <ngs-menu #zoomMenu=\"ngsMenu\">\n @for (zoomPreset of zoomPresets; track zoomPreset) {\n <button\n ngs-menu-item\n type=\"button\"\n [selected]=\"isZoomPresetSelected(zoomPreset)\"\n (click)=\"setZoomPreset(zoomPreset)\">\n <span>{{ zoomPreset * 100 }}%</span>\n </button>\n }\n\n <ngs-menu-divider />\n <button\n ngs-menu-item\n type=\"button\"\n [selected]=\"zoomMode() === 'fit-page'\"\n (click)=\"fitToPage()\">\n <ngs-icon name=\"fluent:resize-large-24-regular\" />\n <span>Fit to Page</span>\n </button>\n <button\n ngs-menu-item\n type=\"button\"\n [selected]=\"zoomMode() === 'fit-width'\"\n (click)=\"fitToWidth()\">\n <ngs-icon name=\"fluent:resize-24-regular\" />\n <span>Fit to Width</span>\n </button>\n </ngs-menu>\n\n <ngs-menu #settingsMenu=\"ngsMenu\" xPosition=\"before\">\n <ngs-menu-heading>SPREAD MODE</ngs-menu-heading>\n <button\n ngs-menu-item\n type=\"button\"\n [selected]=\"spreadMode() === 'single'\"\n (click)=\"setSpreadMode('single')\">\n <ngs-icon name=\"fluent:document-24-regular\" />\n <span>Single Page</span>\n </button>\n <button\n ngs-menu-item\n type=\"button\"\n [selected]=\"spreadMode() === 'two-odd'\"\n (click)=\"setSpreadMode('two-odd')\">\n <ngs-icon name=\"fluent:book-open-24-regular\" />\n <span>Two Page (Odd)</span>\n </button>\n <button\n ngs-menu-item\n type=\"button\"\n [selected]=\"spreadMode() === 'two-even'\"\n (click)=\"setSpreadMode('two-even')\">\n <ngs-icon name=\"fluent:document-text-24-regular\" />\n <span>Two Page (Even)</span>\n </button>\n\n <ngs-menu-divider />\n <ngs-menu-heading>SCROLL LAYOUT</ngs-menu-heading>\n <button\n ngs-menu-item\n type=\"button\"\n [selected]=\"scrollLayout() === 'vertical'\"\n (click)=\"setScrollLayout('vertical')\">\n <ngs-icon name=\"fluent:arrow-sort-24-regular\" />\n <span>Vertical</span>\n </button>\n <button\n ngs-menu-item\n type=\"button\"\n [selected]=\"scrollLayout() === 'horizontal'\"\n (click)=\"setScrollLayout('horizontal')\">\n <ngs-icon name=\"fluent:arrow-swap-24-regular\" />\n <span>Horizontal</span>\n </button>\n\n <ngs-menu-divider />\n <ngs-menu-heading>PAGE ROTATION</ngs-menu-heading>\n <button ngs-menu-item type=\"button\" (click)=\"rotateClockwise()\">\n <ngs-icon name=\"fluent:arrow-clockwise-24-regular\" />\n <span>Rotate Clockwise</span>\n </button>\n <button ngs-menu-item type=\"button\" (click)=\"rotateCounterClockwise()\">\n <ngs-icon name=\"fluent:arrow-counterclockwise-24-regular\" />\n <span>Rotate Counter-Clockwise</span>\n </button>\n\n <ngs-menu-divider />\n <button ngs-menu-item type=\"button\" (click)=\"toggleFullscreen()\">\n <ngs-icon name=\"fluent:arrow-maximize-24-regular\" />\n <span>Fullscreen</span>\n </button>\n </ngs-menu>\n </ngs-panel-header>\n }\n\n @if (isPageListVisible()) {\n <ngs-panel-sidebar class=\"w-[var(--ngs-pdf-viewer-sidebar-width)] min-w-[var(--ngs-pdf-viewer-sidebar-width)] border-r border-border bg-surface\">\n <div class=\"pdf-viewer-sidebar flex h-full min-h-0 flex-col gap-2 overflow-auto p-3\" #pageList>\n <div class=\"pdf-viewer-sidebar__title px-1 text-xs font-semibold text-on-surface-variant\">Pages</div>\n <div class=\"pdf-viewer-sidebar__pages flex min-h-0 flex-col gap-2\" aria-label=\"PDF pages\">\n @for (pageItem of pageItems(); track pageItem.pageNumber) {\n <button\n class=\"pdf-viewer-sidebar__page group flex w-full cursor-pointer flex-col items-stretch gap-1 rounded border border-transparent bg-transparent p-1 text-left text-on-surface-variant focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-primary [&.is-active]:text-on-surface\"\n type=\"button\"\n [class.is-active]=\"pageItem.pageNumber === activePage()\"\n [attr.aria-current]=\"pageItem.pageNumber === activePage() ? 'page' : null\"\n [attr.aria-label]=\"'Open page ' + pageItem.pageNumber\"\n [attr.data-ngs-pdf-page-button]=\"pageItem.pageNumber\"\n (click)=\"setPage(pageItem.pageNumber)\">\n <span class=\"pdf-viewer-sidebar__thumb flex aspect-[3/4] w-full items-center justify-center overflow-hidden rounded border border-border bg-surface shadow-xs group-hover:border-border group-[.is-active]:border-border group-[.is-active]:bg-primary-container group-[.is-active]:shadow-xs group-[.is-active]:ring-2 group-[.is-active]:ring-primary-container\" aria-hidden=\"true\">\n @if (pageItem.thumbnail; as thumbnail) {\n <img\n class=\"block h-full w-full select-none object-contain\"\n [src]=\"thumbnail.url\"\n [attr.width]=\"thumbnail.width\"\n [attr.height]=\"thumbnail.height\"\n alt=\"\"\n draggable=\"false\" />\n } @else {\n <ngs-image-placeholder class=\"h-full w-full\" />\n }\n </span>\n <span class=\"pdf-viewer-sidebar__label block truncate text-center text-xs font-medium leading-tight\">Page {{ pageItem.pageNumber }}</span>\n </button>\n }\n </div>\n </div>\n </ngs-panel-sidebar>\n }\n\n <ngs-panel-content class=\"min-h-0 min-w-0 overflow-hidden bg-surface-container-low\">\n <div class=\"pdf-viewer-body absolute inset-0 overflow-auto\" #viewerBody (scroll)=\"onViewerScroll()\" (wheel)=\"onViewerWheel($event)\">\n <ngs-block-loader [loading]=\"isLoading()\">Loading PDF...</ngs-block-loader>\n @if (errorState()) {\n <div class=\"pdf-viewer-state pdf-viewer-state--error absolute inset-0 flex flex-col items-center justify-center gap-3 p-6 text-center text-danger\">\n <ngs-icon class=\"text-3xl\" name=\"fluent:error-circle-24-regular\" />\n <span>PDF could not be loaded.</span>\n </div>\n } @else if (!src()) {\n <div class=\"pdf-viewer-state absolute inset-0 flex flex-col items-center justify-center gap-3 p-6 text-center text-on-surface-variant\">\n <ngs-icon class=\"text-3xl\" name=\"fluent:document-pdf-24-regular\" />\n <span>No PDF selected.</span>\n </div>\n } @else {\n <div class=\"pdf-viewer-pages grid min-h-full min-w-full w-max content-start grid-flow-row box-border justify-items-center gap-6 p-6\">\n @for (pdfPage of renderedPages(); track pdfPage.pageNumber) {\n <article\n class=\"pdf-viewer-page w-max max-w-none\"\n [attr.aria-label]=\"'PDF page ' + pdfPage.pageNumber\"\n [attr.data-ngs-pdf-page]=\"pdfPage.pageNumber\">\n <div\n class=\"pdf-viewer-page__surface relative cursor-text overflow-hidden rounded bg-surface shadow-md select-none touch-none\"\n [style.width.px]=\"pdfPage.width\"\n [style.height.px]=\"pdfPage.height\"\n (pointerdown)=\"startTextSelection($event, pdfPage)\"\n (pointermove)=\"updateTextSelection($event, pdfPage)\"\n (pointerup)=\"finishTextSelection($event, pdfPage)\"\n (pointercancel)=\"cancelTextSelection()\"\n (mousedown)=\"startTextSelection($event, pdfPage)\"\n (mousemove)=\"updateTextSelection($event, pdfPage)\"\n (mouseup)=\"finishTextSelection($event, pdfPage)\">\n @if (pdfPage.url; as pageUrl) {\n <img\n class=\"block h-full w-full max-w-none select-none pointer-events-none\"\n [src]=\"pageUrl\"\n [alt]=\"'PDF page ' + pdfPage.pageNumber\"\n [attr.width]=\"pdfPage.width\"\n [attr.height]=\"pdfPage.height\"\n draggable=\"false\" />\n } @else {\n <div class=\"pdf-viewer-page__placeholder pointer-events-none absolute inset-0 flex items-center justify-center bg-surface\" aria-hidden=\"true\">\n @if (pdfPage.isRendering) {\n <span class=\"size-8 animate-spin rounded-full border-2 border-border border-t-primary\"></span>\n }\n </div>\n }\n @if (selectionRectsForPage(pdfPage.pageNumber); as selectionRects) {\n @if (selectionRects.length > 0) {\n <div class=\"pdf-viewer-page__selection-layer pointer-events-none absolute inset-0 z-[1] isolate overflow-hidden mix-blend-multiply\" aria-hidden=\"true\">\n @for (selectionRect of selectionRects; track selectionRect.left + '-' + selectionRect.top + '-' + $index) {\n <div\n class=\"pdf-viewer-page__selection-rect pointer-events-none absolute bg-primary/30\"\n [style.left.px]=\"selectionRect.left\"\n [style.top.px]=\"selectionRect.top\"\n [style.width.px]=\"selectionRect.width\"\n [style.height.px]=\"selectionRect.height\"></div>\n }\n </div>\n }\n }\n </div>\n </article>\n }\n </div>\n }\n </div>\n </ngs-panel-content>\n\n @if (isSearchPanelVisible() || isAnnotationsPanelVisible()) {\n <ngs-panel-aside class=\"w-[var(--ngs-pdf-viewer-aside-width)] min-w-[var(--ngs-pdf-viewer-aside-width)] border-l border-border bg-surface\">\n @if (isSearchPanelVisible()) {\n <ngs-pdf-viewer-search\n [query]=\"activeSearchQuery()\"\n [results]=\"pdfSearchResults()\"\n (closed)=\"closeAsidePanel()\"\n (searchChanged)=\"updatePdfSearch($event)\"\n (resultSelected)=\"selectSearchResult($event)\" />\n } @else {\n <ngs-pdf-viewer-annotations\n [annotations]=\"annotationItems()\"\n [annotationDefs]=\"annotationDefs()\"\n [annotationTypeProperty]=\"annotationTypeProperty()\"\n (closed)=\"closeAsidePanel()\"\n (pageSelected)=\"setPage($event)\" />\n }\n </ngs-panel-aside>\n }\n</ngs-panel>\n", styles: [":host{--ngs-pdf-viewer-height: 720px;--ngs-pdf-viewer-min-height: 720px;--ngs-pdf-viewer-sidebar-width: 152px;--ngs-pdf-viewer-aside-width: 360px;display:block;height:var(--ngs-pdf-viewer-height);min-height:var(--ngs-pdf-viewer-min-height);overflow:hidden}:host .pdf-viewer-page__selection-layer{forced-color-adjust:none}\n"] }]
|
|
1866
|
+
}], ctorParameters: () => [], propDecorators: { viewerBody: [{ type: i0.ViewChild, args: ['viewerBody', { isSignal: true }] }], pageList: [{ type: i0.ViewChild, args: ['pageList', { isSignal: true }] }], annotationDefs: [{ type: i0.ContentChildren, args: [i0.forwardRef(() => PdfViewerAnnotationDef), { ...{ descendants: true }, isSignal: true }] }], src: [{ type: i0.Input, args: [{ isSignal: true, alias: "src", required: false }] }], documentName: [{ type: i0.Input, args: [{ isSignal: true, alias: "documentName", required: false }] }], wasmUrl: [{ type: i0.Input, args: [{ isSignal: true, alias: "wasmUrl", required: false }] }], page: [{ type: i0.Input, args: [{ isSignal: true, alias: "page", required: false }] }], scale: [{ type: i0.Input, args: [{ isSignal: true, alias: "scale", required: false }] }], minScale: [{ type: i0.Input, args: [{ isSignal: true, alias: "minScale", required: false }] }], maxScale: [{ type: i0.Input, args: [{ isSignal: true, alias: "maxScale", required: false }] }], zoomStep: [{ type: i0.Input, args: [{ isSignal: true, alias: "zoomStep", required: false }] }], maxRenderPixels: [{ type: i0.Input, args: [{ isSignal: true, alias: "maxRenderPixels", required: false }] }], maxRenderDimension: [{ type: i0.Input, args: [{ isSignal: true, alias: "maxRenderDimension", required: false }] }], renderAll: [{ type: i0.Input, args: [{ isSignal: true, alias: "renderAll", required: false }] }], showToolbar: [{ type: i0.Input, args: [{ isSignal: true, alias: "showToolbar", required: false }] }], showPageList: [{ type: i0.Input, args: [{ isSignal: true, alias: "showPageList", required: false }] }], showSearchPanel: [{ type: i0.Input, args: [{ isSignal: true, alias: "showSearchPanel", required: false }] }], showAnnotationsPanel: [{ type: i0.Input, args: [{ isSignal: true, alias: "showAnnotationsPanel", required: false }] }], annotations: [{ type: i0.Input, args: [{ isSignal: true, alias: "annotations", required: false }] }], annotationsDataSource: [{ type: i0.Input, args: [{ isSignal: true, alias: "annotationsDataSource", required: false }] }], annotationTypeProperty: [{ type: i0.Input, args: [{ isSignal: true, alias: "annotationTypeProperty", required: false }] }], searchQuery: [{ type: i0.Input, args: [{ isSignal: true, alias: "searchQuery", required: false }] }], withAnnotations: [{ type: i0.Input, args: [{ isSignal: true, alias: "withAnnotations", required: false }] }], withForms: [{ type: i0.Input, args: [{ isSignal: true, alias: "withForms", required: false }] }], loaded: [{ type: i0.Output, args: ["loaded"] }], pageChanged: [{ type: i0.Output, args: ["pageChanged"] }], pageRendered: [{ type: i0.Output, args: ["pageRendered"] }], error: [{ type: i0.Output, args: ["error"] }] } });
|
|
1867
|
+
|
|
1868
|
+
/**
|
|
1869
|
+
* Generated bundle index. Do not edit.
|
|
1870
|
+
*/
|
|
1871
|
+
|
|
1872
|
+
export { PdfViewer, PdfViewerAnnotationDef, PdfViewerAnnotations, PdfViewerEngineService, PdfViewerSearch };
|
|
1873
|
+
//# sourceMappingURL=ngstarter-ui-components-pdf-viewer.mjs.map
|