@inizioevoke/astro-core 1.5.2 → 1.6.1

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.
@@ -127,13 +127,13 @@
127
127
  }*/
128
128
  .evo-pdf-viewer-container {
129
129
  height: calc(var(--evo-pdf-viewer-height) - var(--evo-pdf-viewer-toolbar-height));
130
- padding: 0 9px 9px 9px;
130
+ padding: 0 9px 9px var(--evo-scroll-container-content-padding-right);
131
131
  box-sizing: border-box;
132
132
 
133
133
  .evo-scroll-container {
134
134
  --evo-scroll-container-track-color: #cccccc;
135
135
  .evo-scroll-content {
136
- canvas {
136
+ .evo-pdf-viewer-page-wrapper {
137
137
  display: block;
138
138
  margin: 18px auto;
139
139
  background-color: white;
@@ -145,6 +145,21 @@
145
145
  margin-bottom: 0;
146
146
  }
147
147
  }
148
+
149
+ canvas {
150
+ display: block;
151
+ width: 100%;
152
+ height: 100%;
153
+ }
154
+
155
+ .evo-pdf-viewer-link-overlay {
156
+ pointer-events: auto;
157
+ z-index: 1;
158
+
159
+ rect {
160
+ cursor: pointer;
161
+ }
162
+ }
148
163
  }
149
164
  }
150
165
  }
@@ -4,6 +4,25 @@ import * as ScrollContainer from '../ScrollContainer/archive-20260417';
4
4
 
5
5
  pdfjs.GlobalWorkerOptions.workerSrc = './pdf.worker.min.mjs';
6
6
 
