pdf-lib と PDF.js がブラウザ内 PDF 処理をどう分担しているか
NoSend Tools の PDF 関連ツール群は pdf-lib と PDF.js という 2 つのライブラリを組み合わせて動いています。「作成・編集」と「描画・解析」で役割が分かれている理由、PDF フォーマット自体の構造的難しさ、そして復号に WASM 版 qpdf が必要な事情を技術的に整理します。
pdf-lib が担う「書き込み」側の操作
pdf-lib (https://pdf-lib.js.org) は純粋な JavaScript / TypeScript で書かれた PDF 生成・編集ライブラリです。Node.js もブラウザも同じコードで動くため、ネイティブ拡張や WASM コンパイルを必要とせず、import するだけで使えます。NoSend Tools では pdf-merge (複数 PDF の結合)、pdf-split (ページ単位の分割)、pdf-extract-pages (指定ページの抜き出し)、pdf-rotate (ページ回転)、pdf-meta-strip (メタデータ削除) などのツールで pdf-lib を中心に使っています。
基本的なパターンは `PDFDocument.load(arrayBuffer)` でドキュメントを読み込み、`copyPages(srcDoc, [0, 1, 2])` で別の PDFDocument にページを移し、`save()` で Uint8Array として書き出す、という 3 ステップです。ページを 1 枚ずつ操作できるため、「奇数ページだけ抜く」「2 つの PDF を交互に並べる」といった操作も比較的素直に記述できます。pdf-lib はオブジェクト参照や XRef テーブルの整合性を維持しながら構造を書き換えてくれるので、壊れた PDF を出力しにくい設計になっています。
PDF.js が担う「読み取り・描画」側の操作
PDF.js (https://mozilla.github.io/pdf.js/) は Mozilla が管理するブラウザ向け PDF レンダリングエンジンで、Firefox の組み込みビューアとして長年使われてきた実績があります。テキスト抽出、アノテーション解析、Canvas へのページ描画を得意としており、NoSend Tools の pdf-to-jpg / pdf-to-png (PDF を画像に変換) や pdf-extract-text (テキスト抽出) で使っています。
PDF.js には描画処理をメインスレッドから切り離すための Worker スレッドが付属しており、重いページのラスタライズ中もブラウザ UI がフリーズしません。ページを Canvas に描画する API は `page.render({ canvasContext, viewport })` という形で、viewport の scale を調整するだけで任意の解像度に出力できます。pdf-lib が「ページオブジェクトの組み替え」に特化しているのに対し、PDF.js は「ページの見た目を画像として取り出す」ことに特化しており、2 つのライブラリが得意領域で補い合う形になっています。
PDF フォーマットの構造的な難しさ — インクリメンタル更新とオブジェクトストリーム
PDF が「一見シンプルなのに扱いが難しい」理由の一つは、インクリメンタル更新 (incremental update) という仕組みです。PDF は既存の内容を書き換えず、ファイル末尾に差分を追記することで内容を更新します。XRef テーブルには古いオブジェクト番号と新しい世代番号の対応が記録されており、最終的な状態はファイル末尾の XRef から逆算する必要があります。署名付き PDF やフォーム入力済みの PDF を扱うとき、インクリメンタル更新の連鎖を正しく追わないと内容が欠落したり、署名が無効化されたりします。
加えて、PDF 1.5 以降はオブジェクトストリーム (object stream) という圧縮機構があり、複数のオブジェクトを 1 つの圧縮ストリームにまとめて格納できます。これにより構造解析がさらに複雑になります。pdf-lib はこれらを内部で処理してくれますが、あまりに壊れた PDF (クロスリファレンスが整合しない、ストリーム長が嘘など) を入力すると例外を投げることがあります。NoSend Tools の各ツールではそうした例外をキャッチして、ユーザーに「このファイルは読み込めませんでした」と明示するエラー処理を入れています。
復号には WASM 版 qpdf が必要な理由と、ブラウザ内で完結することの意味
pdf-lib はパスワード付き PDF の復号 (decrypt) をサポートしていません。PDF の暗号化仕様 (RC4 40/128-bit、AES-128/256) を正しく実装するのは複雑であり、pdf-lib の設計方針として意図的に対象外にされています。そこで NoSend Tools の pdf-unlock ツールでは、C++ で書かれた qpdf を Emscripten で WASM にコンパイルしたビルドを使っています。qpdf は 20 年以上の歴史を持つ PDF 変換ツールで、RC4 / AES のすべての PDF 暗号化バリアントに対応しています。WASM 版の qpdf はブラウザのサンドボックス内で動くため、復号処理がローカルのメモリ内で完結し、パスワードも暗号化されたファイル本体もネットワークに出ません。
PDF ファイルが機密性の高いデータを含みやすいことは、フォーマットの用途を見れば明らかです。契約書、医療記録、源泉徴収票、登記簿謄本 — いずれも「第三者のサーバーに一時的であっても送りたくない」書類です。pdf-lib / PDF.js / WASM 版 qpdf をすべてブラウザ内で呼び出す設計にすることで、DevTools の Network タブを見ればリクエストがゼロであることを誰でも確認できます。「外部送信しない」という主張を、コードと実行時の振る舞いの両面から検証可能にすることが、このアーキテクチャの最も重要な性質です。