Para escanear un documento a PDF en una aplicación Capacitor, abre el escáner nativo con @capgo/capacitor-document-scannertomar las páginas JPEG que devuelve y combinarlas en un PDF con pdf-lib. A partir de allí, escribe el PDF con @capacitor/filesystem y luego pasarla a la hoja de compartir, un visor de PDF o el diálogo de impresión del sistema. Este guía te guía a través del flujo completo con code que se ejecuta en Capacitor 8, en iOS y Android.
¿Cómo funciona la escaneo a PDF en iOS y Android?
Ambas plataformas incluyen un escáner de documentos integrado, por lo que no es necesario implementar detección de bordes o corrección de perspectiva por su cuenta.
| iOS | Android | |
|---|---|---|
| Escáner nativo | VisionKit VNDocumentCameraViewController |
Google ML Kit Document Scanner (Servicios de Play) |
| Detección de bordes y recorte | Sí | Sí |
| ¿Filtros / limpieza? | VisionKit integrado | base, base_with_filter, full modos (eliminación de manchas y huellas en full) |
| Límite de páginas por sesión | 24 | 1 a 24, valor predeterminado 20 |
| Solicitud de permiso de cámara | Sí, necesita NSCameraUsageDescription |
No, el escáner se ejecuta en Play services |
| Funciona en emulador / simulador | No | No |
| Salida del plugin | Páginas JPEG | páginas JPEG |
El plugin devuelve imágenes, no un PDF. Esa es una separación deliberada: controlas el tamaño de la página, la compresión y el nombre de archivo, y el mismo PDF code funciona para fotos seleccionadas desde la galería o archivos recibidos desde tu backend.
Instalar los plugins
bun add @capgo/capacitor-document-scanner @capacitor/filesystem pdf-lib
bunx cap sync
El plugin de escáner sigue Capacitor versiones principales, por lo que el plugin v8 se dirige a Capacitor 8. pdf-lib es una biblioteca de JavaScript pura sin code nativo, lo que significa que se ejecuta en el WebView y no necesita sincronización.
iOS setup
Agregar una cadena de uso de cámara a ios/App/App/Info.plistVisionKit abre la cámara, y iOS termina la aplicación si la clave está ausente:
<key>NSCameraUsageDescription</key>
<string>We use the camera to scan your documents.</string>
El paquete de Swift del plugin se dirige a iOS 15 o posterior, lo que coincide con el Capacitor 8 mínimo. Recuerda que las subidas a la Tienda de Mac App ahora requieren Xcode 26, así que construye con él antes de enviar. Si no tienes un Mac Capgo Construye puede producir el binario iOS firmado en la nube.
Configuración de Android
Nada que agregar a AndroidManifest.xml. El escáner de ML Kit es un módulo de servicios de Google Play que se descarga por primera vez y maneja la cámara por sí mismo, por lo que tu aplicación no solicita CAMERA. El dispositivo necesita servicios de Google Play, lo que elimina la mayoría de los emuladores y teléfonos des-Googled. Planifica un fallback (una captura de cámara simple) si envías tu aplicación a mercados donde los servicios de Play están ausentes.
Escanea las páginas
Comienza con una función que abre el escáner y devuelve los caminos de página, o null cuando el usuario cancela:
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 ?? [];
}
Algunas opciones son dignas de conocer:
responseType:ImageFilePath(por defecto) devuelve rutas de archivoBase64devuelve cadenas base64. Prefiere rutas de archivo para documentos de varias páginas. Las cadenas base64 para 20 páginas de alta resolución pueden alcanzar decenas de megabytes y cada byte cruza el Capacitor bridge.maxNumDocumentsEstablezca1for single-page flows like receipts or ID cards.reviewCapturedDocumentMuestra cada página capturada antes de continuar, útil cuando los usuarios escanean documentos importantes.brightnessycontrast: se aplican procesos posteriores a las imágenes. Un contraste de1.2para1.5a menudo ayuda con recibos desvaídos.scannerMode(Android):Fullagrega limpieza basada en ML,Basees más rápido y mantiene la imagen sin tocar.
La ubicación de los archivos difiere según la plataforma. En Android, las páginas van al directorio de caché de la aplicación. En iOS, el plugin las escribe en el directorio de documentos de la aplicación. Ninguna de estas ubicaciones es un buen hogar a largo plazo, por lo que el flujo a continuación crea el PDF, lo almacena donde lo desees y elimina las imágenes de la página.
Combina las páginas en un PDF
pdf-lib Puede incorporar JPEGs directamente sin re-encodificarlos, por lo que el tamaño del PDF se mantiene cerca de la suma de los tamaños de las páginas. Lea cada página a través Capacitor.convertFileSrc, que convierte una ruta nativa en una URL que el navegador web puede recuperar:
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 prefieres páginas que coincidan exactamente con el papel escaneado, reemplaza la lógica de A4 con pdf.addPage([image.width, image.height]) y dibuja la imagen a tamaño completo. Los recibos se ven mejor de esta manera porque son largos y estrechos.
Opción alternativa: renderizar el PDF nativamente
Si su aplicación ya construye PDFs a partir de HTML (facturas, informes), puede reutilizar ese camino con @capgo/capacitor-pdf-generatorIt renders an HTML string with the platform's print engine and returns 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
}
Es útil cuando deseas una página de portada, un título o un pie de página con una fecha en cada página. Para imágenes puras a PDF, pdf-lib te da más control sobre la geometría de la página y la compresión de imágenes.
Escriba la salida en base64 con
Escriba la salida en base64 con @capacitor/filesystemElija el directorio según cuánto tiempo debería durar el archivo:
| iOS | Directory | Android | Utilízalo para |
|---|---|---|---|
Directory.Cache |
App cache, can be cleared by the OS | Library/Caches, se puede eliminar por el sistema operativo |
Archivos PDF que subes o comparten de inmediato |
Directory.Data |
Private app files, removed on uninstall | Aplicación Documents carpeta de la aplicación, respaldada en iCloud |
PDFs que tu aplicación gestiona y muestra de nuevo más tarde |
Directory.LibraryNoCloud |
Archivos de la aplicación privados | Library/NoCloud, no respaldados |
Grandes PDFs que puedes descargar de nuevo |
En iOS, Directory.Data y Directory.Documents apunte a la misma carpeta. Agregue UIFileSharingEnabled y LSSupportsOpeningDocumentsInPlace a Info.plist si los usuarios deben ver esos PDF en la aplicación de archivos.
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)),
);
}
En plataformas nativas, writeFile interpreta una cadena sin una encoding opción como base64 y escribe los bytes binarios. No pase Encoding.UTF8 para PDFs, o el archivo estará dañado.
Si el usuario debe poder encontrar el PDF en la carpeta Descargas de su dispositivo Android, utilice @capgo/capacitor-file-sharer's save() método, que escribe en MediaStore Descargas sin ninguna permiso de almacenamiento en Android 10+. En iOS el mismo llamado abre la hoja de compartir para que el usuario pueda elegir "Guardar en Archivos".
Coloque todo el flujo en conjunto
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);
}
}
Conecte a un botón y muestre un indicador de progreso mientras pagesToPdf corre. En un teléfono Android de gama media, 10 páginas suelen tardar menos de unos segundos ya que los datos JPEG se copian, no se decodifican.
Compartir, abrir o imprimir el PDF
Compartir con la hoja de compartir del sistema
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 copia el archivo en su propio carpeta de caché y lo sirve a través de su propia FileProvider en Android, por lo que no necesita editar file_paths.xmlni siquiera si el PDF vive en Directory.DataSi utiliza la versión oficial @capacitor/share plugin en su lugar files Opción funciona solo por defecto para el directorio de caché en Android. Otros carpetas deben agregarse a android/app/src/main/res/xml/file_paths.xml.
Compartir con la hoja de compartir del sistema
Para una vista previa rápida, el archivo de la comunidad abre el archivo con Quick Look en iOS o con el visor de PDF por defecto en 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',
});
Imprimir
@capgo/capacitor-printer abre UIPrintInteractionController en iOS y PrintManager en Android:
import { Printer } from '@capgo/capacitor-printer';
await Printer.printPdf({
name: 'Signed contract',
path: pdfUri,
});
El name se convierte en el nombre de la impresión y el nombre de archivo predeterminado si el usuario selecciona “Guardar como PDF” en el diálogo de impresión.
Subir el PDF
Para enviar el PDF a su servidor, léalo de nuevo como un Blob y póngalo con fetcho utilice un subidor nativo para archivos grandes para que la subida continúe cuando la aplicación se ponga en segundo plano:
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',
});
Consulte el Capacitor plugin de subida para eventos de progreso y subidas de múltiples partes.
Hacer PDFs escaneados buscables
Los PDFs solo de imagen no pueden ser buscados ni copiados. Si los usuarios esperan buscar sus escaneos:
- Ejecutar OCR en cada imagen de página antes de eliminarla. Las opciones en dispositivo incluyen el reconocimiento de texto de ML Kit en Android y el marco de visión en iOS, ambos accesibles a través de plugins de comunidad Capacitor o un servicio de OCR en servidor.
- Almacenar el texto reconocido en tu base de datos junto al camino del PDF y buscar allí.
- Si el PDF mismo debe contener texto, generar el mismo en servidor con una herramienta de OCR que escribe una capa de texto invisible, como OCRmyPDF.
Para la mayoría de las aplicaciones, este paso es suficiente y evita enviar un segundo modelo grande en la aplicación.
Solución de problemas
El escáner de documentos no está soportado en emuladores de Android“El escáner de documentos no está soportado en emuladores de Android”
: esperado. Utiliza un dispositivo físico con servicios de Google Play.: NSCameraUsageDescription is missing from Info.plist, o el usuario denegó el acceso a la cámara anteriormente. Ver Configuración, Privacidad, Cámara.
embedJpg lanza “SOI no encontrado”: los bytes no son un JPEG. Esto ocurre si pasaste una URL de datos base64 donde pdf-lib esperaba bytes crudos, o si obtuviste un camino que devolvió una página de error HTML. Registra Capacitor.convertFileSrc(path) y abrílo en el inspector de Safari o Chrome.
El PDF es enorme: reducir croppedImageQuality (Android) o recomprimir páginas con @capgo/capacitor-file-compressor antes de incorporar. Una calidad de 70 a 80 es usualmente legible para documentos de texto.
Las páginas aparecen rotas: gira dentro de pdf-lib con page.setRotation(degrees(90)) conceda al usuario la corrección de la rotación en la pantalla de revisión del escáner reviewCapturedDocument: true.
Compartir falla en Android con FileUriExposedException o “No se encontró la raíz configurada”: has pasado un camino fuera de los carpetas declaradas en file_paths.xml para @capacitor/share. Utilice el plugin de compartir archivos o copie el PDF a Directory.Cache primero.
Imagenes de página antiguas llenan almacenamiento en iOS: el escáner escribe JPEGs a la carpeta de Documentos. Elimínelas después de construir el PDF, como se muestra en scanToPdf.
Actualizaciones de envío en el flujo de escaneo
La mayoría de esta característica vive en TypeScript: la disposición de la página del PDF, el nombre de archivo, la lógica de carga y la IU. Los cambios en esas partes no requieren una nueva versión del almacén. Con Capgo actualizaciones en vivo puede enviar una corrección a la disposición de la página del PDF o el punto final de carga a cada usuario el mismo día, mientras que los cambios nativos (agregar el plugin, editar Info.pliststill go through una compilación normal. El documentos de escaneo de documentos lista todas las opciones si necesita ajustar el escáner aún más.