メインコンテンツへスキップ

Capacitor アプリでドキュメントをPDFにスキャンする方法

Capacitor アプリでドキュメントをPDFにスキャン:ネイティブスキャナーでページを取り込み、1つのPDFにまとめて、iOSとAndroidで保存・共有・開く・印刷ができます。

Capacitor モバイルアプリで紙の書類をPDFにスキャンする方法

Capacitor アプリで書類をPDFにスキャンするには、ネイティブスキャナーを次のコードで起動し @capgo/capacitor-document-scanner、返されたJPEGページを取得して、次のコードで1つのPDFに結合します pdf-lib. そこから、次の方法でPDFを書き出します: @capacitor/filesystem 共有シート、PDFビューア、またはシステムの印刷ダイアログに渡します。このガイドでは、Capacitor 8上で動作するcodeを使った一連の流れを、iOSとAndroidの両方で解説します。

iOSとAndroidでスキャン→PDFはどう動くか

両プラットフォームにはシステム標準のドキュメントスキャナーが搭載されているので、エッジ検出や台形補正を自前で実装する必要はありません:

iOSと Androidでは
ネイティブスキャナー VisionKit VNDocumentCameraViewController Google ML Kit Document Scanner (Play services)
エッジ検出とクロップ はい はい
フィルター / クリーンアップ VisionKit 内蔵 base, base_with_filter, full モード(汚れや指の除去など) full)
セッションごとのページ制限 24 1〜24(デフォルトは20)
カメラ使用の許可プロンプト はい、必要です NSCameraUsageDescription いいえ、Play services で動作します
エミュレータ / シミュレータで動作 いいえ いいえ
プラグインの出力結果 JPEG ページ JPEG ページ

このプラグインは PDF ではなく画像を返します。これは意図的な設計で、ページサイズ、圧縮率、ファイル名を自分で制御でき、ギャラリーから選択した写真やバックエンドから受信したファイルでも同じ PDF code が機能します。

プラグインをインストール

bun add @capgo/capacitor-document-scanner @capacitor/filesystem pdf-lib
bunx cap sync

スキャナープラグインは Capacitor のメジャーバージョンに追従するため、プラグイン v8 は Capacitor 8 に対応しています。 pdf-lib 純粋な JavaScript ライブラリであり、ネイティブ code を必要としないため、WebView 内で動作し、同期も不要です。

iOSの設定

にカメラ使用文字列を追加します ios/App/App/Info.plist。VisionKit がカメラを開き、このキーが欠けていると 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 バイナリを生成できます。

Androidの設定

に追加するものは何もありません AndroidManifest.xml。ML Kit スキャナーは Google Play サービスのモジュールであり、初回使用時にダウンロードされ、カメラを自分で処理するため、アプリは を要求しません CAMERA。デバイスには Google Play サービスが必要であり、これによりほとんどのエミュレーターと Googled されていないスマートフォンが除外されます。Play サービスがない市場にリリースする場合は、フォールバック(通常のカメラキャプチャ)を計画してください。

ページをスキャンします

スキャナーを開き、ページパスを返す関数から始めます。または 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文字列は数十MBに達し、そのすべてのバイトがCapacitorブリッジを通過します。
  • maxNumDocuments: 次の値に設定します 1 領収書やIDカードなどの1ページフロー向けです。
  • reviewCapturedDocument: 続行する前に撮影した各ページを表示します。重要な書類をスキャンするときに便利です。
  • brightness と contrast: 画像に適用される後処理。コントラストを 1.2 まで 1.5 に設定すると、色褪せた領収書に効果的です。
  • scannerMode (Android): Full MLベースのクリーンアップを追加します, Base はより高速で、画像をそのまま保持します。

ファイルの保存先はプラットフォームによって異なります。Android ではページがアプリのキャッシュディレクトリに、iOS ではプラグインがアプリの Documents ディレクトリに書き込みます。どちらも長期的な保存先としては適していないため、以下のフローでは PDF を生成し、任意の場所に保存してからページ画像を削除します。

ページを 1 つの PDF に結合する

pdf-lib は JPEG を再エンコードせずに直接埋め込めるため、PDF サイズはページサイズの合計とほぼ同じに保たれます。各ページは次を介して読み取ります Capacitor.convertFileSrc。これはネイティブパスを WebView が読み込める URL に変換します:

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();
}

スキャンした紙と正確に一致するページにしたい場合は、A4 のロジックを次に置き換えて pdf.addPage([image.width, image.height]) 画像をフルサイズで描画します。レシートは細長いため、この方がきれいに見えます。

代替案: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への変換には、 pdf-lib の方がページ構成や画像の圧縮をより細かく制御できます。

PDFを保存

base64 の出力を次で書き込みます: @capacitor/filesystemファイルの保存期間に応じてディレクトリを選択してください:

