仕事でCloud functionsを用いてPDF作成機能を実装することになったので、その時のメモです。
やりたかったこと
- Cloud functionsにてonCallでデータを受け取る
- 受け取ったデータを元にPDFを作成する
- 作成したPDFをfirebase storageに保存する
- 保存したファイルのダウンロードリンクを返す
では、いきましょう。
全体のコード
module.exports = functions
.runWith(Constant.runtimeOptions)
.region("asia-northeast1")
.https.onCall(async (data, context) => {
if (!context.auth) {
console.error("認証されていないユーザーからのアクセスです。");
throw new functions.https.HttpsError(
"failed-precondition",
"認証されていないユーザーからのアクセスです。"
);
}
const noData = " -";
let sample1 = data.sample1;
let sample2 = data.sample2;
try {
// A4サイズのPDFでマージンを50にする
const doc = new pdf({ size: "A4", margin: 50 });
// データをメモリで受け取るStreamを初期化
const memoryStream = new MemoryStream(null, {
readable: false,
});
// docにmemoryStreamを設定
doc.pipe(memoryStream);
// PDFで用いるフォントを読み込む
doc.font("fonts/ipaexm.ttf");
// y軸
let currentYAxis = 0;
// タイトル
currentYAxis += 80;
doc.fontSize(18).text("詳 細", 0, currentYAxis, {
width: 600,
align: "center",
});
// サブタイトル
currentYAxis += 70;
doc.fontSize(14).text("サブタイトル", 50, currentYAxis);
// サンプル1
if (typeof sample1 !== "string") {
sample1 = noData;
}
currentYAxis += 30;
buildTitleValueTextWithLine({
doc: doc,
title: "サンプル1",
value: sample1,
yAxis: currentYAxis,
LineFromXAxis: 140,
lineToXAxis: 250,
});
// サンプル2
if (typeof sample2 !== "string") {
sample2 = noData;
}
currentYAxis += 30;
buildTitleValueTextWithLine({
doc: doc,
title: "サンプル2",
value: sample2,
yAxis: currentYAxis,
LineFromXAxis: 140,
lineToXAxis: 250,
});
// 書き込み完了
doc.end();
let isCreatePdfEnded = false;
let url: any = "";
doc.on("end", async () => {
const fileName = `sample.pdf`;
// メモリーに書き込んだデータをバッファーにいれる
const buffer = Buffer.concat(memoryStream.queue);
// firebase storageへの参照を作成
const file = admin
.storage()
.bucket("bucket_name")
.file(`directory/${fileName}`);
// fileの保存
await file.save(buffer, {
metadata: { contentType: "image/pdf" },
});
// 認証用トークン付きのダウンロードURLを取得
url = await file.getSignedUrl({
action: "read",
expires: moment().add(2, "minutes").toDate(),
});
// PDF作成完了フラグをtrueに
isCreatePdfEnded = true;
});
// PDF作成完了フラグがtrueになるまで1秒ずつ待ち続ける
while (!isCreatePdfEnded) {
await sleepByPromise(1);
}
// urlは配列で返ってくるため、stringとして返す
return {
url: url[0],
};
} catch (e) {
console.error(e);
throw new functions.https.HttpsError(
"internal",
"PDF出力中にエラーが発生しました。"
);
}
});
function buildTitleValueTextWithLine({
doc,
title,
value,
yAxis,
LineFromXAxis,
lineToXAxis,
}: {
doc: any;
title: string;
value: string;
yAxis: number;
LineFromXAxis: number;
lineToXAxis: number;
}) {
// x軸が50で、y軸がyAxisの場所からフォント10pxの文字をかく
doc.fontSize(10).text(title, 50, yAxis);
// x軸が140で、y軸がyAxisの場所からフォント10pxの文字をかく
doc.fontSize(10).text(value, 140, yAxis);
// x軸がLineFromXAxisからlineToXAxis、y軸がyAxis + 15の範囲に下線を引く
doc
.moveTo(LineFromXAxis, yAxis + 15)
.lineTo(lineToXAxis, yAxis + 15)
.stroke();
}
export const sleepByPromise = function (sec) {
return new Promise(resolve => setTimeout(resolve, sec * 1000));
}