@eternalheart/angular-file-preview 1.6.5

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (119) hide show
  1. package/LICENSE +22 -0
  2. package/README.md +879 -0
  3. package/README.zh-CN.md +876 -0
  4. package/lib/LICENSE +22 -0
  5. package/lib/README.md +879 -0
  6. package/lib/components/preview/file-preview-toolbar.d.ts +19 -0
  7. package/lib/components/preview/nav-arrows.d.ts +25 -0
  8. package/lib/components/preview/renderer-outlet.d.ts +23 -0
  9. package/lib/components/preview/toolbar-button.d.ts +12 -0
  10. package/lib/components/resizable-split.d.ts +32 -0
  11. package/lib/di/locale.service.d.ts +12 -0
  12. package/lib/di/request.service.d.ts +20 -0
  13. package/lib/di/theme-mode.d.ts +6 -0
  14. package/lib/di/theme.service.d.ts +9 -0
  15. package/lib/fesm2022/eternalheart-angular-file-preview-avifLoader-BP3fgcMm-CZsJ9svm.mjs +55 -0
  16. package/lib/fesm2022/eternalheart-angular-file-preview-avifLoader-BP3fgcMm-CZsJ9svm.mjs.map +1 -0
  17. package/lib/fesm2022/eternalheart-angular-file-preview-eternalheart-angular-file-preview-RendererError-DtNihCIX-DtNihCIX.mjs +58 -0
  18. package/lib/fesm2022/eternalheart-angular-file-preview-eternalheart-angular-file-preview-RendererError-DtNihCIX-DtNihCIX.mjs.map +1 -0
  19. package/lib/fesm2022/eternalheart-angular-file-preview-eternalheart-angular-file-preview-UobTf36G.mjs +5835 -0
  20. package/lib/fesm2022/eternalheart-angular-file-preview-eternalheart-angular-file-preview-UobTf36G.mjs.map +1 -0
  21. package/lib/fesm2022/eternalheart-angular-file-preview-eternalheart-angular-file-preview-base.types-D5x7QPey-D5x7QPey.mjs +17 -0
  22. package/lib/fesm2022/eternalheart-angular-file-preview-eternalheart-angular-file-preview-base.types-D5x7QPey-D5x7QPey.mjs.map +1 -0
  23. package/lib/fesm2022/eternalheart-angular-file-preview-eternalheart-angular-file-preview-index--7d8Hgi0-D-sg24Qs.mjs +284 -0
  24. package/lib/fesm2022/eternalheart-angular-file-preview-eternalheart-angular-file-preview-index--7d8Hgi0-D-sg24Qs.mjs.map +1 -0
  25. package/lib/fesm2022/eternalheart-angular-file-preview-eternalheart-angular-file-preview-index-6dZS_Cfs-B1itLV5R.mjs +703 -0
  26. package/lib/fesm2022/eternalheart-angular-file-preview-eternalheart-angular-file-preview-index-6dZS_Cfs-B1itLV5R.mjs.map +1 -0
  27. package/lib/fesm2022/eternalheart-angular-file-preview-eternalheart-angular-file-preview-index-B1ANUCTy-BRs7TMLn.mjs +231 -0
  28. package/lib/fesm2022/eternalheart-angular-file-preview-eternalheart-angular-file-preview-index-B1ANUCTy-BRs7TMLn.mjs.map +1 -0
  29. package/lib/fesm2022/eternalheart-angular-file-preview-eternalheart-angular-file-preview-index-BEaqpRV8-BidVIVlO.mjs +552 -0
  30. package/lib/fesm2022/eternalheart-angular-file-preview-eternalheart-angular-file-preview-index-BEaqpRV8-BidVIVlO.mjs.map +1 -0
  31. package/lib/fesm2022/eternalheart-angular-file-preview-eternalheart-angular-file-preview-index-BGqTRZbm-CAOLRw20.mjs +688 -0
  32. package/lib/fesm2022/eternalheart-angular-file-preview-eternalheart-angular-file-preview-index-BGqTRZbm-CAOLRw20.mjs.map +1 -0
  33. package/lib/fesm2022/eternalheart-angular-file-preview-eternalheart-angular-file-preview-index-Bv3JRXKn-B4Wewc-4.mjs +273 -0
  34. package/lib/fesm2022/eternalheart-angular-file-preview-eternalheart-angular-file-preview-index-Bv3JRXKn-B4Wewc-4.mjs.map +1 -0
  35. package/lib/fesm2022/eternalheart-angular-file-preview-eternalheart-angular-file-preview-index-C0McUoqT-DJ35bhIz.mjs +288 -0
  36. package/lib/fesm2022/eternalheart-angular-file-preview-eternalheart-angular-file-preview-index-C0McUoqT-DJ35bhIz.mjs.map +1 -0
  37. package/lib/fesm2022/eternalheart-angular-file-preview-eternalheart-angular-file-preview-index-C518mdUd-B5SCzrzU.mjs +550 -0
  38. package/lib/fesm2022/eternalheart-angular-file-preview-eternalheart-angular-file-preview-index-C518mdUd-B5SCzrzU.mjs.map +1 -0
  39. package/lib/fesm2022/eternalheart-angular-file-preview-eternalheart-angular-file-preview-index-CDjKPqyW-CmX5sdkL.mjs +102 -0
  40. package/lib/fesm2022/eternalheart-angular-file-preview-eternalheart-angular-file-preview-index-CDjKPqyW-CmX5sdkL.mjs.map +1 -0
  41. package/lib/fesm2022/eternalheart-angular-file-preview-eternalheart-angular-file-preview-index-CHi9tOe0-NOeQYAnM.mjs +516 -0
  42. package/lib/fesm2022/eternalheart-angular-file-preview-eternalheart-angular-file-preview-index-CHi9tOe0-NOeQYAnM.mjs.map +1 -0
  43. package/lib/fesm2022/eternalheart-angular-file-preview-eternalheart-angular-file-preview-index-CQwhffym-DAGw4Xy_.mjs +823 -0
  44. package/lib/fesm2022/eternalheart-angular-file-preview-eternalheart-angular-file-preview-index-CQwhffym-DAGw4Xy_.mjs.map +1 -0
  45. package/lib/fesm2022/eternalheart-angular-file-preview-eternalheart-angular-file-preview-index-CSCTj7ft-BfqruUyY.mjs +164 -0
  46. package/lib/fesm2022/eternalheart-angular-file-preview-eternalheart-angular-file-preview-index-CSCTj7ft-BfqruUyY.mjs.map +1 -0
  47. package/lib/fesm2022/eternalheart-angular-file-preview-eternalheart-angular-file-preview-index-Cb1RLXeO-Bj9gjS1Z.mjs +655 -0
  48. package/lib/fesm2022/eternalheart-angular-file-preview-eternalheart-angular-file-preview-index-Cb1RLXeO-Bj9gjS1Z.mjs.map +1 -0
  49. package/lib/fesm2022/eternalheart-angular-file-preview-eternalheart-angular-file-preview-index-Cw90Kdyg-BzrW_cyx.mjs +207 -0
  50. package/lib/fesm2022/eternalheart-angular-file-preview-eternalheart-angular-file-preview-index-Cw90Kdyg-BzrW_cyx.mjs.map +1 -0
  51. package/lib/fesm2022/eternalheart-angular-file-preview-eternalheart-angular-file-preview-index-D35x1nwM-CugEEXjg.mjs +199 -0
  52. package/lib/fesm2022/eternalheart-angular-file-preview-eternalheart-angular-file-preview-index-D35x1nwM-CugEEXjg.mjs.map +1 -0
  53. package/lib/fesm2022/eternalheart-angular-file-preview-eternalheart-angular-file-preview-index-D9jJNzeM-mWX59Aj2.mjs +194 -0
  54. package/lib/fesm2022/eternalheart-angular-file-preview-eternalheart-angular-file-preview-index-D9jJNzeM-mWX59Aj2.mjs.map +1 -0
  55. package/lib/fesm2022/eternalheart-angular-file-preview-eternalheart-angular-file-preview-index-DbmuvdvN-C29uLyt2.mjs +94 -0
  56. package/lib/fesm2022/eternalheart-angular-file-preview-eternalheart-angular-file-preview-index-DbmuvdvN-C29uLyt2.mjs.map +1 -0
  57. package/lib/fesm2022/eternalheart-angular-file-preview-eternalheart-angular-file-preview-index-DsNNZ59Q-C8MaSME6.mjs +468 -0
  58. package/lib/fesm2022/eternalheart-angular-file-preview-eternalheart-angular-file-preview-index-DsNNZ59Q-C8MaSME6.mjs.map +1 -0
  59. package/lib/fesm2022/eternalheart-angular-file-preview-eternalheart-angular-file-preview-index-DufW2Fuc-CTosCut6.mjs +193 -0
  60. package/lib/fesm2022/eternalheart-angular-file-preview-eternalheart-angular-file-preview-index-DufW2Fuc-CTosCut6.mjs.map +1 -0
  61. package/lib/fesm2022/eternalheart-angular-file-preview-eternalheart-angular-file-preview-index-TSTa_Q7E-DBhkL6pV.mjs +184 -0
  62. package/lib/fesm2022/eternalheart-angular-file-preview-eternalheart-angular-file-preview-index-TSTa_Q7E-DBhkL6pV.mjs.map +1 -0
  63. package/lib/fesm2022/eternalheart-angular-file-preview-eternalheart-angular-file-preview-index-pG0Ko5mI-DthQJg-X.mjs +492 -0
  64. package/lib/fesm2022/eternalheart-angular-file-preview-eternalheart-angular-file-preview-index-pG0Ko5mI-DthQJg-X.mjs.map +1 -0
  65. package/lib/fesm2022/eternalheart-angular-file-preview-eternalheart-angular-file-preview-shiki-highlight-CL9cG481-BKfwdOpN.mjs +68 -0
  66. package/lib/fesm2022/eternalheart-angular-file-preview-eternalheart-angular-file-preview-shiki-highlight-CL9cG481-BKfwdOpN.mjs.map +1 -0
  67. package/lib/fesm2022/eternalheart-angular-file-preview-heicLoader-CH_raQNn-BSXHd_oi.mjs +36 -0
  68. package/lib/fesm2022/eternalheart-angular-file-preview-heicLoader-CH_raQNn-BSXHd_oi.mjs.map +1 -0
  69. package/lib/fesm2022/eternalheart-angular-file-preview-jp2Loader-ldvCSsvm-DJEhfimG.mjs +121 -0
  70. package/lib/fesm2022/eternalheart-angular-file-preview-jp2Loader-ldvCSsvm-DJEhfimG.mjs.map +1 -0
  71. package/lib/fesm2022/eternalheart-angular-file-preview-psdLoader-D5CkupyY-D5Gegcdg.mjs +78 -0
  72. package/lib/fesm2022/eternalheart-angular-file-preview-psdLoader-D5CkupyY-D5Gegcdg.mjs.map +1 -0
  73. package/lib/fesm2022/eternalheart-angular-file-preview-rawLoader-0mLvxCNp-CCXn9Rdp.mjs +81 -0
  74. package/lib/fesm2022/eternalheart-angular-file-preview-rawLoader-0mLvxCNp-CCXn9Rdp.mjs.map +1 -0
  75. package/lib/fesm2022/eternalheart-angular-file-preview-tiffLoader-CmUAD1vO-0JqIws9t.mjs +59 -0
  76. package/lib/fesm2022/eternalheart-angular-file-preview-tiffLoader-CmUAD1vO-0JqIws9t.mjs.map +1 -0
  77. package/lib/fesm2022/eternalheart-angular-file-preview.mjs +2 -0
  78. package/lib/fesm2022/eternalheart-angular-file-preview.mjs.map +1 -0
  79. package/lib/file-preview-content.d.ts +65 -0
  80. package/lib/file-preview-embed.d.ts +30 -0
  81. package/lib/file-preview-modal.d.ts +34 -0
  82. package/lib/fp-core.d.ts +1 -0
  83. package/lib/index.css +2119 -0
  84. package/lib/index.d.ts +11 -0
  85. package/lib/renderers/Audio/index.d.ts +74 -0
  86. package/lib/renderers/Cad/index.d.ts +45 -0
  87. package/lib/renderers/Cad/toolbar.d.ts +15 -0
  88. package/lib/renderers/Csv/index.d.ts +26 -0
  89. package/lib/renderers/Doc/index.d.ts +19 -0
  90. package/lib/renderers/Docx/index.d.ts +42 -0
  91. package/lib/renderers/Epub/index.d.ts +57 -0
  92. package/lib/renderers/Font/font-preview-line.d.ts +26 -0
  93. package/lib/renderers/Font/index.d.ts +52 -0
  94. package/lib/renderers/Image/index.d.ts +79 -0
  95. package/lib/renderers/Json/index.d.ts +61 -0
  96. package/lib/renderers/Markdown/index.d.ts +35 -0
  97. package/lib/renderers/Mobi/index.d.ts +51 -0
  98. package/lib/renderers/Msg/index.d.ts +47 -0
  99. package/lib/renderers/Pdf/index.d.ts +55 -0
  100. package/lib/renderers/Ppt/index.d.ts +19 -0
  101. package/lib/renderers/Pptx/index.d.ts +29 -0
  102. package/lib/renderers/RendererError.d.ts +9 -0
  103. package/lib/renderers/RendererLoading.d.ts +7 -0
  104. package/lib/renderers/Subtitle/index.d.ts +25 -0
  105. package/lib/renderers/Text/index.d.ts +29 -0
  106. package/lib/renderers/Unsupported/index.d.ts +15 -0
  107. package/lib/renderers/Video/index.d.ts +28 -0
  108. package/lib/renderers/Xlsx/index.d.ts +25 -0
  109. package/lib/renderers/Xml/index.d.ts +26 -0
  110. package/lib/renderers/Zip/index.d.ts +58 -0
  111. package/lib/renderers/Zip/tree-item.d.ts +29 -0
  112. package/lib/renderers/base.types.d.ts +10 -0
  113. package/lib/renderers/lazy.d.ts +23 -0
  114. package/lib/renderers/registry.d.ts +13 -0
  115. package/lib/renderers/toolbar.types.d.ts +19 -0
  116. package/lib/types.d.ts +26 -0
  117. package/lib/utils/audio-player.d.ts +20 -0
  118. package/lib/utils/shiki-highlight.d.ts +7 -0
  119. package/package.json +119 -0
