svelte-pdf-view 0.3.2 → 0.3.3

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.
@@ -6,7 +6,7 @@
6
6
  * - Browser fullscreen mode
7
7
  * - Black background
8
8
  * - Single page view scaled to fit screen
9
- * - No text layer or toolbar
9
+ * - Annotation layer for clickable links
10
10
  * - Mouse/keyboard/touch navigation
11
11
  */
12
12
  import type { PDFDocumentProxy } from 'pdfjs-dist/legacy/build/pdf.mjs';
@@ -25,15 +25,23 @@ export declare class PdfPresentationMode {
25
25
  private pdfDocument;
26
26
  private currentPageNumber;
27
27
  private totalPages;
28
+ private hostElement;
29
+ private shadowRoot;
28
30
  private container;
31
+ private pageWrapper;
29
32
  private canvas;
33
+ private annotationLayerDiv;
30
34
  private callbacks;
35
+ private annotationLayer;
36
+ private linkService;
37
+ private eventBus;
31
38
  private fullscreenChangeAbortController;
32
39
  private windowAbortController;
33
40
  private mouseScrollTimeStamp;
34
41
  private mouseScrollDelta;
35
42
  private touchSwipeState;
36
43
  private renderingPage;
44
+ private currentViewport;
37
45
  constructor(callbacks?: PresentationModeCallbacks);
38
46
  /**
39
47
  * Set the PDF document for presentation
@@ -76,8 +84,10 @@ export declare class PdfPresentationMode {
76
84
  */
77
85
  destroy(): void;
78
86
  private createPresentationContainer;
87
+ private getPresentationStyles;
79
88
  private destroyPresentationContainer;
80
89
  private renderCurrentPage;
90
+ private renderAnnotationLayer;
81
91
  private notifyStateChange;
82
92
  private enter;
83
93
  private doExit;
@@ -12,6 +12,16 @@
12
12
  * See the License for the specific language governing permissions and
13
13
  * limitations under the License.
14
14
  */
15
+ import { EventBus } from './EventBus.js';
16
+ import { SimpleLinkService } from './SimpleLinkService.js';
17
+ // Dynamically imported
18
+ let AnnotationLayer;
19
+ async function ensureAnnotationLayerLoaded() {
20
+ if (!AnnotationLayer) {
21
+ const pdfjs = await import('pdfjs-dist/legacy/build/pdf.mjs');
22
+ AnnotationLayer = pdfjs.AnnotationLayer;
23
+ }
24
+ }
15
25
  export var PresentationModeState;
16
26
  (function (PresentationModeState) {
17
27
  PresentationModeState[PresentationModeState["UNKNOWN"] = 0] = "UNKNOWN";
@@ -28,15 +38,25 @@ export class PdfPresentationMode {
28
38
  pdfDocument = null;
29
39
  currentPageNumber = 1;
30
40
  totalPages = 0;
41
+ hostElement = null;
42
+ shadowRoot = null;
31
43
  container = null;
44
+ pageWrapper = null;
32
45
  canvas = null;
46
+ annotationLayerDiv = null;
33
47
  callbacks;
48
+ // Annotation layer support
49
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
50
+ annotationLayer = null;
51
+ linkService = null;
52
+ eventBus = null;
34
53
  fullscreenChangeAbortController = null;
35
54
  windowAbortController = null;
36
55
  mouseScrollTimeStamp = 0;
37
56
  mouseScrollDelta = 0;
38
57
  touchSwipeState = null;
39
58
  renderingPage = false;
59
+ currentViewport = null;
40
60
  constructor(callbacks = {}) {
41
61
  this.callbacks = callbacks;
42
62
  }
@@ -149,37 +169,133 @@ export class PdfPresentationMode {
149
169
  }
150
170
  // Private methods
151
171
  createPresentationContainer() {
152
- // Create fullscreen container
153
- this.container = document.createElement('div');
154
- this.container.className = 'pdf-presentation-mode';
155
- this.container.style.cssText = `
172
+ // Create event bus and link service for annotations
173
+ this.eventBus = new EventBus();
174
+ this.linkService = new SimpleLinkService({
175
+ eventBus: this.eventBus
176
+ });
177
+ // Override goToPage to navigate within presentation
178
+ const originalGoToPage = this.linkService.goToPage.bind(this.linkService);
179
+ this.linkService.goToPage = (pageNumber) => {
180
+ // Navigate within presentation mode
181
+ this.goToPage(pageNumber);
182
+ // Also call original for event dispatch
183
+ originalGoToPage(pageNumber);
184
+ };
185
+ // Set pagesCount getter for link service
186
+ Object.defineProperty(this.linkService, 'pagesCount', {
187
+ get: () => this.totalPages
188
+ });
189
+ // Create host element for Shadow DOM
190
+ this.hostElement = document.createElement('div');
191
+ this.hostElement.style.cssText = `
156
192
  position: fixed;
157
193
  top: 0;
158
194
  left: 0;
159
195
  width: 100%;
160
196
  height: 100%;
161
- background-color: #000;
162
- display: flex;
163
- align-items: center;
164
- justify-content: center;
165
197
  z-index: 999999;
166
198
  `;
199
+ // Create Shadow DOM
200
+ this.shadowRoot = this.hostElement.attachShadow({ mode: 'open' });
201
+ // Inject styles into Shadow DOM
202
+ const styleEl = document.createElement('style');
203
+ styleEl.textContent = this.getPresentationStyles();
204
+ this.shadowRoot.appendChild(styleEl);
205
+ // Create fullscreen container inside Shadow DOM
206
+ this.container = document.createElement('div');
207
+ this.container.className = 'pdf-presentation-container';
208
+ // Create page wrapper (holds canvas + annotation layer)
209
+ this.pageWrapper = document.createElement('div');
210
+ this.pageWrapper.className = 'page-wrapper';
167
211
  // Create canvas for rendering
168
212
  this.canvas = document.createElement('canvas');
169
- this.canvas.style.cssText = `
170
- max-width: 100%;
171
- max-height: 100%;
172
- object-fit: contain;
213
+ this.canvas.className = 'presentation-canvas';
214
+ // Create annotation layer div
215
+ this.annotationLayerDiv = document.createElement('div');
216
+ this.annotationLayerDiv.className = 'annotationLayer';
217
+ this.pageWrapper.appendChild(this.canvas);
218
+ this.pageWrapper.appendChild(this.annotationLayerDiv);
219
+ this.container.appendChild(this.pageWrapper);
220
+ this.shadowRoot.appendChild(this.container);
221
+ document.body.appendChild(this.hostElement);
222
+ }
223
+ getPresentationStyles() {
224
+ return `
225
+ .pdf-presentation-container {
226
+ width: 100%;
227
+ height: 100%;
228
+ background-color: #000;
229
+ display: flex;
230
+ align-items: center;
231
+ justify-content: center;
232
+ }
233
+
234
+ .page-wrapper {
235
+ position: relative;
236
+ }
237
+
238
+ .presentation-canvas {
239
+ display: block;
240
+ position: relative;
241
+ z-index: 0;
242
+ }
243
+
244
+ .annotationLayer {
245
+ position: absolute;
246
+ top: 0;
247
+ left: 0;
248
+ pointer-events: none;
249
+ transform-origin: 0 0;
250
+ z-index: 1;
251
+ }
252
+
253
+ .annotationLayer section {
254
+ position: absolute;
255
+ text-align: initial;
256
+ pointer-events: auto;
257
+ box-sizing: border-box;
258
+ transform-origin: 0 0;
259
+ user-select: none;
260
+ }
261
+
262
+ .annotationLayer .linkAnnotation > a,
263
+ .annotationLayer .buttonWidgetAnnotation.pushButton > a {
264
+ position: absolute;
265
+ font-size: 1em;
266
+ top: 0;
267
+ left: 0;
268
+ width: 100%;
269
+ height: 100%;
270
+ }
271
+
272
+ .annotationLayer .linkAnnotation > a:hover,
273
+ .annotationLayer .buttonWidgetAnnotation.pushButton > a:hover {
274
+ opacity: 0.2;
275
+ background-color: rgb(255 255 0);
276
+ box-shadow: 0 2px 10px rgb(255 255 0);
277
+ }
278
+
279
+ .annotationLayer .linkAnnotation.hasBorder:hover {
280
+ background-color: rgb(255 255 0 / 0.2);
281
+ }
173
282
  `;
174
- this.container.appendChild(this.canvas);
175
- document.body.appendChild(this.container);
176
283
  }
177
284
  destroyPresentationContainer() {
178
- if (this.container) {
179
- this.container.remove();
285
+ if (this.hostElement) {
286
+ this.hostElement.remove();
287
+ this.hostElement = null;
288
+ this.shadowRoot = null;
180
289
  this.container = null;
290
+ this.pageWrapper = null;
181
291
  this.canvas = null;
292
+ this.annotationLayerDiv = null;
182
293
  }
294
+ this.annotationLayer = null;
295
+ this.linkService = null;
296
+ this.eventBus?.destroy();
297
+ this.eventBus = null;
298
+ this.currentViewport = null;
183
299
  }
184
300
  async renderCurrentPage() {
185
301
  if (!this.pdfDocument || !this.canvas || !this.container || this.renderingPage) {
@@ -199,6 +315,7 @@ export class PdfPresentationMode {
199
315
  const scaleY = containerHeight / pageHeight;
200
316
  const scale = Math.min(scaleX, scaleY);
201
317
  const scaledViewport = page.getViewport({ scale, rotation: 0 });
318
+ this.currentViewport = scaledViewport;
202
319
  // Set canvas size
203
320
  this.canvas.width = scaledViewport.width;
204
321
  this.canvas.height = scaledViewport.height;
@@ -214,6 +331,13 @@ export class PdfPresentationMode {
214
331
  viewport: scaledViewport,
215
332
  canvas: this.canvas
216
333
  }).promise;
334
+ // Set page wrapper size to match canvas
335
+ if (this.pageWrapper) {
336
+ this.pageWrapper.style.width = `${this.canvas.width}px`;
337
+ this.pageWrapper.style.height = `${this.canvas.height}px`;
338
+ }
339
+ // Render annotation layer
340
+ await this.renderAnnotationLayer(page, scaledViewport);
217
341
  }
218
342
  catch (e) {
219
343
  console.error('Failed to render presentation page:', e);
@@ -222,6 +346,63 @@ export class PdfPresentationMode {
222
346
  this.renderingPage = false;
223
347
  }
224
348
  }
349
+ async renderAnnotationLayer(page, viewport) {
350
+ if (!this.annotationLayerDiv || !this.linkService) {
351
+ return;
352
+ }
353
+ try {
354
+ await ensureAnnotationLayerLoaded();
355
+ // Clear previous annotations
356
+ this.annotationLayerDiv.innerHTML = '';
357
+ const width = Math.floor(viewport.width);
358
+ const height = Math.floor(viewport.height);
359
+ // Set CSS variables that PDF.js AnnotationLayer expects
360
+ // (it uses these for CSS round() function in dimensions)
361
+ this.annotationLayerDiv.style.setProperty('--scale-factor', '1');
362
+ this.annotationLayerDiv.style.setProperty('--total-scale-factor', '1');
363
+ this.annotationLayerDiv.style.setProperty('--scale-round-x', '1px');
364
+ this.annotationLayerDiv.style.setProperty('--scale-round-y', '1px');
365
+ // Get annotations
366
+ const annotations = await page.getAnnotations({ intent: 'display' });
367
+ if (annotations.length === 0) {
368
+ return;
369
+ }
370
+ // Set document on link service
371
+ this.linkService.setDocument(this.pdfDocument);
372
+ // Create annotation layer
373
+ this.annotationLayer = new AnnotationLayer({
374
+ div: this.annotationLayerDiv,
375
+ accessibilityManager: null,
376
+ annotationCanvasMap: null,
377
+ annotationEditorUIManager: null,
378
+ page: page,
379
+ viewport: viewport.clone({ dontFlip: true }),
380
+ structTreeLayer: null,
381
+ commentManager: null,
382
+ linkService: this.linkService,
383
+ annotationStorage: null
384
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
385
+ });
386
+ // Render annotations
387
+ await this.annotationLayer.render({
388
+ annotations,
389
+ imageResourcesPath: '',
390
+ renderForms: false, // No forms in presentation mode
391
+ linkService: this.linkService,
392
+ downloadManager: undefined,
393
+ annotationStorage: null,
394
+ enableScripting: false,
395
+ hasJSActions: false,
396
+ fieldObjects: null
397
+ });
398
+ // Override dimensions AFTER render (PDF.js sets CSS variable-based dimensions)
399
+ this.annotationLayerDiv.style.width = `${width}px`;
400
+ this.annotationLayerDiv.style.height = `${height}px`;
401
+ }
402
+ catch (e) {
403
+ console.error('Failed to render presentation annotation layer:', e);
404
+ }
405
+ }
225
406
  notifyStateChange(newState) {
226
407
  this.state = newState;
227
408
  this.callbacks.onStateChange?.(newState);
@@ -281,6 +462,12 @@ export class PdfPresentationMode {
281
462
  return delta / 30;
282
463
  }
283
464
  handleMouseDown = (evt) => {
465
+ // Check if click is on an annotation link
466
+ const target = evt.target;
467
+ if (target.closest('.annotationLayer a')) {
468
+ // Don't interfere with link clicks
469
+ return;
470
+ }
284
471
  // Left click (0) = next page, Right click (2) = previous page
285
472
  if (evt.button === 0) {
286
473
  evt.preventDefault();
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "svelte-pdf-view",
3
- "version": "0.3.2",
3
+ "version": "0.3.3",
4
4
  "description": "A modern, modular PDF viewer component for Svelte 5. Built on PDF.js with TypeScript support",
5
5
  "author": "Louis Li",
6
6
  "license": "Apache-2.0",