[GCP Cloud Functions]Puppeteerのスクショ時に日本語フォントを適用させる

GCP

Puppeteerはスクレイピングやスクショを取ったりと非常に便利で、使用されている方は多いのではないでしょうか。

ただし、その一方Puppeteerは環境にかなり依存するので、なかなか痒いところに手が届かなかったりします。

その中でも頭を悩ませるのがスクショを撮るときの日本語対応です。一番酷い時は「豆腐」が表示されて何も分からない状態です。ただこれはCloud Functionsのリージョンを日本にしたり、Puppeteerのオプションに「lang」オプションを付ければ解消はできると思います。

langに関してはこんな感じです。

const puppeteer = require('puppeteer');

(async () => {
  const browser = await puppeteer.launch({args: ['--lang=ja']});
  const page = await browser.newPage();
  await page.goto('https://example.com');
  await page.screenshot({ path: 'example.png' });

  await browser.close();
})();

 
あち
今回はPuppeteerにフォーカスしているのでCloud Functionsの作成・デプロイ方法とかは割愛するよ

大体の方は「豆腐」問題はクリアできるかもしれませんが、それよりも大変なのがフォントです。

Cloud Functionsで日本語ページのスクショを撮ると、文字化けはしていませんが、漢字が簡体字みたいになっていませんか?気にならない人はそのままでも良いかもしれませんが私は直したかったので色々トライしてみました。

それでは詳しい内容に入っていきましょう。

Cloud Functionsに既にインストールされているフォントを確認

そもそもなぜこういった現象が起きているかというと、それはサーバにインストールされているフォントとデフォルト設定が原因です。パソコンと一緒ですね。今回はPuppeteerをCloud Functions上で動かしているので、Cloud Functionsがパソコンとしてブラウザを立ち上げた際にどういったフォントがサーバ上に設定されているのかに依存するイメージです。

なので、まずはCloud Functionsにどういったフォントが入っているかを確認する必要があります。ただし、Cloud Functionsは完全にGoogle側が管理しているので、SSHなどで接続することはできませんし、GCPの管理画面上でフォントが確認できる訳でもありません。

ですので、取れる手段としてはサーバにインストールされているフォントをログに出力するプログラムをCloud Functions上で実行します。

const { execFile } = require('child_process');
const child = execFile('fc-list', [''], (error, stdout, stderr) => {
  if (error) {
    throw error;
  }
  console.log(stdout);
});

fc-listを実行すると、フォントの一覧が出力されます。

実際に試して頂ければ分かりますが、かなり多くのフォントが既に入っています。探してみると日本語フォント(ゴシックや明朝)もちゃんと含まれています。

では、なぜPuppeteerのスクショ時に見た目が変になるのでしょう。

フォントのデフォルト設定を確認

先ほどのコードで、Cloud Functionsには日本語を含む多くのフォントが既に入っていることが分かったと思います。

しかし、Puppeteerでスクショを撮るときには簡体字のようになってしまっています。

これは前述の通り、サーバ上のフォントのデフォルト設定が原因です。

いくら色んなフォントが入っていたとしても、それを全て使う訳ではありません。その中でメインとなるフォントをサーバに設定し、優先的にそのフォントを使用します。

なので、次は日本語フォントの中で、何がデフォルトとして設定されているかを確認します。前回同様サーバに入ったりはできないのでプログラムを実行して、結果をログに出力します。

const { execFile } = require('child_process');
const child = execFile('fc-match', [':lang=ja'], (error, stdout, stderr) => {
  if (error) {
    throw error;
  }
  console.log(stdout);
});

こちらの出力結果を見てみると、「Droid Sans Fallback」と出てきました。

ドンピシャです。このフォント名で調べて、Puppeteerが撮ったスクショと比較してみると完全に一致していました。

どうやらこのフォントがデフォルトとして設定されているので、変になってしまっていたようです。

 

フォントを指定してスクショを撮る

ようやく原因が分かったので、あとは対応するだけです。

色々調べてみると、多くの人はフォントファイルやfontconfigファイルをCloud Functionsにソースと一緒にアップしているみたいですね。その後、ソース上でPuppeteerを走らせる前に「fc-cache」を実行し、フォントのデフォルト設定を変更しているみたいです。

これはPuppeteerでファイルやPDFを生成する際には必要な処理かもしれませんが、私はスクショのみが対応できれば十分だったので、少し強引なやり方を取りました。

実はPuppeteerはスクショを撮る前に、対象のページに変更を加えることができます。

例えば、任意のJSを埋め込んで処理が完了したらスクショを撮ったりすることができます。

私はこの機能を流用し、スクショを撮る前にフォントを上書きするCSSを埋め込みました。

const puppeteer = require('puppeteer');

(async () => {
  const browser = await puppeteer.launch({args: ['--lang=ja']});
  const page = await browser.newPage();
  await page.goto('https://example.com');
  // これ
  await page.addStyleTag({content: `body {font-family: "IPA Pゴシック","IPA PGothic" !important;}`});

  await page.screenshot({ path: 'example.png' });

  await browser.close();
})();

上記ではスクショを撮る前に「addStyleTag」を挟んでおり、ここでフォントを無理やりゴシックに向けています。

この状態でPuppeteerを実行すると、スクショを取りに行ったページはフォントをゴシックとされているので、Cloud Functionsもデフォルト設定されている「Droid Sans Fallback」ではなく「IPAGothic」を採用してくれます。


以上がPuppeteerでスクショ撮るときに日本語フォントを適用させる方法です。

少々強引なやり方かつスクショの時しか使えないので、ご注意頂ければと思います。

また、どのようなページでもゴシックと固定してしまっているので、特殊なフォントを使っているサイトがスクショの対象となった場合、実際の見た目とは異なってしまう点も気をつけて頂ければと思います。

ただこれに関してはフォントの名前を外部からパラメータとして渡してあげれば対処はできるかもしれませんね。