@@ -0,0 +1,703 @@
1
+ import * as i0 from '@angular/core';
2
+ import { input, inject, DestroyRef, signal, viewChild, computed, afterNextRender, effect, untracked, ChangeDetectionStrategy, Component } from '@angular/core';
3
+ import { DomSanitizer } from '@angular/platform-browser';
4
+ import { L as LocaleService, g as getFallbackTranslator, Y as Yg, A as Ag, b as i0$1 } from './eternalheart-angular-file-preview-eternalheart-angular-file-preview-UobTf36G.mjs';
5
+ import * as pdfjsLib from 'pdfjs-dist/legacy/build/pdf.mjs';
6
+ import * as i1 from 'lucide-angular';
7
+ import { X, Menu, ChevronLeft, ChevronRight, ZoomOut, ZoomIn, RefreshCw, LucideAngularModule } from 'lucide-angular';
8
+ import { R as RendererError } from './eternalheart-angular-file-preview-eternalheart-angular-file-preview-RendererError-DtNihCIX-DtNihCIX.mjs';
9
+ import { T as ToolbarEventEmitter } from './eternalheart-angular-file-preview-eternalheart-angular-file-preview-base.types-D5x7QPey-D5x7QPey.mjs';
10
+
11
+ i0$1();
12
+ /**
13
+ * 准备 PDF worker。
14
+ * - 浏览器:使用 CDN 上的 legacy worker(独立 worker 线程,性能好)。
15
+ * - Electron:worker 独立作用域缺 Uint8Array hex/base64 polyfill,会抛 `toHex is not a function`。
16
+ * 将 worker 模块挂到 `globalThis.pdfjsWorker`,强制 pdfjs 主线程执行 worker 逻辑,
17
+ * 复用主线程已装的 polyfill,并绕开 CDN / worker 作用域问题。
18
+ */
19
+ let pdfWorkerPrepared = null;
20
+ function preparePdfWorker() {
21
+ if (pdfWorkerPrepared)
22
+ return pdfWorkerPrepared;
23
+ pdfWorkerPrepared = (async () => {
24
+ const isElectron = typeof navigator !== 'undefined' && /electron/i.test(navigator.userAgent);
25
+ if (isElectron) {
26
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
27
+ const g = globalThis;
28
+ if (typeof globalThis !== 'undefined' && !g.pdfjsWorker) {
29
+ // @ts-ignore - pdfjs worker 无类型声明
30
+ const workerModule = await import(/* webpackChunkName: "pdf.worker" */ /* @vite-ignore */ 'pdfjs-dist/legacy/build/pdf.worker.mjs');
31
+ g.pdfjsWorker = workerModule;
32
+ }
33
+ }
34
+ const configuredOptions = Yg();
35
+ Ag(pdfjsLib, {
36
+ ...configuredOptions,
37
+ workerSrc: pdfjsLib.GlobalWorkerOptions.workerSrc || undefined,
38
+ });
39
+ })();
40
+ return pdfWorkerPrepared;
41
+ }
42
+ class PdfRenderer {
43
+ constructor() {
44
+ this.url = input.required();
45
+ this.emitter = new ToolbarEventEmitter();
46
+ this.locale = inject(LocaleService, { optional: true });
47
+ this.sanitizer = inject(DomSanitizer);
48
+ this.destroyRef = inject(DestroyRef);
49
+ this.t = this.locale?.t() ?? getFallbackTranslator();
50
+ this.xIcon = X;
51
+ this.zoom = signal(1);
52
+ this.currentPage = signal(1);
53
+ this.showOutline = signal(false);
54
+ this.numPages = signal(0);
55
+ this.error = signal(null);
56
+ this.loading = signal(true);
57
+ this.outline = signal([]);
58
+ this.activeOutlineItem = signal(null);
59
+ this.scrollContainerRef = viewChild('scrollContainerRef');
60
+ this.outlinePageMap = new Map();
61
+ this.pdfDoc = null;
62
+ this.pageStates = new Map();
63
+ this.observer = null;
64
+ this.zoomWatchReady = false;
65
+ this.outlineHtml = computed(() => {
66
+ const items = this.outline();
67
+ this.activeOutlineItem();
68
+ if (items.length === 0)
69
+ return '';
70
+ return `<ul>${this.renderOutlineItemsHtml(items)}</ul>`;
71
+ });
72
+ this.getToolbarGroups = () => {
73
+ const groups = [];
74
+ if (this.numPages() === 0)
75
+ return groups;
76
+ if (this.outline().length > 0) {
77
+ groups.push({
78
+ items: [
79
+ {
80
+ type: 'button',
81
+ icon: Menu,
82
+ tooltip: this.t('toolbar.outline'),
83
+ action: this.handleToggleOutline,
84
+ active: this.showOutline(),
85
+ },
86
+ ],
87
+ });
88
+ }
89
+ groups.push({
90
+ items: [
91
+ {
92
+ type: 'button',
93
+ icon: ChevronLeft,
94
+ tooltip: this.t('toolbar.prev_page'),
95
+ action: this.handlePrevPage,
96
+ disabled: this.currentPage() <= 1,
97
+ },
98
+ {
99
+ type: 'text',
100
+ content: `${this.currentPage()} / ${this.numPages()}`,
101
+ minWidth: '4rem',
102
+ },
103
+ {
104
+ type: 'button',
105
+ icon: ChevronRight,
106
+ tooltip: this.t('toolbar.next_page'),
107
+ action: this.handleNextPage,
108
+ disabled: this.currentPage() >= this.numPages(),
109
+ },
110
+ ],
111
+ });
112
+ groups.push({
113
+ items: [
114
+ {
115
+ type: 'button',
116
+ icon: ZoomOut,
117
+ tooltip: this.t('toolbar.zoom_out'),
118
+ action: this.handleZoomOut,
119
+ disabled: this.zoom() <= 0.5,
120
+ },
121
+ {
122
+ type: 'text',
123
+ content: `${Math.round(this.zoom() * 100)}%`,
124
+ minWidth: '3rem',
125
+ },
126
+ {
127
+ type: 'button',
128
+ icon: ZoomIn,
129
+ tooltip: this.t('toolbar.zoom_in'),
130
+ action: this.handleZoomIn,
131
+ disabled: this.zoom() >= 3,
132
+ },
133
+ ],
134
+ });
135
+ groups.push({
136
+ items: [
137
+ {
138
+ type: 'button',
139
+ icon: RefreshCw,
140
+ tooltip: this.t('toolbar.reset'),
141
+ action: this.handleReset,
142
+ },
143
+ ],
144
+ });
145
+ return groups;
146
+ };
147
+ this.onToolbarChange = (listener) => this.emitter.subscribe(listener);
148
+ this.handleZoomIn = () => {
149
+ this.zoom.update((v) => Math.min(3, v + 0.1));
150
+ };
151
+ this.handleZoomOut = () => {
152
+ this.zoom.update((v) => Math.max(0.5, v - 0.1));
153
+ };
154
+ this.handleReset = () => {
155
+ this.zoom.set(1);
156
+ };
157
+ this.handlePrevPage = () => {
158
+ const container = this.scrollContainerRef()?.nativeElement;
159
+ if (!container)
160
+ return;
161
+ const pages = container.querySelectorAll('[data-page-number]');
162
+ const targetPage = pages[Math.max(0, this.currentPage() - 2)];
163
+ if (targetPage) {
164
+ targetPage.scrollIntoView({ behavior: 'smooth', block: 'start' });
165
+ }
166
+ };
167
+ this.handleNextPage = () => {
168
+ const container = this.scrollContainerRef()?.nativeElement;
169
+ if (!container)
170
+ return;
171
+ const pages = container.querySelectorAll('[data-page-number]');
172
+ const targetPage = pages[Math.min(pages.length - 1, this.currentPage())];
173
+ if (targetPage) {
174
+ targetPage.scrollIntoView({ behavior: 'smooth', block: 'start' });
175
+ }
176
+ };
177
+ this.handleToggleOutline = () => {
178
+ this.showOutline.update((v) => !v);
179
+ };
180
+ this.handleScroll = () => {
181
+ if (!this.scrollContainerRef()?.nativeElement || this.pageStates.size === 0)
182
+ return;
183
+ const container = this.scrollContainerRef().nativeElement;
184
+ const scrollTop = container.scrollTop;
185
+ const containerHeight = container.clientHeight;
186
+ const scrollCenter = scrollTop + containerHeight / 2;
187
+ let currentVisiblePage = 1;
188
+ let minDistance = Infinity;
189
+ this.pageStates.forEach((state, pageNumber) => {
190
+ const rect = state.element.getBoundingClientRect();
191
+ const containerRect = container.getBoundingClientRect();
192
+ const pageCenter = rect.top - containerRect.top + rect.height / 2 + scrollTop;
193
+ const distance = Math.abs(scrollCenter - pageCenter);
194
+ if (distance < minDistance) {
195
+ minDistance = distance;
196
+ currentVisiblePage = pageNumber;
197
+ }
198
+ });
199
+ if (currentVisiblePage !== this.currentPage()) {
200
+ this.currentPage.set(currentVisiblePage);
201
+ if (this.outlinePageMap.size > 0) {
202
+ this.updateActiveOutlineByPage(currentVisiblePage);
203
+ }
204
+ }
205
+ };
206
+ afterNextRender(() => {
207
+ this.observer = new IntersectionObserver((entries) => {
208
+ entries.forEach((entry) => {
209
+ const pageNumber = Number(entry.target.getAttribute('data-page-number'));
210
+ if (!pageNumber)
211
+ return;
212
+ if (entry.isIntersecting) {
213
+ void this.renderPage(pageNumber, this.zoom());
214
+ }
215
+ else {
216
+ const state = this.pageStates.get(pageNumber);
217
+ if (state && state.rendered) {
218
+ this.clearPageCanvas(pageNumber);
219
+ }
220
+ }
221
+ });
222
+ }, {
223
+ root: this.scrollContainerRef()?.nativeElement ?? null,
224
+ rootMargin: '500px 0px',
225
+ threshold: 0,
226
+ });
227
+ const container = this.scrollContainerRef()?.nativeElement;
228
+ if (container) {
229
+ container.addEventListener('scroll', this.handleScroll);
230
+ }
231
+ if (this.numPages() > 0) {
232
+ this.initPagePlaceholders();
233
+ }
234
+ });
235
+ this.destroyRef.onDestroy(() => {
236
+ if (this.observer) {
237
+ this.observer.disconnect();
238
+ this.observer = null;
239
+ }
240
+ const container = this.scrollContainerRef()?.nativeElement;
241
+ if (container) {
242
+ container.removeEventListener('scroll', this.handleScroll);
243
+ }
244
+ this.pageStates.forEach((state) => {
245
+ if (state.renderTask) {
246
+ state.renderTask.cancel();
247
+ }
248
+ });
249
+ this.pageStates.clear();
250
+ if (this.pdfDoc) {
251
+ try {
252
+ this.pdfDoc.destroy();
253
+ }
254
+ catch {
255
+ // ignore
256
+ }
257
+ this.pdfDoc = null;
258
+ }
259
+ });
260
+ effect(() => {
261
+ this.zoom();
262
+ this.currentPage();
263
+ this.numPages();
264
+ this.showOutline();
265
+ this.outline().length;
266
+ this.emitter.notify();
267
+ });
268
+ effect(() => {
269
+ const url = this.url();
270
+ if (url) {
271
+ untracked(() => {
272
+ void this.loadPdf();
273
+ });
274
+ }
275
+ });
276
+ effect(() => {
277
+ const val = this.numPages();
278
+ if (val > 0) {
279
+ untracked(() => {
280
+ queueMicrotask(() => this.initPagePlaceholders());
281
+ });
282
+ }
283
+ });
284
+ effect(() => {
285
+ this.zoom();
286
+ if (!this.zoomWatchReady) {
287
+ this.zoomWatchReady = true;
288
+ return;
289
+ }
290
+ untracked(() => {
291
+ this.pageStates.forEach((state, pageNumber) => {
292
+ if (state.rendered) {
293
+ this.clearPageCanvas(pageNumber);
294
+ }
295
+ });
296
+ window.setTimeout(() => {
297
+ if (this.observer && this.scrollContainerRef()?.nativeElement) {
298
+ this.pageStates.forEach((state) => {
299
+ this.observer?.unobserve(state.element);
300
+ this.observer?.observe(state.element);
301
+ });
302
+ }
303
+ }, 150);
304
+ });
305
+ });
306
+ }
307
+ trust(html) {
308
+ return this.sanitizer.bypassSecurityTrustHtml(html);
309
+ }
310
+ onOutlineClick(e) {
311
+ const target = e.target;
312
+ const button = target.closest('[data-outline-key]');
313
+ if (!button)
314
+ return;
315
+ const itemKey = button.getAttribute('data-outline-key');
316
+ const destStr = button.getAttribute('data-outline-dest');
317
+ if (!itemKey || !destStr)
318
+ return;
319
+ try {
320
+ const dest = JSON.parse(destStr);
321
+ void this.handleOutlineClick(dest, itemKey);
322
+ }
323
+ catch (err) {
324
+ console.error('解析大纲目标失败:', err);
325
+ }
326
+ }
327
+ async buildOutlinePageMap(items, pdfDocument, depth = 0) {
328
+ for (let i = 0; i < items.length; i++) {
329
+ const item = items[i];
330
+ const itemKey = `${item.title}-${i}-${depth}`;
331
+ try {
332
+ let pageNumber = null;
333
+ const dest = item.dest;
334
+ if (typeof dest === 'string') {
335
+ const namedDest = await pdfDocument.getDestination?.(dest);
336
+ if (Array.isArray(namedDest) && namedDest[0] && typeof namedDest[0] === 'object') {
337
+ const idx = await pdfDocument.getPageIndex?.(namedDest[0]);
338
+ if (idx != null)
339
+ pageNumber = idx + 1;
340
+ }
341
+ }
342
+ else if (Array.isArray(dest) && dest[0] && typeof dest[0] === 'object') {
343
+ const idx = await pdfDocument.getPageIndex?.(dest[0]);
344
+ if (idx != null)
345
+ pageNumber = idx + 1;
346
+ }
347
+ if (pageNumber !== null && pageNumber > 0) {
348
+ this.outlinePageMap.set(itemKey, pageNumber);
349
+ }
350
+ if (item.items && item.items.length > 0) {
351
+ await this.buildOutlinePageMap(item.items, pdfDocument, depth + 1);
352
+ }
353
+ }
354
+ catch {
355
+ // 静默失败
356
+ }
357
+ }
358
+ }
359
+ updateActiveOutlineByPage(page) {
360
+ let closestItem = null;
361
+ let closestDistance = Infinity;
362
+ this.outlinePageMap.forEach((itemPage, itemKey) => {
363
+ if (itemPage <= page) {
364
+ const distance = page - itemPage;
365
+ if (distance < closestDistance) {
366
+ closestDistance = distance;
367
+ closestItem = itemKey;
368
+ }
369
+ }
370
+ });
371
+ if (closestItem !== this.activeOutlineItem()) {
372
+ this.activeOutlineItem.set(closestItem);
373
+ }
374
+ }
375
+ async handleOutlineClick(dest, itemKey) {
376
+ if (!this.pdfDoc || !this.scrollContainerRef()?.nativeElement)
377
+ return;
378
+ try {
379
+ let pageNumber;
380
+ if (typeof dest === 'string') {
381
+ const namedDest = await this.pdfDoc.getDestination?.(dest);
382
+ if (Array.isArray(namedDest) && namedDest[0]) {
383
+ const idx = await this.pdfDoc.getPageIndex?.(namedDest[0]);
384
+ if (idx == null)
385
+ return;
386
+ pageNumber = idx + 1;
387
+ }
388
+ else {
389
+ return;
390
+ }
391
+ }
392
+ else if (Array.isArray(dest) && dest[0]) {
393
+ const idx = await this.pdfDoc.getPageIndex?.(dest[0]);
394
+ if (idx == null)
395
+ return;
396
+ pageNumber = idx + 1;
397
+ }
398
+ else {
399
+ return;
400
+ }
401
+ this.activeOutlineItem.set(itemKey);
402
+ const pages = this.scrollContainerRef().nativeElement.querySelectorAll('[data-page-number]');
403
+ const targetPage = pages[pageNumber - 1];
404
+ if (targetPage) {
405
+ targetPage.scrollIntoView({ behavior: 'smooth', block: 'start' });
406
+ }
407
+ window.setTimeout(() => {
408
+ this.showOutline.set(false);
409
+ }, 300);
410
+ }
411
+ catch (err) {
412
+ console.error('大纲跳转失败:', err);
413
+ }
414
+ }
415
+ renderOutlineItemsHtml(items, depth = 0) {
416
+ return items.map((item, i) => {
417
+ const itemKey = `${item.title}-${i}-${depth}`;
418
+ const isActive = this.activeOutlineItem() === itemKey;
419
+ const activeClass = isActive
420
+ ? 'afp-bg-surface-2 afp-text-fg-primary afp-font-medium'
421
+ : 'afp-text-fg-secondary hover:afp-text-fg-primary hover:afp-bg-surface-2';
422
+ let html = `<li style="margin-left: ${depth > 0 ? 16 : 0}px;">
423
+ <button
424
+ data-outline-key="${itemKey}"
425
+ data-outline-dest="${JSON.stringify(item.dest).replace(/"/g, '&quot;')}"
426
+ class="afp-w-full afp-text-left afp-py-2 afp-px-3 afp-text-sm afp-rounded afp-transition-all afp-truncate ${activeClass}"
427
+ title="${item.title}"
428
+ >
429
+ ${item.title}
430
+ </button>`;
431
+ if (item.items && item.items.length > 0) {
432
+ html += `<ul>${this.renderOutlineItemsHtml(item.items, depth + 1)}</ul>`;
433
+ }
434
+ html += '</li>';
435
+ return html;
436
+ }).join('');
437
+ }
438
+ async renderPage(pageNumber, scale) {
439
+ if (!this.pdfDoc)
440
+ return;
441
+ const state = this.pageStates.get(pageNumber);
442
+ if (!state || state.rendering)
443
+ return;
444
+ state.rendering = true;
445
+ try {
446
+ const page = await this.pdfDoc.getPage(pageNumber);
447
+ const viewport = page.getViewport({ scale });
448
+ const outputScale = typeof window !== 'undefined' ? window.devicePixelRatio || 1 : 1;
449
+ // 页面盒子使用当前 PDF viewport 的真实尺寸;容器不足时再等比缩小。
450
+ // Angular 模板中的 pdf-pages 是全宽容器,若不覆盖页面宽度,所有页面都会被撑成容器宽度。
451
+ state.element.style.width = `${viewport.width}px`;
452
+ state.element.style.maxWidth = '100%';
453
+ state.element.style.aspectRatio = `${viewport.width} / ${viewport.height}`;
454
+ state.element.style.minHeight = '0';
455
+ const canvas = document.createElement('canvas');
456
+ // Canvas 位图按 DPR 放大,避免 Retina 等高 DPI 屏幕把 1x 位图插值放大后发糊。
457
+ canvas.width = Math.ceil(viewport.width * outputScale);
458
+ canvas.height = Math.ceil(viewport.height * outputScale);
459
+ // Canvas 填满与 PDF 同比例的页面盒子,高度由自身宽高比自动计算。
460
+ canvas.style.width = '100%';
461
+ canvas.style.maxWidth = '100%';
462
+ canvas.style.height = 'auto';
463
+ canvas.style.borderRadius = '0';
464
+ canvas.style.display = 'block';
465
+ const ctx = canvas.getContext('2d');
466
+ if (!ctx)
467
+ return;
468
+ const renderTask = page.render({
469
+ canvasContext: ctx,
470
+ viewport,
471
+ transform: outputScale === 1 ? undefined : [outputScale, 0, 0, outputScale, 0, 0],
472
+ });
473
+ state.renderTask = renderTask;
474
+ await renderTask.promise;
475
+ state.element.innerHTML = '';
476
+ state.element.appendChild(canvas);
477
+ state.rendered = true;
478
+ }
479
+ catch (err) {
480
+ if (err?.name !== 'RenderingCancelledException') {
481
+ console.error(`渲染页面 ${pageNumber} 失败:`, err);
482
+ }
483
+ }
484
+ finally {
485
+ state.rendering = false;
486
+ state.renderTask = null;
487
+ }
488
+ }
489
+ clearPageCanvas(pageNumber) {
490
+ const state = this.pageStates.get(pageNumber);
491
+ if (!state)
492
+ return;
493
+ if (state.renderTask) {
494
+ state.renderTask.cancel();
495
+ state.renderTask = null;
496
+ }
497
+ const canvas = state.element.querySelector('canvas');
498
+ if (canvas) {
499
+ const ctx = canvas.getContext('2d');
500
+ if (ctx) {
501
+ ctx.clearRect(0, 0, canvas.width, canvas.height);
502
+ }
503
+ }
504
+ state.element.innerHTML = '';
505
+ state.rendered = false;
506
+ state.rendering = false;
507
+ }
508
+ initPagePlaceholders() {
509
+ if (!this.pdfDoc || !this.scrollContainerRef()?.nativeElement)
510
+ return;
511
+ const wrapper = this.scrollContainerRef().nativeElement.querySelector('.pdf-pages');
512
+ if (!wrapper)
513
+ return;
514
+ wrapper.innerHTML = '';
515
+ this.pageStates.clear();
516
+ for (let i = 1; i <= this.numPages(); i++) {
517
+ const pageDiv = document.createElement('div');
518
+ pageDiv.className = 'afp-pdf-page-placeholder afp-relative afp-flex afp-justify-center';
519
+ pageDiv.setAttribute('data-page-number', String(i));
520
+ wrapper.appendChild(pageDiv);
521
+ this.pageStates.set(i, {
522
+ element: pageDiv,
523
+ rendered: false,
524
+ rendering: false,
525
+ renderTask: null,
526
+ });
527
+ if (this.observer) {
528
+ this.observer.observe(pageDiv);
529
+ }
530
+ }
531
+ }
532
+ async loadPdf() {
533
+ this.error.set(null);
534
+ this.loading.set(true);
535
+ this.numPages.set(0);
536
+ if (this.pdfDoc) {
537
+ try {
538
+ this.pdfDoc.destroy();
539
+ }
540
+ catch {
541
+ // ignore
542
+ }
543
+ this.pdfDoc = null;
544
+ }
545
+ try {
546
+ await preparePdfWorker();
547
+ const loadingTask = pdfjsLib.getDocument({
548
+ url: this.url(),
549
+ ...Yg(),
550
+ });
551
+ this.pdfDoc = await loadingTask.promise;
552
+ const total = this.pdfDoc.numPages;
553
+ this.numPages.set(total);
554
+ this.currentPage.set(1);
555
+ try {
556
+ const outlineData = await this.pdfDoc.getOutline();
557
+ if (outlineData) {
558
+ this.outline.set(outlineData);
559
+ this.outlinePageMap.clear();
560
+ await this.buildOutlinePageMap(outlineData, this.pdfDoc);
561
+ this.updateActiveOutlineByPage(this.currentPage());
562
+ }
563
+ }
564
+ catch (err) {
565
+ console.warn('PDF 大纲提取失败:', err);
566
+ }
567
+ this.loading.set(false);
568
+ }
569
+ catch (err) {
570
+ console.error('PDF 加载错误:', err);
571
+ this.error.set(this.t('pdf.load_failed'));
572
+ this.loading.set(false);
573
+ }
574
+ }
575
+ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.25", ngImport: i0, type: PdfRenderer, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
576
+ static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "19.2.25", type: PdfRenderer, isStandalone: true, selector: "afp-pdf-renderer", inputs: { url: { classPropertyName: "url", publicName: "url", isSignal: true, isRequired: true, transformFunction: null } }, host: { classAttribute: "afp-block afp-w-full afp-h-full" }, viewQueries: [{ propertyName: "scrollContainerRef", first: true, predicate: ["scrollContainerRef"], descendants: true, isSignal: true }], ngImport: i0, template: `
577
+ <div #containerRef class="afp-relative afp-w-full afp-h-full">
578
+ @if (outline().length > 0) {
579
+ <div
580
+ class="afp-absolute afp-inset-0 afp-z-20 afp-flex afp-transition-opacity afp-duration-300"
581
+ [style.opacity]="showOutline() ? 1 : 0"
582
+ [style.pointer-events]="showOutline() ? 'auto' : 'none'"
583
+ >
584
+ <div
585
+ class="afp-w-72 afp-max-w-[80%] afp-h-full afp-bg-surface-overlay afp-backdrop-blur-xl afp-border-r afp-border-line-weak afp-flex afp-flex-col afp-shadow-2xl afp-transition-transform afp-duration-300"
586
+ [style.transform]="showOutline() ? 'translateX(0)' : 'translateX(-100%)'"
587
+ >
588
+ <div class="afp-flex afp-items-center afp-justify-between afp-px-4 afp-py-3 afp-border-b afp-border-line-weak afp-flex-shrink-0">
589
+ <span class="afp-text-fg-primary afp-font-medium afp-text-sm">{{ t('toolbar.outline') }}</span>
590
+ <button
591
+ type="button"
592
+ class="afp-text-fg-tertiary hover:afp-text-fg-primary afp-transition-colors"
593
+ (click)="showOutline.set(false)"
594
+ >
595
+ <i-lucide [img]="xIcon" class="afp-w-4 afp-h-4" />
596
+ </button>
597
+ </div>
598
+ <div
599
+ class="afp-flex-1 afp-overflow-y-auto afp-py-4 afp-px-1 outline-items"
600
+ [innerHTML]="trust(outlineHtml())"
601
+ (click)="onOutlineClick($event)"
602
+ ></div>
603
+ </div>
604
+ <div
605
+ class="afp-flex-1 afp-transition-opacity afp-duration-300"
606
+ [style.background]="showOutline() ? 'rgba(0,0,0,0.3)' : 'transparent'"
607
+ (click)="showOutline.set(false)"
608
+ ></div>
609
+ </div>
610
+ }
611
+
612
+ <div #scrollContainerRef class="afp-pdf-container afp-w-full afp-h-full afp-overflow-auto">
613
+ @if (error()) {
614
+ <afp-renderer-error [message]="error()!" />
615
+ }
616
+
617
+ @if (!error() && loading()) {
618
+ <div class="afp-flex afp-items-center afp-justify-center afp-min-h-screen">
619
+ <div
620
+ class="afp-w-12 afp-h-12 afp-border-4 afp-border-line-strong afp-border-t-spinner-head afp-rounded-full afp-animate-spin"
621
+ ></div>
622
+ </div>
623
+ }
624
+
625
+ @if (!error()) {
626
+ <div class="afp-flex afp-flex-col afp-items-center afp-w-full">
627
+ <div class="pdf-pages afp-flex afp-flex-col afp-gap-4 afp-w-full afp-items-center"></div>
628
+ </div>
629
+ }
630
+ </div>
631
+ </div>
632
+ `, isInline: true, dependencies: [{ kind: "component", type: RendererError, selector: "afp-renderer-error", inputs: ["message", "detail", "showIcon"] }, { kind: "ngmodule", type: LucideAngularModule }, { kind: "component", type: i1.LucideAngularComponent, selector: "lucide-angular, lucide-icon, i-lucide, span-lucide", inputs: ["class", "name", "img", "color", "absoluteStrokeWidth", "size", "strokeWidth"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush }); }
633
+ }
634
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.25", ngImport: i0, type: PdfRenderer, decorators: [{
635
+ type: Component,
636
+ args: [{
637
+ selector: 'afp-pdf-renderer',
638
+ standalone: true,
639
+ imports: [RendererError, LucideAngularModule],
640
+ changeDetection: ChangeDetectionStrategy.OnPush,
641
+ host: { class: 'afp-block afp-w-full afp-h-full' },
642
+ template: `
643
+ <div #containerRef class="afp-relative afp-w-full afp-h-full">
644
+ @if (outline().length > 0) {
645
+ <div
646
+ class="afp-absolute afp-inset-0 afp-z-20 afp-flex afp-transition-opacity afp-duration-300"
647
+ [style.opacity]="showOutline() ? 1 : 0"
648
+ [style.pointer-events]="showOutline() ? 'auto' : 'none'"
649
+ >
650
+ <div
651
+ class="afp-w-72 afp-max-w-[80%] afp-h-full afp-bg-surface-overlay afp-backdrop-blur-xl afp-border-r afp-border-line-weak afp-flex afp-flex-col afp-shadow-2xl afp-transition-transform afp-duration-300"
652
+ [style.transform]="showOutline() ? 'translateX(0)' : 'translateX(-100%)'"
653
+ >
654
+ <div class="afp-flex afp-items-center afp-justify-between afp-px-4 afp-py-3 afp-border-b afp-border-line-weak afp-flex-shrink-0">
655
+ <span class="afp-text-fg-primary afp-font-medium afp-text-sm">{{ t('toolbar.outline') }}</span>
656
+ <button
657
+ type="button"
658
+ class="afp-text-fg-tertiary hover:afp-text-fg-primary afp-transition-colors"
659
+ (click)="showOutline.set(false)"
660
+ >
661
+ <i-lucide [img]="xIcon" class="afp-w-4 afp-h-4" />
662
+ </button>
663
+ </div>
664
+ <div
665
+ class="afp-flex-1 afp-overflow-y-auto afp-py-4 afp-px-1 outline-items"
666
+ [innerHTML]="trust(outlineHtml())"
667
+ (click)="onOutlineClick($event)"
668
+ ></div>
669
+ </div>
670
+ <div
671
+ class="afp-flex-1 afp-transition-opacity afp-duration-300"
672
+ [style.background]="showOutline() ? 'rgba(0,0,0,0.3)' : 'transparent'"
673
+ (click)="showOutline.set(false)"
674
+ ></div>
675
+ </div>
676
+ }
677
+
678
+ <div #scrollContainerRef class="afp-pdf-container afp-w-full afp-h-full afp-overflow-auto">
679
+ @if (error()) {
680
+ <afp-renderer-error [message]="error()!" />
681
+ }
682
+
683
+ @if (!error() && loading()) {
684
+ <div class="afp-flex afp-items-center afp-justify-center afp-min-h-screen">
685
+ <div
686
+ class="afp-w-12 afp-h-12 afp-border-4 afp-border-line-strong afp-border-t-spinner-head afp-rounded-full afp-animate-spin"
687
+ ></div>
688
+ </div>
689
+ }
690
+
691
+ @if (!error()) {
692
+ <div class="afp-flex afp-flex-col afp-items-center afp-w-full">
693
+ <div class="pdf-pages afp-flex afp-flex-col afp-gap-4 afp-w-full afp-items-center"></div>
694
+ </div>
695
+ }
696
+ </div>
697
+ </div>
698
+ `,
699
+ }]
700
+ }], ctorParameters: () => [] });
701
+
702
+ export { PdfRenderer };
703
+ //# sourceMappingURL=eternalheart-angular-file-preview-eternalheart-angular-file-preview-index-6dZS_Cfs-B1itLV5R.mjs.map