Google Apps ScriptでPDFを作成する【GAS】

Google Apps Scriptそのものには、印刷に関するメソッドがありません。しかし、ファイルのエクスポートではPDF形式がサポートされています。これは、URLを組み立てて投げることで、プログラム側からも利用出来るようになっています。

PDF化は何かと利用価値はありますが、これが実装できると、自動的にPDF化してメールに添付して送る、ドライブの所定の場所に自動的に格納するなどが可能になります。また、スプレッドシート固有の機能ではないので、他のドキュメントも利用することが出来ますよ。

※旧方式のPDF出力では、現在のような詳細な出力指定は出来ません。新方式のPDF出力では詳細な出力が可能であり、またカスタム改ページや、指定したシートのみPDF化なども可能になっています。

使用するメソッド・クラス、準備するもの

  1. 今回使用するスプレッドシート
  2. UrlfetchAppクラスの各種メソッド
  3. DriveAppクラスのgetAsメソッド

ソースコード

メソッドを使って簡単にPDF化して取得

この方法は最も手っ取り早いPDF化の方法です。しかし、制御が出来ません。よって、細かいPDF化の為のオプションが存在しないので、そのドキュメントおよびスプレッドシートがまるごとPDFになるだけです。今回はスプレッドシートをPDF化して、ファイルを生成し、そのままログインユーザ名を取得して、操作者にそのままPDFをメールに添付して送り付けます。

  • 今回はフォルダの指定はしていないので、ルートディレクトリにPDFが生成されます。
  • 生成しなくてもメールに添付してそのまま送りつけてしまってもOKです。
  • getAsメソッドの引数にPDFのMIMETYPEを渡しています。これが最も重要です。
  • 特定のシートのみPDF化が出来ません。
  • 何も他にオプションが無いので、スプレッドシートではセルの枠線などもバッチリ映り込んでしまいますので、ドキュメント向けです。

URLを組み立ててPDFをエクスポートして取得

getAsメソッドだと自由が効かないので、使い勝手が悪いです。そこで、URLを組み立ててPDF化する方法があります。特定の1シートのみをPDF化することが可能です申請書類などをスプレッドシートで生成し、PDF化してメールで送る場合にはこの手法が最も良い方法でしょう。

ソースコード

  • 今回は、アクティブシートをA4用紙横印刷でグリッドライン無しのフィットページにてPDFを作成しています。
  • スクリプトトリガーなどで実行する場合は、実行者の権限で動かせます。また、ウェブアプリケーション上でもこのコードは実行者の権限設定ができますので、匿名の者であっても、ウェブアプリケーション化してしまえば、コードを実行することが可能です。
  • アクセス者の権限で呼び出す場合、必ずその人のアカウントで認証が必要です。またこの場合、実行時のQuotaは一人単位なので、設置者権限だとQuotaに引っかかる場合は、アクセスしてきてるユーザの権限で動かすと良い。
  • 但し上記の場合、PDFをドライブに生成する時は、そのフォルダへのアクセス権限が必要。
  • urlfetchappは連続で呼び出すと、途中でアクセス過多で503エラーや403エラー、429エラーが出るので、沢山呼び出す時は、10秒くらいUtilities.sleep入れてウェイトする必要があります。一日に呼び出せるQuotaにも注意が必要です。PDFのファイルサイズにも注意。
  • ちなみにAPI呼び出しすぎの場合の403エラーは、User Rate Limit Execeedというエラーがそれです。間隔を空けましょう。

書き込みが間に合わない時の処置

データの書き込みを行い、其の流れでPDFを生成する場合には、データの書き込みが間に合わずPDFが生成されてしまうケースが、しばしばあります。このままでは、折角のPDFもデータが欠けた状態で作成されてしまうので、以下のような無限ループを利用して、「最終書き込みセル」を確認しながら、スクリプトをウェイトさせます。

最終書き込みをするであろう場所を今回は、名前付き範囲で指定しています。
その中身が空の状態の時は無限ループします。値が確認できたら、PDF生成ルーチンへ進むといった感じです。

また、データ取得前にSpreadsheetApp.flush()をすれば即データが取得できる場合もありますので、活用してみてください。

URLオプション

URLに続けてオプションを付加すると、Chromeでの印刷時と同じオプションを付け加えることが出来る。重要度の高い物を赤字、次に必要な物を青字で説明してみた。

  • key : スプレッドシートのIDを指定する
  • gid : シートIDを指定する。シート毎に付いているもので、URLに入ってる。このオプション指定をしないと、全ページ印刷の対象となる。省略可。
  • format : 出力フォーマットを指定。PDFなので、format=pdfとして引数を取る。以前は、fmcmdで引数に数字を取っていた。

