qpdf-wasmで「ブラウザ内完結」のPDF操作を実装する
「PDFをサーバーにアップロードせず、ブラウザ内で完結させたい」という場合に使えるのが、PDF変換ツール qpdf を WebAssembly(WASM) にした qpdf-wasm です。
この記事では、@neslinesli93/qpdf-wasm を例に、ブラウザ上で ページ抽出・分割・linearize(Web最適化)・暗号化/復号 を行う実装パターンをまとめます。
ref: neslinesli93/qpdf-wasm: QPDF compiled to WASM, ready for the browser
仕組み
- WASMモジュール(qpdf本体)をロード
- FSに
input.pdfを書き込む qpdf.callMain([...args])でCLI引数を渡して実行- 生成された
output.pdfをFSから読み出し、Blobにしてダウンロード/表示
インストール
npm i @neslinesli93/qpdf-wasmこのパッケージでは、locateFile で qpdf-wasm のURLを指定して初期化します。
PDFの1〜2ページだけ抽出する最小実装
import createModule from "@neslinesli93/qpdf-wasm";
export async function extractPages(input: ArrayBuffer) {
const qpdf = await createModule({
// bundlerやCDNで配布される wasm のURLを返す
locateFile: () => "/wasm/qpdf-wasm",
});
// 仮想FSに入力を書き込み
qpdf.FS.writeFile("/input.pdf", new Uint8Array(input));
// 例: 1-2ページを抽出して output.pdf を作る
qpdf.callMain(["/input.pdf", "--pages", ".", "1-2", "--", "/output.pdf"]);
// 仮想FSから読み出し
const out = qpdf.FS.readFile("/output.pdf");
// ブラウザで扱いやすい形に(Blob URL)
return new Blob([out], { type: "application/pdf" });
}実行側:
const file = await (await fetch(fileUrl)).arrayBuffer();
const blob = await extractPages(file);
const url = URL.createObjectURL(blob);
window.open(url, "_blank");以下では、このような汎用ラッパーを作っている前提で話を進めていきます。
import createModule from "@neslinesli93/qpdf-wasm";
type RunQpdfOptions = {
wasmUrl: string; // "/wasm/qpdf-wasm" など
input: ArrayBuffer;
args: string[]; // qpdf CLI引数
inputPath?: string; // default "/input.pdf"
outputPath?: string; // default "/output.pdf"
print?: (msg: string) => void; // stdout
printErr?: (msg: string) => void; // stderr
};
export async function runQpdf({
wasmUrl,
input,
args,
inputPath = "/input.pdf",
outputPath = "/output.pdf",
print,
printErr,
}: RunQpdfOptions): Promise<Uint8Array> {
const qpdf = await createModule({
locateFile: () => wasmUrl,
print,
printErr,
});
qpdf.FS.writeFile(inputPath, new Uint8Array(input));
qpdf.callMain([...args, "--", outputPath]);
return qpdf.FS.readFile(outputPath);
}代表的なPDF操作レシピ
qpdfでできることは基本的にはqpdf-wasmでも可能です。例えば、ページの抽出やパスワードの設定などが挙げられます。
1) ページ抽出
この例では3〜5ページを抽出しています。
const out = await runQpdf({
wasmUrl: "/wasm/qpdf-wasm",
input,
args: ["/input.pdf", "--pages", ".", "3-5"],
});2) Web最適化(linearize)
--linearize は「完全ダウンロード前に表示を開始できるように整形」するオプションです。
const out = await runQpdf({
wasmUrl: "/wasm/qpdf-wasm",
input,
args: ["/input.pdf", "--linearize"],
});3) パスワード設定
--encrypt は暗号化オプションの開始フラグとして公式CLIにあります。
基本的にはここに書かれているようにqpdfをCLIとして利用するのと同じように利用することができます。 https://qpdf.readthedocs.io/en/stable/cli.html#encryption
// 例:ユーザーパスワード/オーナーパスワードや権限などは適宜置き換えてください。
const out = await runQpdf({
wasmUrl: "/wasm/qpdf-wasm",
input,
args: [
"/input.pdf",
"--encrypt",
"user-password",
"owner-password",
"256",
"--", // qpdfの引数構造上、最後の -- の前にオプションが来る設計が多い
],
});4) パスワード解除
--decrypt オプションを使うことでパスワードを解除したPDFを返すことができます。
const out = await runQpdf({
wasmUrl: "/wasm/qpdf-wasm",
input,
args: [
"/input.pdf",
"--password=YOUR_PASSWORD", // パスワードが必要なPDFでは指定
"--decrypt",
],
});5) 制限の除去(remove-restrictions)
--remove-restrictions は暗号化/署名に関連する制限を外す用途で、ドキュメントでも --decrypt と併用できる旨が触れられています。
const out = await runQpdf({
wasmUrl: "/wasm/qpdf-wasm",
input,
args: [
"/input.pdf",
"--password=YOUR_PASSWORD",
"--decrypt",
"--remove-restrictions",
],
});Vite / Next.js で wasm を配る
qpdf-wasmをどこに置くか
一番簡単なのは public/wasm/qpdf-wasm に配置して、/wasm/qpdf-wasm で配信することです。
READMEでも「publicフォルダやCDNで配信し、locateFile でURLを返す」と書かれています。
Next.js での使い方
qpdf-wasmはブラウザ向けなので、Next.jsでは client component / dynamic import でSSRを避けるのが安全です。
"use client";
import { useState } from "react";
import dynamic from "next/dynamic";
export default function Page() {
const [url, setUrl] = useState<string | null>(null);
async function onFile(file: File) {
const { runQpdf } = await import("../lib/runQpdf"); // client側で読み込む
const input = await file.arrayBuffer();
const out = await runQpdf({
wasmUrl: "/wasm/qpdf-wasm",
input,
args: ["/input.pdf", "--linearize"],
});
const blobUrl = URL.createObjectURL(new Blob([out], { type: "application/pdf" }));
setUrl(blobUrl);
}
return (
<div>
<input type="file" accept="application/pdf" onChange={(e) => {
const f = e.target.files?.[0];
if (f) onFile(f);
}} />
{url && <a href={url} target="_blank" rel="noreferrer">出力PDFを開く</a>}
</div>
);
}まとめ
qpdf-wasmを使ってブラウザ上でPDF操作を行う例を解説しました。
qpdf-wasmを使うことで、バックエンドのサーバーへアップロードすることなくブラウザ上でPDF操作を完結させることできます。 また、それによってプライバシーを守りつつPDF操作ツールを提供することができるという利点もあります。 RayPDFでは、qpdf-wasmを使ってブラウザ上で完結するPDFツールを提供しています。こちらから利用可能です。