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
|
-
* -
|
|
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
|
|
153
|
-
this.
|
|
154
|
-
this.
|
|
155
|
-
|
|
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.
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
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.
|
|
179
|
-
this.
|
|
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();
|