要在 Capacitor 应用程序中扫描文档到 PDF,请打开原生扫描仪 @capgo/capacitor-document-scanner,然后将它返回的 JPEG 页面合并成一个 PDF pdf-lib。从那里你可以写入 PDF @capacitor/filesystem 并将其交给分享面板、PDF查看器或系统打印对话框。 本指南将逐步介绍使用code在Capacitor 8上运行的应用程序扫描PDF的完整流程,适用于iOS和Android。
iOS和Android上的扫描PDF功能如何工作
两者都内置了系统文档扫描器,因此您不需要自己构建边缘检测或透视校正:
| iOS | Android | |
|---|---|---|
| 原生扫描器 | VisionKit VNDocumentCameraViewController |
Google ML Kit文档扫描器(Play服务) |
| 边缘检测和裁剪 | 是的 | 是的 |
| 过滤/清理 | VisionKit内置 | base, base_with_filter, full 模式(污渍和手指去除在 full) |
| 每页限制 | 24 | 1至24, 默认20 |
| 相机权限提示 | 是,需要 NSCameraUsageDescription |
否,扫描器在Play服务中运行 |
| 在模拟器/模拟器中工作 | 否 | 否 |
| 插件输出 | JPEG页面 | JPEG 页 |
The plugin returns images, not a PDF. That is a deliberate split: you control the page size, compression, and file name, and the same PDF code works for photos picked from the gallery or files received from your backend.
安装插件
bun add @capgo/capacitor-document-scanner @capacitor/filesystem pdf-lib
bunx cap sync
扫描器插件遵循 Capacitor 主要版本,因此插件 v8 targets Capacitor 8。 pdf-lib 是一个纯 JavaScript 库,没有本机 code,这意味着它在 WebView 中运行,需要同步。
iOS 配置
添加一个摄像头使用字符串到 ios/App/App/Info.plistVisionKit 打开摄像头,iOS 如果缺少该键会终止应用:
<key>NSCameraUsageDescription</key>
<string>We use the camera to scan your documents.</string>
该插件的 Swift 包目标 iOS 15 或更高版本,这与 Capacitor 8 最小要求相匹配。请记住,App Store 上传现在要求 Xcode 26,因此在提交之前使用它来构建。如果您没有 Mac Capgo 构建 可以在云中生成签名的 iOS 二进制文件。
安卓配置
无需添加任何内容到 AndroidManifest.xml. ML Kit 扫描器是一个 Google Play 服务模块,首次使用时下载并处理相机本身,因此您的应用程序不需要请求 CAMERA. 设备需要 Google Play 服务,这意味着大多数模拟器和去 Google 手机都无法使用。 如果您要将应用程序推送到 Google 服务缺失的市场,请计划一个备用方案(一个普通的摄像头捕获)。
扫描页面
从一个函数开始,打开扫描器并返回页面路径,或者 null 当用户取消时:
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 ?? [];
}
有一些值得知道的选项:
responseType:ImageFilePath(默认) 返回文件路径Base64返回 base64 字符串。 对于多页文档,优先使用文件路径。 对于 20 个高分辨率页面的 base64 字符串,大小可以达到数十兆字节,每个字节都需要跨越 Capacitor 桥。maxNumDocuments: 将其设置为1适用于单页流程,如收据或身份证。reviewCapturedDocument: 在继续之前显示每个捕获的页面,特别是在用户扫描重要文件时有用。brightness和contrast: 对图像应用了后处理1.2to1.5(Android):scannerMode添加基于ML的清理Full速度更快且不会改变原始图像。Base更快且不改变原始图像。
合并页面为一个PDF
可以直接嵌入JPEG而不重新编码它们,因此PDF大小接近页面大小的总和。 读取每个页面通过
pdf-lib ,将原生路径转换为WebView可以获取的URL: Capacitor.convertFileSrc如果您希望页面与扫描的纸张匹配,请将A4逻辑替换为
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();
}
将页面写入应用缓存目录 pdf.addPage([image.width, image.height]) 并绘制图像以全尺寸。收据看起来更好,因为它们很长很窄。
Alternative: 原生渲染 PDF
如果您的应用程序已经从 HTML 构建 PDF(发票、报告),您可以重用该路径并使用 @capgo/capacitor-pdf-generator它使用平台的打印引擎渲染一个 HTML 字符串并返回 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
}
这很方便,当您想要一个封面页、一个标题或一个带日期的页脚时。 pdf-lib 纯图像转 PDF 时,
保存 PDF
将 base64 输出写入 @capacitor/filesystem。根据文件存活时间选择目录:
| 目录 | Android | iOS | 使用它来 |
|---|---|---|---|
Directory.Cache |
应用程序缓存,可能由操作系统清除 | Library/Caches, 可能由操作系统清除 |
您上传或立即共享的 PDF |
Directory.Data |
私有应用程序文件,卸载后删除 | 应用程序 Documents 文件夹,备份到 iCloud |
您的应用程序管理并显示的 PDF |
Directory.LibraryNoCloud |
私有应用程序文件 | Library/NoCloud, 不备份 |
您可以再次下载的大 PDF |
在 iOS 上, Directory.Data 和 Directory.Documents 指向相同的文件夹。添加 UIFileSharingEnabled 和 LSSupportsOpeningDocumentsInPlace 到 Info.plist 如果用户应该在文件应用程序中看到这些PDF。
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)),
);
}
在原生平台上 writeFile 将一个没有选项的字符串视为base64,并将二进制字节写入。不要传递 encoding 对于PDF,或者文件会被损坏。 Encoding.UTF8 如果用户应该能够在Android的下载文件夹中找到PDF,请使用
的 @capgo/capacitor-file-sharer’s save() method, which writes to MediaStore Downloads without any storage permission on Android 10+. On iOS the same call opens the share sheet so the user can choose “Save to Files”.
将整个流程组合起来
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);
}
}
将其连接到一个按钮,并在运行时显示一个加载指示器 pagesToPdf 在一台中档Android手机上,10页的PDF通常在几秒钟内生成,因为JPEG数据被复制,而不是解码
分享、打开或打印PDF
使用系统分享面板分享
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 将文件复制到其自己的缓存文件夹,并通过其自己的 FileProvider 在Android上,甚至如果PDF位于 file_paths.xml中也不需要编辑 Directory.Data如果您使用官方 @capacitor/share 插件,则其 files 选项在Android上只对缓存目录有效。其他文件夹必须添加到 android/app/src/main/res/xml/file_paths.xml.
在用户的PDF应用中打开
为了快速预览,社区文件打开器将文件传递给iOS上的Quick Look或Android上的默认PDF查看器:
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 在iOS和 UIPrintInteractionController 在Android上: PrintManager 它
import { Printer } from '@capgo/capacitor-printer';
await Printer.printPdf({
name: 'Signed contract',
path: pdfUri,
});
The name 上传PDF
要将PDF发送到您的后端,请将其读回为一个
并使用 Blob ,或使用本机上传器上传大文件,以便在应用程序切换到后台时上传继续: fetch查看
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',
});
看一下 Capacitor 上传插件 用于进度事件和多部分上传。
让扫描的 PDF 可以被搜索
无法被搜索或复制的图片 PDF。如果用户期望可以在扫描中搜索:
- 在删除每个页面图片之前,使用 Android 的 ML Kit 文本识别或 iOS 的 Vision 框架进行设备端的文本识别,或者使用社区 Capacitor 插件中的文本识别功能,或者使用服务器端的文本识别服务。
- 将识别出的文本存储在数据库中,下面是 PDF 路径,进行搜索。
- 如果 PDF 本身必须包含文本,使用服务器端的文本识别工具生成 PDF,例如 OCRmyPDF,写入不可见的文本层。
对于大多数应用,第二步足够了,避免在应用中部署第二个大型模型。
故障排除
“Android 模拟器中不支持文档扫描器”“Android 模拟器中无法支持文档扫描”
:预期结果。使用带有 Google Play 服务的物理设备。: NSCameraUsageDescription 缺少 Info.plist, 或用户之前拒绝了摄像头访问。检查设置、隐私、摄像头。
embedJpg 抛出“SOI未找到”:字节不是JPEG。这可能是因为你传递了一个base64数据URL,而pdf-lib期望的是原始字节,或者你尝试了一个返回HTML错误页面的路径。查看 Capacitor.convertFileSrc(path) 在 Safari 或 Chrome 检查器中打开它。
PDF 文件非常庞大:降低 croppedImageQuality (Android) 或重新压缩页面以 @capgo/capacitor-file-compressor 在嵌入之前。通常,质量在 70 到 80 之间的文本文档是可读的。
页面出现旋转:在pdf-lib中旋转 page.setRotation(degrees(90)) 让用户在扫描器预览屏幕中修正旋转 reviewCapturedDocument: true.
Android 上分享失败 FileUriExposedException 或“找不到配置的根”: 您传递的路径超出了声明的文件夹 file_paths.xml 到 @capacitor/share。使用文件共享插件或将 PDF 复制到 Directory.Cache 首先。
iOS 上的旧页面图片占用了存储空间: 扫描器将 JPEG 写入 Documents 目录。删除它们后,构建 PDF,如下所示 scanToPdf.
将扫描流程中的更新发送
本功能的大部分在 TypeScript 中:PDF 布局、文件命名、上传逻辑和 UI。对这些部分的更改不需要新商店发布。使用 Capgo 实时更新 您可以将 PDF 布局或上传端点的修复发送到每个用户同一天,而原生更改(添加插件、编辑 Info.plist)仍然会经过正常的构建。 文档扫描器文档 如果您需要调整扫描器,列表每个选项。