svelte-pdf-view 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE.md +13 -0
- package/README.md +293 -0
- package/dist/PdfRenderer.svelte +238 -0
- package/dist/PdfRenderer.svelte.d.ts +21 -0
- package/dist/PdfToolbar.svelte +229 -0
- package/dist/PdfToolbar.svelte.d.ts +3 -0
- package/dist/PdfViewer.svelte +118 -0
- package/dist/PdfViewer.svelte.d.ts +17 -0
- package/dist/PdfViewerInner.svelte +302 -0
- package/dist/PdfViewerInner.svelte.d.ts +11 -0
- package/dist/index.d.ts +3 -0
- package/dist/index.js +4 -0
- package/dist/pdf-viewer/EventBus.d.ts +12 -0
- package/dist/pdf-viewer/EventBus.js +42 -0
- package/dist/pdf-viewer/FindController.d.ts +53 -0
- package/dist/pdf-viewer/FindController.js +423 -0
- package/dist/pdf-viewer/PDFPageView.d.ts +58 -0
- package/dist/pdf-viewer/PDFPageView.js +281 -0
- package/dist/pdf-viewer/PDFViewerCore.d.ts +45 -0
- package/dist/pdf-viewer/PDFViewerCore.js +225 -0
- package/dist/pdf-viewer/context.d.ts +31 -0
- package/dist/pdf-viewer/context.js +15 -0
- package/dist/pdf-viewer/renderer-styles.css +203 -0
- package/dist/pdf-viewer/styles.css +281 -0
- package/package.json +88 -0
package/LICENSE.md
ADDED
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
Copyright 2025 Louis Li
|
|
2
|
+
|
|
3
|
+
Licensed under the Apache License, Version 2.0 (the "License");
|
|
4
|
+
you may not use this file except in compliance with the License.
|
|
5
|
+
You may obtain a copy of the License at
|
|
6
|
+
|
|
7
|
+
http://www.apache.org/licenses/LICENSE-2.0
|
|
8
|
+
|
|
9
|
+
Unless required by applicable law or agreed to in writing, software
|
|
10
|
+
distributed under the License is distributed on an "AS IS" BASIS,
|
|
11
|
+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
12
|
+
See the License for the specific language governing permissions and
|
|
13
|
+
limitations under the License.
|
package/README.md
ADDED
|
@@ -0,0 +1,293 @@
|
|
|
1
|
+
# svelte-pdf-view
|
|
2
|
+
|
|
3
|
+
A modern, modular PDF viewer component for Svelte 5. Built on top of [PDF.js](https://mozilla.github.io/pdf.js/), with full TypeScript support and Shadow DOM isolation.
|
|
4
|
+
|
|
5
|
+
## Features
|
|
6
|
+
|
|
7
|
+
- **PDF Rendering** - High-quality PDF rendering powered by PDF.js
|
|
8
|
+
- **Text Search** - Full-text search with highlight navigation
|
|
9
|
+
- **Rotation** - Rotate pages clockwise/counter-clockwise
|
|
10
|
+
- **Zoom** - Zoom in/out controls
|
|
11
|
+
- **Responsive** - Works on desktop and mobile
|
|
12
|
+
- **Customizable** - Style the scrollbar, background, and more
|
|
13
|
+
- **Modular** - Use the default layout or build your own toolbar
|
|
14
|
+
- **Shadow DOM** - Renderer styles are isolated and won't leak
|
|
15
|
+
- **Multiple Sources** - Load from URL, ArrayBuffer, Uint8Array, or Blob
|
|
16
|
+
|
|
17
|
+
## Installation
|
|
18
|
+
|
|
19
|
+
```bash
|
|
20
|
+
npm install svelte-pdf-view
|
|
21
|
+
# or
|
|
22
|
+
pnpm add svelte-pdf-view
|
|
23
|
+
# or
|
|
24
|
+
yarn add svelte-pdf-view
|
|
25
|
+
```
|
|
26
|
+
|
|
27
|
+
If you want to use the default `<PdfToolbar>` component, also install:
|
|
28
|
+
|
|
29
|
+
```bash
|
|
30
|
+
npm install @lucide/svelte
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
## Quick Start
|
|
34
|
+
|
|
35
|
+
### Basic Usage
|
|
36
|
+
|
|
37
|
+
```svelte
|
|
38
|
+
<script lang="ts">
|
|
39
|
+
import { PdfViewer, PdfToolbar, PdfRenderer } from 'svelte-pdf-view';
|
|
40
|
+
</script>
|
|
41
|
+
|
|
42
|
+
<div style="height: 100vh;">
|
|
43
|
+
<PdfViewer src="/document.pdf">
|
|
44
|
+
<PdfToolbar />
|
|
45
|
+
<PdfRenderer src="/document.pdf" />
|
|
46
|
+
</PdfViewer>
|
|
47
|
+
</div>
|
|
48
|
+
```
|
|
49
|
+
|
|
50
|
+
### Loading from Different Sources
|
|
51
|
+
|
|
52
|
+
```svelte
|
|
53
|
+
<script lang="ts">
|
|
54
|
+
import { PdfViewer, PdfToolbar, PdfRenderer, type PdfSource } from 'svelte-pdf-view';
|
|
55
|
+
|
|
56
|
+
// From URL
|
|
57
|
+
let pdfSource: PdfSource = '/document.pdf';
|
|
58
|
+
|
|
59
|
+
// From file input
|
|
60
|
+
function handleFileSelect(e: Event) {
|
|
61
|
+
const file = (e.target as HTMLInputElement).files?.[0];
|
|
62
|
+
if (file) {
|
|
63
|
+
pdfSource = file; // Blob works directly
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
// From fetch response
|
|
68
|
+
async function loadFromApi() {
|
|
69
|
+
const response = await fetch('/api/document');
|
|
70
|
+
const arrayBuffer = await response.arrayBuffer();
|
|
71
|
+
pdfSource = arrayBuffer;
|
|
72
|
+
}
|
|
73
|
+
</script>
|
|
74
|
+
|
|
75
|
+
<input type="file" accept=".pdf" onchange={handleFileSelect} />
|
|
76
|
+
|
|
77
|
+
<PdfViewer src={pdfSource}>
|
|
78
|
+
<PdfToolbar />
|
|
79
|
+
<PdfRenderer src={pdfSource} />
|
|
80
|
+
</PdfViewer>
|
|
81
|
+
```
|
|
82
|
+
|
|
83
|
+
## Components
|
|
84
|
+
|
|
85
|
+
### `<PdfViewer>`
|
|
86
|
+
|
|
87
|
+
The main container component that provides context for toolbar and renderer.
|
|
88
|
+
|
|
89
|
+
```svelte
|
|
90
|
+
<PdfViewer src={pdfSource} scale={1.0} class="my-viewer">
|
|
91
|
+
<!-- Children -->
|
|
92
|
+
</PdfViewer>
|
|
93
|
+
```
|
|
94
|
+
|
|
95
|
+
| Prop | Type | Default | Description |
|
|
96
|
+
| ------- | ----------- | -------- | -------------------------------------------------- |
|
|
97
|
+
| `src` | `PdfSource` | required | PDF source (URL, ArrayBuffer, Uint8Array, or Blob) |
|
|
98
|
+
| `scale` | `number` | `1.0` | Initial zoom scale |
|
|
99
|
+
| `class` | `string` | `''` | CSS class for the container |
|
|
100
|
+
|
|
101
|
+
### `<PdfToolbar>`
|
|
102
|
+
|
|
103
|
+
The default toolbar with page navigation, zoom, rotation, and search controls.
|
|
104
|
+
|
|
105
|
+
```svelte
|
|
106
|
+
<PdfToolbar />
|
|
107
|
+
```
|
|
108
|
+
|
|
109
|
+
The toolbar automatically connects to the viewer context - no props needed.
|
|
110
|
+
|
|
111
|
+
### `<PdfRenderer>`
|
|
112
|
+
|
|
113
|
+
The PDF rendering component. Uses Shadow DOM for style isolation.
|
|
114
|
+
|
|
115
|
+
```svelte
|
|
116
|
+
<PdfRenderer
|
|
117
|
+
src={pdfSource}
|
|
118
|
+
backgroundColor="#e8e8e8"
|
|
119
|
+
scrollbarThumbColor="#c1c1c1"
|
|
120
|
+
scrollbarTrackColor="#f1f1f1"
|
|
121
|
+
scrollbarThumbHoverColor="#a1a1a1"
|
|
122
|
+
scrollbarWidth="10px"
|
|
123
|
+
pageShadow="0 2px 8px rgba(0, 0, 0, 0.12)"
|
|
124
|
+
/>
|
|
125
|
+
```
|
|
126
|
+
|
|
127
|
+
| Prop | Type | Default | Description |
|
|
128
|
+
| -------------------------- | ----------- | ----------------------------------- | ------------------------------------ |
|
|
129
|
+
| `src` | `PdfSource` | required | PDF source |
|
|
130
|
+
| `backgroundColor` | `string` | `'#e8e8e8'` | Background color of scroll container |
|
|
131
|
+
| `pageShadow` | `string` | `'0 2px 8px rgba(0,0,0,0.12), ...'` | Box shadow for PDF pages |
|
|
132
|
+
| `scrollbarTrackColor` | `string` | `'#f1f1f1'` | Scrollbar track background |
|
|
133
|
+
| `scrollbarThumbColor` | `string` | `'#c1c1c1'` | Scrollbar thumb color |
|
|
134
|
+
| `scrollbarThumbHoverColor` | `string` | `'#a1a1a1'` | Scrollbar thumb hover color |
|
|
135
|
+
| `scrollbarWidth` | `string` | `'10px'` | Scrollbar width |
|
|
136
|
+
|
|
137
|
+
## Custom Toolbar
|
|
138
|
+
|
|
139
|
+
You can create your own toolbar using the context API:
|
|
140
|
+
|
|
141
|
+
```svelte
|
|
142
|
+
<script lang="ts">
|
|
143
|
+
import { PdfViewer, PdfRenderer, getPdfViewerContext } from 'svelte-pdf-view';
|
|
144
|
+
</script>
|
|
145
|
+
|
|
146
|
+
<PdfViewer src="/document.pdf">
|
|
147
|
+
<MyCustomToolbar />
|
|
148
|
+
<PdfRenderer src="/document.pdf" />
|
|
149
|
+
</PdfViewer>
|
|
150
|
+
```
|
|
151
|
+
|
|
152
|
+
```svelte
|
|
153
|
+
<!-- MyCustomToolbar.svelte -->
|
|
154
|
+
<script lang="ts">
|
|
155
|
+
import { getPdfViewerContext } from 'svelte-pdf-view';
|
|
156
|
+
|
|
157
|
+
const { state, actions } = getPdfViewerContext();
|
|
158
|
+
</script>
|
|
159
|
+
|
|
160
|
+
<div class="toolbar">
|
|
161
|
+
<span>Page {state.currentPage} of {state.totalPages}</span>
|
|
162
|
+
|
|
163
|
+
<button onclick={() => actions.zoomOut()}>-</button>
|
|
164
|
+
<span>{Math.round(state.scale * 100)}%</span>
|
|
165
|
+
<button onclick={() => actions.zoomIn()}>+</button>
|
|
166
|
+
|
|
167
|
+
<button onclick={() => actions.rotateCounterClockwise()}>↺</button>
|
|
168
|
+
<button onclick={() => actions.rotateClockwise()}>↻</button>
|
|
169
|
+
</div>
|
|
170
|
+
|
|
171
|
+
<style>
|
|
172
|
+
/* Your custom styles */
|
|
173
|
+
</style>
|
|
174
|
+
```
|
|
175
|
+
|
|
176
|
+
### Context API
|
|
177
|
+
|
|
178
|
+
#### `state: PdfViewerState`
|
|
179
|
+
|
|
180
|
+
| Property | Type | Description |
|
|
181
|
+
| --------------- | ---------------- | ---------------------------------- |
|
|
182
|
+
| `loading` | `boolean` | Whether the PDF is loading |
|
|
183
|
+
| `error` | `string \| null` | Error message if loading failed |
|
|
184
|
+
| `totalPages` | `number` | Total number of pages |
|
|
185
|
+
| `currentPage` | `number` | Current visible page |
|
|
186
|
+
| `scale` | `number` | Current zoom scale |
|
|
187
|
+
| `rotation` | `number` | Current rotation (0, 90, 180, 270) |
|
|
188
|
+
| `searchQuery` | `string` | Current search query |
|
|
189
|
+
| `searchCurrent` | `number` | Current match index |
|
|
190
|
+
| `searchTotal` | `number` | Total number of matches |
|
|
191
|
+
| `isSearching` | `boolean` | Whether search is in progress |
|
|
192
|
+
|
|
193
|
+
#### `actions: PdfViewerActions`
|
|
194
|
+
|
|
195
|
+
| Method | Description |
|
|
196
|
+
| -------------------------- | ---------------------------- |
|
|
197
|
+
| `zoomIn()` | Increase zoom level |
|
|
198
|
+
| `zoomOut()` | Decrease zoom level |
|
|
199
|
+
| `setScale(scale: number)` | Set specific zoom scale |
|
|
200
|
+
| `rotateClockwise()` | Rotate 90° clockwise |
|
|
201
|
+
| `rotateCounterClockwise()` | Rotate 90° counter-clockwise |
|
|
202
|
+
| `goToPage(page: number)` | Navigate to specific page |
|
|
203
|
+
| `search(query: string)` | Search for text |
|
|
204
|
+
| `searchNext()` | Go to next search match |
|
|
205
|
+
| `searchPrevious()` | Go to previous search match |
|
|
206
|
+
| `clearSearch()` | Clear search highlights |
|
|
207
|
+
|
|
208
|
+
## Types
|
|
209
|
+
|
|
210
|
+
```typescript
|
|
211
|
+
// PDF source can be URL, ArrayBuffer, Uint8Array, or Blob
|
|
212
|
+
type PdfSource = string | ArrayBuffer | Uint8Array | Blob;
|
|
213
|
+
|
|
214
|
+
interface PdfViewerState {
|
|
215
|
+
loading: boolean;
|
|
216
|
+
error: string | null;
|
|
217
|
+
totalPages: number;
|
|
218
|
+
currentPage: number;
|
|
219
|
+
scale: number;
|
|
220
|
+
rotation: number;
|
|
221
|
+
searchQuery: string;
|
|
222
|
+
searchCurrent: number;
|
|
223
|
+
searchTotal: number;
|
|
224
|
+
isSearching: boolean;
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
interface PdfViewerActions {
|
|
228
|
+
zoomIn: () => void;
|
|
229
|
+
zoomOut: () => void;
|
|
230
|
+
setScale: (scale: number) => void;
|
|
231
|
+
rotateClockwise: () => void;
|
|
232
|
+
rotateCounterClockwise: () => void;
|
|
233
|
+
goToPage: (page: number) => void;
|
|
234
|
+
search: (query: string) => Promise<void>;
|
|
235
|
+
searchNext: () => void;
|
|
236
|
+
searchPrevious: () => void;
|
|
237
|
+
clearSearch: () => void;
|
|
238
|
+
}
|
|
239
|
+
```
|
|
240
|
+
|
|
241
|
+
## Styling Examples
|
|
242
|
+
|
|
243
|
+
### Dark Theme
|
|
244
|
+
|
|
245
|
+
```svelte
|
|
246
|
+
<PdfRenderer
|
|
247
|
+
src={pdfSource}
|
|
248
|
+
backgroundColor="#1a1a1a"
|
|
249
|
+
scrollbarTrackColor="#2d2d2d"
|
|
250
|
+
scrollbarThumbColor="#555"
|
|
251
|
+
scrollbarThumbHoverColor="#666"
|
|
252
|
+
pageShadow="0 4px 12px rgba(0, 0, 0, 0.5)"
|
|
253
|
+
/>
|
|
254
|
+
```
|
|
255
|
+
|
|
256
|
+
### Minimal Theme
|
|
257
|
+
|
|
258
|
+
```svelte
|
|
259
|
+
<PdfRenderer
|
|
260
|
+
src={pdfSource}
|
|
261
|
+
backgroundColor="#ffffff"
|
|
262
|
+
scrollbarWidth="6px"
|
|
263
|
+
scrollbarTrackColor="transparent"
|
|
264
|
+
scrollbarThumbColor="#ddd"
|
|
265
|
+
pageShadow="none"
|
|
266
|
+
/>
|
|
267
|
+
```
|
|
268
|
+
|
|
269
|
+
## Browser Support
|
|
270
|
+
|
|
271
|
+
- Chrome/Edge 88+
|
|
272
|
+
- Firefox 78+
|
|
273
|
+
- Safari 14+
|
|
274
|
+
|
|
275
|
+
## License
|
|
276
|
+
|
|
277
|
+
Apache 2.0
|
|
278
|
+
|
|
279
|
+
## Attribution
|
|
280
|
+
|
|
281
|
+
This project is built on top of [PDF.js](https://mozilla.github.io/pdf.js/) by Mozilla, licensed under the Apache License 2.0.
|
|
282
|
+
|
|
283
|
+
Several files in this project are derivative works based on PDF.js viewer source code:
|
|
284
|
+
|
|
285
|
+
| File | Based on |
|
|
286
|
+
| --------------------- | ------------------------------------------------------- |
|
|
287
|
+
| `EventBus.ts` | `web/event_utils.js` |
|
|
288
|
+
| `FindController.ts` | `web/pdf_find_controller.js`, `web/text_highlighter.js` |
|
|
289
|
+
| `PDFPageView.ts` | `web/pdf_page_view.js` |
|
|
290
|
+
| `PDFViewerCore.ts` | `web/pdf_viewer.js` |
|
|
291
|
+
| `renderer-styles.css` | `web/text_layer_builder.css` |
|
|
292
|
+
|
|
293
|
+
These files retain the original Apache 2.0 license headers with attribution to the Mozilla Foundation.
|
|
@@ -0,0 +1,238 @@
|
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
import { onDestroy, onMount } from 'svelte';
|
|
3
|
+
|
|
4
|
+
const browser = typeof window !== 'undefined';
|
|
5
|
+
import { getPdfViewerContext, type PdfViewerActions } from './pdf-viewer/context.js';
|
|
6
|
+
import rendererStyles from './pdf-viewer/renderer-styles.css?raw';
|
|
7
|
+
|
|
8
|
+
/** PDF source - can be a URL string, ArrayBuffer, Uint8Array, or Blob */
|
|
9
|
+
export type PdfSource = string | ArrayBuffer | Uint8Array | Blob;
|
|
10
|
+
|
|
11
|
+
interface Props {
|
|
12
|
+
/** PDF source - URL string, ArrayBuffer, Uint8Array, or Blob */
|
|
13
|
+
src: PdfSource;
|
|
14
|
+
/** Background color of the scroll container */
|
|
15
|
+
backgroundColor?: string;
|
|
16
|
+
/** Page shadow style */
|
|
17
|
+
pageShadow?: string;
|
|
18
|
+
/** Scrollbar track color */
|
|
19
|
+
scrollbarTrackColor?: string;
|
|
20
|
+
/** Scrollbar thumb color */
|
|
21
|
+
scrollbarThumbColor?: string;
|
|
22
|
+
/** Scrollbar thumb hover color */
|
|
23
|
+
scrollbarThumbHoverColor?: string;
|
|
24
|
+
/** Scrollbar width */
|
|
25
|
+
scrollbarWidth?: string;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
let {
|
|
29
|
+
src,
|
|
30
|
+
backgroundColor = '#e8e8e8',
|
|
31
|
+
pageShadow = '0 2px 8px rgba(0, 0, 0, 0.12), 0 1px 3px rgba(0, 0, 0, 0.08)',
|
|
32
|
+
scrollbarTrackColor = '#f1f1f1',
|
|
33
|
+
scrollbarThumbColor = '#c1c1c1',
|
|
34
|
+
scrollbarThumbHoverColor = '#a1a1a1',
|
|
35
|
+
scrollbarWidth = '10px'
|
|
36
|
+
}: Props = $props();
|
|
37
|
+
|
|
38
|
+
const { state: viewerState, _registerRenderer } = getPdfViewerContext();
|
|
39
|
+
|
|
40
|
+
let hostEl: HTMLDivElement | undefined = $state();
|
|
41
|
+
let shadowRoot: ShadowRoot | null = null;
|
|
42
|
+
let scrollContainerEl: HTMLDivElement | null = null;
|
|
43
|
+
let mounted = $state(false);
|
|
44
|
+
|
|
45
|
+
// Core instances
|
|
46
|
+
let viewer: import('./pdf-viewer/PDFViewerCore.js').PDFViewerCore | null = null;
|
|
47
|
+
let findController: import('./pdf-viewer/FindController.js').FindController | null = null;
|
|
48
|
+
let pdfjsLib: typeof import('pdfjs-dist') | null = null;
|
|
49
|
+
|
|
50
|
+
async function initPdfJs() {
|
|
51
|
+
if (!browser) return null;
|
|
52
|
+
|
|
53
|
+
pdfjsLib = await import('pdfjs-dist');
|
|
54
|
+
const pdfjsWorker = await import('pdfjs-dist/build/pdf.worker.min.mjs?url');
|
|
55
|
+
pdfjsLib.GlobalWorkerOptions.workerSrc = pdfjsWorker.default;
|
|
56
|
+
|
|
57
|
+
return pdfjsLib;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
async function loadPdf(source: PdfSource) {
|
|
61
|
+
if (!browser || !scrollContainerEl) return;
|
|
62
|
+
|
|
63
|
+
viewerState.loading = true;
|
|
64
|
+
viewerState.error = null;
|
|
65
|
+
|
|
66
|
+
try {
|
|
67
|
+
const pdfjs = await initPdfJs();
|
|
68
|
+
if (!pdfjs) return;
|
|
69
|
+
|
|
70
|
+
const { PDFViewerCore } = await import('./pdf-viewer/PDFViewerCore.js');
|
|
71
|
+
const { FindController } = await import('./pdf-viewer/FindController.js');
|
|
72
|
+
const { EventBus } = await import('./pdf-viewer/EventBus.js');
|
|
73
|
+
|
|
74
|
+
if (viewer) {
|
|
75
|
+
viewer.destroy();
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
const eventBus = new EventBus();
|
|
79
|
+
|
|
80
|
+
const newViewer = new PDFViewerCore({
|
|
81
|
+
container: scrollContainerEl,
|
|
82
|
+
eventBus,
|
|
83
|
+
initialScale: viewerState.scale,
|
|
84
|
+
initialRotation: viewerState.rotation
|
|
85
|
+
});
|
|
86
|
+
|
|
87
|
+
findController = new FindController(newViewer, eventBus);
|
|
88
|
+
|
|
89
|
+
// Setup event listeners
|
|
90
|
+
eventBus.on('scalechanged', (data: Record<string, unknown>) => {
|
|
91
|
+
viewerState.scale = data.scale as number;
|
|
92
|
+
});
|
|
93
|
+
|
|
94
|
+
eventBus.on('rotationchanged', (data: Record<string, unknown>) => {
|
|
95
|
+
viewerState.rotation = data.rotation as number;
|
|
96
|
+
});
|
|
97
|
+
|
|
98
|
+
eventBus.on('updateviewarea', (data: Record<string, unknown>) => {
|
|
99
|
+
const location = data.location as { pageNumber: number };
|
|
100
|
+
viewerState.currentPage = location.pageNumber;
|
|
101
|
+
});
|
|
102
|
+
|
|
103
|
+
eventBus.on('pagesloaded', (data: Record<string, unknown>) => {
|
|
104
|
+
viewerState.totalPages = data.pagesCount as number;
|
|
105
|
+
});
|
|
106
|
+
|
|
107
|
+
eventBus.on('updatefindmatchescount', (data: Record<string, unknown>) => {
|
|
108
|
+
const matchesCount = data.matchesCount as { current: number; total: number };
|
|
109
|
+
viewerState.searchCurrent = matchesCount.current;
|
|
110
|
+
viewerState.searchTotal = matchesCount.total;
|
|
111
|
+
});
|
|
112
|
+
|
|
113
|
+
// Handle different source types
|
|
114
|
+
let documentSource: string | { data: ArrayBuffer } | { data: Uint8Array };
|
|
115
|
+
|
|
116
|
+
if (typeof source === 'string') {
|
|
117
|
+
// URL string
|
|
118
|
+
documentSource = source;
|
|
119
|
+
} else if (source instanceof Blob) {
|
|
120
|
+
// Convert Blob to ArrayBuffer
|
|
121
|
+
const arrayBuffer = await source.arrayBuffer();
|
|
122
|
+
documentSource = { data: arrayBuffer };
|
|
123
|
+
} else if (source instanceof ArrayBuffer) {
|
|
124
|
+
documentSource = { data: source };
|
|
125
|
+
} else if (source instanceof Uint8Array) {
|
|
126
|
+
documentSource = { data: source };
|
|
127
|
+
} else {
|
|
128
|
+
throw new Error('Invalid PDF source type');
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
const loadingTask = pdfjs.getDocument(documentSource);
|
|
132
|
+
const pdfDocument = await loadingTask.promise;
|
|
133
|
+
|
|
134
|
+
await newViewer.setDocument(pdfDocument);
|
|
135
|
+
findController.setDocument(pdfDocument);
|
|
136
|
+
|
|
137
|
+
viewer = newViewer;
|
|
138
|
+
viewerState.loading = false;
|
|
139
|
+
} catch (e) {
|
|
140
|
+
viewerState.error = e instanceof Error ? e.message : 'Failed to load PDF';
|
|
141
|
+
viewerState.loading = false;
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
// Register actions with context
|
|
146
|
+
const rendererActions: PdfViewerActions = {
|
|
147
|
+
zoomIn: () => viewer?.zoomIn(),
|
|
148
|
+
zoomOut: () => viewer?.zoomOut(),
|
|
149
|
+
setScale: (scale: number) => {
|
|
150
|
+
if (viewer) viewer.scale = scale;
|
|
151
|
+
},
|
|
152
|
+
rotateClockwise: () => viewer?.rotateClockwise(),
|
|
153
|
+
rotateCounterClockwise: () => viewer?.rotateCounterClockwise(),
|
|
154
|
+
goToPage: (page: number) => viewer?.scrollToPage(page),
|
|
155
|
+
search: async (query: string) => {
|
|
156
|
+
if (!findController) return;
|
|
157
|
+
viewerState.isSearching = true;
|
|
158
|
+
viewerState.searchQuery = query;
|
|
159
|
+
await findController.find({
|
|
160
|
+
query,
|
|
161
|
+
highlightAll: true,
|
|
162
|
+
caseSensitive: false
|
|
163
|
+
});
|
|
164
|
+
viewerState.isSearching = false;
|
|
165
|
+
},
|
|
166
|
+
searchNext: () => findController?.findNext(),
|
|
167
|
+
searchPrevious: () => findController?.findPrevious(),
|
|
168
|
+
clearSearch: () => {
|
|
169
|
+
if (findController) {
|
|
170
|
+
findController.reset();
|
|
171
|
+
viewerState.searchQuery = '';
|
|
172
|
+
viewerState.searchCurrent = 0;
|
|
173
|
+
viewerState.searchTotal = 0;
|
|
174
|
+
}
|
|
175
|
+
}
|
|
176
|
+
};
|
|
177
|
+
|
|
178
|
+
onMount(async () => {
|
|
179
|
+
if (browser && hostEl) {
|
|
180
|
+
// Create shadow root for style isolation
|
|
181
|
+
shadowRoot = hostEl.attachShadow({ mode: 'open' });
|
|
182
|
+
|
|
183
|
+
// Inject styles
|
|
184
|
+
const styleEl = document.createElement('style');
|
|
185
|
+
styleEl.textContent = rendererStyles;
|
|
186
|
+
shadowRoot.appendChild(styleEl);
|
|
187
|
+
|
|
188
|
+
// Create container structure inside shadow DOM
|
|
189
|
+
const container = document.createElement('div');
|
|
190
|
+
container.className = 'pdf-renderer-container';
|
|
191
|
+
|
|
192
|
+
// Apply CSS custom properties for customization
|
|
193
|
+
container.style.setProperty('--pdf-background-color', backgroundColor);
|
|
194
|
+
container.style.setProperty('--pdf-page-shadow', pageShadow);
|
|
195
|
+
container.style.setProperty('--pdf-scrollbar-track-color', scrollbarTrackColor);
|
|
196
|
+
container.style.setProperty('--pdf-scrollbar-thumb-color', scrollbarThumbColor);
|
|
197
|
+
container.style.setProperty('--pdf-scrollbar-thumb-hover-color', scrollbarThumbHoverColor);
|
|
198
|
+
container.style.setProperty('--pdf-scrollbar-width', scrollbarWidth);
|
|
199
|
+
|
|
200
|
+
scrollContainerEl = document.createElement('div');
|
|
201
|
+
scrollContainerEl.className = 'pdf-scroll-container';
|
|
202
|
+
container.appendChild(scrollContainerEl);
|
|
203
|
+
|
|
204
|
+
shadowRoot.appendChild(container);
|
|
205
|
+
|
|
206
|
+
// Register actions
|
|
207
|
+
_registerRenderer(rendererActions);
|
|
208
|
+
|
|
209
|
+
mounted = true;
|
|
210
|
+
}
|
|
211
|
+
});
|
|
212
|
+
|
|
213
|
+
// Load PDF when src changes
|
|
214
|
+
$effect(() => {
|
|
215
|
+
if (browser && src && scrollContainerEl && mounted) {
|
|
216
|
+
loadPdf(src);
|
|
217
|
+
}
|
|
218
|
+
});
|
|
219
|
+
|
|
220
|
+
onDestroy(() => {
|
|
221
|
+
if (viewer) {
|
|
222
|
+
viewer.destroy();
|
|
223
|
+
viewer = null;
|
|
224
|
+
}
|
|
225
|
+
findController = null;
|
|
226
|
+
});
|
|
227
|
+
</script>
|
|
228
|
+
|
|
229
|
+
<div bind:this={hostEl} class="pdf-renderer-host"></div>
|
|
230
|
+
|
|
231
|
+
<style>
|
|
232
|
+
.pdf-renderer-host {
|
|
233
|
+
display: block;
|
|
234
|
+
flex: 1;
|
|
235
|
+
min-height: 0;
|
|
236
|
+
overflow: hidden;
|
|
237
|
+
}
|
|
238
|
+
</style>
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
/** PDF source - can be a URL string, ArrayBuffer, Uint8Array, or Blob */
|
|
2
|
+
export type PdfSource = string | ArrayBuffer | Uint8Array | Blob;
|
|
3
|
+
interface Props {
|
|
4
|
+
/** PDF source - URL string, ArrayBuffer, Uint8Array, or Blob */
|
|
5
|
+
src: PdfSource;
|
|
6
|
+
/** Background color of the scroll container */
|
|
7
|
+
backgroundColor?: string;
|
|
8
|
+
/** Page shadow style */
|
|
9
|
+
pageShadow?: string;
|
|
10
|
+
/** Scrollbar track color */
|
|
11
|
+
scrollbarTrackColor?: string;
|
|
12
|
+
/** Scrollbar thumb color */
|
|
13
|
+
scrollbarThumbColor?: string;
|
|
14
|
+
/** Scrollbar thumb hover color */
|
|
15
|
+
scrollbarThumbHoverColor?: string;
|
|
16
|
+
/** Scrollbar width */
|
|
17
|
+
scrollbarWidth?: string;
|
|
18
|
+
}
|
|
19
|
+
declare const PdfRenderer: import("svelte").Component<Props, {}, "">;
|
|
20
|
+
type PdfRenderer = ReturnType<typeof PdfRenderer>;
|
|
21
|
+
export default PdfRenderer;
|