diff --git a/frontend/e2e/documents.spec.ts b/frontend/e2e/documents.spec.ts index 5743ab02..54e56b1d 100644 --- a/frontend/e2e/documents.spec.ts +++ b/frontend/e2e/documents.spec.ts @@ -1,4 +1,5 @@ import { test, expect } from '@playwright/test'; +import path from 'path'; /** * Document management E2E tests. @@ -142,3 +143,78 @@ test.describe('Document edit', () => { await page.screenshot({ path: 'test-results/e2e/document-edit-date-error.png' }); }); }); + +// ─── PDF Viewer ─────────────────────────────────────────────────────────────── + +const PDF_FIXTURE = path.resolve(__dirname, 'fixtures/minimal.pdf'); + +test.describe('PDF viewer', () => { + let pdfDocHref: string; + + test.beforeAll(async ({ browser }) => { + // Create a document and upload the PDF fixture so later tests have a + // real file attached. Runs once for the whole describe block. + const ctx = await browser.newContext(); + const p = await ctx.newPage(); + + await p.goto('/documents/new'); + await p.waitForSelector('[data-hydrated]'); + await p.getByLabel('Titel').fill('E2E PDF Viewer Test'); + await p.getByRole('button', { name: /Speichern/i }).click(); + await p.waitForURL(/\/documents\/[^/]+$/); + + // Upload the PDF on the edit page + const href = p.url().replace(/\/$/, ''); + pdfDocHref = href; + await p.goto(`${href}/edit`); + await p.waitForSelector('[data-hydrated]'); + await p.locator('input[type="file"][name="file"]').setInputFiles(PDF_FIXTURE); + await p.getByRole('button', { name: /Speichern/i }).click(); + await p.waitForURL(/\/documents\/[^/]+$/); + + await ctx.close(); + }); + + test('PDF renders in the custom viewer — canvas is present instead of iframe', async ({ + page + }) => { + await page.goto(pdfDocHref); + await page.waitForSelector('[data-hydrated]'); + + // There must be NO iframe — we replaced it with PDF.js canvas rendering. + await expect(page.locator('iframe')).not.toBeAttached(); + + // At least one canvas element must be visible (one per rendered page). + await expect(page.locator('canvas').first()).toBeVisible({ timeout: 15000 }); + + await page.screenshot({ path: 'test-results/e2e/pdf-viewer-canvas.png' }); + }); + + test('page navigation controls are visible', async ({ page }) => { + await page.goto(pdfDocHref); + await page.waitForSelector('[data-hydrated]'); + await page.locator('canvas').first().waitFor({ state: 'visible', timeout: 15000 }); + + await expect(page.getByRole('button', { name: /prev|previous|zurück|vorige/i })).toBeVisible(); + await expect(page.getByRole('button', { name: /next|weiter|nächste/i })).toBeVisible(); + + await page.screenshot({ path: 'test-results/e2e/pdf-viewer-nav.png' }); + }); + + test('non-PDF attachment renders as an img element, not canvas', async ({ page }) => { + // The seed document "Urlaubspostkarte Ostsee" has a .jpg original filename. + // Navigate to it and confirm an is used (no canvas, no iframe). + await page.goto('/'); + await page.waitForSelector('[data-hydrated]'); + await page.goto('/?q=Urlaubspostkarte'); + const link = page.getByRole('link', { name: /Urlaubspostkarte/i }).first(); + const href = await link.getAttribute('href'); + await page.goto(href!); + await page.waitForSelector('[data-hydrated]'); + + // No canvas — this is an image document + await expect(page.locator('canvas')).not.toBeAttached(); + + await page.screenshot({ path: 'test-results/e2e/pdf-viewer-image-fallback.png' }); + }); +}); diff --git a/frontend/e2e/fixtures/minimal.pdf b/frontend/e2e/fixtures/minimal.pdf new file mode 100644 index 00000000..c127ad96 --- /dev/null +++ b/frontend/e2e/fixtures/minimal.pdf @@ -0,0 +1,21 @@ +%PDF-1.4 +1 0 obj +<> +endobj +2 0 obj +<> +endobj +3 0 obj +<> +endobj +xref +0 4 +0000000000 65535 f +0000000009 00000 n +0000000054 00000 n +0000000105 00000 n +trailer +<> +startxref +170 +%%EOF diff --git a/frontend/package-lock.json b/frontend/package-lock.json index 6795fc10..493c551e 100644 --- a/frontend/package-lock.json +++ b/frontend/package-lock.json @@ -9,7 +9,8 @@ "version": "0.0.1", "dependencies": { "diff": "^8.0.3", - "openapi-fetch": "^0.13.5" + "openapi-fetch": "^0.13.5", + "pdfjs-dist": "^5.5.207" }, "devDependencies": { "@eslint/compat": "^1.4.0", @@ -885,6 +886,256 @@ "dev": true, "license": "Apache-2.0" }, + "node_modules/@napi-rs/canvas": { + "version": "0.1.97", + "resolved": "https://registry.npmjs.org/@napi-rs/canvas/-/canvas-0.1.97.tgz", + "integrity": "sha512-8cFniXvrIEnVwuNSRCW9wirRZbHvrD3JVujdS2P5n5xiJZNZMOZcfOvJ1pb66c7jXMKHHglJEDVJGbm8XWFcXQ==", + "license": "MIT", + "optional": true, + "workspaces": [ + "e2e/*" + ], + "engines": { + "node": ">= 10" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/Brooooooklyn" + }, + "optionalDependencies": { + "@napi-rs/canvas-android-arm64": "0.1.97", + "@napi-rs/canvas-darwin-arm64": "0.1.97", + "@napi-rs/canvas-darwin-x64": "0.1.97", + "@napi-rs/canvas-linux-arm-gnueabihf": "0.1.97", + "@napi-rs/canvas-linux-arm64-gnu": "0.1.97", + "@napi-rs/canvas-linux-arm64-musl": "0.1.97", + "@napi-rs/canvas-linux-riscv64-gnu": "0.1.97", + "@napi-rs/canvas-linux-x64-gnu": "0.1.97", + "@napi-rs/canvas-linux-x64-musl": "0.1.97", + "@napi-rs/canvas-win32-arm64-msvc": "0.1.97", + "@napi-rs/canvas-win32-x64-msvc": "0.1.97" + } + }, + "node_modules/@napi-rs/canvas-android-arm64": { + "version": "0.1.97", + "resolved": "https://registry.npmjs.org/@napi-rs/canvas-android-arm64/-/canvas-android-arm64-0.1.97.tgz", + "integrity": "sha512-V1c/WVw+NzH8vk7ZK/O8/nyBSCQimU8sfMsB/9qeSvdkGKNU7+mxy/bIF0gTgeBFmHpj30S4E9WHMSrxXGQuVQ==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">= 10" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/Brooooooklyn" + } + }, + "node_modules/@napi-rs/canvas-darwin-arm64": { + "version": "0.1.97", + "resolved": "https://registry.npmjs.org/@napi-rs/canvas-darwin-arm64/-/canvas-darwin-arm64-0.1.97.tgz", + "integrity": "sha512-ok+SCEF4YejcxuJ9Rm+WWunHHpf2HmiPxfz6z1a/NFQECGXtsY7A4B8XocK1LmT1D7P174MzwPF9Wy3AUAwEPw==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">= 10" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/Brooooooklyn" + } + }, + "node_modules/@napi-rs/canvas-darwin-x64": { + "version": "0.1.97", + "resolved": "https://registry.npmjs.org/@napi-rs/canvas-darwin-x64/-/canvas-darwin-x64-0.1.97.tgz", + "integrity": "sha512-PUP6e6/UGlclUvAQNnuXCcnkpdUou6VYZfQOQxExLp86epOylmiwLkqXIvpFmjoTEDmPmXrI+coL/9EFU1gKPA==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">= 10" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/Brooooooklyn" + } + }, + "node_modules/@napi-rs/canvas-linux-arm-gnueabihf": { + "version": "0.1.97", + "resolved": "https://registry.npmjs.org/@napi-rs/canvas-linux-arm-gnueabihf/-/canvas-linux-arm-gnueabihf-0.1.97.tgz", + "integrity": "sha512-XyXH2L/cic8eTNtbrXCcvqHtMX/nEOxN18+7rMrAM2XtLYC/EB5s0wnO1FsLMWmK+04ZSLN9FBGipo7kpIkcOw==", + "cpu": [ + "arm" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/Brooooooklyn" + } + }, + "node_modules/@napi-rs/canvas-linux-arm64-gnu": { + "version": "0.1.97", + "resolved": "https://registry.npmjs.org/@napi-rs/canvas-linux-arm64-gnu/-/canvas-linux-arm64-gnu-0.1.97.tgz", + "integrity": "sha512-Kuq/M3djq0K8ktgz6nPlK7Ne5d4uWeDxPpyKWOjWDK2RIOhHVtLtyLiJw2fuldw7Vn4mhw05EZXCEr4Q76rs9w==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/Brooooooklyn" + } + }, + "node_modules/@napi-rs/canvas-linux-arm64-musl": { + "version": "0.1.97", + "resolved": "https://registry.npmjs.org/@napi-rs/canvas-linux-arm64-musl/-/canvas-linux-arm64-musl-0.1.97.tgz", + "integrity": "sha512-kKmSkQVnWeqg7qdsiXvYxKhAFuHz3tkBjW/zyQv5YKUPhotpaVhpBGv5LqCngzyuRV85SXoe+OFj+Tv0a0QXkQ==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/Brooooooklyn" + } + }, + "node_modules/@napi-rs/canvas-linux-riscv64-gnu": { + "version": "0.1.97", + "resolved": "https://registry.npmjs.org/@napi-rs/canvas-linux-riscv64-gnu/-/canvas-linux-riscv64-gnu-0.1.97.tgz", + "integrity": "sha512-Jc7I3A51jnEOIAXeLsN/M/+Z28LUeakcsXs07FLq9prXc0eYOtVwsDEv913Gr+06IRo34gJJVgT0TXvmz+N2VA==", + "cpu": [ + "riscv64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/Brooooooklyn" + } + }, + "node_modules/@napi-rs/canvas-linux-x64-gnu": { + "version": "0.1.97", + "resolved": "https://registry.npmjs.org/@napi-rs/canvas-linux-x64-gnu/-/canvas-linux-x64-gnu-0.1.97.tgz", + "integrity": "sha512-iDUBe7AilfuBSRbSa8/IGX38Mf+iCSBqoVKLSQ5XaY2JLOaqz1TVyPFEyIck7wT6mRQhQt5sN6ogfjIDfi74tg==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/Brooooooklyn" + } + }, + "node_modules/@napi-rs/canvas-linux-x64-musl": { + "version": "0.1.97", + "resolved": "https://registry.npmjs.org/@napi-rs/canvas-linux-x64-musl/-/canvas-linux-x64-musl-0.1.97.tgz", + "integrity": "sha512-AKLFd/v0Z5fvgqBDqhvqtAdx+fHMJ5t9JcUNKq4FIZ5WH+iegGm8HPdj00NFlCSnm83Fp3Ln8I2f7uq1aIiWaA==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/Brooooooklyn" + } + }, + "node_modules/@napi-rs/canvas-win32-arm64-msvc": { + "version": "0.1.97", + "resolved": "https://registry.npmjs.org/@napi-rs/canvas-win32-arm64-msvc/-/canvas-win32-arm64-msvc-0.1.97.tgz", + "integrity": "sha512-u883Yr6A6fO7Vpsy9YE4FVCIxzzo5sO+7pIUjjoDLjS3vQaNMkVzx5bdIpEL+ob+gU88WDK4VcxYMZ6nmnoX9A==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 10" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/Brooooooklyn" + } + }, + "node_modules/@napi-rs/canvas-win32-x64-msvc": { + "version": "0.1.97", + "resolved": "https://registry.npmjs.org/@napi-rs/canvas-win32-x64-msvc/-/canvas-win32-x64-msvc-0.1.97.tgz", + "integrity": "sha512-sWtD2EE3fV0IzN+iiQUqr/Q1SwqWhs2O1FKItFlxtdDkikpEj5g7DKQpY3x55H/MAOnL8iomnlk3mcEeGiUMoQ==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 10" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/Brooooooklyn" + } + }, "node_modules/@playwright/test": { "version": "1.58.2", "resolved": "https://registry.npmjs.org/@playwright/test/-/test-1.58.2.tgz", @@ -3954,6 +4205,13 @@ "dev": true, "license": "MIT" }, + "node_modules/node-readable-to-web-readable-stream": { + "version": "0.4.2", + "resolved": "https://registry.npmjs.org/node-readable-to-web-readable-stream/-/node-readable-to-web-readable-stream-0.4.2.tgz", + "integrity": "sha512-/cMZNI34v//jUTrI+UIo4ieHAB5EZRY/+7OmXZgBxaWBMcW2tGdceIw06RFxWxrKZ5Jp3sI2i5TsRo+CBhtVLQ==", + "license": "MIT", + "optional": true + }, "node_modules/obug": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/obug/-/obug-2.1.1.tgz", @@ -4129,6 +4387,19 @@ "dev": true, "license": "MIT" }, + "node_modules/pdfjs-dist": { + "version": "5.5.207", + "resolved": "https://registry.npmjs.org/pdfjs-dist/-/pdfjs-dist-5.5.207.tgz", + "integrity": "sha512-WMqqw06w1vUt9ZfT0gOFhMf3wHsWhaCrxGrckGs5Cci6ybDW87IvPaOd2pnBwT6BJuP/CzXDZxjFgmSULLdsdw==", + "license": "Apache-2.0", + "engines": { + "node": ">=20.19.0 || >=22.13.0 || >=24" + }, + "optionalDependencies": { + "@napi-rs/canvas": "^0.1.95", + "node-readable-to-web-readable-stream": "^0.4.2" + } + }, "node_modules/picocolors": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz", diff --git a/frontend/package.json b/frontend/package.json index 7e8ded46..4d622d4d 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -21,7 +21,8 @@ }, "dependencies": { "diff": "^8.0.3", - "openapi-fetch": "^0.13.5" + "openapi-fetch": "^0.13.5", + "pdfjs-dist": "^5.5.207" }, "devDependencies": { "@eslint/compat": "^1.4.0", diff --git a/frontend/src/lib/components/PdfViewer.svelte b/frontend/src/lib/components/PdfViewer.svelte new file mode 100644 index 00000000..224b2cc6 --- /dev/null +++ b/frontend/src/lib/components/PdfViewer.svelte @@ -0,0 +1,290 @@ + + +{#if !url} +
+

Keine Datei vorhanden

+
+{:else if error} +
+

Fehler beim Laden der PDF

+ + Direkt öffnen + +
+{:else} +
+ +
+ +
+ + + {#if totalPages > 0} + + {currentPage} / {totalPages} + + {/if} + + +
+ + +
+ + +
+
+ + +
+ {#if loading} +
+
+
+ {:else} +
+
+ +
+
+
+ {/if} +
+
+{/if} diff --git a/frontend/src/lib/components/PdfViewer.svelte.spec.ts b/frontend/src/lib/components/PdfViewer.svelte.spec.ts new file mode 100644 index 00000000..c8408d71 --- /dev/null +++ b/frontend/src/lib/components/PdfViewer.svelte.spec.ts @@ -0,0 +1,53 @@ +import { vi, describe, it, expect, afterEach } from 'vitest'; +import { cleanup, render } from 'vitest-browser-svelte'; +import { page } from 'vitest/browser'; + +// pdfjs-dist is a rendering dependency — we mock it so unit tests don't need +// a real browser PDF engine. The interesting behaviour under test here is the +// component's own UI logic (controls, page counter), not pdfjs internals. +vi.mock('pdfjs-dist', () => { + function TextLayerMock() {} + TextLayerMock.prototype.render = () => Promise.resolve(); + TextLayerMock.prototype.cancel = () => {}; + + return { + GlobalWorkerOptions: { workerSrc: '' }, + getDocument: vi.fn().mockReturnValue({ + promise: Promise.resolve({ + numPages: 2, + getPage: vi.fn().mockResolvedValue({ + getViewport: vi.fn().mockReturnValue({ width: 595, height: 842 }), + render: vi.fn().mockReturnValue({ promise: Promise.resolve() }), + streamTextContent: vi.fn().mockReturnValue(new ReadableStream()) + }) + }) + }), + TextLayer: TextLayerMock + }; +}); + +vi.mock('pdfjs-dist/build/pdf.worker.min.mjs?url', () => ({ default: '' })); + +import PdfViewer from './PdfViewer.svelte'; + +afterEach(cleanup); + +describe('PdfViewer', () => { + it('shows previous and next page navigation buttons', async () => { + render(PdfViewer, { url: '/api/documents/test-id/file' }); + await expect.element(page.getByRole('button', { name: /zurück/i })).toBeInTheDocument(); + await expect.element(page.getByRole('button', { name: /weiter/i })).toBeInTheDocument(); + }); + + it('shows zoom controls', async () => { + render(PdfViewer, { url: '/api/documents/test-id/file' }); + await expect.element(page.getByRole('button', { name: /vergrößern/i })).toBeInTheDocument(); + await expect.element(page.getByRole('button', { name: /verkleinern/i })).toBeInTheDocument(); + }); + + it('displays the page counter once the PDF has loaded', async () => { + render(PdfViewer, { url: '/api/documents/test-id/file' }); + // Mock resolves synchronously, so "1 / 2" should appear quickly + await expect.element(page.getByText(/1\s*\/\s*2/)).toBeInTheDocument(); + }); +}); diff --git a/frontend/src/routes/documents/[id]/+page.svelte b/frontend/src/routes/documents/[id]/+page.svelte index 75a03373..006e0cbb 100644 --- a/frontend/src/routes/documents/[id]/+page.svelte +++ b/frontend/src/routes/documents/[id]/+page.svelte @@ -3,6 +3,7 @@ import { m } from '$lib/paraglide/messages.js'; import { formatDate } from '$lib/utils/date'; import { diffWords } from 'diff'; import ExpandableText from '$lib/components/ExpandableText.svelte'; +import PdfViewer from '$lib/components/PdfViewer.svelte'; let { data } = $props(); @@ -873,16 +874,12 @@ function versionLabel(v: VersionSummary, index: number): string {

{m.doc_no_scan()}

- {:else if fileUrl && doc.originalFilename.toLowerCase().endsWith('.pdf')} - + {:else if fileUrl && doc.contentType?.startsWith('application/pdf')} + {:else if fileUrl}
{m.doc_image_alt()} diff --git a/frontend/vite.config.ts b/frontend/vite.config.ts index 76be0fe1..c5f71a26 100644 --- a/frontend/vite.config.ts +++ b/frontend/vite.config.ts @@ -6,6 +6,9 @@ import { playwright } from '@vitest/browser-playwright'; import { sveltekit } from '@sveltejs/kit/vite'; export default defineConfig({ + optimizeDeps: { + include: ['pdfjs-dist'] + }, server: { host: '0.0.0.0', // Erlaubt Zugriff von außen port: 5173, // Standard SvelteKit Port diff --git a/frontend/yarn.lock b/frontend/yarn.lock index 386ec56f..c75d7088 100644 --- a/frontend/yarn.lock +++ b/frontend/yarn.lock @@ -207,6 +207,28 @@ resolved "https://registry.npmjs.org/@lix-js/server-protocol-schema/-/server-protocol-schema-0.1.1.tgz" integrity sha512-jBeALB6prAbtr5q4vTuxnRZZv1M2rKe8iNqRQhFJ4Tv7150unEa0vKyz0hs8Gl3fUGsWaNJBh3J8++fpbrpRBQ== +"@napi-rs/canvas-linux-x64-gnu@0.1.97": + version "0.1.97" + resolved "https://registry.npmjs.org/@napi-rs/canvas-linux-x64-gnu/-/canvas-linux-x64-gnu-0.1.97.tgz" + integrity sha512-iDUBe7AilfuBSRbSa8/IGX38Mf+iCSBqoVKLSQ5XaY2JLOaqz1TVyPFEyIck7wT6mRQhQt5sN6ogfjIDfi74tg== + +"@napi-rs/canvas@^0.1.95": + version "0.1.97" + resolved "https://registry.npmjs.org/@napi-rs/canvas/-/canvas-0.1.97.tgz" + integrity sha512-8cFniXvrIEnVwuNSRCW9wirRZbHvrD3JVujdS2P5n5xiJZNZMOZcfOvJ1pb66c7jXMKHHglJEDVJGbm8XWFcXQ== + optionalDependencies: + "@napi-rs/canvas-android-arm64" "0.1.97" + "@napi-rs/canvas-darwin-arm64" "0.1.97" + "@napi-rs/canvas-darwin-x64" "0.1.97" + "@napi-rs/canvas-linux-arm-gnueabihf" "0.1.97" + "@napi-rs/canvas-linux-arm64-gnu" "0.1.97" + "@napi-rs/canvas-linux-arm64-musl" "0.1.97" + "@napi-rs/canvas-linux-riscv64-gnu" "0.1.97" + "@napi-rs/canvas-linux-x64-gnu" "0.1.97" + "@napi-rs/canvas-linux-x64-musl" "0.1.97" + "@napi-rs/canvas-win32-arm64-msvc" "0.1.97" + "@napi-rs/canvas-win32-x64-msvc" "0.1.97" + "@playwright/test@^1.58.2": version "1.58.2" resolved "https://registry.npmjs.org/@playwright/test/-/test-1.58.2.tgz" @@ -1462,6 +1484,11 @@ natural-compare@^1.4.0: resolved "https://registry.npmjs.org/natural-compare/-/natural-compare-1.4.0.tgz" integrity sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw== +node-readable-to-web-readable-stream@^0.4.2: + version "0.4.2" + resolved "https://registry.npmjs.org/node-readable-to-web-readable-stream/-/node-readable-to-web-readable-stream-0.4.2.tgz" + integrity sha512-/cMZNI34v//jUTrI+UIo4ieHAB5EZRY/+7OmXZgBxaWBMcW2tGdceIw06RFxWxrKZ5Jp3sI2i5TsRo+CBhtVLQ== + obug@^2.1.0, obug@^2.1.1: version "2.1.1" resolved "https://registry.npmjs.org/obug/-/obug-2.1.1.tgz" @@ -1553,6 +1580,14 @@ pathe@^2.0.3: resolved "https://registry.npmjs.org/pathe/-/pathe-2.0.3.tgz" integrity sha512-WUjGcAqP1gQacoQe+OBJsFA7Ld4DyXuUIjZ5cc75cLHvJ7dtNsTugphxIADwspS+AraAUePCKrSVtPLFj/F88w== +pdfjs-dist@^5.5.207: + version "5.5.207" + resolved "https://registry.npmjs.org/pdfjs-dist/-/pdfjs-dist-5.5.207.tgz" + integrity sha512-WMqqw06w1vUt9ZfT0gOFhMf3wHsWhaCrxGrckGs5Cci6ybDW87IvPaOd2pnBwT6BJuP/CzXDZxjFgmSULLdsdw== + optionalDependencies: + "@napi-rs/canvas" "^0.1.95" + node-readable-to-web-readable-stream "^0.4.2" + picocolors@^1.0.0, picocolors@^1.1.1: version "1.1.1" resolved "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz"