fix: fetch booklet PDF through Tauri backend to bypass CORS

PDF loading was failing because pdfjs-dist cannot fetch from
static.qobuz.com due to CORS restrictions in the WebView.

Added v2_fetch_url_bytes command that fetches URL bytes through
Rust's reqwest (no CORS), then passes the binary data to pdfjs.

Also added rotation support (R key or toolbar button) for
vertical booklets.
This commit is contained in:
vicrodh committed 2026-02-28 07:48:03 -06:00
1 parent 7e34232823
commit 883f3546c5
7 files changed
+63 -10

No files matched your search

+26 -6
View File
@@ -1,7 +1,7 @@
<script lang="ts">
import { onMount } from 'svelte';
import { t } from 'svelte-i18n';
import { X, ChevronLeft, ChevronRight, ZoomIn, ZoomOut, Maximize } from 'lucide-svelte';
import { invoke } from '@tauri-apps/api/core';
import { X, ChevronLeft, ChevronRight, ZoomIn, ZoomOut, Maximize, RotateCw } from 'lucide-svelte';
import * as pdfjsLib from 'pdfjs-dist';
// Configure worker
@@ -29,6 +29,7 @@
let currentPage = $state(1);
let totalPages = $state(0);
let zoom = $state(1);
let rotation = $state(0);
let isLoading = $state(true);
let error = $state('');
let currentRenderTask: any = null;
@@ -45,9 +46,14 @@
error = '';
try {
// Fetch PDF bytes through Tauri backend to bypass CORS
const bytes = await invoke<number[]>('v2_fetch_url_bytes', { url });
const data = new Uint8Array(bytes);
const loadingTask = pdfjsLib.getDocument({
url,
disableRange: false,
data,
disableRange: true,
disableAutoFetch: true,
});
pdfDoc = await loadingTask.promise;
@@ -56,7 +62,7 @@
await renderPage();
} catch (err: any) {
console.error('[BookletViewer] Failed to load PDF:', err);
error = err?.message || 'Failed to load booklet';
error = err?.message || String(err) || 'Failed to load booklet';
} finally {
isLoading = false;
}
@@ -73,7 +79,8 @@
try {
const page = await pdfDoc.getPage(currentPage);
const dpr = window.devicePixelRatio || 1;
const viewport = page.getViewport({ scale: zoom * dpr * 1.5 });
const scale = zoom * dpr * 1.5;
const viewport = page.getViewport({ scale, rotation });
canvas.width = viewport.width;
canvas.height = viewport.height;
@@ -132,6 +139,11 @@
renderPage();
}
function rotate() {
rotation = (rotation + 90) % 360;
renderPage();
}
function handleKeydown(e: KeyboardEvent) {
if (!isOpen) return;
@@ -152,6 +164,9 @@
case '-':
zoomOut();
break;
case 'r':
rotate();
break;
}
}
@@ -187,6 +202,7 @@
currentPage = 1;
totalPages = 0;
zoom = 1;
rotation = 0;
}
});
</script>
@@ -221,6 +237,10 @@
<button class="toolbar-btn" onclick={fitToWidth} title={$t('album.bookletFitWidth')}>
<Maximize size={16} />
</button>
<div class="toolbar-divider"></div>
<button class="toolbar-btn" onclick={rotate} title={$t('album.bookletRotate')}>
<RotateCw size={16} />
</button>
</div>
<div class="toolbar-right">
<button class="toolbar-btn close-btn" onclick={onClose}>
+2 -1
View File
@@ -232,7 +232,8 @@
"viewBooklet": "Booklet anzeigen",
"bookletLoading": "Booklet wird geladen...",
"bookletError": "Booklet konnte nicht geladen werden",
"bookletFitWidth": "An Breite anpassen"
"bookletFitWidth": "An Breite anpassen",
"bookletRotate": "Drehen"
},
"artist": {
"albums": "Alben",
+2 -1
View File
@@ -244,7 +244,8 @@
"viewBooklet": "View Booklet",
"bookletLoading": "Loading booklet...",
"bookletError": "Could not load booklet",
"bookletFitWidth": "Fit to width"
"bookletFitWidth": "Fit to width",
"bookletRotate": "Rotate"
},
"artist": {
"albums": "Albums",
+2 -1
View File
@@ -244,7 +244,8 @@
"viewBooklet": "Ver libreto",
"bookletLoading": "Cargando libreto...",
"bookletError": "No se pudo cargar el libreto",
"bookletFitWidth": "Ajustar al ancho"
"bookletFitWidth": "Ajustar al ancho",
"bookletRotate": "Rotar"
},
"artist": {
"albums": "Álbumes",
+2 -1
View File
@@ -232,7 +232,8 @@
"viewBooklet": "Voir le livret",
"bookletLoading": "Chargement du livret...",
"bookletError": "Impossible de charger le livret",
"bookletFitWidth": "Ajuster à la largeur"
"bookletFitWidth": "Ajuster à la largeur",
"bookletRotate": "Pivoter"
},
"artist": {
"albums": "Albums",