構築のきっかけ
少し前に学友会が主催する「代議員会」と「部長会」の出席確認を取るためのシステムを構築しました。システム自体は問題なく使用できていたのですが、ひとつ気になることがあります。それは、会議のログを出力する際の形式です。以前までは実装が楽だからという理由で、json形式で出力していました。もちろんログとしては十分役割を果たしてくれてはいますが、実用性には欠けます。加えて、jsonファイルからデータを読み解くのにはクラウドAIを使用していたこともあり、セキュリティ的にも危ういことがあります。ということで今回は会議のログをPDFで出力するために、サーバーサイドでPDFを作るのではなく、クライアントサイド(ブラウザ)だけで、印刷可能なレベルの日本語PDFを生成する機能を実装します。それでは早速やっていきましょう!
構築方法
技術スタック
- 言語: HTML5, CSS3, JavaScript (ES6)
- データ保存: ブラウザのLocal Storage
- PDF生成ライブラリ: pdfmake(クライアントサイドPDF生成のデファクトスタンダード)
- フォント:
Noto Sans JP(Google Fonts / CDN経由) - 環境: Vanilla JS (フレームワークなし)
今回の構築でPDF生成ライブラリにpdfmakeを使用した理由に関して、一般的な jsPDF などは日本語対応が煩雑ですが、pdfmake はフォント定義(VFS)の仕組みが強力で、レイアウト組み(テーブルなど)がHTMLを書く感覚に近いjson定義で記述できるため採用しました。
アーキテクチャ
今回構築したシステムの動作は以下の通りです。基本的な部分は変わっていないため、PDF出力の処理を中心にまとめます。
日本語フォントの生成の問題点
クライアントサイドPDF生成における最大の壁といえば、「日本語フォントどうするか」でしょう。pdfmake のデフォルト設定では欧文フォントしか持たないため、日本語はすべて「豆(□□□)」に文字化けしてしまいます。かといって、数MBあるフォントファイルをアプリにバンドルすると、初期ロードが重くなりすぎます。
Virtual File System (VFS) への動的注入パターンによる解決
上記の問題を解決するべく、以下の3つの方法を用いました。
- Lazy Fetching: PDF出力ボタンが押された瞬間、非同期(
async/await)でCDNからNotoSansJP.woffをフェッチします。 - Binary to Base64: 取得した
ArrayBufferをUint8Array経由でバイナリ文字列化し、さらにBase64エンコードします。 - VFS Injection: エンコードされた文字列を
pdfmake.vfsオブジェクトにキーバリューとして登録します。これにより、ライブラリはメモリ上のデータを「ローカルにあるフォントファイル」として認識します。
PDFレイアウト
PDFのレイアウトは、座標指定ではなく、宣言的なJSONオブジェクト(Document Definition) で記述しています。
// レイアウト定義の例
const docDefinition = {
content: [
{ text: '会議ログ', style: 'header' },
{
table: {
body: [ ['氏名', '判定'], ['山田 太郎', '出席'] ]
}
}
],
defaultStyle: { font: 'NotoSansJP' } // ここで注入したフォントを指定
};このアーキテクチャにより、「サーバーレス構成」と「リッチな日本語帳票出力」という、相反しやすい要件を両立させました。
さて、ここまで見て、専門用語が多くてわからない!という方もいらっしゃるでしょう。そんな方には下の説明をご覧ください。
どうやってPDFを作る?
みなさん、銀行とかで口座のお金の使用履歴を印刷したことはありますか?いやニッチすぎだろ!って思った方、ご安心ください。今回の構築でやっていることはまさにそれなんです。しかし、ここでひとつ問題が。それはサーバーがいないことです。つまりPDFを作ってくれるプリンターがいないのです。さあ困った!一体どうしよう?
ChromeやSafariを一瞬プリンターに変える
ログPDFを作るためには、材料と道具と組み立てが必要です。まず材料、これは出席データを文字列として集めます。続いて道具、PDFを作るには「日本語のフォント」という特別なペンが必要です。これをインターネット上の倉庫(CDN)から一瞬で借りてきます。最後に組み立て、JavaScriptというプログラムが、ブラウザの中で文字と線を組み合わせて、PDFファイルという「データの実体」を作り上げます。
これによって、サーバーと通信しないため、待ち時間がなく、ボタンを押した瞬間にダウンロードが始まるという便利なシステムができました。
検証
ここでは、今回構築したシステムがどのように動作するのかを順を追って解説します。
なお今回構築したシステムに関しては、下記のURLよりご覧いただけます。ぜひご利用ください。
https://attendance-confirmation-experiment.netlify.app
1, システム使用開始
まずは会議データを取得するために、適当に出席確認を行っていきます。