ディレクトリ Androidでは iOSと 用途:
Directory.Cache アプリのキャッシュ(OSによってクリアされる場合があります) Library/Caches(OSによってクリアされる場合があります) すぐにアップロードまたは共有するPDF
Directory.Data アプリのプライベートファイル(アンインストール時に削除されます) アプリ Documents フォルダ(iCloud にバックアップ) アプリが管理し、後で再表示する PDF
Directory.LibraryNoCloud アプリ内の非公開ファイル Library/NoCloud、バックアップなし 再ダウンロード可能な大容量 PDF

iOS では、 Directory.Data と Directory.Documents 同じフォルダを指します。追加してください UIFileSharingEnabled と LSSupportsOpeningDocumentsInPlace まで Info.plist ユーザーがFilesアプリでこれらの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 オプションなしの文字列を encoding base64として扱い、バイナリバイトを書き込みます。渡さないでください。 Encoding.UTF8 そうしないと、PDFの場合はファイルが破損します。

AndroidのダウンロードフォルダでPDFを見つけられるようにするには、以下を使用します: @capgo/capacitor-file-sharerの save() Android 10 以降ではストレージ権限なしで MediaStore の Downloads に書き込むメソッドです。iOS では同じ呼び出しで共有シートが開き、ユーザーは「ファイルに保存」を選べます。

フロー全体を組み立てる

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 端末では、JPEG データはデコードされずコピーされるだけなので、10 ページでも通常は数秒もかかりません。

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 では、編集する必要はありません file_paths.xml、PDF がどこに保存されていても 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 を開きます UIPrintInteractionController (iOS)と PrintManager Android では:

import { Printer } from '@capgo/capacitor-printer';

await Printer.printPdf({
  name: 'Signed contract',
  path: pdfUri,
});

この name 印刷ジョブ名となり、印刷ダイアログで「PDFとして保存」を選択した場合のデフォルトのファイル名になります。

PDFをアップロード

PDFをバックエンドに送信するには、それを Blob として読み取り、 fetchでPOSTします。または、大きなファイルにはネイティブのアップローダーを使い、アプリがバックグラウンドに移行してもアップロードが続くようにします:

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 uploader プラグイン 進捗イベントとマルチパートアップロード用。

スキャンしたPDFを検索可能にする

画像のみのPDFは検索もコピーもできません。ユーザーがスキャン内容を検索することを想定している場合は:

  1. ページ画像を削除する前に、各画像に対してOCRを実行してください。オンデバイスの選択肢としては、Androidの ML Kit Text Recognition や iOSの Vision framework(いずれもコミュニティの Capacitor プラグイン経由で利用できます)があり、ほかにサーバーサイドのOCRサービスを使う方法もあります。
  2. 認識したテキストをPDFのパスと一緒にデータベースに保存し、そこで検索します。
  3. PDF自体にテキストを含める必要がある場合は、OCRmyPDF のように不可視のテキストレイヤーを書き込むOCRツールを使って、サーバーサイドで生成してください。

ほとんどのアプリではステップ2で十分で、アプリに2つ目の大きなモデルを同梱せずに済みます。

トラブルシューティング

「Androidエミュレーターではドキュメントスキャナーはサポートされていません」: 想定内の動作です。Google Play services が利用可能な実機を使用してください。

スキャナーを開いた瞬間にiOSでアプリがクラッシュする: NSCameraUsageDescription が見つかりません Info.plist、または以前にユーザーがカメラへのアクセスを拒否しています。設定 > プライバシー > カメラを確認してください。

embedJpg “SOI not found” というエラーが発生する:バイトデータがJPEGではありません。pdf-lib が生のバイトデータを期待しているところに base64 データURLを渡したか、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 または “Failed to find configured root”:で宣言されたフォルダ外のパスを渡しました file_paths.xml まで @capacitor/share。file sharer プラグインを使用するか、PDF をコピーしてください Directory.Cache してください。

古いページ画像がiOSのストレージを圧迫する:スキャナーはJPEGをDocumentsディレクトリに書き出します。PDFを生成した後は、以下に示すように削除してください。 scanToPdf.

スキャンフローへのアップデート配信

この機能の大部分はTypeScriptで実装されています:PDFレイアウト、ファイル命名、アップロードロジック、UIです。これらの部分の変更には新しいストアリリースは不要です。これにより、 Capgo Live Updates PDFレイアウトやアップロードエンドポイントの修正は、ネイティブの変更(プラグインの追加、 Info.plistの編集)が通常のビルドを経る一方で、同じ日に全ユーザーへ配信できます。 ドキュメントスキャナーのドキュメント には、スキャナーをさらに調整する必要がある場合は、すべてのオプションが記載されています。

Capacitor アプリ向けの Live Updates

Web レイヤーのバグが本番で発生したら、App Store の審査を何日も待たずに Capgo で修正を配信しましょう。ネイティブの変更は通常の審査フローに残したまま、ユーザーはバックグラウンドでアップデートを受け取れます。

最新のブログ記事

Capgoは、真にプロフェッショナルなモバイルアプリを作るために必要な最高のインサイトを提供します。