import { useCallback, useEffect, useRef, useState } from "react"; import * as pdfjs from "pdfjs-dist"; import { ReaderError, readerErrorMessage } from "./ReaderError"; import { configurePdfWorker } from "./pdfWorker"; import type { ReaderControls } from "./ReaderShell"; import type { ReaderMode } from "../api/types"; configurePdfWorker(pdfjs); export function PdfReader({ url, page, backHref, mode, onPageCommit, onControlsChange }: { url: string; page: number; backHref: string; mode: ReaderMode; onPageCommit: (page: number, pages: number) => void; onControlsChange: (controls: ReaderControls) => void; }) { const canvasRef = useRef(null); const frameRef = useRef(null); const [pages, setPages] = useState(1); const [error, setError] = useState(); const [loading, setLoading] = useState(true); const [attempt, setAttempt] = useState(0); const [frameSize, setFrameSize] = useState({ width: 980, height: 900 }); const currentPage = Math.max(1, Math.min(page, pages)); const go = useCallback((nextPage: number) => onPageCommit(Math.max(1, Math.min(nextPage, pages)), pages), [onPageCommit, pages]); useEffect(() => { onControlsChange({ canPrevious: !loading && !error && currentPage > 1, canNext: !loading && !error && currentPage < pages, positionLabel: loading ? "Ouverture PDF" : `${currentPage} / ${pages}`, onPrevious: () => go(currentPage - 1), onNext: () => go(currentPage + 1) }); }, [currentPage, error, go, loading, onControlsChange, pages]); useEffect(() => { const frame = frameRef.current; if (!frame) return; const updateSize = () => { const rect = frame.getBoundingClientRect(); setFrameSize({ width: Math.max(320, rect.width), height: Math.max(320, rect.height) }); }; updateSize(); const observer = new ResizeObserver(updateSize); observer.observe(frame); return () => observer.disconnect(); }, []); useEffect(() => { let cancelled = false; let loadingTask: pdfjs.PDFDocumentLoadingTask | undefined; let renderTask: pdfjs.RenderTask | undefined; async function render() { try { configurePdfWorker(pdfjs); setLoading(true); setError(undefined); loadingTask = pdfjs.getDocument({ url, withCredentials: true }); const document = await loadingTask.promise; if (cancelled) return; setPages(document.numPages); const pdfPage = await document.getPage(Math.max(1, Math.min(page, document.numPages))); const canvas = canvasRef.current; if (!canvas) return; const baseViewport = pdfPage.getViewport({ scale: 1 }); const fitScale = mode === "vertical" ? frameSize.width / baseViewport.width : Math.min(frameSize.width / baseViewport.width, frameSize.height / baseViewport.height); const renderScale = Math.max(0.35, Math.min(3, fitScale)) * Math.min(2, window.devicePixelRatio || 1); const viewport = pdfPage.getViewport({ scale: renderScale }); canvas.width = Math.floor(viewport.width); canvas.height = Math.floor(viewport.height); canvas.style.width = `${Math.floor(viewport.width / Math.min(2, window.devicePixelRatio || 1))}px`; canvas.style.height = `${Math.floor(viewport.height / Math.min(2, window.devicePixelRatio || 1))}px`; const context = canvas.getContext("2d"); if (!context) return; renderTask = pdfPage.render({ canvas, canvasContext: context, viewport }); await renderTask.promise; if (!cancelled) setLoading(false); } catch (renderError) { if (!cancelled) { setError(readerErrorMessage(renderError, "PDF indisponible")); setLoading(false); } } } void render(); return () => { cancelled = true; renderTask?.cancel(); void loadingTask?.destroy(); }; }, [url, page, attempt, frameSize.height, frameSize.width, mode]); return (
{error ? ( setAttempt((value) => value + 1)} /> ) : ( <> {loading && (
Ouverture PDF
)} )}
); }