qpdf-wasmで「ブラウザ内完結」のPDF操作を実装する

qpdf(CLI)をWebAssembly化した qpdf-wasm を使って、PDF操作をサーバーに送らずブラウザ上で行う実装例を紹介します。

Published 2026-01-30

qpdf-wasmで「ブラウザ内完結」のPDF操作を実装する

「PDFをサーバーにアップロードせず、ブラウザ内で完結させたい」という場合に使えるのが、PDF変換ツール qpdfWebAssembly(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

このパッケージでは、locateFileqpdf-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ツールを提供しています。こちらから利用可能です。

参考