Untuk mengscan dokumen ke PDF di aplikasi Capacitor , Buka Scanner Nativ dengan @capgo/capacitor-document-scannerAmbil Halaman JPEG yang Dikembalikan, dan Gabung Mereka ke Satu PDF dengan pdf-libDari sana Anda Tuliskan PDF dengan @capacitor/filesystem dan berikan ke sheet berbagi, pemutar PDF, atau dialog cetak sistem. Panduan ini menjelajahi alur lengkap dengan code yang berjalan pada Capacitor 8, pada kedua iOS dan Android.
Bagaimana cara scan ke PDF di iOS dan Android
Kedua platform tersebut menyediakan scanner dokumen sistem, sehingga Anda tidak perlu membangun pengenalan tepi atau pemotongan sudut sendiri:
| iOS | Android | |
|---|---|---|
| Scanner asli | VisionKit VNDocumentCameraViewController |
Google ML Kit Scanner (Play services) |
| Pengenalan tepi dan pemotongan | Ya | Ya |
| Filter / Pembersihan | VisionKit bawaan | base, base_with_filter, full mode (penghapusan noda dan jejak jari di full) |
| Batasan halaman per sesi | 24 | 1 hingga 24, default 20 |
| Pertanyaan izin kamera | Ya, memerlukan NSCameraUsageDescription |
Tidak, scanner berjalan di Play services |
| Bekerja pada emulator / simulator | Tidak | Tidak |
| Hasil dari plugin | Halaman JPEG | halaman JPEG |
Plugin ini mengembalikan gambar, bukan PDF. Ini adalah pemisahan sengaja: Anda mengontrol ukuran halaman, kompresi, dan nama file, dan PDF yang sama code berfungsi untuk foto yang dipilih dari galeri atau file yang diterima dari backend Anda.
Install the plugins
bun add @capgo/capacitor-document-scanner @capacitor/filesystem pdf-lib
bunx cap sync
Plugin scanner mengikuti Capacitor versi utama, jadi plugin v8 bertarget Capacitor 8. pdf-lib adalah sebuah library JavaScript murni tanpa native code, yang berarti itu berjalan di WebView dan tidak memerlukan sinkronisasi.
pengaturan iOS
Tambahkan string penggunaan kamera ke ios/App/App/Info.plistVisionKit membuka kamera, dan iOS menghentikan aplikasi jika kunci tersebut hilang:
<key>NSCameraUsageDescription</key>
<string>We use the camera to scan your documents.</string>
Package Swift plugin bertarget iOS 15 atau lebih lanjut, yang sesuai dengan Capacitor 8 minimum. Ingatlah bahwa unggahan App Store sekarang memerlukan Xcode 26, jadi bangunlah dengan itu sebelum Anda mengunggah. Jika Anda tidak memiliki Mac, Capgo Bangun Dapat menghasilkan file biner iOS yang ditandatangani di cloud.
Pengaturan Android
Nothing to tambahkan ke AndroidManifest.xml. Scanner ML Kit adalah modul layanan Google Play yang diunduh pada penggunaan pertama dan mengelola kamera sendiri, sehingga aplikasi Anda tidak meminta CAMERA . Perangkat membutuhkan layanan Google Play, yang membuat sebagian besar emulator dan ponsel yang telah dihilangkan Google tidak dapat digunakan. Rencanakan fallback (penangkapan kamera biasa) jika Anda menjual ke pasar di mana layanan Play tidak tersedia.
Scan halaman
Mulai dengan fungsi yang membuka scanner dan mengembalikan jalur halaman, atau null ketika pengguna membatalkan:
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 ?? [];
}
Beberapa pilihan yang perlu diketahui:
responseType:ImageFilePath(default) mengembalikan jalur fileBase64mengembalikan string base64. Pilih jalur file untuk dokumen multi halaman. String base64 untuk 20 halaman resolusi tinggi dapat mencapai puluhan megabyte dan setiap byte melintasi jembatan Capacitor.maxNumDocuments: atur ke1untuk aliran satu halaman seperti bukti pembayaran atau kartu ID.reviewCapturedDocument: menampilkan setiap halaman yang ditangkap sebelum melanjutkan, berguna ketika pengguna memindai dokumen penting.brightnessdancontrast: post-processing dilakukan pada gambar. Kontrasnya1.2ke1.5sering membantu dengan struk yang pudar.scannerMode(Android):Fullmenambahkan pemulihan berdasarkan ML,Baselebih cepat dan menjaga gambar asli.
Dimana file tersebut berada berbeda-beda per platform. Pada Android, halaman berada di direktori cache aplikasi. Pada iOS, plugin menulisnya ke direktori Dokumen aplikasi. Kedua lokasi tersebut tidak baik untuk jangka panjang, sehingga aliran di bawah ini membangun PDF, menyimpannya di lokasi yang Anda inginkan, dan menghapus gambar halaman.
Gabungkan halaman menjadi satu PDF
pdf-lib Bisa menyematkan JPEG secara langsung tanpa mengkode ulang mereka, sehingga ukuran PDF tetap dekat dengan jumlah ukuran halaman. Baca setiap halaman melalui Capacitor.convertFileSrc, yang mengubah jalur native menjadi URL yang dapat diakses oleh WebView:
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();
}
Jika Anda lebih suka halaman yang sesuai dengan kertas asli, gantikan logika A4 dengan pdf.addPage([image.width, image.height]) dan gambarlah di ukuran penuh. Faktur terlihat lebih baik dengan cara ini karena mereka panjang dan sempit.
Alternatif: render PDF secara native
Jika aplikasi Anda sudah membangun PDF dari HTML (faktur, laporan), Anda bisa menggunakannya kembali dengan @capgo/capacitor-pdf-generator. Ia mengrender string HTML dengan mesin cetak platform dan mengembalikan 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
}
Hal ini berguna ketika Anda ingin halaman sampul, judul, atau footer dengan tanggal pada setiap halaman. Untuk konversi gambar ke PDF yang murni, pdf-lib giving Anda lebih banyak kontrol atas geometri halaman dan kompresi gambar.
Simpan PDF
Tulis keluaran base64 dengan @capacitor/filesystem. Pilih direktori berdasarkan berapa lama file tersebut harus bertahan:
| Direktori | Android | iOS | Gunakan untuk |
|---|---|---|---|
Directory.Cache |
Aplikasi cache, dapat dihapus oleh sistem operasi | Library/Caches, dapat dihapus oleh sistem operasi |
PDF yang Anda unggah atau bagikan secara langsung |
Directory.Data |
File-file aplikasi pribadi, dihapus setelah penghapusan. | Aplikasi Documents folder, disimpan ke iCloud |
PDF yang dikelola aplikasi Anda dan ditampilkan lagi nanti |
Directory.LibraryNoCloud |
File aplikasi pribadi | Library/NoCloud, tidak disimpan |
PDF besar yang dapat diunduh lagi |
Pada iOS, Directory.Data dan Directory.Documents arah ke folder yang sama. Tambahkan UIFileSharingEnabled dan LSSupportsOpeningDocumentsInPlace ke Info.plist jika pengguna harus melihat PDF tersebut di aplikasi File.
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)),
);
}
On platform asli, writeFile tidak mengolah sebuah string tanpa encoding Tidak melewati data sebagai base64 dan menulis byte biner. Encoding.UTF8 untuk PDF, atau file akan rusak.
Jika pengguna harus bisa menemukan PDF di folder Download di Android, gunakan @capgo/capacitor-file-sharermetode save() yang menulis ke MediaStore Downloads tanpa izin penyimpanan di Android 10+. Di iOS, panggilan yang sama membuka sheet berbagi sehingga pengguna bisa memilih "Simpan ke File".
Rangkai alur seluruhnya
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);
}
}
Hubungkan ke tombol dan tampilkan indikator loading saat pagesToPdf berjalan. Pada ponsel Android kelas menengah, 10 halaman biasanya membutuhkan waktu beberapa detik saja karena data JPEG disalin, bukan di-dekode.
Bagikan, buka, atau cetak PDF
Bagikan dengan sheet berbagi sistem
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 menggandakan file ke dalam folder cache sendiri dan menyajikannya melalui server sendiri FileProvider pada Android, sehingga Anda tidak perlu mengedit file_paths.xml, bahkan jika PDF hidup di Directory.Data. Jika Anda menggunakan plugin resmi @capacitor/share plugin tersebut files fungsi opsi bekerja secara otomatis hanya untuk folder cache pada Android. Folder lain harus ditambahkan ke android/app/src/main/res/xml/file_paths.xml.
Buka di aplikasi PDF pengguna
Untuk melihat ringkasan cepat, pembuka file komunitas mengirimkan file ke Quick Look di iOS atau ke pembaca PDF bawaan di 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',
});
@capgo/capacitor-printer membuka UIPrintInteractionController di iOS dan PrintManager di Android:
import { Printer } from '@capgo/capacitor-printer';
await Printer.printPdf({
name: 'Signed contract',
path: pdfUri,
});
The name Menggunakan nama pekerjaan cetak dan nama file default jika pengguna memilih "Simpan sebagai PDF" di dialog cetak.
Upload PDF
Untuk mengirimkan PDF ke backend Anda, baca kembali sebagai Blob dan unggahnya fetchatau gunakan uploader asli untuk file besar sehingga proses upload tetap berlanjut ketika aplikasi berada di latar belakang
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',
});
Lihat Capacitor plugin uploader untuk peristiwa kemajuan dan unggahan multipart.
Buatlah PDF yang dapat dicari
PDF hanya gambar tidak dapat dicari atau disalin. Jika pengguna mengharapkan dapat mencari skannya:
- Jalankan OCR pada setiap gambar halaman sebelum menghapusnya. Pilihan perangkat keras termasuk Teknologi Pengenalan Pola ML Kit pada Android dan Framework Penglihatan pada iOS, yang dapat diakses melalui plugin komunitas Capacitor atau layanan OCR server.
- Simpanlah teks yang direcognisasi di database Anda di samping path PDF dan cari di sana.
- Jika PDF itu sendiri harus berisi teks, buatlah secara server dengan alat OCR yang menulis layer teks yang tidak terlihat, seperti OCRmyPDF.
Langkah kedua biasanya sudah cukup untuk kebanyakan aplikasi dan menghindari mengirimkan model besar kedua ke dalam aplikasi.
Troubleshooting
Scanner dokumen tidak tersedia pada emulator Android“Skanner dokumen tidak didukung pada emulator Android”
Aplikasi mengalami crash pada iOS ketika scanner dibuka.: NSCameraUsageDescription is hilang dari Info.plist, atau pengguna menolak akses kamera sebelumnya. Periksa Pengaturan, Privasi, Kamera.
embedJpg mengeluarkan “SOI tidak ditemukan”: byte-nya bukanlah JPEG. Hal ini terjadi jika Anda melewati URL data base64 di mana pdf-lib mengharapkan byte mentah, atau mengambil jalur yang mengembalikan halaman kesalahan HTML. Log Capacitor.convertFileSrc(path) dan buka di inspektor Safari atau Chrome.
The PDF sangat besar: rendahkan croppedImageQuality (Android) atau re-kompresi halaman dengan @capgo/capacitor-file-compressor sebelum mengembangkannya. Kualitas 70 hingga 80 biasanya dapat dibaca untuk dokumen teks.
Halaman tampak terputar: putar di dalam pdf-lib dengan page.setRotation(degrees(90)) atau biarkan pengguna memperbaiki rotasi di layar tinjau scanner dengan reviewCapturedDocument: true.
Bagian berbagi gagal pada Android dengan FileUriExposedException atau 'Tidak dapat menemukan root yang dikonfigurasi'Anda telah melewati sebuah path di luar folder-folder yang telah diumumkan file_paths.xml ke @capacitor/share. Gunakan plugin penerus file atau salin PDF ke Directory.Cache first.
Gambar halaman lama mengisi penyimpanan pada iOS: scanner menulis JPEG ke direktori Dokumen. Hapus setelah membangun PDF, seperti yang ditunjukkan dalam scanToPdf.
Pengiriman pembaruan ke alur scan
Banyak fitur ini berada di TypeScript: tata letak PDF, penamaan file, logika unggah, dan UI. Perubahan pada bagian-bagian tersebut tidak memerlukan rilis toko baru. Dengan Capgo pembaruan hidup anda dapat mengirimkan perbaikan ke tata letak PDF atau endpoint unggah ke setiap pengguna pada hari yang sama, sementara perubahan asli (menambahkan plugin, mengedit Info.plistMasih melalui proses build normal. The scanner dokumen daftar semua opsi jika Anda perlu menyesuaikan scanner lebih lanjut.