どうも。Kenny(tsujikenzo)です。このシリーズでは、 「GASでPDFを分割・結合しよう」 を全3回でお送りします。2025年も、絶好調です。
はじめに
みなさん、日々GASで業務効率化に励んでいると思います。
そんな、GASによる業務効率化で鬼門なのが、 「PDFの分割と結合」 です。
Pythonには便利なライブラリがありますが、GASだと公式のサービスだけでは物足りません(2025年2月現在)。
今回は、「GASでPDFを分割・結合しよう」をお届けします。
ちなみに、BaseサービスのBlobインターフェイス(スプレッドシートやドキュメントなど、GWSで提供されているサービスのオブジェクトをBlobオブジェクトとして操作する)については、こちらの記事を参考にしてください。
GASのUtilities.newBlob()メソッドは、PDFに対応していません。なので、以下のいずれかの方法が必要です。という話です。
- pdf-lib.jsライブラリを使う ⇒ 今回の目的
- GoogleドキュメントをPDFに変換する
- GoogleスプレッドシートなどのURLをfetchする
- HTMLをPDFに変換する
ライブラリ「PDF-LIB」
PDF-LIBは、JavaScriptでPDFを生成したり処理できる、オープンソースライブラリです。
軽量で、誰でも使えるため、サーバーやWEBアプリなどさまざまな場面で使われています。
2020年に、GASがV8に対応したため、GASでもPDF-LIBが使えるようになりました。
公式リファレンスはこちらです。PDFへの画像の挿入やフォーム処理など、いろんなことができます。
今回は、「分割」と「結合」に特化してお送りします。
2種類のPDF-LIB
pdf-libは、2種類のソースコードを公開しています。
- pdf-lib.js(非圧縮版 / 開発向け)
- pdf-lib.min.js(圧縮版 / 本番環境向け)
実装されているメソッドなどを確認するばあいは、非圧縮版を参照しましょう。
ただし、オリジナルのライブラリにはsetTime()メソッドが使われており、これがGASで使えないため、正常に動作しません(エラーは出ないのですがただしく処理されません。)
GAS用に書きかえたものを、わたしのGitHubに公開しましたので、こちらを参照ください。
- pdf-lib.js(非圧縮版 / 開発向け)
- pdf-lib.min.js(圧縮版 / 本番環境向け)
下準備
GASでpdf-libを使うばあいは、上記ソースコード全文をコピーして、スクリプトファイルに貼り付けるだけです。
これで、必要な処理を呼び出すだけで、PDFを処理できます。
処理を行うフォルダIDなどは、事前にプロパティストアに格納しておきましょう。
const FOLDER_ID = "あなたのフォルダID";
PropertiesService.getScriptProperties().setProperty("FOLDER_ID",FOLDER_ID);
それと、テキストのみの単ページのPDFを、2枚準備しておきましょう。こちらもそれぞれのファイルIDをプロパティストアに格納しておきます。
ちょっと長いですが、以下をコピペして実行していただければ。
//単ページのドキュメントを作成する
let doc = DocumentApp.create('サンプルGoogleドキュメント1');
let body = doc.getBody();
body.appendParagraph("このPDFはGoogleドキュメントから作られており、このページは1ページ目です");
//変更を保存
doc.saveAndClose();
let docId = doc.getId();
let docFile = DriveApp.getFileById(docId);
//GoogleドキュメントをPDFに変換
let pdfBlob = docFile.getAs(MimeType.PDF);
pdfBlob.setName('サンプルPDF1');
//Google Driveに保存
let pdf = DriveApp.getFolderById(folderId).createFile(pdfBlob);
//ファイルIDをプロパティストアに格納する
const PAGEPDF_ID = pdf.getId()
PropertiesService.getScriptProperties().setProperty("PAGEPDF_ID", PAGEPDF_ID);
console.log(PropertiesService.getScriptProperties().getProperty("PAGEPDF_ID"));
//単ページのドキュメントを作成する2
doc = DocumentApp.create('サンプルGoogleドキュメント2');
body = doc.getBody();
body.appendParagraph("このPDFはGoogleドキュメントから作られており、このページは2ページ目になる予定です");
//変更を保存
doc.saveAndClose();
docId = doc.getId();
docFile = DriveApp.getFileById(docId);
//GoogleドキュメントをPDFに変換
pdfBlob = docFile.getAs(MimeType.PDF);
pdfBlob.setName('サンプルPDF2');
//Google Driveに保存
pdf = DriveApp.getFolderById(folderId).createFile(pdfBlob);
//ファイルIDをプロパティストアに格納する
const PAGEPDF_ID2 = pdf.getId()
PropertiesService.getScriptProperties().setProperty("PAGEPDF_ID2", PAGEPDF_ID2);
console.log(PropertiesService.getScriptProperties().getProperty("PAGEPDF_ID2"));
フォルダにPDFが2枚作られていたらOKです。もちろん、手動でPDFを準備していただいても構いません。
プロパティストアの運用がめんどくさい人は、ソースコードに直書きでも構いませんからね。
非同期処理
あまり使用する機会がありませんが、GASは、JavaScript同様に非同期処理が使えます。
非同期処理とは、重要な処理は待つが、プログラム全体は止まらないという仕組みです。
PDF-LIBのばあい、PDFの読み込みや書き込みなどが終わるまで次の処理を行わない、という約束(promise)が大切です。
ライブラリのソースコードには、数多くのPromiseが実装されています。awaitの指示を付けると、Promiseの完了を待ってから次の処理を実行できます。
非同期処理の使い方
awaitを使えるようにするために、functionの前にasync(非同期ですよ)という指示が必要です。
メソッドにPromise処理がされているかどうかは、呼び出し側では分かりません。リファレンスなどを確認する必要があります。
async function myAsyncFunction() { //✅関数を`async`にすることで `await`を使えるようにする
// 非同期処理を行う
const pdf = await PDFLib.PDFDocument.load(bytes); //✅ `await` でPDFのロードを待つ
//load()メソッドの中身にはPromiseを返す処理が書かれている
}
Uint8Array
Uint8Arrayとは、バイナリデータ(PDFなど)を扱うための特別な配列です。
PDFは0と1の組み合わせでできており、そのままではGAS(JavaScript)で直接扱うのが難しいため、一度Uint8Arrayに変換してから処理します。
PDF-LIBの.load()メソッド(PDFを読み込むためのメソッド)も、Uint8Arrayを受け取ることで、正しくファイルを読み込めるようになっています。
//PDFファイルのブロブを取得
const blob = DriveApp.getFileById(fileId).getBlob();
//ブロブ(バイナリデータ)をバイト列(Uint8Array)に変換
const bytes = new Uint8Array(blob.getBytes());
// PDFを読み込む
const pdf = await PDFLib.PDFDocument.load(bytes);
後ほど紹介しますが、.save()メソッドもUint8Arrayを返しますので、直接ブロブを作成し、Google Driveへ保存可能です。
//Google Driveにファイルを生成する
const pdfBytes = await mergedPdf.save();
const finalBlob = Utilities.newBlob(pdfBytes).setContentType("application/pdf");
DriveApp.getFolderById(folderId).createFile(finalBlob);
ファイルタイトルを取得してみよう
それでは、最後にPDF-LIBを使って、ファイルタイトルを取得してみましょう。
ファイルを操作するのは、PDFDocumentクラスです。
PDFDocumentoクラスの主なメンバーはこちらです。
ファイルタイトル取得
ファイルのタイトルを取得するのは、.getTitle()メソッドです。
以下のような処理で、タイトルを取得できます。ついでに、PDFファイルの著者名も取得してみましょう。
async function getFileInfomation() {
//プロパティストアからPDFのファイルIDを取得する
const fileId = PropertiesService.getScriptProperties().getProperty('PAGEPDF_ID');
//PDFファイルのブロブを取得
const blob = DriveApp.getFileById(fileId).getBlob();
//ブロブ(バイナリデータ)をバイト列(Uint8Array)に変換
const bytes = new Uint8Array(blob.getBytes());
// PDFを読み込む
const pdf = await PDFLib.PDFDocument.load(bytes);// ✅ `await` をつけることでPDFのロードを待つ
//PDFファイルの著者を取得
const author = pdf.getAuthor();
console.log(author);
//PDFファイルのタイトルを取得
const title = pdf.getTitle();
console.log(title);
}
PDFのファイル名が取得できていればOKです。※著者名は設定されていませんでした。(;^_^A
まとめ
以上で、「GASでPDFを分割・結合しよう」の1回目をお送りしました。
ライブラリって便利ですよね。オープンソースコードの開発をしている(ほぼ)ボランティアの方々はすごいと思います。
次回は、「PDFを結合しよう」をお届けします。お楽しみに。