Fonctionnalités de l'app
PDF → images PNG
Écran Expo en un seul fichier : PDF.js dans une WebView masquée, pages PNG dans une ZIP, partage système. iOS et Android natifs uniquement — les sections ci-dessous décrivent le flux des données.
Vidéo de démonstration
Capture d’écran du flux : choix d’un PDF, progression pendant le rendu des pages, aperçu puis partage ou enregistrement de la ZIP (app native).
Référence en un seul fichier (téléchargement)
Placez le fichier dans votre app Expo Router (`src/app/pdf-to-images-simple.tsx` ou équivalent), ajoutez les UMD PDF.js, enregistrez la route et installez les paquets ci-dessous.
Paquets : expo-asset, expo-document-picker, expo-file-system, expo-keep-awake, expo-router, expo-sharing, jszip, react-native-webview, react-native-safe-area-context.
Rôle de ce fichier
Un seul écran React Native orchestre tout : choisir un PDF, préparer un dossier cache à côté des scripts UMD PDF.js, charger un petit HTML dans une WebView masquée, exécuter Mozilla PDF.js pour dessiner chaque page sur un canvas, encoder en PNG, envoyer les données par le bridge WebView par morceaux, construire une archive JSZip, l'écrire dans le répertoire documents de l'app et ouvrir éventuellement la feuille de partage. Traitement local, aucun envoi serveur.
La plateforme web est volontairement désactivée (`Platform.OS === 'web'`) car ce flux repose sur le staging `file://` et l'accès fichiers WebView natif, pas sur un déploiement web classique.
Flux de bout en bout
- L'utilisateur appuie sur « Choisir un PDF ». `DocumentPicker` renvoie l'asset. Sous Android, les URI `content://` sont copiées vers un chemin cache lisible via `resolveReadableUri`.
- Un dossier de session est créé sous `cacheDirectory/pdf_to_images_session/` avec un identifiant unique dans le chemin. Le PDF est copié en `input.pdf`. `copyBundledPdfJsToWorkDir` copie `pdf.min.js` et `pdf.worker.min.js` depuis les assets Expo pour une origine `file://` unique.
- `webSession` est défini avec le HTML généré, `baseUrl` pointant vers ce dossier et, sur iOS, `allowingReadAccessToURL` sur le chemin sans slash final — nécessaire pour que WKWebView lise `input.pdf` et les scripts.
- `START_PDF_PIPELINE_JS` s'exécute au chargement (et est réinjecté après `onLoadEnd`, avec délais supplémentaires sur Android) jusqu'à ce que `window.pdfjsLib` existe, puis appelle `window.startPdfToPngPipeline` défini dans la chaîne HTML.
- Dans la WebView, PDF.js ouvre le PDF depuis un `ArrayBuffer`, plafonne les pages avec `MAX_PAGES`, met à l'échelle avec `MAX_PAGE_WIDTH_PX`, rend sur un canvas 2D, lit le PNG en base64, le découpe en morceaux d'environ 450k caractères et envoie `pageStart`, `pagePart` et `pageDone` à React Native.
- `onMessage` analyse le JSON : accumule les morceaux par page, écrit `page-001.png`, etc. dans un `JSZip`, met à jour la progression ; sur `done`, appelle `finishZip`. Les erreurs nettoient la session et affichent une alerte.
- `finishZip` génère l'archive en base64, écrit une archive sous `documentDirectory` dans `PdfImageExports/`, nom du type `pdf_pages_*.zip` (partie centrale = horodatage), supprime le dossier de session, affiche un aperçu de la première page ; l'utilisateur peut ensuite `shareAsync` avec le MIME `application/zip` (et UTI `public.zip-archive` sur iOS).
Blocs principaux du code
Constantes et script d'amorçage
`MAX_PAGES`, `MAX_PAGE_WIDTH_PX`, `KEEP_AWAKE_TAG`, `SESSION_DIR` et `START_PDF_PIPELINE_JS` : une petite boucle d'attente pour `pdfjsLib` et `startPdfToPngPipeline`, démarrage de la conversion ou erreur de timeout après de nombreuses tentatives.
Helpers de fichiers
`ensureFileUrl` ajoute `file://` pour le partage. `copyBundledPdfJsToWorkDir` utilise `Asset.fromModule`, `downloadAsync` et `copyAsync` pour matérialiser les fichiers sur disque. `resolveReadableUri` copie les `content://` Android en cache si besoin.
HTML généré (`buildPdfToPngHtml`)
Un template construit une page minimale qui charge `pdf.min.js`, définit `startPdfToPngPipeline` pour le worker, charge `input.pdf` (XHR avec repli fetch), appelle `getDocument`, parcourt les pages, `render` sur le canvas avec garde de délai, puis `postMessage` JSON — base64 découpé pour respecter le bridge.
UI React Native et WebView
L'état gère `busy`, le texte de progression, `webSession` (HTML + baseUrl + readAccessUrl), le chemin ZIP, l'URI d'aperçu et le nombre de pages. Une WebView quasi invisible se monte lorsqu'une session existe ; `activateKeepAwakeAsync` évite la mise en veille pendant de longs rendus. Nettoyage au démontage ou erreur : suppression du dossier temporaire et arrêt du keep-awake.
Gestionnaire `onMessage`
Traite `stage`, `meta`, `pageStart` / `pagePart` / `pageDone`, `done` pour finaliser le ZIP, `error` pour réinitialiser l'UI. Une ref suit la page en cours d'assemblage.
Création ZIP et nettoyage
`finishZip` lit les entrées PNG, construit un data URI d'aperçu depuis la première image, écrit le ZIP en base64, puis `teardown` supprime le dossier de session et l'état WebView.
Partage
`shareZip` vérifie `Sharing.isAvailableAsync`, puis `shareAsync` sur le chemin ZIP avec le bon MIME pour enregistrer dans Fichiers ou une autre app.
Sur le web, le bouton principal est désactivé et un message indique que la démo cible iOS et Android natifs.