Pour scanner un document en PDF dans une application Capacitor, ouvrez le scanner natif avec @capgo/capacitor-document-scanner, prenez les pages JPEG qu'il retourne, et combinez-les en un seul PDF avec pdf-lib. Dès là, écrivez le PDF avec @capacitor/filesystem et remettez-l’à la feuille de partage, un visionneuse PDF ou le dialogue d'impression système. Ce guide vous guide à travers la marche à suivre avec code qui fonctionne sur Capacitor 8, sur les deux iOS et Android.
Comment fonctionne la conversion en PDF sur iOS et Android
Les deux plateformes embarquent un scanner de documents système, vous n'avez donc pas besoin de développer la détection d'angle ou la correction de perspective vous-même.
| IOS | Android | |
|---|---|---|
| Scanneur natif | VisionKit VNDocumentCameraViewController |
Scanneur de documents Google ML Kit (Services Google Play) |
| Détecter les bords et couper | Oui | Oui |
| Filtres / nettoyage | Filtres / nettoyage | base, base_with_filter, full modes (stain and finger removal in full) |
| Limite de page par session | 24 | 1 à 24, par défaut 20 |
| Permission de caméra | Oui, nécessite NSCameraUsageDescription |
Non, le scanner fonctionne dans les services Play |
| Fonctionne sur l'émulateur / simulateur | Non | Non |
| Sortie du plugin | Pages JPEG | Pages JPEG |
Le plugin renvoie des images, pas un PDF. C'est une séparation délibérée : vous contrôlez la taille de la page, la compression et le nom de fichier, et le même PDF code fonctionne pour les photos sélectionnées dans la galerie ou les fichiers reçus de votre serveur.
Installez les plugins
bun add @capgo/capacitor-document-scanner @capacitor/filesystem pdf-lib
bunx cap sync
Le plugin de scanner suit Capacitor versions majeures, donc le plugin v8 cible Capacitor 8. pdf-lib est une bibliothèque JavaScript pure sans native code, ce qui signifie qu'elle fonctionne dans le WebView et nécessite aucune synchronisation.
Configuration iOS
Ajoutez une chaîne d'utilisation de la caméra à ios/App/App/Info.plistVisionKit ouvre la caméra, et iOS arrête l'application si la clé est manquante :
<key>NSCameraUsageDescription</key>
<string>We use the camera to scan your documents.</string>
Le package Swift du plugin cible iOS 15 ou ultérieur, ce qui correspond à la Capacitor 8 minimum. N'oubliez pas que les téléchargements de l'App Store nécessitent désormais Xcode 26, donc construisez-l’avant de soumettre. Si vous n'avez pas d'iMac, Capgo Création peut produire le binaire iOS signé dans le cloud.
Configuration Android
Rien à ajouter à AndroidManifest.xmlLe scanner ML Kit est un module de services Google Play qui télécharge à la première utilisation et gère la caméra lui-même, donc votre application ne demande pas CAMERALe dispositif nécessite les services Google Play, ce qui élimine la plupart des émulateurs et des téléphones dé-googlisés. Planifiez un fallback (une capture de caméra simple) si vous expédiez dans des marchés où les services Play sont manquants.
Balaye les pages
Commencez par une fonction qui ouvre le scanner et renvoie les chemins de page, ou null lorsque l'utilisateur annule :
import {
DocumentScanner,
ResponseType,
ScannerMode,
ScanDocumentResponseStatus,
} from '@capgo/capacitor-document-scanner';
export async function scanPages(): Promise<string[] | null> {
const result = await DocumentScanner.scanDocument({
responseType: ResponseType.ImageFilePath,
maxNumDocuments: 20,
letUserAdjustCrop: true,
scannerMode: ScannerMode.Full, // Android only
croppedImageQuality: 90, // Android only
});
if (result.status === ScanDocumentResponseStatus.Cancel) {
return null;
}
return result.scannedImages ?? [];
}
Quelques options sont à connaître :
responseType:ImageFilePath(par défaut) renvoie les chemins de fichier ;Base64renvoie des chaînes base64. Préférez les chemins de fichier pour les documents à plusieurs pages. Les chaînes base64 pour 20 pages à haute résolution peuvent atteindre des dizaines de mégaoctets et chaque octet traverse le pont Capacitor.maxNumDocuments: définissez-le sur1pour les flux à une page comme les reçus ou les cartes d'identité.reviewCapturedDocument: montre chaque page capturée avant de continuer, utile lorsque les utilisateurs scannent des documents importants.brightnessetcontrast: des post-traitements appliqués aux images. Un contraste de1.2to1.5Aide souvent avec les reçus usés.scannerMode(Android) :Fullajoute une nettoyage basé sur l'apprentissage automatique,Baseest plus rapide et laisse l'image intacte.
La localisation des fichiers varie en fonction de la plateforme. Sur Android, les pages se trouvent dans le répertoire de cache de l'application. Sur iOS, le plugin écrit les fichiers dans le répertoire Documents de l'application. Aucune de ces emplacements n'est un bon endroit à long terme, donc le flux suivant construit le PDF, le stocke où vous le souhaitez et supprime les images de page.
Combinez les pages en un seul PDF
pdf-lib Puisque les JPEGs peuvent être intégrés directement sans les re-encoder, la taille du PDF reste proche de la somme des tailles des pages. Lit chaque page à travers Capacitor.convertFileSrc, qui transforme un chemin natif en une URL que le WebView peut charger :
import { Capacitor } from '@capacitor/core';
import { PDFDocument } from 'pdf-lib';
const A4 = { width: 595.28, height: 841.89 }; // points
export async function pagesToPdf(paths: string[]): Promise<string> {
const pdf = await PDFDocument.create();
pdf.setTitle('Scanned document');
pdf.setCreator('My Capacitor App');
for (const path of paths) {
const bytes = await fetch(Capacitor.convertFileSrc(path)).then((r) => r.arrayBuffer());
const image = await pdf.embedJpg(bytes);
// Fit the image inside an A4 page, keeping its aspect ratio
const scale = Math.min(A4.width / image.width, A4.height / image.height);
const width = image.width * scale;
const height = image.height * scale;
const page = pdf.addPage([A4.width, A4.height]);
page.drawImage(image, {
x: (A4.width - width) / 2,
y: (A4.height - height) / 2,
width,
height,
});
}
// Base64 without the data URI prefix, ready for Filesystem.writeFile
return pdf.saveAsBase64();
}
Si vous préférez des pages qui correspondent exactement au papier numérisé, remplacez la logique A4 par pdf.addPage([image.width, image.height]) et dessinez l'image à taille plein. Les factures ont l'air mieux ainsi car elles sont longues et étroites.
Alternative : affichez le PDF nativement
Si votre application construit déjà des PDF à partir de HTML (factures, rapports), vous pouvez réutiliser ce chemin avec @capgo/capacitor-pdf-generatorIl affiche une chaîne HTML avec le moteur d'impression de la plateforme et retourne une valeur base64 :
import { PdfGenerator } from '@capgo/capacitor-pdf-generator';
import { Capacitor } from '@capacitor/core';
const html = `
<html><body style="margin:0">
${paths
.map((p) => `<img src="${Capacitor.convertFileSrc(p)}" style="width:100%;page-break-after:always" />`)
.join('')}
</body></html>`;
const result = await PdfGenerator.fromData({
data: html,
documentSize: 'A4',
orientation: 'portrait',
type: 'base64',
});
if (result.type === 'base64') {
// result.base64 contains the PDF
}
Cela est utile lorsque vous souhaitez une page de garde, un titre ou un pied de page avec une date sur chaque page. Pour des conversions image-vers-PDF, pdf-lib vous avez plus de contrôle sur la géométrie de la page et la compression de l'image.
Sauvegardez le PDF
Écrivez la sortie en base64 avec @capacitor/filesystemChoisissez le répertoire en fonction de la durée pendant laquelle le fichier doit vivre :
| Répertoire | Android | iOS | Utilisez-le pour |
|---|---|---|---|
Directory.Cache |
App cache, can be cleared by the OS | Library/Cachespeut être effacé par le système d'exploitation |
PDFs you upload or share right away |
Directory.Data |
Private app files, removed on uninstall | Application Documents Dossier de l'application, sauvegardé sur iCloud |
PDFs gérés et affichés à nouveau par votre application |
Directory.LibraryNoCloud |
Documents privés de l'application | Library/NoCloud, pas sauvegardés |
Grands PDFs que vous pouvez télécharger à nouveau |
Sur iOS, Directory.Data et Directory.Documents Dans le même dossier, ajoutez UIFileSharingEnabled et LSSupportsOpeningDocumentsInPlace à Info.plist Si les utilisateurs doivent voir ces PDF dans l'application de fichiers.
import { Filesystem, Directory } from '@capacitor/filesystem';
export async function savePdf(base64: string, name: string) {
const fileName = `${name.replace(/[^a-z0-9-_]/gi, '_')}.pdf`;
const { uri } = await Filesystem.writeFile({
path: `scans/${fileName}`,
data: base64,
directory: Directory.Data,
recursive: true,
});
return uri; // file:// URI you can share, open, or print
}
export async function deletePages(paths: string[]) {
await Promise.all(
paths.map((path) => Filesystem.deleteFile({ path }).catch(() => undefined)),
);
}
Sur les plateformes natives, writeFile traite une chaîne sans un encoding option comme base64 et écrit les octets binaires. Encoding.UTF8 pour les PDF, ou le fichier sera corrompu.
Si l'utilisateur doit pouvoir trouver le PDF dans son dossier Téléchargements sur Android, utilisez @capgo/capacitor-file-sharer’s save() méthode, qui écrit dans MediaStore Téléchargements sans aucune autorisation de stockage sur Android 10+. Sur iOS, la même appelle ouvre la feuille de partage afin que l'utilisateur puisse choisir « Enregistrer dans les fichiers ».
Assemblez tout le flux.
export async function scanToPdf(title: string) {
const pages = await scanPages();
if (!pages || pages.length === 0) return null;
try {
const base64 = await pagesToPdf(pages);
return await savePdf(base64, title);
} finally {
await deletePages(pages);
}
}
Connectez-l’à un bouton et montrez un curseur d'attente pendant pagesToPdf s'exécute. Sur un téléphone Android de milieu de gamme, 10 pages prennent généralement moins de quelques secondes puisque les données JPEG sont copiées, pas décodées.
Partagez, ouvrez ou imprimez le PDF
Partagez avec la feuille de partage du système
import { FileSharer } from '@capgo/capacitor-file-sharer';
await FileSharer.share({
filename: 'contract.pdf',
path: pdfUri,
contentType: 'application/pdf',
subject: 'Signed contract',
});
@capgo/capacitor-file-sharer copie le fichier dans son propre dossier de cache et le sert à travers son propre FileProvider sur Android, vous n'avez donc pas besoin de modifier file_paths.xml, même si le PDF vit dans Directory.Data. Si vous utilisez l'extension officielle @capacitor/share plugin au lieu de cela, son files option fonctionne par défaut uniquement pour le dossier de cache sur Android. Les autres dossiers doivent être ajoutés à android/app/src/main/res/xml/file_paths.xml.
Ouvrez dans l'application PDF de l'utilisateur
Pour une vision rapide, l'ouvreur de fichiers de la communauté transmet le fichier à Quick Look sur iOS ou au lecteur PDF par défaut sur Android :
bun add @capacitor-community/file-opener
bunx cap sync
import { FileOpener } from '@capacitor-community/file-opener';
await FileOpener.open({
filePath: pdfUri,
contentType: 'application/pdf',
});
Imprime
@capgo/capacitor-printer ouvre UIPrintInteractionController sur iOS et PrintManager On Android :
import { Printer } from '@capgo/capacitor-printer';
await Printer.printPdf({
name: 'Signed contract',
path: pdfUri,
});
Le name devient le nom de l'impression et le nom de fichier par défaut si l'utilisateur sélectionne « Enregistrer sous PDF » dans le dialogue d'impression.
Télécharger le PDF
Pour envoyer le PDF à votre backend, le lire à nouveau en tant que Blob et le poster avec fetch, ou utilisez un téléchargeur natif pour les fichiers volumineux afin que le téléchargement continue lorsque l'application passe en arrière-plan :
import { Uploader } from '@capgo/capacitor-uploader';
const { id } = await Uploader.startUpload({
filePath: pdfUri,
serverUrl: 'https://api.example.com/documents',
method: 'POST',
headers: { Authorization: `Bearer ${token}` },
mimeType: 'application/pdf',
});
Consultez le Capacitor plugin de téléchargeur for progress events and multipart uploads.
Rendre les PDF de scans recherchables
Les PDF à l'image ne peuvent pas être recherchés ou copiés. Si les utilisateurs s'attendent à rechercher leurs scans :
- Exécutez l'OCR sur chaque image de page avant de la supprimer. Les options sur appareil incluent le ML Kit Text Recognition sur Android et le framework Vision sur iOS, accessibles à travers les plugins de la communauté Capacitor ou un service OCR côté serveur.
- Enregistrez le texte reconnu dans votre base de données à côté de la voie PDF et recherchez-y.
- Si le PDF doit contenir lui-même du texte, générez-le côté serveur avec un outil OCR qui écrit une couche de texte invisible, comme OCRmyPDF.
Pour la plupart des applications, l'étape 2 suffit et évite de faire circuler une deuxième grande modèle dans l'application.
Troubleshooting
Le scanner de document n'est pas pris en charge sur les émulateurs AndroidUtilisez un appareil physique avec Google Play services.
Le scanner de documents n'est pas pris en charge sur les émulateurs Android.: NSCameraUsageDescription est manquant de Info.plistL'application s'effondre sur iOS dès que l'ouverture du scanner est déclenchée
embedJpg lance 'SOI non trouvé': the bytes are not a JPEG. This happens if you passed a base64 data URL where pdf-lib expected raw bytes, or fetched a path that returned an HTML error page. Log Capacitor.convertFileSrc(path) et l'ouvrir dans l'inspecteur de Safari ou Chrome.
Le PDF est énorme.: inférieur. croppedImageQuality (Android) ou recompresser les pages avec @capgo/capacitor-file-compressor avant d'incorporer. Une qualité de 70 à 80 est généralement lisible pour les documents de texte.
Les pages apparaissent tournées.: tourner à l'intérieur de pdf-lib avec page.setRotation(degrees(90)) ou laissez l'utilisateur corriger l'orientation dans l'écran de revue de l'scanner. reviewCapturedDocument: true.
La partage faille sur Android avec FileUriExposedException ou « Échec de recherche de racine configurée »: vous avez passé un chemin en dehors des dossiers déclarés dans file_paths.xml vers @capacitor/share. Utilisez le plugin de partage de fichiers ou copiez le PDF à Directory.Cache Premièrement.
Old page images fill up storage on iOS: l'application de scanner écrit des JPEGs dans le répertoire Documents. Supprimez-les après avoir construit le PDF, comme montré dans scanToPdf.
Mises à jour de livraison dans le flux de scan
Most of this feature lives in TypeScript: the PDF layout, file naming, upload logic, and UI. Changes to those parts do not need a new store release. With Capgo mises à jour en direct you can ship a fix to the PDF layout or upload endpoint to every user the same day, while native changes (adding the plugin, editing Info.plist) passent encore par une mise à jour normale. La documents scanner docs liste toutes les options si vous avez besoin de personnaliser le scanner davantage.