svelte-pdf-view 0.1.13 → 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.
@@ -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
+ }
@@ -1,5 +1,12 @@
1
1
  /** PDF source - can be a URL string, ArrayBuffer, Uint8Array, or Blob */
2
2
  export type PdfSource = string | ArrayBuffer | Uint8Array | Blob;
3
+ /** Presentation mode state */
4
+ export declare enum PresentationModeState {
5
+ UNKNOWN = 0,
6
+ NORMAL = 1,
7
+ CHANGING = 2,
8
+ FULLSCREEN = 3
9
+ }
3
10
  export interface PdfViewerState {
4
11
  loading: boolean;
5
12
  error: string | null;
@@ -11,6 +18,7 @@ export interface PdfViewerState {
11
18
  searchCurrent: number;
12
19
  searchTotal: number;
13
20
  isSearching: boolean;
21
+ presentationMode: PresentationModeState;
14
22
  }
15
23
  export interface PdfViewerActions {
16
24
  zoomIn: () => void;
@@ -24,6 +32,10 @@ export interface PdfViewerActions {
24
32
  searchPrevious: () => void;
25
33
  clearSearch: () => void;
26
34
  download: (filename?: string) => Promise<void>;
35
+ /** Enter fullscreen presentation mode */
36
+ enterPresentationMode: () => Promise<boolean>;
37
+ /** Exit fullscreen presentation mode */
38
+ exitPresentationMode: () => Promise<void>;
27
39
  }
28
40
  export interface PdfViewerContext {
29
41
  state: PdfViewerState;
@@ -3,6 +3,14 @@
3
3
  */
4
4
  import { getContext, setContext } from 'svelte';
5
5
  const PDF_VIEWER_CONTEXT_KEY = Symbol('pdf-viewer');
6
+ /** Presentation mode state */
7
+ export var PresentationModeState;
8
+ (function (PresentationModeState) {
9
+ PresentationModeState[PresentationModeState["UNKNOWN"] = 0] = "UNKNOWN";
10
+ PresentationModeState[PresentationModeState["NORMAL"] = 1] = "NORMAL";
11
+ PresentationModeState[PresentationModeState["CHANGING"] = 2] = "CHANGING";
12
+ PresentationModeState[PresentationModeState["FULLSCREEN"] = 3] = "FULLSCREEN";
13
+ })(PresentationModeState || (PresentationModeState = {}));
6
14
  export function setPdfViewerContext(ctx) {
7
15
  setContext(PDF_VIEWER_CONTEXT_KEY, ctx);
8
16
  }
@@ -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 - Sandboxed for Shadow DOM */
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 {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "svelte-pdf-view",
3
- "version": "0.1.13",
3
+ "version": "0.3.0",
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",