formatの引数に指定できるものは以下の数種類。要するに標準で装備してる「形式を指定してダウンロード」の対象物が取れるようになっているわけです。今回はスプレッドシートのみ紹介しますが、ドキュメントやスライドなどでは若干使えるもの使えないもの、違うものがあります(例えば、ドキュメントの場合、4.はodsではなくodtとなる)。

  1. pdf : 今回のテーマ。PDF形式で出力します。
  2. xlsx : Microsoft Excel形式で出力します。出力は、xlsx形式なので注意
  3. csv : UTF-8でエンコードされたCSVファイル。正直あまり使わない。Excelにインポートする時は、拡張子をtxtに変更してから取り込みましょう。
  4. ods : OpenDocument形式。但しこれは、スプレッドシートの場合の形式です。OpenOffice.orgのcalcの拡張子を指定すると覚えておけばOK。使わないと思うけれど。
  5. tsv : タブ区切りのテキストファイル形式です。
  6. zip : シートをHTML形式で出力しZIPで固めてエクスポート。その為、このオプションだけは、gid=の指定を入れてしまうとエラーになります。単純にexport?format=zipだけしか指定してはいけません。

その他のオプション指定項目は以下の通り。よく使うものだけ青色で表示。margin系は4方向すべて指定しなければなりません。現在、複数のsheetidを指定したPDF出力法がわからない・・・・カンマ区切りで渡してもだめでした。

  1. size : 出力するサイズ。A4と入れるとA4形式となる。番号での指定もできるようだ。また、10x20とするとインチ指定ですが横x縦のカスタムサイズ指定で出力可能。
  2. fzr : 各ページに行見出しを含めるかどうか。trueで含める、falseで含めない。
  3. fzc : 各ページに列見出しを含めるかどうか。trueで含める、falseで含めない。
  4. portrait : trueで縦出力、falseで横出力
  5. fitw : ページをフィットさせるかどうか。trueでフィット、falseで原寸大。
  6. gridlines : グリッドラインの有無。trueで有り、falseで無し。
  7. printtitle : ドキュメントのタイトルの有無。trueで有り、falseで無し。
  8. sheetnames : シート名の有無。trueで有り、falseで無し。
  9. pagenum : ページ番号の指定。CENTERと入れると真ん中に表示される。
  10. attachment : 不明。getFileというコマンドと連携してるとかしてないとか。
  11. locale : 言語の指定。必要ないと思う。
  12. range : シート単位ではなく、指定範囲内を印刷。名前付き範囲を指定したり、A1:D5の範囲指定ならば、A1%3AD5(%3Aでコロン)といったURLエンコードした範囲を与える
  13. widget : rangeの指定時のみ使用可能。今見ている範囲内をtrueとすると印刷範囲とする変なオプション。使いドコロ不明。
  14. top_margin : 上の余白(cmで指定)
  15. bottom_margin : 下の余白
  16. left_margin : 左の余白
  17. right_margin : 右の余白
  18. scale : 1= 標準100%, 2= 幅に合わせる, 3= 高さに合わせる,  4= ページに合わせる
  19. printnotes:コメントを印刷するかどうか。trueで印刷する。
  20. ir:不明
  21. ic:不明
  22. r1c1 : R1C1方式でのrangeの指定。&c1=0&r1=0&c2=4&r2=42といった感じに指定する。
  23. horizontal_alignment:水平方向の位置。LEFT/CENTER/RIGHTを指定
  24. vertical_alignment:垂直方向の位置。TOP/MIDDLE/BOTTOMを指定
  25. pageorder:複数ページの印刷時にどちらの方向から印刷するか。1で前から後ろ、2で後ろから前。

とりわけ、特定の範囲を指定してのPDF化の場合には、URLエンコードが必要なので、以下のように予め範囲指定をURLエンコードしておいて、URL組み立てでつないで上げれば楽です。encodeURIComponentでそれが可能です。

こうすることで、指定のシートのA1:E21の範囲をA4横でグリッドライン非表示、自動でフィットするようにPDF化してくれます。

暫定:特定の複数のページをPDF化する

現在、gidは1個しか指定ができません。印刷オプション的には特定のシートを複数選択して、それだけを印刷可能なので何らかのオプションが隠れていると思うのですが、指定方法が不明です。

