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

# はじめに

[AWS LambdaでPhantomJS日本語フォント対応](https://www.rco.recruit.co.jp/career/engineer/blog/34/)では 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.js` は `bin` ディレクトリに配置する。

phantomjs でのスクリーンキャプチャーは、[Screen Capture | PhantomJS](http://phantomjs.org/screen-capture.html)を参考に、 [rasterize.js](https://github.com/ariya/phantomjs/blob/master/examples/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以上にしておかないと、メモリ不足で処理が途中で打ち切られる。