PIL 1.1.7 で日本語縦書き描画を頑張る

# 要約

- Google App Engine の Standard Environment (Python2.7) 上のシステムで日本語縦書きテキストの画像を生成したかった。
  - GAE 上では PIL 1.1.7 しか使えないが、縦書きサポートをしていない。
- 頑張った結果、それなりの縦書き画像を生成できた。

# PIL と GAE の制限
Python で画像操作をするライブラリとして老舗なのが PIL (Python Imaging Library) です。しかし、2009年にリリースされた 1.1.7 で開発が止まっており、現在は PIL から fork した Pillow を使うケースが多いかと思います。

しかし、GAE の Standard Environment (SE) では、古い PIL 1.1.7 しかサポートしていません。この PIL 1.1.7 では、TrueType で描画は可能なものの、縦書きのサポートがありません。

SE では pure python のライブラリはユーザが追加することができますが、ネイティブコードを含むライブラリは[公式に提供されている物](https://cloud.google.com/appengine/docs/standard/python/tools/built-in-libraries-27)しか使えないという制約があります。（その代わりに、インスタンスの高速起動という大きなメリットを享受できます）

Flexible Environment を使えば、このあたりの制約もなくなるのですが、なんのために GAE を使っているのか分からなくなってきますので、できれば SE ですませたい、というのが、この記事の主題です。

# １文字ずつ縦に並べてみる
とりあえず、1文字ずつ縦に並べて書いてみます。
プロポーショナルなレイアウトは諦めて、四角い等幅フォントを使うことで妥協しましょう。フォントはSourceHanSerifのttf版を[こちら](https://github.com/Pal3love/Source-Han-TrueType)からDLしてきました。

```python
# coding: utf-8
from PIL import Image, ImageFont, ImageDraw

FONT_SIZE = 60
LINE_HEIGHT = 80
BASE_LINE_OFFSET = -16

def draw_text_tate(image, font, coord, size, text):
    draw = ImageDraw.Draw(image)

    sx, sy = coord
    nx, ny = size
    ix, iy = 0, 0
    for c in text:
        if c == u"\n":
            ix += 1
            iy = 0
            continue

        if iy >= ny:
            ix += 1
            iy = 0
        if ix >= nx:
            # 表示が溢れた
            return False

        x = sx - ix * LINE_HEIGHT - FONT_SIZE
        y = sy + iy * FONT_SIZE

        char_width, char_height = font.getsize(c)
        x += (FONT_SIZE - char_width) / 2
        y += BASE_LINE_OFFSET

        draw.text((x, y), c, font=font)

        iy += 1

    return True

image = Image.new('L', (800, 600), 'white')
font = ImageFont.truetype('SourceHanSerifJP-ExtraLight.ttf', FONT_SIZE)
draw_text_tate(image, font, (780, 20), (9, 9), u"「けれどもほんとうのさいわいは一体何だろう。」ジョバンニが云いました。\n「僕わからない。」カムパネルラがぼんやり云いました。")
image.show()
```

## 描画結果
![「けれどもほんとうのさいわいは一体何だろう。」ジョバンニが云いました。「僕わからない。」カムパネルラがぼんやり云いました。](https://qiita-image-store.s3.amazonaws.com/0/151254/22131464-b455-a2bb-8e85-9b3545eb6057.png)

想像通りの結果です。。。

# 縦書き用のフォントを使いたい
普段、何気なく、Word などで縦書きの文章を出力していますが、どういう仕組みなのでしょう。

https://lists.w3.org/Archives/Public/public-html-ig-jp/2011Oct/0005.html
で議論されている内容をチラ見すると、どうやら TrueType ファイルの中に、縦書き用のグリフが含まれているようです。
（実際に自分で取り出す実装をしたい場合は[こちらの記事](http://d.hatena.ne.jp/project_the_tower2/20100509/1273370298)がリファレンスも豊富でとても参考になりそうです）

ただ、この "vert" という feature に PIL 1.1.7 は対応していません。困った。。。

ここで閃きます。もし、最初から縦書きのグリフしか入っていないフォントを作れたら、問答無用で縦書き用の出力ができるのでは！

# 縦書き専用フォントの作成
武蔵システムの[サブセットフォントメーカー](https://opentype.jp/subsetfontmk.htm)を使います。

使い方は簡単。適当なフォントを入力に入れ、文字組方向に「縦書き」を指定して出力するだけです。

ただ、このツールは本来は Web Font などで日本語フォントを利用する際に、ファイルサイズを小さくするために文字セットを絞るツールです。今回は文字セットを絞りたいわけではないのですが、文字集合は何かを指定する必要があります。

私は[こちら](http://d.hatena.ne.jp/nakamura001/20120913/1347553798)で公開されていた「[Shift JIS に含まれる文字の一覧](https://raw.githubusercontent.com/nakamura001/JIS_CharacterSet/master/SHIFTJIS_custom/SHIFTJIS_custom_win_bom_utf8.txt)」を使わせていただきました。丸付き数字などが含まれないとのことですので、必要に応じて適切な文字集合を追加してください。

# 縦書き専用フォントで出力してみた
![「けれどもほんとうのさいわいは一体何だろう。」ジョバンニが云いました。「僕わからない。」カムパネルラがぼんやり云いました。](https://qiita-image-store.s3.amazonaws.com/0/151254/6e52799e-6ad4-388b-1f23-c544d4e8a16c.png)

約物を詰めたい気持ちに駆られたりしますが、ザックリ作る範囲ではこんな物で良いでしょう。

あとは、禁則対応をしたり、どうしても気になるところを文字毎に微調整するコードを書いたりすることで、クォリティはさらに高まるかと思われます。

# おまけ: 最新の Pillow を使えば縦書きできるの？
柔軟なフォントレイアウトをサポートする [libraqm](https://github.com/HOST-Oman/libraqm) に対応した Pillow 4.2.0 以降では、おそらくもう少しましな対応があるはずなんですが、[ダメかも？](https://github.com/python-pillow/Pillow/issues/2656)

libraqm をインストールするのが意外と面倒で試せていないのですが、 以下のコードでどうなるのか試してみたいところ。features=['vert']だけでも動いてくれると、上記の縦書き用フォントを別に書き出す作業が不要になります。

```python
from PIL import Image, ImageDraw, ImageFont
image = Image.new('L', (600, 600), 'white')
draw = ImageDraw.Draw(image)
font = ImageFont.truetype('何かの日本語フォント.ttf', size=40, layout_engine=ImageFont.LAYOUT_RAQM)
draw.text((280, 0), u'「キャッシュディスペンサー。」', font=font, direction='ttb', features=['vert'])
image.show()
```