そこで暫定的な方法としては、PDF化をする際に印刷対象にしないスプレッドシートを隠してからPDF化を実行すると、非表示にしたシートは印刷対象から外れます。非表示にするコードは以下の通り。このコードだと、水根貨物線跡というシートが一旦非表示になってからPDF化されて、再度表示しています。全3枚中2枚がPDF化されて印刷されます。gid指定は行っていません。エラー発生時を想定して、try{}catch(e){}で、エラー時には再度、showするコードを入れておくと良いと思います。

新方式対応のPDFエクスポート

基本的な作り方

URLを組み立ててPDFをエクスポートする手段は、Google Apps初期の頃からあった古い手段です。現在、Google Spreadsheetなどのファイル⇒ダウンロードで使用されているのはPOST通信でパラメータを送る手段になっているようです。ダウンロード実行時にChrome Devtoolで確認すると、そのパラメータらしきものが、Form Dataに存在します。

※ちょっとリンクの表示が遅い・・・

図:現在のエクスポート用パラメータ

この内容をロシアのとある人が解析しウェブサイトで情報とサンプルコードを公開しています。StackOverFlowでも紹介されています。カスタム改ページについても実行ができるようになりました。

  • 直接ダウンロードは、実行するとユーザのアクションを待たずにダイレクトにダウンロードが始まります。
  • 直接ダウンロードの場合、UrlfetchAppを利用していないので、制限に掛からず実行が可能です。
  • メール添付やドライブ保存はPOST通信をUrlfetchAppを利用しているので、URL組立方式と同様です。
  • nullやパラメータ値が入ってるものの、まだ未解明部分のパラメータがあるみたい・・・
  • パラメータは、pcと書かれている変数の配列部分がそれに該当します。
  • 上記のコードはGASを実行するとwindow.openで新しいタブでファイルがダイレクトダウンロードされ、google.script.host.closeで閉じれる変わった仕様です。実行しただけでダウンロード出来るので、他でも利用できそうなテクニックですね。
  • Google Chrome 83.x以降は、GASからwindow.openで開いてもURLは実行されなくなってしまいました・・・ので、GAS実行でダイレクトダウンロードは出来ないです(ついでにURL表示してクリックしてもダウンロードされずエラー)。。。こちらにそのIssueが登録されています。詳細な情報はこちらにあります。GAS側に.setXFrameOptionsMode(HtmlService.XFrameOptionsMode.ALLOWALL)をつけるべしとありますが、つけてもallow-downloadがiframeに付与されないので、GAS側の対応待になってるようです。(この問題は解消されました)
  • 2020/11/20 - Google側仕様変更で、PDF生成URLにランダムな数値をつけるesidというフラグが追加されていないと動作しないということで、本家コードに追記がされました。

参考:解説動画

複数ページを指定して出力

旧方式の場合、指定したシートだけを選択してPDF化が、シートの非表示というテクニックを使ってでないと出力が出来ませんでした。新方式では、配列に複数のシートIDを指定することができるので、非常に便利です。

  • サンプルシートの1枚目と3枚目を指定して、1つのPDFとして出力させています。
  • 配列で複数シートを指定すれば飛び飛びでの指定も可能となっています。

カスタム改ページを指定してPDF化

StackOverFlowにて、カスタム改ページ時のパラメータが解析され、今回の新方式のPDFでそれらを指定して出力する事ができるようになりました。今回のコードで言えば、exportPDFのパラメータのpc変数の中にあるformat指定の後の部分がそれになります。ただし、指定方法がとてもむずかしいので、固定的に改ページ指定するようなケースで利用することになると思います。

  • 今回は改ページがわかりやすいように、横向き印刷としています。そのため、myExportPDF3にて、A4横で指定を掛けています。
  • exportPDF2にて出力をするのですが、変更箇所はpc変数の一番最後。通常ここはnull,0として終わっているのですが、ここが変更箇所になります。
  • sourceはシートのIDです。それに加えて配列でcm指定だと思われる2箇所の改ページの場所を縦横で入れてると思われる数値を指定。
  • これらは、Google Chromeのデベロッパーツール、ネットワーク内のpdf出力と思われるURL内にあるform dataにて確認が出来ます。
  • 実際にこれでPDF出力を行うと本来は1ページ目に2つ画像が入るところが、1ページ目は1個のみ、残りは2ページ目以降に収まるように印刷をしてくれます。