7
+
8
+ export interface PdfViewerCustomEventDetail {
9
+ type: string;
10
+ cmd?: string;
11
+ args?: Record<string, string>
12
+ }
13
+ declare global {
14
+ interface ElementEventMap {
15
+ 'pdfviewer-click': CustomEvent<PdfViewerCustomEventDetail>
16
+ }
17
+ }
18
+
19
+ interface LinkAnnotation {
20
+ rect: [number, number, number, number];
21
+ url?: string;
22
+ dest?: any;
23
+ newWindow?: boolean;
24
+ }
25
+
7
26
  interface PdfViewerSettings {
8
27
  workerDir?: string;
9
28
  workerSrc?: string;
@@ -18,6 +37,186 @@ export function init(settings: PdfViewerSettings = {}) {
18
37
  pdfjs.GlobalWorkerOptions.workerSrc = settings.workerSrc;
19
38
  }
20
39
 
40
+ /**
41
+ * Extract link annotations from a PDF page
42
+ */
43
+ async function getPageLinks(pdfPage: any): Promise<LinkAnnotation[]> {
44
+ const annotations = await pdfPage.getAnnotations();
45
+ const links: LinkAnnotation[] = [];
46
+
47
+ for (const annotation of annotations) {
48
+ // Only process link annotations (type 2 is subtype for URI links and internal links)
49
+ if (annotation.subtype === 'Link') {
50
+ const link: LinkAnnotation = {
51
+ rect: annotation.rect
52
+ };
53
+
54
+ if (annotation.url) {
55
+ link.url = annotation.url;
56
+ link.newWindow = annotation.newWindow;
57
+ } else if (annotation.dest) {
58
+ link.dest = annotation.dest;
59
+ }
60
+
61
+ links.push(link);
62
+ }
63
+ }
64
+
65
+ return links;
66
+ }
67
+
68
+ /**
69
+ * Convert PDF coordinates to viewport coordinates
70
+ * PDF coords: [left, bottom, right, top] with origin at bottom-left
71
+ * Return: {x, y, width, height} with origin at top-left
72
+ */
73
+ function convertPdfRectToViewport(
74
+ pdfRect: [number, number, number, number],
75
+ viewport: any
76
+ ): { x: number; y: number; width: number; height: number } {
77
+ const [pdfLeft, pdfBottom, pdfRight, pdfTop] = pdfRect;
78
+ const pageHeight = viewport.height / viewport.scale;
79
+
80
+ // Scale coordinates
81
+ const x = pdfLeft * viewport.scale;
82
+ const y = (pageHeight - pdfTop) * viewport.scale;
83
+ const width = (pdfRight - pdfLeft) * viewport.scale;
84
+ const height = (pdfTop - pdfBottom) * viewport.scale;
85
+
86
+ return { x, y, width, height };
87
+ }
88
+
89
+ /*
90
+ * Custom PDF link events
91
+ * To create a custom event, create a link in the PDF that starts with `http://pdfviewer`
92
+ * Arguments can be passed via the path and query string
93
+ * http://pdfviewer/type/cmd/?args=1
94
+ */
95
+ /**
96
+ * Create an SVG overlay with link areas above a canvas element
97
+ */
98
+ function createLinkOverlay(
99
+ canvas: HTMLCanvasElement,
100
+ links: LinkAnnotation[],
101
+ viewport: any,
102
+ pageNum: number,
103
+ pdfViewer: HTMLElement,
104
+ gotoPage: (page: number | string) => void,
105
+ pdf: PDFDocumentProxy
106
+ ): SVGSVGElement {
107
+ const trackWidth = parseInt(
108
+ getComputedStyle(canvas.parentElement!).getPropertyValue('--evo-scroll-container-track-width')
109
+ );
110
+
111
+ const canvasWidth = Math.floor(viewport.width) - trackWidth * 2;
112
+ const canvasHeight = Math.floor(viewport.height);
113
+
114
+ // Create SVG overlay
115
+ const svg = document.createElementNS('http://www.w3.org/2000/svg', 'svg');
116
+ svg.setAttribute('class', 'evo-pdf-viewer-link-overlay');
117
+ svg.setAttribute('data-page', pageNum.toString());
118
+ svg.setAttribute('viewBox', `0 0 ${viewport.width} ${viewport.height}`);
119
+ svg.setAttribute('width', `calc(${canvasWidth}px * var(--evo-pdf-viewer-scale))`);
120
+ svg.setAttribute('height', `calc(${canvasHeight}px * var(--evo-pdf-viewer-scale))`);
121
+ svg.style.position = 'absolute';
122
+ svg.style.top = '0';
123
+ svg.style.left = '0';
124
+ svg.style.cursor = 'pointer';
125
+
126
+ // Add link rectangles
127
+ for (const link of links) {
128
+ const rect = convertPdfRectToViewport(link.rect, viewport);
129
+
130
+ // Create invisible clickable area
131
+ const linkRect = document.createElementNS('http://www.w3.org/2000/svg', 'rect');
132
+ linkRect.setAttribute('x', rect.x.toString());
133
+ linkRect.setAttribute('y', rect.y.toString());
134
+ linkRect.setAttribute('width', rect.width.toString());
135
+ linkRect.setAttribute('height', rect.height.toString());
136
+ linkRect.setAttribute('fill', 'transparent');
137
+ linkRect.setAttribute('stroke', 'none');
138
+ linkRect.style.cursor = 'pointer';
139
+
140
+ // Handle click on link
141
+ linkRect.addEventListener('click', (e) => {
142
+ e.preventDefault();
143
+ e.stopPropagation();
144
+
145
+ if (link.url) {
146
+ if (link.url.startsWith('http://pdfviewer')) {
147
+ const url = new URL(link.url);
148
+ const [ type, cmd ] = url.pathname.split('/').filter(p => p.trim() !== '');
149
+ const args = url.search === '' ? undefined :
150
+ Object.fromEntries(url.search.slice(1).split('&')
151
+ .map((pair) => {
152
+ return pair.split('=').slice(0, 2);
153
+ })
154
+ );
155
+
156
+ pdfViewer.dispatchEvent(new CustomEvent('pdfviewer-click', {
157
+ detail: { type: type ?? '', cmd, args }
158
+ }));
159
+ return;
160
+ }
161
+ // Check for custom protocol
162
+ // if (link.url.includes('://')) {
163
+ // const protocol = link.url.split('://')[0];
164
+ // if (!['http', 'https', 'ftp', 'ftps', 'mailto'].includes(protocol)) {
165
+ // // Dispatch custom event for non-standard protocols
166
+ // const event = new CustomEvent('pdf-link-click', {
167
+ // detail: {
168
+ // type: 'uri',
169
+ // uri: link.url,
170
+ // pageNum,
171
+ // newWindow: link.newWindow
172
+ // }
173
+ // });
174
+ // pdfViewer.dispatchEvent(event);
175
+ // return;
176
+ // }
177
+ // }
178
+
179
+ // Handle standard URLs
180
+ const target = link.newWindow ? '_blank' : '_self';
181
+ window.open(link.url, target);
182
+ } else if (link.dest) {
183
+ // Handle internal navigation
184
+ // dest is typically an array [pageRef, ...] where pageRef is {num, gen} or a page index
185
+ if (Array.isArray(link.dest)) {
186
+ const destRef = link.dest[0];
187
+
188
+ // Check if it's a reference object with num and gen properties
189
+ if (destRef && typeof destRef === 'object' && 'num' in destRef && 'gen' in destRef) {
190
+ // Resolve the reference to a page index using the PDF document
191
+ pdf.getPageIndex(destRef).then((pageIndex: number) => {
192
+ gotoPage(pageIndex + 1); // PDF pages are 0-indexed, but gotoPage expects 1-indexed
193
+ }).catch((err: Error) => {
194
+ console.error('Failed to resolve PDF link destination:', err);
195
+ });
196
+ } else if (typeof destRef === 'number') {
197
+ // Direct page index
198
+ gotoPage(destRef + 1);
199
+ }
200
+ }
201
+
202
+ // Dispatch event for custom dest handling
203
+ const event = new CustomEvent('pdf-link-click', {
204
+ detail: {
205
+ type: 'goto',
206
+ dest: link.dest,
207
+ pageNum
208
+ }
209
+ });
210
+ pdfViewer.dispatchEvent(event);
211
+ }
212
+ }, { passive: false });
213
+
214
+ svg.appendChild(linkRect);
215
+ }
216
+
217
+ return svg;
218
+ }
219
+
21
220
  export async function createViewer(selector: string) {
22
221
  const pdfViewer = document.querySelector<HTMLElement>(selector);
23
222
  if (pdfViewer) {
@@ -26,11 +225,11 @@ export async function createViewer(selector: string) {
26
225
  const toolbarHeight = parseFloat(getComputedStyle(toolbar).height);
27
226
  const pageSelect = toolbar.querySelector('.evo-pdf-viewer-nav-page select') as HTMLSelectElement;
28
227
  const scrollContainer = ScrollContainer.getScrollContainer(pdfViewer.querySelector('.evo-scroll-container') as HTMLElement) as ScrollContainer.IScrollContainer;
29
-
228
+ const defaultZoom = parseInt(pdfViewer.getAttribute('data-zoom') ?? '100', 10);
30
229
  const zoom = (z: number) => {
31
230
  const style = getComputedStyle(pdfViewer);
32
231
  const current = Math.floor(parseFloat(style.getPropertyValue('--evo-pdf-viewer-scale')) * 10);
33
- const scale = z === 0 ? 10 : current + z;
232
+ const scale = z === 0 ? defaultZoom / 10 : current + z;
34
233
  if (scale >= 1) {
35
234
  const scroll = ScrollContainer.getScroll(scrollContainer);
36
235
  pdfViewer.style.setProperty('--evo-pdf-viewer-scale', (scale/10).toString());
@@ -38,6 +237,7 @@ export async function createViewer(selector: string) {
38
237
  ScrollContainer.refresh(scrollContainer);
39
238
  }
40
239
  };
240
+ zoom(0);
41
241
 
42
242
  const gotoPage = (page: number | string) => {
43
243
  if (typeof page == 'string') {
@@ -72,11 +272,13 @@ export async function createViewer(selector: string) {
72
272
 
73
273
  const pdf: PDFDocumentProxy = await pdfjs.getDocument(pdfViewer.getAttribute('data-pdf') as string).promise;
74
274
 
75
- const bookmarksSelect = toolbar.querySelector('.evo-pdf-viewer-toolbar-bookmarks select') as HTMLSelectElement;
76
- bookmarksSelect.addEventListener('change', () => {
77
- gotoPage(bookmarksSelect.options[bookmarksSelect.selectedIndex].value);
78
- bookmarksSelect.selectedIndex = 0;
79
- });
275
+ const bookmarksSelect = toolbar.querySelector<HTMLSelectElement>('.evo-pdf-viewer-toolbar-bookmarks select');
276
+ if (bookmarksSelect) {
277
+ bookmarksSelect.addEventListener('change', () => {
278
+ gotoPage(bookmarksSelect.options[bookmarksSelect.selectedIndex].value);
279
+ bookmarksSelect.selectedIndex = 0;
280
+ });
281
+ }
80
282
 
81
283
  const outputScale = window.devicePixelRatio || 1;
82
284
  const pages = new Array(pdf.numPages).fill(1).map((v, i) => { return i + 1; });
@@ -96,7 +298,6 @@ export async function createViewer(selector: string) {
96
298
  }
97
299
  }, { passive: true });
98
300
 
99
-
100
301
  const pdfContainer = pdfViewer.querySelector('.evo-scroll-content') as HTMLElement;
101
302
  pdfViewer.querySelector<HTMLElement>('.evo-pdf-viewer-nav-page-count')!.innerText = pages.length.toString();
102
303
  pageSelect.options.remove(0);
@@ -123,7 +324,16 @@ export async function createViewer(selector: string) {
123
324
  canvas.style.aspectRatio = `${width}/${height}`;
124
325
  canvas.style.width = `calc(${Math.floor(viewport.width) - (trackWidth * 2)}px * var(--evo-pdf-viewer-scale)`;
125
326
  // canvas.style.height = `${Math.floor(viewport.height)}px`;
126
- pdfContainer.append(canvas);
327
+
328
+ // Create wrapper for canvas and overlay
329
+ const pageWrapper = document.createElement('div');
330
+ pageWrapper.className = 'evo-pdf-viewer-page-wrapper';
331
+ pageWrapper.style.position = 'relative';
332
+ pageWrapper.style.width = canvas.style.width;
333
+ pageWrapper.style.aspectRatio = canvas.style.aspectRatio;
334
+ pageWrapper.appendChild(canvas);
335
+
336
+ pdfContainer.append(pageWrapper);
127
337
 
128
338
  pdfPage.render({
129
339
  canvas,
@@ -131,6 +341,14 @@ export async function createViewer(selector: string) {
131
341
  transform: outputScale !== 1 ? [outputScale, 0, 0, outputScale, 0, 0] : undefined,
132
342
  viewport
133
343
  });
344
+
345
+ // Extract and render links
346
+ const links = await getPageLinks(pdfPage);
347
+ if (links.length > 0) {
348
+ const linkOverlay = createLinkOverlay(canvas, links, viewport, pageNum, pdfViewer, gotoPage, pdf);
349
+ pageWrapper.appendChild(linkOverlay);
350
+ }
351
+
134
352
  ScrollContainer.refresh(scrollContainer);
135
353
  }
136
354