会議が開始できるようになったので、早速ボタンを押してログを取得しましょう。
2,会議開始処理とPDFの出力
今回の会議は12人出席しているようです。ここからの操作は以前と変わりません。会議が終わったら「会議終了」ボタンを押します。

ボタンを押すと、ログ出力の画面になります。HTMLを少しいじって、文章もPDFに書き換えておきました。

ではログファイルをダウンロードしましょう。PDFの生成には少し時間がかかるため、気長に待ちましょう。少し待つと下のようなポップアップが出てきます。許可を押せばファイルがダウンロードされます。

3,ログPDFの確認
それでは完成したログファイルを確認していきましょう!ログファイルはPDFでの出力のため、記事に直接埋め込むことはできません。ということでスクリーンショットにて掲載します。

こちらは代議員会のログです。会議の名前と出席数、出席率、判定などは上に記載し、各クラスの詳細な出席確認については表として記載しています。細かい部分としては、ログを生成した日時と会議ログを印刷し保管することを考えて、署名欄を設けました。同様に部長会のログはこんな感じです。

こちらも代議員会のログとほぼ同じレイアウトで生成されています。異なる部分として、部長と副部長の代理出席は認められないため、表からは除外しています。
まとめ
今回のシステム構築から、バックエンドなしのLocalstorageのみで、ログファイルをPDFとして出力する手法を学べました。PDFは現代ではなくてはならない存在ですから、現場のニーズとユーザーのフィードバックをもとに考えた構築ができて、非常に良い経験ができたと思います。構築自体も、先人たちの知見とpdfmakeというメッッチャ便利なものを見つけられたこともあり、非常に簡単に実装ができました。また、今回の構築で発生したエラーと今後の計画についてもまとめます。
環境依存文字への対応
PDFには、出席状況を表でまとめたパートがありますが、出席と表す際に「◯」を用いようとしましたが、どうしても文字化けしてしまいました。調べたところ、pdfmakeと日本語フォントの相性によっては、特定の記号が表示できないことがあるそうです。このままでは見栄えが悪いため、小文字の「o」を使用して表現しました。ぱっと見、「なんか歪な丸だな」ぐらいの印象しか抱かなかったため、しばらくはこれで運用していこうと思います。相性問題に関しては、今後も調べてちゃんと◯を表示できるようにしたいです。
今後の計画
現在の出席確認システムは、学友会の「代議員会」と「部長会」の会議のみを想定して作成しましたが、今後部活動が変わったり、代議員会の定員数が変わったりすると、プログラム自体を書き換えて、デプロイ…という手間が発生するでしょう。これでは引き継ぎ業務が多くなり、いつかシステムを保守・運用する人がいなくなれば廃れてしまう…なんてこともあり得なくはないです。そのため、このシステムの構造を大きく書き換え、誰もがこのシステムを自分好みにカスタマイズできる「マスター設定」機能を追加したり、出力したログPDFをボタン一つでGoogle Driveにアップロードするといった共有機能の実装も行なっていきたいと考えています。新機能として考えているのはこの2つですが、システムの運用という面では、このシステムを構築してからまだ数回程度しか使っていないこともあるため、今後より多くの場面で使ってもらいフィードバックを元に改善を重ねていきたいと考えています。
宣伝
このサイトでは、他にも技術的な取り組みを定期的に更新しております。もしご興味がありましたら、ぜひご覧ください!
- 【高専】慶松 海風について
- 【Next.js】VPSホスティングサービスのフロントシステム開発
- 【Discord】Discordのメッセージを取得しWEBに公開する
- 【分解】GPUの分解清掃を行う【DC増設編①】
- 【高専】坂本 弘樹について
より詳細な構築方法について
今回のシステムについて、より詳細な技術仕様、ソースコードにつきましては安全のため非公開としております。万が一、ご覧になりたい方は下記のコメントをご利用ください。



コメントする
コメントを投稿するにはログインしてください。