svelte-pdf-view 0.2.0 → 0.3.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/README.md +2 -4
- package/dist/pdf-viewer/AnnotationLayerBuilder.d.ts +54 -0
- package/dist/pdf-viewer/AnnotationLayerBuilder.js +145 -0
- package/dist/pdf-viewer/PDFPageView.d.ts +7 -1
- package/dist/pdf-viewer/PDFPageView.js +51 -0
- package/dist/pdf-viewer/PDFViewerCore.d.ts +1 -0
- package/dist/pdf-viewer/PDFViewerCore.js +12 -1
- package/dist/pdf-viewer/SimpleLinkService.d.ts +74 -0
- package/dist/pdf-viewer/SimpleLinkService.js +212 -0
- package/dist/pdf-viewer/renderer-styles.d.ts +1 -1
- package/dist/pdf-viewer/renderer-styles.js +155 -0
- package/dist/pdf-viewer/styles.css +1 -137
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -314,11 +314,9 @@ interface PdfViewerActions {
|
|
|
314
314
|
/>
|
|
315
315
|
```
|
|
316
316
|
|
|
317
|
-
##
|
|
317
|
+
## Use Cases
|
|
318
318
|
|
|
319
|
-
|
|
320
|
-
- Firefox 78+
|
|
321
|
-
- Safari 14+
|
|
319
|
+
**Texpile** ([texpile.com](https://texpile.com/)) uses this package to display generated documents (PDFs) directly in the browser.
|
|
322
320
|
|
|
323
321
|
## License
|
|
324
322
|
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* AnnotationLayerBuilder - Renders PDF annotations (links, form widgets, popups).
|
|
3
|
+
* Adapted from PDF.js annotation_layer_builder.js
|
|
4
|
+
*/
|
|
5
|
+
import type { PDFPageProxy, PageViewport } from 'pdfjs-dist/legacy/build/pdf.mjs';
|
|
6
|
+
import type { SimpleLinkService } from './SimpleLinkService.js';
|
|
7
|
+
export interface AnnotationLayerBuilderOptions {
|
|
8
|
+
pdfPage: PDFPageProxy;
|
|
9
|
+
linkService: SimpleLinkService;
|
|
10
|
+
annotationStorage?: any;
|
|
11
|
+
imageResourcesPath?: string;
|
|
12
|
+
renderForms?: boolean;
|
|
13
|
+
onAppend?: (div: HTMLDivElement) => void;
|
|
14
|
+
}
|
|
15
|
+
export interface AnnotationLayerBuilderRenderOptions {
|
|
16
|
+
viewport: PageViewport;
|
|
17
|
+
intent?: string;
|
|
18
|
+
}
|
|
19
|
+
export declare class AnnotationLayerBuilder {
|
|
20
|
+
private pdfPage;
|
|
21
|
+
private linkService;
|
|
22
|
+
private annotationStorage;
|
|
23
|
+
private imageResourcesPath;
|
|
24
|
+
private renderForms;
|
|
25
|
+
private onAppend;
|
|
26
|
+
private annotationLayer;
|
|
27
|
+
div: HTMLDivElement | null;
|
|
28
|
+
private cancelled;
|
|
29
|
+
constructor(options: AnnotationLayerBuilderOptions);
|
|
30
|
+
/**
|
|
31
|
+
* Render the annotation layer.
|
|
32
|
+
*/
|
|
33
|
+
render(options: AnnotationLayerBuilderRenderOptions): Promise<void>;
|
|
34
|
+
/**
|
|
35
|
+
* Update the annotation layer viewport.
|
|
36
|
+
*/
|
|
37
|
+
update(viewport: PageViewport): void;
|
|
38
|
+
/**
|
|
39
|
+
* Cancel rendering.
|
|
40
|
+
*/
|
|
41
|
+
cancel(): void;
|
|
42
|
+
/**
|
|
43
|
+
* Hide the annotation layer.
|
|
44
|
+
*/
|
|
45
|
+
hide(): void;
|
|
46
|
+
/**
|
|
47
|
+
* Show the annotation layer.
|
|
48
|
+
*/
|
|
49
|
+
show(): void;
|
|
50
|
+
/**
|
|
51
|
+
* Destroy the annotation layer.
|
|
52
|
+
*/
|
|
53
|
+
destroy(): void;
|
|
54
|
+
}
|
|
@@ -0,0 +1,145 @@
|
|
|
1
|
+
/* Copyright 2024 Mozilla Foundation
|
|
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.
|
|
14
|
+
*/
|
|
15
|
+
// Dynamically loaded pdfjs components
|
|
16
|
+
let AnnotationLayer;
|
|
17
|
+
async function ensurePdfJsLoaded() {
|
|
18
|
+
if (!AnnotationLayer) {
|
|
19
|
+
const pdfjs = await import('pdfjs-dist/legacy/build/pdf.mjs');
|
|
20
|
+
AnnotationLayer = pdfjs.AnnotationLayer;
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
export class AnnotationLayerBuilder {
|
|
24
|
+
pdfPage;
|
|
25
|
+
linkService;
|
|
26
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
27
|
+
annotationStorage;
|
|
28
|
+
imageResourcesPath;
|
|
29
|
+
renderForms;
|
|
30
|
+
onAppend;
|
|
31
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
32
|
+
annotationLayer = null;
|
|
33
|
+
div = null;
|
|
34
|
+
cancelled = false;
|
|
35
|
+
constructor(options) {
|
|
36
|
+
this.pdfPage = options.pdfPage;
|
|
37
|
+
this.linkService = options.linkService;
|
|
38
|
+
this.annotationStorage = options.annotationStorage ?? null;
|
|
39
|
+
this.imageResourcesPath = options.imageResourcesPath ?? '';
|
|
40
|
+
this.renderForms = options.renderForms ?? true;
|
|
41
|
+
this.onAppend = options.onAppend ?? null;
|
|
42
|
+
}
|
|
43
|
+
/**
|
|
44
|
+
* Render the annotation layer.
|
|
45
|
+
*/
|
|
46
|
+
async render(options) {
|
|
47
|
+
const { viewport, intent = 'display' } = options;
|
|
48
|
+
if (this.div) {
|
|
49
|
+
// Already rendered - just update
|
|
50
|
+
if (this.cancelled || !this.annotationLayer) {
|
|
51
|
+
return;
|
|
52
|
+
}
|
|
53
|
+
this.annotationLayer.update({
|
|
54
|
+
viewport: viewport.clone({ dontFlip: true })
|
|
55
|
+
});
|
|
56
|
+
return;
|
|
57
|
+
}
|
|
58
|
+
await ensurePdfJsLoaded();
|
|
59
|
+
const annotations = await this.pdfPage.getAnnotations({ intent });
|
|
60
|
+
if (this.cancelled) {
|
|
61
|
+
return;
|
|
62
|
+
}
|
|
63
|
+
// Create annotation layer div
|
|
64
|
+
this.div = document.createElement('div');
|
|
65
|
+
this.div.className = 'annotationLayer';
|
|
66
|
+
this.onAppend?.(this.div);
|
|
67
|
+
// Set layer dimensions directly (setLayerDimensions uses CSS round() which may not work everywhere)
|
|
68
|
+
const { width, height } = viewport;
|
|
69
|
+
this.div.style.width = `${Math.floor(width)}px`;
|
|
70
|
+
this.div.style.height = `${Math.floor(height)}px`;
|
|
71
|
+
// Initialize the annotation layer
|
|
72
|
+
this.annotationLayer = new AnnotationLayer({
|
|
73
|
+
div: this.div,
|
|
74
|
+
accessibilityManager: null,
|
|
75
|
+
annotationCanvasMap: null,
|
|
76
|
+
annotationEditorUIManager: null,
|
|
77
|
+
page: this.pdfPage,
|
|
78
|
+
viewport: viewport.clone({ dontFlip: true }),
|
|
79
|
+
structTreeLayer: null,
|
|
80
|
+
commentManager: null,
|
|
81
|
+
linkService: this.linkService,
|
|
82
|
+
annotationStorage: this.annotationStorage
|
|
83
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
84
|
+
});
|
|
85
|
+
if (annotations.length === 0) {
|
|
86
|
+
return;
|
|
87
|
+
}
|
|
88
|
+
// Render annotations
|
|
89
|
+
await this.annotationLayer.render({
|
|
90
|
+
annotations,
|
|
91
|
+
imageResourcesPath: this.imageResourcesPath,
|
|
92
|
+
renderForms: this.renderForms,
|
|
93
|
+
linkService: this.linkService,
|
|
94
|
+
downloadManager: undefined,
|
|
95
|
+
annotationStorage: this.annotationStorage,
|
|
96
|
+
enableScripting: false,
|
|
97
|
+
hasJSActions: false,
|
|
98
|
+
fieldObjects: null
|
|
99
|
+
});
|
|
100
|
+
}
|
|
101
|
+
/**
|
|
102
|
+
* Update the annotation layer viewport.
|
|
103
|
+
*/
|
|
104
|
+
update(viewport) {
|
|
105
|
+
if (!this.div || !this.annotationLayer) {
|
|
106
|
+
return;
|
|
107
|
+
}
|
|
108
|
+
this.annotationLayer.update({
|
|
109
|
+
viewport: viewport.clone({ dontFlip: true })
|
|
110
|
+
});
|
|
111
|
+
}
|
|
112
|
+
/**
|
|
113
|
+
* Cancel rendering.
|
|
114
|
+
*/
|
|
115
|
+
cancel() {
|
|
116
|
+
this.cancelled = true;
|
|
117
|
+
}
|
|
118
|
+
/**
|
|
119
|
+
* Hide the annotation layer.
|
|
120
|
+
*/
|
|
121
|
+
hide() {
|
|
122
|
+
if (this.div) {
|
|
123
|
+
this.div.hidden = true;
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
/**
|
|
127
|
+
* Show the annotation layer.
|
|
128
|
+
*/
|
|
129
|
+
show() {
|
|
130
|
+
if (this.div) {
|
|
131
|
+
this.div.hidden = false;
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
/**
|
|
135
|
+
* Destroy the annotation layer.
|
|
136
|
+
*/
|
|
137
|
+
destroy() {
|
|
138
|
+
this.cancel();
|
|
139
|
+
if (this.div) {
|
|
140
|
+
this.div.remove();
|
|
141
|
+
this.div = null;
|
|
142
|
+
}
|
|
143
|
+
this.annotationLayer = null;
|
|
144
|
+
}
|
|
145
|
+
}
|
|
@@ -1,9 +1,10 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* PDFPageView - Renders a single PDF page with canvas and
|
|
2
|
+
* PDFPageView - Renders a single PDF page with canvas, text layer, and annotation layer.
|
|
3
3
|
* This is a derivative work based on PDF.js pdf_page_view.js
|
|
4
4
|
*/
|
|
5
5
|
import type { PDFPageProxy, PageViewport } from 'pdfjs-dist/legacy/build/pdf.mjs';
|
|
6
6
|
import type { EventBus } from './EventBus.js';
|
|
7
|
+
import type { SimpleLinkService } from './SimpleLinkService.js';
|
|
7
8
|
export interface PDFPageViewOptions {
|
|
8
9
|
container: HTMLElement;
|
|
9
10
|
id: number;
|
|
@@ -11,6 +12,7 @@ export interface PDFPageViewOptions {
|
|
|
11
12
|
eventBus: EventBus;
|
|
12
13
|
scale?: number;
|
|
13
14
|
rotation?: number;
|
|
15
|
+
linkService?: SimpleLinkService;
|
|
14
16
|
}
|
|
15
17
|
export declare const RenderingStates: {
|
|
16
18
|
readonly INITIAL: 0;
|
|
@@ -33,6 +35,9 @@ export declare class PDFPageView {
|
|
|
33
35
|
private canvasWrapper;
|
|
34
36
|
private textLayerDiv;
|
|
35
37
|
private loadingDiv;
|
|
38
|
+
private linkService;
|
|
39
|
+
private annotationLayerBuilder;
|
|
40
|
+
private annotationLayerRendered;
|
|
36
41
|
renderingState: RenderingState;
|
|
37
42
|
private renderTask;
|
|
38
43
|
private textLayer;
|
|
@@ -51,6 +56,7 @@ export declare class PDFPageView {
|
|
|
51
56
|
reset(): void;
|
|
52
57
|
draw(): Promise<void>;
|
|
53
58
|
private renderTextLayer;
|
|
59
|
+
private renderAnnotationLayer;
|
|
54
60
|
cancelRendering(): void;
|
|
55
61
|
destroy(): void;
|
|
56
62
|
get width(): number;
|
|
@@ -12,6 +12,7 @@
|
|
|
12
12
|
* See the License for the specific language governing permissions and
|
|
13
13
|
* limitations under the License.
|
|
14
14
|
*/
|
|
15
|
+
import { AnnotationLayerBuilder } from './AnnotationLayerBuilder.js';
|
|
15
16
|
// Dynamically loaded pdfjs utilities
|
|
16
17
|
let setLayerDimensions;
|
|
17
18
|
async function ensurePdfJsLoaded() {
|
|
@@ -40,6 +41,10 @@ export class PDFPageView {
|
|
|
40
41
|
canvasWrapper = null;
|
|
41
42
|
textLayerDiv = null;
|
|
42
43
|
loadingDiv = null;
|
|
44
|
+
// Annotation layer
|
|
45
|
+
linkService = null;
|
|
46
|
+
annotationLayerBuilder = null;
|
|
47
|
+
annotationLayerRendered = false;
|
|
43
48
|
renderingState = RenderingStates.INITIAL;
|
|
44
49
|
renderTask = null;
|
|
45
50
|
// Text layer instance for updates
|
|
@@ -55,6 +60,7 @@ export class PDFPageView {
|
|
|
55
60
|
this.scale = options.scale ?? 1.0;
|
|
56
61
|
this.rotation = options.rotation ?? 0;
|
|
57
62
|
this.viewport = options.defaultViewport;
|
|
63
|
+
this.linkService = options.linkService ?? null;
|
|
58
64
|
// Create page container
|
|
59
65
|
this.div = document.createElement('div');
|
|
60
66
|
this.div.className = 'page';
|
|
@@ -120,6 +126,10 @@ export class PDFPageView {
|
|
|
120
126
|
});
|
|
121
127
|
this.textLayerDiv.hidden = false;
|
|
122
128
|
}
|
|
129
|
+
// Update annotation layer
|
|
130
|
+
if (this.annotationLayerBuilder && this.annotationLayerRendered) {
|
|
131
|
+
this.annotationLayerBuilder.update(this.viewport);
|
|
132
|
+
}
|
|
123
133
|
// Re-render canvas
|
|
124
134
|
this.resetCanvas();
|
|
125
135
|
this.draw();
|
|
@@ -163,6 +173,12 @@ export class PDFPageView {
|
|
|
163
173
|
this.textLayerRendered = false;
|
|
164
174
|
this.textDivs = [];
|
|
165
175
|
this.textContentItemsStr = [];
|
|
176
|
+
// Clear annotation layer
|
|
177
|
+
if (this.annotationLayerBuilder) {
|
|
178
|
+
this.annotationLayerBuilder.destroy();
|
|
179
|
+
this.annotationLayerBuilder = null;
|
|
180
|
+
}
|
|
181
|
+
this.annotationLayerRendered = false;
|
|
166
182
|
// Show loading
|
|
167
183
|
if (this.loadingDiv) {
|
|
168
184
|
this.loadingDiv.style.display = '';
|
|
@@ -205,6 +221,10 @@ export class PDFPageView {
|
|
|
205
221
|
if (!this.textLayerRendered) {
|
|
206
222
|
await this.renderTextLayer();
|
|
207
223
|
}
|
|
224
|
+
// Render annotation layer (only if not already rendered)
|
|
225
|
+
if (!this.annotationLayerRendered) {
|
|
226
|
+
await this.renderAnnotationLayer();
|
|
227
|
+
}
|
|
208
228
|
this.renderingState = RenderingStates.FINISHED;
|
|
209
229
|
this.eventBus.dispatch('pagerendered', {
|
|
210
230
|
pageNumber: this.id,
|
|
@@ -271,6 +291,37 @@ export class PDFPageView {
|
|
|
271
291
|
console.error('Error rendering text layer:', error);
|
|
272
292
|
}
|
|
273
293
|
}
|
|
294
|
+
async renderAnnotationLayer() {
|
|
295
|
+
if (!this.pdfPage || !this.linkService) {
|
|
296
|
+
return;
|
|
297
|
+
}
|
|
298
|
+
// If annotation layer already rendered, just update it
|
|
299
|
+
if (this.annotationLayerRendered && this.annotationLayerBuilder) {
|
|
300
|
+
this.annotationLayerBuilder.update(this.viewport);
|
|
301
|
+
return;
|
|
302
|
+
}
|
|
303
|
+
try {
|
|
304
|
+
this.annotationLayerBuilder = new AnnotationLayerBuilder({
|
|
305
|
+
pdfPage: this.pdfPage,
|
|
306
|
+
linkService: this.linkService,
|
|
307
|
+
renderForms: true,
|
|
308
|
+
onAppend: (div) => {
|
|
309
|
+
this.div.appendChild(div);
|
|
310
|
+
}
|
|
311
|
+
});
|
|
312
|
+
await this.annotationLayerBuilder.render({
|
|
313
|
+
viewport: this.viewport
|
|
314
|
+
});
|
|
315
|
+
this.annotationLayerRendered = true;
|
|
316
|
+
this.eventBus.dispatch('annotationlayerrendered', {
|
|
317
|
+
pageNumber: this.id,
|
|
318
|
+
source: this
|
|
319
|
+
});
|
|
320
|
+
}
|
|
321
|
+
catch (error) {
|
|
322
|
+
console.error('Error rendering annotation layer:', error);
|
|
323
|
+
}
|
|
324
|
+
}
|
|
274
325
|
cancelRendering() {
|
|
275
326
|
if (this.renderTask) {
|
|
276
327
|
this.renderTask.cancel();
|
|
@@ -22,6 +22,7 @@ export declare class PDFViewerCore {
|
|
|
22
22
|
private scrollAbortController;
|
|
23
23
|
private renderingQueue;
|
|
24
24
|
private isRendering;
|
|
25
|
+
private linkService;
|
|
25
26
|
constructor(options: PDFViewerOptions);
|
|
26
27
|
private setupScrollListener;
|
|
27
28
|
setDocument(pdfDocument: PDFDocumentProxy): Promise<void>;
|
|
@@ -14,6 +14,7 @@
|
|
|
14
14
|
*/
|
|
15
15
|
import { EventBus } from './EventBus.js';
|
|
16
16
|
import { PDFPageView, RenderingStates } from './PDFPageView.js';
|
|
17
|
+
import { SimpleLinkService } from './SimpleLinkService.js';
|
|
17
18
|
const DEFAULT_SCALE = 1.0;
|
|
18
19
|
const MIN_SCALE = 0.1;
|
|
19
20
|
const MAX_SCALE = 10.0;
|
|
@@ -31,6 +32,8 @@ export class PDFViewerCore {
|
|
|
31
32
|
scrollAbortController = null;
|
|
32
33
|
renderingQueue = new Set();
|
|
33
34
|
isRendering = false;
|
|
35
|
+
// Link service for annotation navigation
|
|
36
|
+
linkService;
|
|
34
37
|
constructor(options) {
|
|
35
38
|
this.container = options.container;
|
|
36
39
|
this.eventBus = options.eventBus ?? new EventBus();
|
|
@@ -40,6 +43,10 @@ export class PDFViewerCore {
|
|
|
40
43
|
this.viewer = document.createElement('div');
|
|
41
44
|
this.viewer.className = 'pdfViewer';
|
|
42
45
|
this.container.appendChild(this.viewer);
|
|
46
|
+
// Create link service for annotation navigation
|
|
47
|
+
this.linkService = new SimpleLinkService({
|
|
48
|
+
eventBus: this.eventBus
|
|
49
|
+
});
|
|
43
50
|
// Setup scroll listener for lazy rendering
|
|
44
51
|
this.setupScrollListener();
|
|
45
52
|
}
|
|
@@ -61,6 +68,9 @@ export class PDFViewerCore {
|
|
|
61
68
|
this.cleanup();
|
|
62
69
|
this.pdfDocument = pdfDocument;
|
|
63
70
|
const numPages = pdfDocument.numPages;
|
|
71
|
+
// Setup link service with document and viewer
|
|
72
|
+
this.linkService.setDocument(pdfDocument);
|
|
73
|
+
this.linkService.setViewer(this);
|
|
64
74
|
// Create page views
|
|
65
75
|
for (let i = 1; i <= numPages; i++) {
|
|
66
76
|
const page = await pdfDocument.getPage(i);
|
|
@@ -74,7 +84,8 @@ export class PDFViewerCore {
|
|
|
74
84
|
defaultViewport: viewport,
|
|
75
85
|
eventBus: this.eventBus,
|
|
76
86
|
scale: this.currentScale,
|
|
77
|
-
rotation: this.currentRotation
|
|
87
|
+
rotation: this.currentRotation,
|
|
88
|
+
linkService: this.linkService
|
|
78
89
|
});
|
|
79
90
|
pageView.setPdfPage(page);
|
|
80
91
|
this.pages.push(pageView);
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* SimpleLinkService - Provides PDF navigation for annotation links.
|
|
3
|
+
* Adapted from PDF.js pdf_link_service.js
|
|
4
|
+
*/
|
|
5
|
+
import type { PDFDocumentProxy } from 'pdfjs-dist/legacy/build/pdf.mjs';
|
|
6
|
+
import type { EventBus } from './EventBus.js';
|
|
7
|
+
export interface SimpleLinkServiceOptions {
|
|
8
|
+
eventBus: EventBus;
|
|
9
|
+
externalLinkTarget?: number;
|
|
10
|
+
externalLinkRel?: string;
|
|
11
|
+
}
|
|
12
|
+
export declare const LinkTarget: {
|
|
13
|
+
readonly NONE: 0;
|
|
14
|
+
readonly SELF: 1;
|
|
15
|
+
readonly BLANK: 2;
|
|
16
|
+
readonly PARENT: 3;
|
|
17
|
+
readonly TOP: 4;
|
|
18
|
+
};
|
|
19
|
+
export declare class SimpleLinkService {
|
|
20
|
+
readonly eventBus: EventBus;
|
|
21
|
+
private externalLinkTarget;
|
|
22
|
+
private externalLinkRel;
|
|
23
|
+
private pdfDocument;
|
|
24
|
+
private pdfViewer;
|
|
25
|
+
externalLinkEnabled: boolean;
|
|
26
|
+
constructor(options: SimpleLinkServiceOptions);
|
|
27
|
+
setDocument(pdfDocument: PDFDocumentProxy | null): void;
|
|
28
|
+
setViewer(pdfViewer: {
|
|
29
|
+
scrollToPage: (page: number) => void;
|
|
30
|
+
pagesCount: number;
|
|
31
|
+
}): void;
|
|
32
|
+
get pagesCount(): number;
|
|
33
|
+
get page(): number;
|
|
34
|
+
set page(value: number);
|
|
35
|
+
get rotation(): number;
|
|
36
|
+
set rotation(_value: number);
|
|
37
|
+
get isInPresentationMode(): boolean;
|
|
38
|
+
/**
|
|
39
|
+
* Navigate to a PDF destination (internal link).
|
|
40
|
+
*/
|
|
41
|
+
goToDestination(dest: unknown): Promise<void>;
|
|
42
|
+
/**
|
|
43
|
+
* Navigate to a specific page.
|
|
44
|
+
*/
|
|
45
|
+
goToPage(pageNumber: number): void;
|
|
46
|
+
/**
|
|
47
|
+
* Get a hash string for a destination (for href).
|
|
48
|
+
*/
|
|
49
|
+
getDestinationHash(dest: unknown): string;
|
|
50
|
+
/**
|
|
51
|
+
* Get anchor URL with proper prefix.
|
|
52
|
+
*/
|
|
53
|
+
getAnchorUrl(anchor: string): string;
|
|
54
|
+
/**
|
|
55
|
+
* Add attributes to external link elements.
|
|
56
|
+
*/
|
|
57
|
+
addLinkAttributes(link: HTMLAnchorElement, url: string, newWindow?: boolean): void;
|
|
58
|
+
/**
|
|
59
|
+
* Execute a named action (e.g., Print, GoBack).
|
|
60
|
+
*/
|
|
61
|
+
executeNamedAction(action: string): void;
|
|
62
|
+
/**
|
|
63
|
+
* Execute a SetOCGState action.
|
|
64
|
+
*/
|
|
65
|
+
executeSetOCGState(_action: unknown): void;
|
|
66
|
+
/**
|
|
67
|
+
* Navigate to specific coordinates on a page.
|
|
68
|
+
*/
|
|
69
|
+
goToXY(pageNumber: number, _x: number, _y: number): void;
|
|
70
|
+
/**
|
|
71
|
+
* Set hash for navigation.
|
|
72
|
+
*/
|
|
73
|
+
setHash(_hash: string): void;
|
|
74
|
+
}
|
|
@@ -0,0 +1,212 @@
|
|
|
1
|
+
/* Copyright 2024 Mozilla Foundation
|
|
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.
|
|
14
|
+
*/
|
|
15
|
+
const DEFAULT_LINK_REL = 'noopener noreferrer nofollow';
|
|
16
|
+
export const LinkTarget = {
|
|
17
|
+
NONE: 0,
|
|
18
|
+
SELF: 1,
|
|
19
|
+
BLANK: 2,
|
|
20
|
+
PARENT: 3,
|
|
21
|
+
TOP: 4
|
|
22
|
+
};
|
|
23
|
+
export class SimpleLinkService {
|
|
24
|
+
eventBus;
|
|
25
|
+
externalLinkTarget;
|
|
26
|
+
externalLinkRel;
|
|
27
|
+
pdfDocument = null;
|
|
28
|
+
pdfViewer = null;
|
|
29
|
+
externalLinkEnabled = true;
|
|
30
|
+
constructor(options) {
|
|
31
|
+
this.eventBus = options.eventBus;
|
|
32
|
+
this.externalLinkTarget = options.externalLinkTarget ?? LinkTarget.BLANK;
|
|
33
|
+
this.externalLinkRel = options.externalLinkRel ?? DEFAULT_LINK_REL;
|
|
34
|
+
}
|
|
35
|
+
setDocument(pdfDocument) {
|
|
36
|
+
this.pdfDocument = pdfDocument;
|
|
37
|
+
}
|
|
38
|
+
setViewer(pdfViewer) {
|
|
39
|
+
this.pdfViewer = pdfViewer;
|
|
40
|
+
}
|
|
41
|
+
get pagesCount() {
|
|
42
|
+
return this.pdfViewer?.pagesCount ?? 0;
|
|
43
|
+
}
|
|
44
|
+
get page() {
|
|
45
|
+
return this.pdfViewer ? 1 : 0;
|
|
46
|
+
}
|
|
47
|
+
set page(value) {
|
|
48
|
+
this.goToPage(value);
|
|
49
|
+
}
|
|
50
|
+
get rotation() {
|
|
51
|
+
return 0;
|
|
52
|
+
}
|
|
53
|
+
set rotation(_value) {
|
|
54
|
+
// Not implemented - rotation is handled by viewer
|
|
55
|
+
}
|
|
56
|
+
get isInPresentationMode() {
|
|
57
|
+
return false;
|
|
58
|
+
}
|
|
59
|
+
/**
|
|
60
|
+
* Navigate to a PDF destination (internal link).
|
|
61
|
+
*/
|
|
62
|
+
async goToDestination(dest) {
|
|
63
|
+
if (!this.pdfDocument || !this.pdfViewer) {
|
|
64
|
+
return;
|
|
65
|
+
}
|
|
66
|
+
let explicitDest;
|
|
67
|
+
if (typeof dest === 'string') {
|
|
68
|
+
// Named destination - look it up
|
|
69
|
+
explicitDest = (await this.pdfDocument.getDestination(dest));
|
|
70
|
+
if (!explicitDest) {
|
|
71
|
+
console.warn(`SimpleLinkService: "${dest}" is not a valid destination.`);
|
|
72
|
+
return;
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
else if (Array.isArray(dest)) {
|
|
76
|
+
explicitDest = dest;
|
|
77
|
+
}
|
|
78
|
+
else {
|
|
79
|
+
console.warn('SimpleLinkService: Invalid destination:', dest);
|
|
80
|
+
return;
|
|
81
|
+
}
|
|
82
|
+
// Extract page reference from destination
|
|
83
|
+
const destRef = explicitDest[0];
|
|
84
|
+
let pageNumber;
|
|
85
|
+
if (typeof destRef === 'object' && destRef !== null && 'num' in destRef && 'gen' in destRef) {
|
|
86
|
+
// It's a reference object, resolve to page index
|
|
87
|
+
pageNumber =
|
|
88
|
+
(await this.pdfDocument.getPageIndex(destRef)) + 1;
|
|
89
|
+
}
|
|
90
|
+
else if (typeof destRef === 'number') {
|
|
91
|
+
// It's already a page number (0-indexed)
|
|
92
|
+
pageNumber = destRef + 1;
|
|
93
|
+
}
|
|
94
|
+
else {
|
|
95
|
+
console.warn('SimpleLinkService: Invalid destination reference:', destRef);
|
|
96
|
+
return;
|
|
97
|
+
}
|
|
98
|
+
if (pageNumber < 1 || pageNumber > this.pagesCount) {
|
|
99
|
+
console.warn(`SimpleLinkService: Page ${pageNumber} out of range.`);
|
|
100
|
+
return;
|
|
101
|
+
}
|
|
102
|
+
this.pdfViewer.scrollToPage(pageNumber);
|
|
103
|
+
this.eventBus.dispatch('navigateto', { dest: explicitDest, pageNumber });
|
|
104
|
+
}
|
|
105
|
+
/**
|
|
106
|
+
* Navigate to a specific page.
|
|
107
|
+
*/
|
|
108
|
+
goToPage(pageNumber) {
|
|
109
|
+
if (!this.pdfViewer) {
|
|
110
|
+
return;
|
|
111
|
+
}
|
|
112
|
+
if (pageNumber < 1 || pageNumber > this.pagesCount) {
|
|
113
|
+
console.warn(`SimpleLinkService: Page ${pageNumber} out of range.`);
|
|
114
|
+
return;
|
|
115
|
+
}
|
|
116
|
+
this.pdfViewer.scrollToPage(pageNumber);
|
|
117
|
+
this.eventBus.dispatch('pagechanged', { pageNumber });
|
|
118
|
+
}
|
|
119
|
+
/**
|
|
120
|
+
* Get a hash string for a destination (for href).
|
|
121
|
+
*/
|
|
122
|
+
getDestinationHash(dest) {
|
|
123
|
+
if (typeof dest === 'string') {
|
|
124
|
+
return `#${escape(dest)}`;
|
|
125
|
+
}
|
|
126
|
+
if (Array.isArray(dest)) {
|
|
127
|
+
return `#${escape(JSON.stringify(dest))}`;
|
|
128
|
+
}
|
|
129
|
+
return '#';
|
|
130
|
+
}
|
|
131
|
+
/**
|
|
132
|
+
* Get anchor URL with proper prefix.
|
|
133
|
+
*/
|
|
134
|
+
getAnchorUrl(anchor) {
|
|
135
|
+
return anchor;
|
|
136
|
+
}
|
|
137
|
+
/**
|
|
138
|
+
* Add attributes to external link elements.
|
|
139
|
+
*/
|
|
140
|
+
addLinkAttributes(link, url, newWindow = false) {
|
|
141
|
+
if (!url || typeof url !== 'string') {
|
|
142
|
+
return;
|
|
143
|
+
}
|
|
144
|
+
link.href = url;
|
|
145
|
+
link.rel = this.externalLinkRel;
|
|
146
|
+
if (newWindow || this.externalLinkTarget === LinkTarget.BLANK) {
|
|
147
|
+
link.target = '_blank';
|
|
148
|
+
}
|
|
149
|
+
else if (this.externalLinkTarget === LinkTarget.SELF) {
|
|
150
|
+
link.target = '_self';
|
|
151
|
+
}
|
|
152
|
+
else if (this.externalLinkTarget === LinkTarget.PARENT) {
|
|
153
|
+
link.target = '_parent';
|
|
154
|
+
}
|
|
155
|
+
else if (this.externalLinkTarget === LinkTarget.TOP) {
|
|
156
|
+
link.target = '_top';
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
/**
|
|
160
|
+
* Execute a named action (e.g., Print, GoBack).
|
|
161
|
+
*/
|
|
162
|
+
executeNamedAction(action) {
|
|
163
|
+
switch (action) {
|
|
164
|
+
case 'GoBack':
|
|
165
|
+
history.back();
|
|
166
|
+
break;
|
|
167
|
+
case 'GoForward':
|
|
168
|
+
history.forward();
|
|
169
|
+
break;
|
|
170
|
+
case 'NextPage':
|
|
171
|
+
this.eventBus.dispatch('nextpage', {});
|
|
172
|
+
break;
|
|
173
|
+
case 'PrevPage':
|
|
174
|
+
this.eventBus.dispatch('previouspage', {});
|
|
175
|
+
break;
|
|
176
|
+
case 'LastPage':
|
|
177
|
+
if (this.pdfViewer) {
|
|
178
|
+
this.goToPage(this.pagesCount);
|
|
179
|
+
}
|
|
180
|
+
break;
|
|
181
|
+
case 'FirstPage':
|
|
182
|
+
this.goToPage(1);
|
|
183
|
+
break;
|
|
184
|
+
case 'Print':
|
|
185
|
+
this.eventBus.dispatch('print', {});
|
|
186
|
+
break;
|
|
187
|
+
default:
|
|
188
|
+
console.warn(`SimpleLinkService: Unknown named action "${action}".`);
|
|
189
|
+
}
|
|
190
|
+
}
|
|
191
|
+
/**
|
|
192
|
+
* Execute a SetOCGState action.
|
|
193
|
+
*/
|
|
194
|
+
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
|
195
|
+
executeSetOCGState(_action) {
|
|
196
|
+
// Not implemented - optional content is not supported
|
|
197
|
+
}
|
|
198
|
+
/**
|
|
199
|
+
* Navigate to specific coordinates on a page.
|
|
200
|
+
*/
|
|
201
|
+
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
|
202
|
+
goToXY(pageNumber, _x, _y) {
|
|
203
|
+
this.goToPage(pageNumber);
|
|
204
|
+
}
|
|
205
|
+
/**
|
|
206
|
+
* Set hash for navigation.
|
|
207
|
+
*/
|
|
208
|
+
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
|
209
|
+
setHash(_hash) {
|
|
210
|
+
// Not implemented - hash navigation not supported
|
|
211
|
+
}
|
|
212
|
+
}
|
|
@@ -2,4 +2,4 @@
|
|
|
2
2
|
* PDF Renderer Styles - Shadow DOM Isolated
|
|
3
3
|
* This is a derivative work based on PDF.js text_layer_builder.css
|
|
4
4
|
*/
|
|
5
|
-
export declare const rendererStyles = "\n/* CSS Custom Properties with defaults */\n.pdf-renderer-container {\n\t--pdf-background-color: #e8e8e8;\n\t--pdf-page-shadow: 0 2px 8px rgba(0, 0, 0, 0.12), 0 1px 3px rgba(0, 0, 0, 0.08);\n\t--pdf-scrollbar-track-color: #f1f1f1;\n\t--pdf-scrollbar-thumb-color: #c1c1c1;\n\t--pdf-scrollbar-thumb-hover-color: #a1a1a1;\n\t--pdf-scrollbar-width: 10px;\n\n\tdisplay: flex;\n\tflex-direction: column;\n\twidth: 100%;\n\theight: 100%;\n\tbackground-color: var(--pdf-background-color);\n\toverflow: hidden;\n}\n\n/* Scroll container */\n.pdf-scroll-container {\n\tflex: 1;\n\toverflow: auto;\n\tposition: relative;\n\tbackground-color: var(--pdf-background-color);\n}\n\n/* Custom scrollbar styling */\n.pdf-scroll-container::-webkit-scrollbar {\n\twidth: var(--pdf-scrollbar-width);\n\theight: var(--pdf-scrollbar-width);\n}\n\n.pdf-scroll-container::-webkit-scrollbar-track {\n\tbackground: var(--pdf-scrollbar-track-color);\n\tborder-radius: calc(var(--pdf-scrollbar-width) / 2);\n}\n\n.pdf-scroll-container::-webkit-scrollbar-thumb {\n\tbackground: var(--pdf-scrollbar-thumb-color);\n\tborder-radius: calc(var(--pdf-scrollbar-width) / 2);\n}\n\n.pdf-scroll-container::-webkit-scrollbar-thumb:hover {\n\tbackground: var(--pdf-scrollbar-thumb-hover-color);\n}\n\n/* Firefox scrollbar */\n.pdf-scroll-container {\n\tscrollbar-width: thin;\n\tscrollbar-color: var(--pdf-scrollbar-thumb-color) var(--pdf-scrollbar-track-color);\n}\n\n/* Viewer - dynamically created */\n.pdfViewer {\n\tdisplay: flex;\n\tflex-direction: column;\n\talign-items: center;\n\tpadding: 20px;\n\tgap: 16px;\n}\n\n/* Page - dynamically created with CSS variables for text layer */\n.page {\n\t--user-unit: 1;\n\t--total-scale-factor: calc(var(--scale-factor, 1) * var(--user-unit));\n\t--scale-round-x: 1px;\n\t--scale-round-y: 1px;\n\n\tposition: relative;\n\tbackground-color: white;\n\tbox-shadow: var(--pdf-page-shadow);\n\tborder-radius: 2px;\n\tmargin: 0;\n\tdirection: ltr;\n}\n\n.page .loadingIcon {\n\tposition: absolute;\n\ttop: 50%;\n\tleft: 50%;\n\ttransform: translate(-50%, -50%);\n\tcolor: #666;\n\tfont-size: 14px;\n}\n\n.page .canvasWrapper {\n\tposition: absolute;\n\tinset: 0;\n\toverflow: hidden;\n\tz-index: 0;\n}\n\n.page .pdf-canvas {\n\tdisplay: block;\n}\n\n/* Text layer - essential styles from PDF.js */\n.textLayer {\n\tposition: absolute;\n\ttext-align: initial;\n\tinset: 0;\n\toverflow: clip;\n\topacity: 1;\n\tline-height: 1;\n\t-webkit-text-size-adjust: none;\n\t-moz-text-size-adjust: none;\n\ttext-size-adjust: none;\n\tforced-color-adjust: none;\n\ttransform-origin: 0 0;\n\tcaret-color: CanvasText;\n\tz-index: 2;\n}\n\n/* Text layer rotation transforms */\n.textLayer[data-main-rotation='90'] {\n\ttransform: rotate(90deg) translateY(-100%);\n}\n\n.textLayer[data-main-rotation='180'] {\n\ttransform: rotate(180deg) translate(-100%, -100%);\n}\n\n.textLayer[data-main-rotation='270'] {\n\ttransform: rotate(270deg) translateX(-100%);\n}\n\n.textLayer :is(span, br) {\n\tcolor: transparent;\n\tposition: absolute;\n\twhite-space: pre;\n\tcursor: text;\n\ttransform-origin: 0% 0%;\n}\n\n.textLayer > :not(.markedContent),\n.textLayer .markedContent span:not(.markedContent) {\n\tz-index: 1;\n}\n\n.textLayer span.markedContent {\n\ttop: 0;\n\theight: 0;\n}\n\n.textLayer ::-moz-selection {\n\tbackground: rgba(0, 0, 255, 0.25);\n}\n\n.textLayer ::selection {\n\tbackground: rgba(0, 0, 255, 0.25);\n}\n\n.textLayer br::-moz-selection,\n.textLayer br::selection {\n\tbackground: transparent;\n}\n\n/* Search highlights */\n.textLayer .highlight {\n\tmargin: -1px;\n\tpadding: 1px;\n\tbackground-color: rgba(255, 255, 0, 0.4);\n\tborder-radius: 4px;\n}\n\n.textLayer .highlight.appended {\n\tposition: initial;\n}\n\n.textLayer .highlight.selected {\n\tbackground-color: rgba(255, 128, 0, 0.6);\n}\n\n.textLayer .highlight.begin {\n\tborder-radius: 4px 0 0 4px;\n}\n\n.textLayer .highlight.end {\n\tborder-radius: 0 4px 4px 0;\n}\n\n.textLayer .highlight.middle {\n\tborder-radius: 0;\n}\n";
|
|
5
|
+
export declare const rendererStyles = "\n/* CSS Custom Properties with defaults */\n.pdf-renderer-container {\n\t--pdf-background-color: #e8e8e8;\n\t--pdf-page-shadow: 0 2px 8px rgba(0, 0, 0, 0.12), 0 1px 3px rgba(0, 0, 0, 0.08);\n\t--pdf-scrollbar-track-color: #f1f1f1;\n\t--pdf-scrollbar-thumb-color: #c1c1c1;\n\t--pdf-scrollbar-thumb-hover-color: #a1a1a1;\n\t--pdf-scrollbar-width: 10px;\n\n\tdisplay: flex;\n\tflex-direction: column;\n\twidth: 100%;\n\theight: 100%;\n\tbackground-color: var(--pdf-background-color);\n\toverflow: hidden;\n}\n\n/* Scroll container */\n.pdf-scroll-container {\n\tflex: 1;\n\toverflow: auto;\n\tposition: relative;\n\tbackground-color: var(--pdf-background-color);\n}\n\n/* Custom scrollbar styling */\n.pdf-scroll-container::-webkit-scrollbar {\n\twidth: var(--pdf-scrollbar-width);\n\theight: var(--pdf-scrollbar-width);\n}\n\n.pdf-scroll-container::-webkit-scrollbar-track {\n\tbackground: var(--pdf-scrollbar-track-color);\n\tborder-radius: calc(var(--pdf-scrollbar-width) / 2);\n}\n\n.pdf-scroll-container::-webkit-scrollbar-thumb {\n\tbackground: var(--pdf-scrollbar-thumb-color);\n\tborder-radius: calc(var(--pdf-scrollbar-width) / 2);\n}\n\n.pdf-scroll-container::-webkit-scrollbar-thumb:hover {\n\tbackground: var(--pdf-scrollbar-thumb-hover-color);\n}\n\n/* Firefox scrollbar */\n.pdf-scroll-container {\n\tscrollbar-width: thin;\n\tscrollbar-color: var(--pdf-scrollbar-thumb-color) var(--pdf-scrollbar-track-color);\n}\n\n/* Viewer - dynamically created */\n.pdfViewer {\n\tdisplay: flex;\n\tflex-direction: column;\n\talign-items: center;\n\tpadding: 20px;\n\tgap: 16px;\n}\n\n/* Page - dynamically created with CSS variables for text layer */\n.page {\n\t--user-unit: 1;\n\t--total-scale-factor: calc(var(--scale-factor, 1) * var(--user-unit));\n\t--scale-round-x: 1px;\n\t--scale-round-y: 1px;\n\n\tposition: relative;\n\tbackground-color: white;\n\tbox-shadow: var(--pdf-page-shadow);\n\tborder-radius: 2px;\n\tmargin: 0;\n\tdirection: ltr;\n}\n\n.page .loadingIcon {\n\tposition: absolute;\n\ttop: 50%;\n\tleft: 50%;\n\ttransform: translate(-50%, -50%);\n\tcolor: #666;\n\tfont-size: 14px;\n}\n\n.page .canvasWrapper {\n\tposition: absolute;\n\tinset: 0;\n\toverflow: hidden;\n\tz-index: 0;\n}\n\n.page .pdf-canvas {\n\tdisplay: block;\n}\n\n/* Text layer - essential styles from PDF.js */\n.textLayer {\n\tposition: absolute;\n\ttext-align: initial;\n\tinset: 0;\n\toverflow: clip;\n\topacity: 1;\n\tline-height: 1;\n\t-webkit-text-size-adjust: none;\n\t-moz-text-size-adjust: none;\n\ttext-size-adjust: none;\n\tforced-color-adjust: none;\n\ttransform-origin: 0 0;\n\tcaret-color: CanvasText;\n\tz-index: 2;\n}\n\n/* Text layer rotation transforms */\n.textLayer[data-main-rotation='90'] {\n\ttransform: rotate(90deg) translateY(-100%);\n}\n\n.textLayer[data-main-rotation='180'] {\n\ttransform: rotate(180deg) translate(-100%, -100%);\n}\n\n.textLayer[data-main-rotation='270'] {\n\ttransform: rotate(270deg) translateX(-100%);\n}\n\n.textLayer :is(span, br) {\n\tcolor: transparent;\n\tposition: absolute;\n\twhite-space: pre;\n\tcursor: text;\n\ttransform-origin: 0% 0%;\n}\n\n.textLayer > :not(.markedContent),\n.textLayer .markedContent span:not(.markedContent) {\n\tz-index: 1;\n}\n\n.textLayer span.markedContent {\n\ttop: 0;\n\theight: 0;\n}\n\n.textLayer ::-moz-selection {\n\tbackground: rgba(0, 0, 255, 0.25);\n}\n\n.textLayer ::selection {\n\tbackground: rgba(0, 0, 255, 0.25);\n}\n\n.textLayer br::-moz-selection,\n.textLayer br::selection {\n\tbackground: transparent;\n}\n\n/* Search highlights */\n.textLayer .highlight {\n\tmargin: -1px;\n\tpadding: 1px;\n\tbackground-color: rgba(255, 255, 0, 0.4);\n\tborder-radius: 4px;\n}\n\n.textLayer .highlight.appended {\n\tposition: initial;\n}\n\n.textLayer .highlight.selected {\n\tbackground-color: rgba(255, 128, 0, 0.6);\n}\n\n.textLayer .highlight.begin {\n\tborder-radius: 4px 0 0 4px;\n}\n\n.textLayer .highlight.end {\n\tborder-radius: 0 4px 4px 0;\n}\n\n.textLayer .highlight.middle {\n\tborder-radius: 0;\n}\n\n/* Annotation Layer - for links, form widgets, popups */\n.annotationLayer {\n\t--annotation-unfocused-field-background: url(\"data:image/svg+xml;charset=UTF-8,<svg width='1px' height='1px' xmlns='http://www.w3.org/2000/svg'><rect width='100%' height='100%' style='fill:rgba(0, 54, 255, 0.13);'/></svg>\");\n\t--input-focus-border-color: Highlight;\n\t--input-focus-outline: 1px solid Canvas;\n\t--input-unfocused-border-color: transparent;\n\t--input-disabled-border-color: transparent;\n\t--input-hover-border-color: black;\n\n\tposition: absolute;\n\ttop: 0;\n\tleft: 0;\n\tpointer-events: none;\n\ttransform-origin: 0 0;\n\tz-index: 3;\n}\n\n.annotationLayer[data-main-rotation='90'] .norotate {\n\ttransform: rotate(270deg) translateX(-100%);\n}\n.annotationLayer[data-main-rotation='180'] .norotate {\n\ttransform: rotate(180deg) translate(-100%, -100%);\n}\n.annotationLayer[data-main-rotation='270'] .norotate {\n\ttransform: rotate(90deg) translateY(-100%);\n}\n\n.annotationLayer section {\n\tposition: absolute;\n\ttext-align: initial;\n\tpointer-events: auto;\n\tbox-sizing: border-box;\n\ttransform-origin: 0 0;\n\tuser-select: none;\n}\n\n/* Link annotations */\n.annotationLayer .linkAnnotation > a,\n.annotationLayer .buttonWidgetAnnotation.pushButton > a {\n\tposition: absolute;\n\tfont-size: 1em;\n\ttop: 0;\n\tleft: 0;\n\twidth: 100%;\n\theight: 100%;\n}\n\n.annotationLayer .linkAnnotation > a:hover,\n.annotationLayer .buttonWidgetAnnotation.pushButton > a:hover {\n\topacity: 0.2;\n\tbackground-color: rgb(255 255 0);\n\tbox-shadow: 0 2px 10px rgb(255 255 0);\n}\n\n.annotationLayer .linkAnnotation.hasBorder:hover {\n\tbackground-color: rgb(255 255 0 / 0.2);\n}\n\n/* Text annotations (comments/notes) */\n.annotationLayer .textAnnotation img {\n\tposition: absolute;\n\tcursor: pointer;\n\twidth: 100%;\n\theight: 100%;\n\ttop: 0;\n\tleft: 0;\n}\n\n/* Form widgets */\n.annotationLayer .textWidgetAnnotation input,\n.annotationLayer .textWidgetAnnotation textarea,\n.annotationLayer .choiceWidgetAnnotation select,\n.annotationLayer .buttonWidgetAnnotation.checkBox input,\n.annotationLayer .buttonWidgetAnnotation.radioButton input {\n\tbackground-image: var(--annotation-unfocused-field-background);\n\tborder: 2px solid var(--input-unfocused-border-color);\n\tbox-sizing: border-box;\n\tfont: calc(9px * var(--total-scale-factor)) sans-serif;\n\theight: 100%;\n\tmargin: 0;\n\tvertical-align: top;\n\twidth: 100%;\n}\n\n.annotationLayer .textWidgetAnnotation input:hover,\n.annotationLayer .textWidgetAnnotation textarea:hover,\n.annotationLayer .choiceWidgetAnnotation select:hover,\n.annotationLayer .buttonWidgetAnnotation.checkBox input:hover,\n.annotationLayer .buttonWidgetAnnotation.radioButton input:hover {\n\tborder: 2px solid var(--input-hover-border-color);\n}\n\n.annotationLayer .textWidgetAnnotation input:focus,\n.annotationLayer .textWidgetAnnotation textarea:focus,\n.annotationLayer .choiceWidgetAnnotation select:focus {\n\tbackground: none;\n\tborder: 2px solid var(--input-focus-border-color);\n\tborder-radius: 2px;\n\toutline: var(--input-focus-outline);\n}\n\n.annotationLayer .textWidgetAnnotation textarea {\n\tresize: none;\n}\n\n.annotationLayer .buttonWidgetAnnotation.radioButton input {\n\tborder-radius: 50%;\n}\n\n.annotationLayer .buttonWidgetAnnotation.checkBox input,\n.annotationLayer .buttonWidgetAnnotation.radioButton input {\n\tappearance: none;\n}\n\n/* Popup annotations */\n.annotationLayer .popupAnnotation {\n\tposition: absolute;\n\tfont-size: calc(9px * var(--total-scale-factor));\n\tpointer-events: none;\n\twidth: max-content;\n\tmax-width: 45%;\n\theight: auto;\n}\n\n.annotationLayer .popup {\n\tbackground-color: rgb(255 255 153);\n\tbox-shadow: 0 calc(2px * var(--total-scale-factor)) calc(5px * var(--total-scale-factor)) rgb(136 136 136);\n\tborder-radius: calc(2px * var(--total-scale-factor));\n\toutline: 1.5px solid rgb(255 255 74);\n\tpadding: calc(6px * var(--total-scale-factor));\n\tcursor: pointer;\n\tfont: message-box;\n\twhite-space: normal;\n\tword-wrap: break-word;\n\tpointer-events: auto;\n}\n\n.annotationLayer .popup > .header {\n\tdisplay: inline-block;\n}\n\n.annotationLayer .popup > .header > .title {\n\tfont-weight: bold;\n}\n\n.annotationLayer .popupContent {\n\tborder-top: 1px solid rgb(51 51 51);\n\tmargin-top: calc(2px * var(--total-scale-factor));\n\tpadding-top: calc(2px * var(--total-scale-factor));\n}\n\n.annotationLayer .popupTriggerArea {\n\tcursor: pointer;\n}\n";
|
|
@@ -200,4 +200,159 @@ export const rendererStyles = `
|
|
|
200
200
|
.textLayer .highlight.middle {
|
|
201
201
|
border-radius: 0;
|
|
202
202
|
}
|
|
203
|
+
|
|
204
|
+
/* Annotation Layer - for links, form widgets, popups */
|
|
205
|
+
.annotationLayer {
|
|
206
|
+
--annotation-unfocused-field-background: url("data:image/svg+xml;charset=UTF-8,<svg width='1px' height='1px' xmlns='http://www.w3.org/2000/svg'><rect width='100%' height='100%' style='fill:rgba(0, 54, 255, 0.13);'/></svg>");
|
|
207
|
+
--input-focus-border-color: Highlight;
|
|
208
|
+
--input-focus-outline: 1px solid Canvas;
|
|
209
|
+
--input-unfocused-border-color: transparent;
|
|
210
|
+
--input-disabled-border-color: transparent;
|
|
211
|
+
--input-hover-border-color: black;
|
|
212
|
+
|
|
213
|
+
position: absolute;
|
|
214
|
+
top: 0;
|
|
215
|
+
left: 0;
|
|
216
|
+
pointer-events: none;
|
|
217
|
+
transform-origin: 0 0;
|
|
218
|
+
z-index: 3;
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
.annotationLayer[data-main-rotation='90'] .norotate {
|
|
222
|
+
transform: rotate(270deg) translateX(-100%);
|
|
223
|
+
}
|
|
224
|
+
.annotationLayer[data-main-rotation='180'] .norotate {
|
|
225
|
+
transform: rotate(180deg) translate(-100%, -100%);
|
|
226
|
+
}
|
|
227
|
+
.annotationLayer[data-main-rotation='270'] .norotate {
|
|
228
|
+
transform: rotate(90deg) translateY(-100%);
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
.annotationLayer section {
|
|
232
|
+
position: absolute;
|
|
233
|
+
text-align: initial;
|
|
234
|
+
pointer-events: auto;
|
|
235
|
+
box-sizing: border-box;
|
|
236
|
+
transform-origin: 0 0;
|
|
237
|
+
user-select: none;
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
/* Link annotations */
|
|
241
|
+
.annotationLayer .linkAnnotation > a,
|
|
242
|
+
.annotationLayer .buttonWidgetAnnotation.pushButton > a {
|
|
243
|
+
position: absolute;
|
|
244
|
+
font-size: 1em;
|
|
245
|
+
top: 0;
|
|
246
|
+
left: 0;
|
|
247
|
+
width: 100%;
|
|
248
|
+
height: 100%;
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
.annotationLayer .linkAnnotation > a:hover,
|
|
252
|
+
.annotationLayer .buttonWidgetAnnotation.pushButton > a:hover {
|
|
253
|
+
opacity: 0.2;
|
|
254
|
+
background-color: rgb(255 255 0);
|
|
255
|
+
box-shadow: 0 2px 10px rgb(255 255 0);
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
.annotationLayer .linkAnnotation.hasBorder:hover {
|
|
259
|
+
background-color: rgb(255 255 0 / 0.2);
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
/* Text annotations (comments/notes) */
|
|
263
|
+
.annotationLayer .textAnnotation img {
|
|
264
|
+
position: absolute;
|
|
265
|
+
cursor: pointer;
|
|
266
|
+
width: 100%;
|
|
267
|
+
height: 100%;
|
|
268
|
+
top: 0;
|
|
269
|
+
left: 0;
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
/* Form widgets */
|
|
273
|
+
.annotationLayer .textWidgetAnnotation input,
|
|
274
|
+
.annotationLayer .textWidgetAnnotation textarea,
|
|
275
|
+
.annotationLayer .choiceWidgetAnnotation select,
|
|
276
|
+
.annotationLayer .buttonWidgetAnnotation.checkBox input,
|
|
277
|
+
.annotationLayer .buttonWidgetAnnotation.radioButton input {
|
|
278
|
+
background-image: var(--annotation-unfocused-field-background);
|
|
279
|
+
border: 2px solid var(--input-unfocused-border-color);
|
|
280
|
+
box-sizing: border-box;
|
|
281
|
+
font: calc(9px * var(--total-scale-factor)) sans-serif;
|
|
282
|
+
height: 100%;
|
|
283
|
+
margin: 0;
|
|
284
|
+
vertical-align: top;
|
|
285
|
+
width: 100%;
|
|
286
|
+
}
|
|
287
|
+
|
|
288
|
+
.annotationLayer .textWidgetAnnotation input:hover,
|
|
289
|
+
.annotationLayer .textWidgetAnnotation textarea:hover,
|
|
290
|
+
.annotationLayer .choiceWidgetAnnotation select:hover,
|
|
291
|
+
.annotationLayer .buttonWidgetAnnotation.checkBox input:hover,
|
|
292
|
+
.annotationLayer .buttonWidgetAnnotation.radioButton input:hover {
|
|
293
|
+
border: 2px solid var(--input-hover-border-color);
|
|
294
|
+
}
|
|
295
|
+
|
|
296
|
+
.annotationLayer .textWidgetAnnotation input:focus,
|
|
297
|
+
.annotationLayer .textWidgetAnnotation textarea:focus,
|
|
298
|
+
.annotationLayer .choiceWidgetAnnotation select:focus {
|
|
299
|
+
background: none;
|
|
300
|
+
border: 2px solid var(--input-focus-border-color);
|
|
301
|
+
border-radius: 2px;
|
|
302
|
+
outline: var(--input-focus-outline);
|
|
303
|
+
}
|
|
304
|
+
|
|
305
|
+
.annotationLayer .textWidgetAnnotation textarea {
|
|
306
|
+
resize: none;
|
|
307
|
+
}
|
|
308
|
+
|
|
309
|
+
.annotationLayer .buttonWidgetAnnotation.radioButton input {
|
|
310
|
+
border-radius: 50%;
|
|
311
|
+
}
|
|
312
|
+
|
|
313
|
+
.annotationLayer .buttonWidgetAnnotation.checkBox input,
|
|
314
|
+
.annotationLayer .buttonWidgetAnnotation.radioButton input {
|
|
315
|
+
appearance: none;
|
|
316
|
+
}
|
|
317
|
+
|
|
318
|
+
/* Popup annotations */
|
|
319
|
+
.annotationLayer .popupAnnotation {
|
|
320
|
+
position: absolute;
|
|
321
|
+
font-size: calc(9px * var(--total-scale-factor));
|
|
322
|
+
pointer-events: none;
|
|
323
|
+
width: max-content;
|
|
324
|
+
max-width: 45%;
|
|
325
|
+
height: auto;
|
|
326
|
+
}
|
|
327
|
+
|
|
328
|
+
.annotationLayer .popup {
|
|
329
|
+
background-color: rgb(255 255 153);
|
|
330
|
+
box-shadow: 0 calc(2px * var(--total-scale-factor)) calc(5px * var(--total-scale-factor)) rgb(136 136 136);
|
|
331
|
+
border-radius: calc(2px * var(--total-scale-factor));
|
|
332
|
+
outline: 1.5px solid rgb(255 255 74);
|
|
333
|
+
padding: calc(6px * var(--total-scale-factor));
|
|
334
|
+
cursor: pointer;
|
|
335
|
+
font: message-box;
|
|
336
|
+
white-space: normal;
|
|
337
|
+
word-wrap: break-word;
|
|
338
|
+
pointer-events: auto;
|
|
339
|
+
}
|
|
340
|
+
|
|
341
|
+
.annotationLayer .popup > .header {
|
|
342
|
+
display: inline-block;
|
|
343
|
+
}
|
|
344
|
+
|
|
345
|
+
.annotationLayer .popup > .header > .title {
|
|
346
|
+
font-weight: bold;
|
|
347
|
+
}
|
|
348
|
+
|
|
349
|
+
.annotationLayer .popupContent {
|
|
350
|
+
border-top: 1px solid rgb(51 51 51);
|
|
351
|
+
margin-top: calc(2px * var(--total-scale-factor));
|
|
352
|
+
padding-top: calc(2px * var(--total-scale-factor));
|
|
353
|
+
}
|
|
354
|
+
|
|
355
|
+
.annotationLayer .popupTriggerArea {
|
|
356
|
+
cursor: pointer;
|
|
357
|
+
}
|
|
203
358
|
`;
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
/* PDF Viewer Styles -
|
|
1
|
+
/* PDF Viewer Styles - For PdfViewerInner (non-Shadow DOM) */
|
|
2
2
|
|
|
3
3
|
/* Container */
|
|
4
4
|
.pdf-viewer-container {
|
|
@@ -128,142 +128,6 @@
|
|
|
128
128
|
background-color: #e8e8e8;
|
|
129
129
|
}
|
|
130
130
|
|
|
131
|
-
/* Viewer - dynamically created */
|
|
132
|
-
.pdfViewer {
|
|
133
|
-
display: flex;
|
|
134
|
-
flex-direction: column;
|
|
135
|
-
align-items: center;
|
|
136
|
-
padding: 20px;
|
|
137
|
-
gap: 16px;
|
|
138
|
-
}
|
|
139
|
-
|
|
140
|
-
/* Page - dynamically created with CSS variables for text layer */
|
|
141
|
-
.page {
|
|
142
|
-
--user-unit: 1;
|
|
143
|
-
--total-scale-factor: calc(var(--scale-factor, 1) * var(--user-unit));
|
|
144
|
-
--scale-round-x: 1px;
|
|
145
|
-
--scale-round-y: 1px;
|
|
146
|
-
|
|
147
|
-
position: relative;
|
|
148
|
-
background-color: white;
|
|
149
|
-
box-shadow:
|
|
150
|
-
0 2px 8px rgba(0, 0, 0, 0.12),
|
|
151
|
-
0 1px 3px rgba(0, 0, 0, 0.08);
|
|
152
|
-
border-radius: 2px;
|
|
153
|
-
margin: 0;
|
|
154
|
-
direction: ltr;
|
|
155
|
-
}
|
|
156
|
-
|
|
157
|
-
.page .loadingIcon {
|
|
158
|
-
position: absolute;
|
|
159
|
-
top: 50%;
|
|
160
|
-
left: 50%;
|
|
161
|
-
transform: translate(-50%, -50%);
|
|
162
|
-
color: #666;
|
|
163
|
-
font-size: 14px;
|
|
164
|
-
}
|
|
165
|
-
|
|
166
|
-
.page .canvasWrapper {
|
|
167
|
-
position: absolute;
|
|
168
|
-
inset: 0;
|
|
169
|
-
overflow: hidden;
|
|
170
|
-
z-index: 0;
|
|
171
|
-
}
|
|
172
|
-
|
|
173
|
-
.page .pdf-canvas {
|
|
174
|
-
display: block;
|
|
175
|
-
}
|
|
176
|
-
|
|
177
|
-
/* Text layer - essential styles from PDF.js */
|
|
178
|
-
.textLayer {
|
|
179
|
-
position: absolute;
|
|
180
|
-
text-align: initial;
|
|
181
|
-
inset: 0;
|
|
182
|
-
overflow: clip;
|
|
183
|
-
opacity: 1;
|
|
184
|
-
line-height: 1;
|
|
185
|
-
-webkit-text-size-adjust: none;
|
|
186
|
-
-moz-text-size-adjust: none;
|
|
187
|
-
text-size-adjust: none;
|
|
188
|
-
forced-color-adjust: none;
|
|
189
|
-
transform-origin: 0 0;
|
|
190
|
-
caret-color: CanvasText;
|
|
191
|
-
z-index: 2;
|
|
192
|
-
}
|
|
193
|
-
|
|
194
|
-
/* Text layer rotation transforms - text is rendered in raw page coordinates,
|
|
195
|
-
then rotated via CSS to match the canvas orientation */
|
|
196
|
-
.textLayer[data-main-rotation='90'] {
|
|
197
|
-
transform: rotate(90deg) translateY(-100%);
|
|
198
|
-
}
|
|
199
|
-
|
|
200
|
-
.textLayer[data-main-rotation='180'] {
|
|
201
|
-
transform: rotate(180deg) translate(-100%, -100%);
|
|
202
|
-
}
|
|
203
|
-
|
|
204
|
-
.textLayer[data-main-rotation='270'] {
|
|
205
|
-
transform: rotate(270deg) translateX(-100%);
|
|
206
|
-
}
|
|
207
|
-
|
|
208
|
-
.textLayer :is(span, br) {
|
|
209
|
-
color: transparent;
|
|
210
|
-
position: absolute;
|
|
211
|
-
white-space: pre;
|
|
212
|
-
cursor: text;
|
|
213
|
-
transform-origin: 0% 0%;
|
|
214
|
-
}
|
|
215
|
-
|
|
216
|
-
.textLayer > :not(.markedContent),
|
|
217
|
-
.textLayer .markedContent span:not(.markedContent) {
|
|
218
|
-
z-index: 1;
|
|
219
|
-
}
|
|
220
|
-
|
|
221
|
-
.textLayer span.markedContent {
|
|
222
|
-
top: 0;
|
|
223
|
-
height: 0;
|
|
224
|
-
}
|
|
225
|
-
|
|
226
|
-
.textLayer ::-moz-selection {
|
|
227
|
-
background: rgba(0, 0, 255, 0.25);
|
|
228
|
-
}
|
|
229
|
-
|
|
230
|
-
.textLayer ::selection {
|
|
231
|
-
background: rgba(0, 0, 255, 0.25);
|
|
232
|
-
}
|
|
233
|
-
|
|
234
|
-
.textLayer br::-moz-selection,
|
|
235
|
-
.textLayer br::selection {
|
|
236
|
-
background: transparent;
|
|
237
|
-
}
|
|
238
|
-
|
|
239
|
-
/* Search highlights */
|
|
240
|
-
.textLayer .highlight {
|
|
241
|
-
margin: -1px;
|
|
242
|
-
padding: 1px;
|
|
243
|
-
background-color: rgba(255, 255, 0, 0.4);
|
|
244
|
-
border-radius: 4px;
|
|
245
|
-
}
|
|
246
|
-
|
|
247
|
-
.textLayer .highlight.appended {
|
|
248
|
-
position: initial;
|
|
249
|
-
}
|
|
250
|
-
|
|
251
|
-
.textLayer .highlight.selected {
|
|
252
|
-
background-color: rgba(255, 128, 0, 0.6);
|
|
253
|
-
}
|
|
254
|
-
|
|
255
|
-
.textLayer .highlight.begin {
|
|
256
|
-
border-radius: 4px 0 0 4px;
|
|
257
|
-
}
|
|
258
|
-
|
|
259
|
-
.textLayer .highlight.end {
|
|
260
|
-
border-radius: 0 4px 4px 0;
|
|
261
|
-
}
|
|
262
|
-
|
|
263
|
-
.textLayer .highlight.middle {
|
|
264
|
-
border-radius: 0;
|
|
265
|
-
}
|
|
266
|
-
|
|
267
131
|
/* Loading state */
|
|
268
132
|
.pdf-loading,
|
|
269
133
|
.pdf-error {
|