StreamPDFStreamPDF
ブログに戻る

PDF.js をデモから深掘り 1: カスタム viewer で PDF をレンダリングする

2026年10月19日 · StreamPDF Team

PDF.js をデモから深掘り 1: カスタム viewer で PDF をレンダリングする

はじめに

以前のシリーズ記事では、PDF.js のアーキテクチャや、レンダリング層・メッセージ通信といった技術的な話題を探求しました。しかし、記事を書きながらいくつかの問題に気づきました:

  • 記事同士がつながっておらず、完全な知識体系を形成できていない
  • PDF.js のコードと対応づけられないため、記事を追いかける際の精神的な負担が大きい

そこで、新しいシリーズを立ち上げることにしました。デモコードから出発し、呼び出しチェーンをたどりながら少しずつ PDF.js の内部へと深く入っていきます。そして、コードの原理を次の 2 つの側面から解説します:

  • デモ解説: インスタンスごとに解読し、浅いところから深いところへとつなげて掘り下げる
  • コードリポジトリ解説: 汎用的な解説で、ブロック単位で読め、それぞれ独立した記事になる

さあ、最初から始めてみましょう。

カスタム viewer で PDF をレンダリングする

極限までシンプルなカスタム viewer を作って PDF をレンダリングし、そのうえで 1 行ずつコードを追いかけながら PDF.js の内部に入っていきます。追いかける過程では、変数の値をコードに当てはめながら進めることで、実行フローを理解しやすくします。

import * as pdfjsLib from 'https://cdn.jsdelivr.net/npm/pdfjs-dist@6.3.289/build/pdf.mjs';
pdfjsLib.GlobalWorkerOptions.workerSrc =
  'https://cdn.jsdelivr.net/npm/pdfjs-dist@6.3.289/build/pdf.worker.mjs';
const loadingTask = pdfjsLib.getDocument({
  url: 'https://assets-blog.streampdf.com/PDF32000_2008.pdf',
});
const pdf = await loadingTask.promise;
// 1 ページ目のレンダリングデータを取得する
const page = await pdf.getPage(1);
// 1 ページ目のビューポートデータを取得する
const viewport = page.getViewport({ scale: 0.5 });
// canvas を取得し、ここにレンダリングする
const canvas = document.getElementById('canvas-id');
const context = canvas.getContext('2d');
// canvas のサイズを PDF のビューポートサイズに合わせる
canvas.width = Math.floor(viewport.width);
canvas.height = Math.floor(viewport.height);
canvas.style.width = Math.floor(viewport.width) + 'px';
canvas.style.height = Math.floor(viewport.height) + 'px';
// レンダリングパラメータを準備する
const renderContext = {
  canvasContext: context,
  viewport,
};
// render メソッドを呼び出して実際のレンダリングを行い、await で完了を待つ
await page.render(renderContext);

demo: https://streampdf.com/blog/demos/a-simple-pdf-render-demo.html

1〜3 行目: Worker ファイルのパスを設定する

PDF.js ライブラリのエントリーファイルをインポートし、Worker ファイルのパスを設定します。

これは、PDF.js を使う際に、インターフェース仕様さえ守れば Worker を完全に独自のものへ置き換えられることを意味します。

ここではデフォルトの Worker ファイルのパスを設定しています:

GlobalWorkerOptions.workerSrc = https://cdn.jsdelivr.net/npm/pdfjs-dist@6.3.289/build/pdf.worker.mjs

4〜7 行目: PDF ドキュメントを読み込む

getDocument メソッドを呼び出して PDF ドキュメントを読み込みます。object パラメータを渡します:

  • url: PDF ドキュメントの URL。値は https://assets-blog.streampdf.com/PDF32000_2008.pdf

getDocument メソッドは Promise を返し、PDF ドキュメントの読み込みが完了すると PDF ドキュメントオブジェクトで resolve されます。

const loadingTask = pdfjsLib.getDocument(...) は同期呼び出しです: 呼び出しを見る

loadingTask.promise は getter で、Promise.withResolvers() メソッドで作成された Promise を返します。

ここでは await loadingTask.promise で PDF ドキュメントの読み込み完了を待ち、その結果を pdf 変数に代入します。

8〜9 行目: PDF ドキュメントの 1 ページ目を取得する

ドキュメントの初期化が完了したら、getPage メソッドを呼び出して 1 ページ目のレンダリングデータを取得できます。

getPage メソッドは Promise を返し、ページデータの読み込みが完了するとページオブジェクトで resolve されます。

const page = await pdf.getPage(1); は非同期呼び出しです: 呼び出しを見る

1 ページ目のデータの読み込みが完了したら、それを page 変数に代入します。

10〜11 行目: 1 ページ目のビューポートデータを取得する

page データの読み込みが完了したら、getViewport メソッドを呼び出して 1 ページ目のビューポートデータを取得できます。

const viewport = page.getViewport({ scale: 0.5 }); は同期呼び出しです: 呼び出しを見る

1 ページ目のビューポートデータを取得します。object パラメータを渡します:

  • scale: 拡大縮小率。値は 0.5

返される viewport はオブジェクトで、ビューポートの幅と高さを含みます。値は次のとおりです:

{
  width: 297.5,
  height: 421,
  // その他, ここでは使わないので省略...
}

これを viewport 変数に代入します。

12〜19 行目: canvas を取得し、ここにレンダリングする

viewport 変数を取得したら、canvas の width と height を PDF のビューポートサイズに合わせます。

canvas.width = 297; // Math.floor(viewport.width);
canvas.height = 421; // Math.floor(viewport.height);

canvas の CSS 幅と高さも PDF のビューポートサイズに設定します。

canvas.style.width = 297 + 'px'; // Math.floor(viewport.width) + 'px';
canvas.style.height = 421 + 'px'; // Math.floor(viewport.height) + 'px';

なぜ canvas の CSS 幅と高さを設定する必要があるのか? canvas の width, height 属性が制御するのは canvas のピクセルサイズであり、CSS の幅と高さは canvas がページ上に表示されるサイズです。一般的にはこの 2 つは同じです。しかし高解像度の画面では異なります。たとえば window.devicePixelRatio が 2 の場合、canvas のピクセルサイズは 594, 842 に設定し、CSS の幅と高さは 297, 421 のままにして、レンダリングがぼやけるのを防ぎます。

20〜26 行目: 1 ページ目をレンダリングする

canvas の準備ができたら、render メソッドを呼び出して 1 ページ目をレンダリングできます。

canvas コンテキスト、ビューポートデータ、レンダリングパラメータを準備して、render メソッドを呼び出せるようにします。

await page.render(renderContext); は非同期呼び出しです: 呼び出しを見る

これでコードの実行は終了です。

まとめ

本章では、カスタム viewer を使って PDF ファイルの 1 ページ目をレンダリングする流れを追いかけました。

他の記事と最も大きく異なるのは、変数の値を解析対象のコードチェーンに当てはめることで、異なる実行パスを確定できるようにした点です。異なるファイルや解析記事から異なる分岐パスへと入っていくため、記事を読む過程はまるでコードをデバッグしているかのようです。

これまでとは違うソースコード読解体験を届けられれば幸いです。

happy coding!


About me: StreamPDF の創作者で、AI と PDF 領域の研究開発に従事しています。Big PDF, slow viewer? StreamPDF opens 1GB+ PDFs in seconds. Get a 6-month free tier: VIBLO_PRO_6