図:Developer Toolのネットワークにて解析

図:これをGASで実行するのが今回の目的

パスワード付きPDFを作りたい

Google Apps Scriptだけでは暗号化PDFは作る手段がありません。しかし、Node.jsのモジュールが使えれば作成する事ができます。自分もようやく最近作れるようになったのですが。。。

Node.jsが使える環境がGoogle Cloud Platformには複数あります。其のうちの一つである「Google Cloud Functions」にて、Node.js + hummus-recipeにて実現が可能です。無料枠がかなり広いので、超低コストでGoogle Apps Scriptの機能を拡張し、パスワード付きPDFを作る事が可能になります。

詳細については、以下のエントリーを参考に挑戦してみてください。サンプルも用意してあります。ちなみに、このモジュール、パスワード付きPDFを作るだけでなく、結合や加工(テキスト挿入やイメージ追加)、挿入や追加、分割やオーバーレイなど様々な事ができます。実装次第では一気にPDF作成の可能性が広がります。

Google Apps ScriptでCloud Functionsの関数を実行する【GAS】

外部のPDFファイルを自分のドライブにコピーしたい

外部で公開してるPDFファイルを、自分のDriveに定期的にコピーしたい要望はちょいちょい伺います。これは非常に簡単で、スクリプトトリガーなどと組み合わせると、自動で定期的に自分のドライブにつっこんでくれるので、取りこぼすことなく全自動でPDFを入手することが可能です。

  • ついでに、自分自身へメールに添付して送るコードもついています。
  • 上記コードはgetBlobで取得したものをattachmentで添付してしまっていますが、bodyの中に生成したPDFへの共有リンクなどを貼り付けて送るほうが、ドライブ容量無駄遣いせずに済みます。共有リンクは、https://drive.google.com/file/d/ファイルのID/viewとなります。
  • Google Driveのファイルの直リンクで入れるとダウンロードになります。https://drive.google.com/uc?export=download&id=ファイルのIDとなりますので、上記コードの場合、pdfidをファイルのIDに入れて、bodyに1文追加すればOK。

複数のPDFを結合する

Cloud Functionsを使わないと出来ないと思っていたPDFの結合ですが、Google Apps ScriptのV8対応によって、スクリプトだけで結合が出来るようになったようです。こちらのサイトに掲示されてるコードを利用する事で、個別PDFファイルの配列を渡すだけで、個別PDFのあるフォルダ内に結合したPDFファイルが生成されるようになります。ベースになってるコードはこちらのコード

実際にこのフォルダに3個のPDFファイルをアップロードしておき、スクリプト内でフォルダIDを指定し、投げてみた所、バッチリ結合したファイルが生成されました。同じスクリプトにはSplitPdfという関数(PDFの分割用かな?)もあるのですが、こちらはうまく動作せず。

このコードはV8を有効化しないと利用できません。ひょっとしたらV8対応によってこれまでGASでは実現できなかったものが、GASだけで色々と実現出来るようになってるかもしれませんね。

複数のPDFを結合する2

前項のPDF結合は、こちらのサイトによるとPDF v1.5までの規格のものしか結合ができず、PDF v1.7以降の規格には対応していないとのこと。pdf-libを使っての結合を試みて、出来なかったということなので、実際にpdf-libを使っての結合が出来ないのか?やってみました。

事前準備として、以下の処置をしています

  1. こちらのライブラリをスクリプトファイルを用意してコピペ
  2. コードの中のsetTimeoutはGASでは使えないので、return t()だけを残して削除
  3. V8ランタイムはオンにしています

この状態のライブラリのコードでこちらのサイトのコードを参考に構築してみました。ファイル1ファイル2を結合して1つのファイルとしてドライブに生成します(両ファイルともPDF 1.7規格です)。サンプルスプレッドシートはこちらです。

  • 結果、無事に結合ファイル.pdfが生成されました。
  • PDF v1.7でも問題なく結合が出来ました。
  • このライブラリは結合以外にも色々とできるっぽいので、PDF生成の様々な用途で使えそうです。

図:GCF無しでv1.7規格の結合に成功

セル内に画像表示時の挙動

スプレッドシートでは現在、セル内に画像表示は2種類のパターンが利用できます。1つはimage関数を使った手法(但し、誰でもアクセス出来る必要がある)。もう一つがセル内に埋め込みする方法。

