Edited at

Lambdaの実行環境にフォントを追加する


はじめに

AWS LambdaでPhantomJS日本語フォント対応では fontconfig をビルドしてデプロイパッケージに含めているが、 Lambdaの実行環境を確認したところ、fontconfig は導入されている。

したがって、フォントキャッシュさえ生成すれば、実行環境のfontconfigが利用できる。

なおLambdaの実行環境のfontconfigはXDGには対応していない。

検証時、fc-cache のバージョンは 2.8.0 であった。

Lambda の実行時 LAMBDA_TASK_ROOT=/var/task/share/fonts となっている。

fontconfig は

~/.fonts.conf

~/.fonts
~/.fontconfig

は見るので、HOME=$LAMBDA_TASK_ROOT に設定して、これらが

$HOME/

.fontconfig
.fonts
.fonts.conf

というツリー構造で見えるようにデプロイパッケージに含めてやれば良い。


フォントキャッシュの生成

NotoSansCJK のパッケージから NotoSansCJK-Regular.ttc を抽出して、.fonts に配置する。

Lambda の実行時に $LAMBDA_TASK_ROOT/.fontconfig は書き込みできないため、そのままでは fc-cache の実行でキャッシュファイルの作成に失敗する。

いったん /tmp/cache/fontconfig にキャッシュを生成して、デプロイパッケージでは .fontconfig に配置する。

このため、次の内容で .fonts.conf を作成する。


fonts.conf

<fontconfig>

<cachedir>/tmp/cache/fontconfig</cachedir>
</fontconfig>

フォントキャッシュを生成する Lambda 関数は次の通り。


fontcache.py

from __future__ import print_function

import subprocess

import sys
import os
import base64
import boto3
import logging
from os.path import join

logger = logging.getLogger()
logger.setLevel(logging.INFO)

BUCKET_NAME = 'バケット名'

def lambda_handler(event, context):
os.environ['HOME'] = os.environ['LAMBDA_TASK_ROOT']
try:
os.makedirs('/tmp/cache/fontconfig')
except OSError as e:
(errno, strerror) = e
print('OSError {}'.format(strerror))

args = [ 'fc-cache', '-v', join(os.environ['HOME'], '.fonts') ]
p = subprocess.Popen(args, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
returncode = p.returncode
stdout_data, stderr_data = p.communicate()

print('stdout_data:\n' + stdout_data.decode('utf-8'))
print('stderr_data:\n' + stderr_data.decode('utf-8'))
s3bucket = boto3.resource('s3').Bucket(BUCKET_NAME)
tmp_fontconfig = '/tmp/cache/fontconfig'
for cache in os.listdir(tmp_fontconfig):
response = s3bucket.upload_file(join(tmp_fontconfig, cache), cache)
with open(join(tmp_fontconfig, cache), mode='rb') as f:
print("{}:\n{}\n".format(cache, base64.b64encode(f.read())))


デプロイパッケージの内容

% unzip -l fontcache.zip 

Archive: fontcache.zip
Length Date Time Name
-------- ---- ---- ----
1116 10-16-17 15:34 fontcache.py
105 10-12-17 20:54 .fonts.conf
0 10-12-17 22:01 .fonts/
18748872 10-12-17 17:04 .fonts/NotoSansCJK-Regular.ttc
-------- -------
18750093 4 files

この Lambda 関数を実行すると、S3 にキャッシュファイルがアップロードされる。


テスト

先ほど生成したキャッシュファイルを .fontconfig に配置する。

phantomjs のロードモジュールと rasterize.jsbin ディレクトリに配置する。

phantomjs でのスクリーンキャプチャーは、Screen Capture | PhantomJSを参考に、 rasterize.js 使用してテストプログラムを作成した。


phantomjs.py

from __future__ import print_function

import subprocess
import tempfile
import os
import boto3

def phantomjs(url, bucket, name):
from os.path import join
run_dir = os.environ['LAMBDA_TASK_ROOT']
bin_dir = join(run_dir, 'bin')
args = [ join(bin_dir, 'phantomjs'), join(bin_dir, 'rasterize.js'), url ]

with tempfile.NamedTemporaryFile(suffix='.png') as f:
args.append(f.name)
print('{}\n'.format(' '.join(args)))
p = subprocess.Popen(args, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
returncode = p.returncode
stdout_data, stderr_data = p.communicate()

s3 = boto3.resource('s3')
bucket = s3.Bucket(bucket)
response = bucket.upload_file(f.name, name)

print('returncode {}\n'.format(returncode))
print('stdout:: \n{}\n'.format(stdout_data.decode('utf-8')))
print('stderr:: \n{}\n'.format(stderr_data.decode('utf-8')))
print('stderr:: \n{}\n'.format(stderr_data.decode('utf-8')))
print('response {}\n'.format(response))

def lambda_handler(event, context):
os.environ['HOME'] = os.environ['LAMBDA_TASK_ROOT']
os.chdir(os.environ['LAMBDA_TASK_ROOT'])
phantomjs('https://www.amazon.co.jp', 'バケット名', 'screenshot.png')


デプロイパッケージの内容

% unzip -l phantomjs.zip 

Archive: phantomjs.zip
Length Date Time Name
-------- ---- ---- ----
1304 10-13-17 17:09 phantomjs.py
105 10-12-17 20:54 .fonts.conf
0 10-12-17 22:01 .fonts/
18748872 10-12-17 17:04 .fonts/NotoSansCJK-Regular.ttc
0 10-12-17 23:05 .fontconfig/
12792 10-12-17 22:27 .fontconfig/996789b4ba9d471a5fd80c008c2b2acf-le64.cache-3
67932064 01-25-16 10:01 bin/phantomjs
2241 10-12-17 23:19 bin/rasterize.js
-------- -------
86697378 8 files

この Lambda 関数を実行して、うまく画面がキャプチャーされていればよい。

phantomjs の実行のために、Lambda が使用するメモリを192MB以上にしておかないと、メモリ不足で処理が途中で打ち切られる。