この時、このシートを印刷すると以下の挙動になります。

  1. 埋め込みの場合はきちんとPDF化で表示可能です
  2. image関数の場合、Google Drive上の直リンクの場合、画像が公開されていてもPDF化で表示が出来ません
  3. また、画像非公開の場合はimage関数の場合そもそもシート上でも表示出来ません
  4. 但し、外部のサーバで公開されている画像の場合は、image関数の場合であってもPDF化で表示可能です。

よって、このような画像埋め込みのケースのシートをPDF化して画像が見える状態にしたい場合は、image関数を使わずに画像を外部サーバで公開するか?埋め込みを利用するようにしましょう。これは新旧両方式共に同じです。

埋め込みに関しては最近ようやくメソッドも実装されたので動的に埋め込んで印刷も可能になりました。

Google Apps Scriptでスプレッドシートに画像を貼り付ける

図:image関数の場合の挙動が怪しい

フォームPDFの読み書き

入力のできるフォーム付きPDFファイル、この操作はGoogle Apps Script単体では実現出来ません。しかし、以下のエントリーにあるようにPDF-LIBとfontkitを利用することで、GASからフォームの値を取得したり、フォームに値をセットできるようになりました。

フォーム付PDFを配布して回収データの集計の自動化や、役場への申請用PDF(就労証明書等)の作成の自動化などがこれで可能になります。

Google Apps ScriptでPDFフォームを操作する

ボタンをクリックでダウンロード

これまでの説明は、スプレッドシートをPDFへ変換してUrlfetchAppで取得後にメールに添付して送るというものでした。しかし、メールではなくボタンを押したら直接ダウンロードしたいという要望があると思います。その場合、直接GASのコマンドを叩いてダウンロードは出来ませんが、例えばSidebarやダイアログにボタンを表示して、クリックするとPDFでダウンロードされるみたいな事は可能です。

GAS側コード

※現在見てるスプレッドシートをPDF化するリンクとファイル名を取得して、HTML側へ返しています。

HTML側コード(サイドバー)

※google.script.run.withSuccessHandlerにてGAS側へリンクとファイル名を要求。返って来た値を元にjQueryでボタンにそのリンクとファイル名を設定するというものです。ボタンをクリックするとリンクが叩かれてファイルがダウンロードされます。

関連リンク

Google Apps ScriptでPDFを作成する【GAS】” に対して4件のコメントがあります。

  1. sunsun7clap より:

    分かりやすい解説ありがとうございます。
    大変恐縮ですが、googleフォームに紐ついた、スプレッドシートで集計等したシートをこちらのページにあるようにpdf化するようにしました。
    pdf化まではできたのですが、画像表示がうまく出来ず、4個の画像のうち反映できるのが1個だけだったりと、うまく読み取ってくれません。
    この集計等したシートにはimage関数を使っており、googleドライブから画像をセル内に表示するようにしています。

    シートの読み込み自体はしっかりできていることを確認できてますが、image関数のところだけうまく行きません。
    sleepしたり、flushしたりもいろいろ試していますが上手くいかないのですが、何かご存知であれば教えて頂けますと大変嬉しいです。

    1. officeの杜 より:

      sunsun7clap様

      image関数のケースと画像埋め込みのケースで検証をしてみました。
      記事にも新しいエントリーとして、「セル内に画像表示時の挙動」として検証結果を追記しました。

      結論から言えば、image関数を使ったケースでPDF化で表示出来たのは、外部のサーバに公開してる画像のケースのみで、Google Drive上の画像はシート状で表示されていても、PDF化では表示されません。

      また、画像埋め込みのケースはきちんと表示出来るので、可能な限り埋め込みを利用すると良いでしょう(こちらはGoogle Drive上の画像でもOK)

      1. sunsun7clap より:

        Officeの杜様

        早急な回答ありがとうございます!
        やはりGoogle drive上の画像をImage関数による表示では、pdf化とは相性が悪かったのですね。。。
        仰るように外部サーバーから読み込むことで動きました!
        埋め込みと削除を繰り返す形にはなりますが、これで日々使っていこうと思います!
        ありがとうございました!

        1. officeの杜 より:

          普通に手動で印刷時は出るのですが、一度GETのリクエストで外に出ると、動かないようですね。

          ちょっと手間が増えますが、埋め込みのメソッド活用で回避出来るので、ぜひチャレンジしてみて下さい

コメントを残す

メールアドレスが公開されることはありません。 が付いている欄は必須項目です

日本語が含まれない投稿は無視されますのでご注意ください。(スパム対策)