LoginSignup
23
16

More than 5 years have passed since last update.

ImageMagickで全世界のテキストを文字化けせずに表示する方法

Last updated at Posted at 2018-08-07

概要

全世界向けに提供しているサービスで、サーバーでユーザーが指定した文字列を画像化する必要がありました。
ImageMagickで行おうと思ったら、3時間はまりました。
これを読むと、人生の時間を3時間節約できます。

結論

Unifontを使ってください。

コマンド例1 (Ubuntuでapt-get installをする場合)

# Unifontをインストール 
sudo apt-get install -y unifont

# unifont.ttfの場所を確認
fc-match --format=%{file} unifont

# input.pngにテキストを描画し、output.pngとして保存
convert input.png -gravity southeast -font $(fc-match --format=%{file} unifont) -draw "text 0,0 'うんこABC'" output.png

コマンド例2 (直にダウンロードする場合)

# Unifontをダウンロード 
wget http://unifoundry.com/pub/unifont/unifont-11.0.01/font-builds/unifont-11.0.01.ttf

# input.pngにテキストを描画し、output.pngとして保存
convert input.png -gravity southeast -font /path/to/unifont-11.0.01.ttf -draw "text 0,0 'うんこABC'" output.png

経緯

日本語が表示されない

MiniMagickで以下のようなコードを実行しようとしたら、日本語が表示されなかった。

MiniMagick::Tool::Convert.new do |image|
  image << input_path
  image.gravity 'southeast'
  image.draw "text 0,0 'うんこABC'"
  image << output_path
end

順に以下の項目を確認した。

  • 送っている文字列の文字コードは問題ないか?
  • policy.xmlが問題ないか?
  • フォントが問題ないか? (<- 結局これだった)

送っている文字コードはUTF8で問題なかった

二つの方法で確認した。
一つ目はネットで動作報告のあるコードと比較する方法。
MiniMagickで日本語が表示されないと見比べたが、問題なさそう。

二つ目は、ファイル経由で読み込ませる方法。
そのために、label機能を使う必要があった。
以下のようなコードになる。

MiniMagick::Tool::Convert.new do |image|
  image << input_path
  image.gravity 'southeast'
  File.write('test.txt', 'うんこABC')
  image.label '@test.txt'
  image << output_path
end

実行したら、以下のようなエラーが出た。次項へ続く。

convert.im6: not authorized `@test.txt' @ error/property.c/InterpretImageProperties/3057.

policy.xmlとセキュリティホール

前項のエラーをぐぐったら、ImageMagickのセキュリティホールをふさぐためのものらしい
たしかに、今回のユースケースのように、ユーザーの入力を複雑なロジックで処理するようなケースは、セキュリティホールが生まれやすいことに気付く。

※ ちゃんと守りたいならapparmorを使えば良い。以前、ユーザーが任意のネイティブコードを実行できるようなサービスを運営したときに使っていた。

ぐぐって、/etc/ImageMagick/policy.xmlの以下の行をコメントアウトする。

<policy domain="path" rights="none" pattern="@*" />

念のために/etc/ImageMagick/policy.xml全体も記載。

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE policymap [
<!ELEMENT policymap (policy)+>
<!ELEMENT policy (#PCDATA)>
<!ATTLIST policy domain (delegate|coder|filter|path|resource) #IMPLIED>
<!ATTLIST policy name CDATA #IMPLIED>
<!ATTLIST policy rights CDATA #IMPLIED>
<!ATTLIST policy pattern CDATA #IMPLIED>
<!ATTLIST policy value CDATA #IMPLIED>
]>
<!--
  Configure ImageMagick policies.

  Domains include system, delegate, coder, filter, path, or resource.

  Rights include none, read, write, and execute.  Use | to combine them,
  for example: "read | write" to permit read from, or write to, a path.

  Use a glob expression as a pattern.

  Suppose we do not want users to process MPEG video images:

    <policy domain="delegate" rights="none" pattern="mpeg:decode" />

  Here we do not want users reading images from HTTP:

    <policy domain="coder" rights="none" pattern="HTTP" />

  Lets prevent users from executing any image filters:

    <policy domain="filter" rights="none" pattern="*" />

  The /repository file system is restricted to read only.  We use a glob
  expression to match all paths that start with /repository:

    <policy domain="path" rights="read" pattern="/repository/*" />

  Any large image is cached to disk rather than memory:

    <policy domain="resource" name="area" value="1GB"/>

  Define arguments for the memory, map, area, and disk resources with
  SI prefixes (.e.g 100MB).  In addition, resource policies are maximums for
  each instance of ImageMagick (e.g. policy memory limit 1GB, -limit 2GB
  exceeds policy maximum so memory limit is 1GB).
-->
<policymap>
  <!-- <policy domain="system" name="precision" value="6"/> -->
  <!-- <policy domain="resource" name="temporary-path" value="/tmp"/> -->
  <!-- <policy domain="resource" name="memory" value="2GiB"/> -->
  <!-- <policy domain="resource" name="map" value="4GiB"/> -->
  <!-- <policy domain="resource" name="area" value="1GB"/> -->
  <!-- <policy domain="resource" name="disk" value="16EB"/> -->
  <!-- <policy domain="resource" name="file" value="768"/> -->
  <!-- <policy domain="resource" name="thread" value="4"/> -->
  <!-- <policy domain="resource" name="throttle" value="0"/> -->
  <!-- <policy domain="resource" name="time" value="3600"/> -->
  <policy domain="coder" rights="none" pattern="EPHEMERAL" />
  <policy domain="coder" rights="none" pattern="URL" />
  <policy domain="coder" rights="none" pattern="HTTPS" />
  <policy domain="coder" rights="none" pattern="MVG" />
  <policy domain="coder" rights="none" pattern="MSL" />
  <policy domain="coder" rights="none" pattern="TEXT" />
  <policy domain="coder" rights="none" pattern="SHOW" />
  <policy domain="coder" rights="none" pattern="WIN" />
  <policy domain="coder" rights="none" pattern="PLT" />
  <!-- <policy domain="path" rights="none" pattern="@*" /> -->
</policymap>

これで実行してみたら、エラーは出ないが、なぜか普通のアルファベットとかも描画されなくなってしまった。理由はわからないが、一旦この方針はPEND。

フォントの問題を疑う

フォントが日本語に対応していない可能性を探る。
そのために日本語に対応しているフォントを探す。

全世界の文字に対応しているフォント NotoFontを見つける

googleがフォントを提供していた記憶から、全世界の文字に対応しているらしいNotoFontにたどりつく。

が、俺氏、ここでディスク容量をケチろうとするあまり、全世界の文字に対応していないほうのフォントをダウンロードしてしまうorz
結果、小一時間ハマる。

ややこしいのが、全世界の文字に対応していないフォントが582言語に対応していること。そこに日本語は含まれていない。Javaneseは含まれている。

全世界の文字に対応していないフォント(16MB, 582言語): https://noto-website-2.storage.googleapis.com/pkgs/NotoSans-hinted.zip
全世界の文字に対応しているフォント(1.1GB): https://noto-website-2.storage.googleapis.com/pkgs/Noto-hinted.zip

ImageMagickは一つのフォントファイルにしか対応していないことに気付く

NotoFontの構造を見ていたら、言語ごとにわかれた複数のフォントファイルで構成されていることに気付く。まあ、このへんはだれでも気付くわな。
ImageMagickはこれらをどう使い分けるのだろうか?

適当にfont fallbackとかでぐぐってみたら、フォントが対応していない文字に遭遇した場合に、別のフォントを探す仕組みがあるらしい

ImageMagickがfont fallbackに対応しているかぐぐった。

I don't think you can specify automatic fallback fonts for "label:", You would have to find if the one(s) you want are available, with "-list font".
I was worried that might be the case... lol...
http://www.imagemagick.org/discourse-server/viewtopic.php?t=29928

However, when I try to use the font family (both listed by identify -list font and listed in type.xml) I get the default system font:
https://stackoverflow.com/questions/10577197/specifying-a-font-with-the-font-family-using-imagemagick

注意:要求されたフォントが見つからなかった場合、ImageMagickはArialやTimesなどのデフォルトフォントにそっと置き換えてきました。まだそうなってはいますが、最近は警告が表示されます。
https://qiita.com/mtakizawa/items/a74bd91f7b3835976461

対応していないみたいだ。

これの何がまずいか?
例えば、ImageMagickで日本語フォントを指定したところに、日本語とアラビア語と中国語が混じったテキストがやってくると、アラビア語と中国語が化けてしまう。

もう少しぐぐって、fallback fontに行き着く。

Thanks all! I ended up using the TTF version of GNU Unifont - http://aur.archlinux.org/packages.php?ID=33588
https://bbs.archlinux.org/viewtopic.php?id=112428

これが欲しかったものだ。

Unifontを使う

fallback fontの一つ、Unifontを使ったら、無事に日本語を表示できた。多分、他の言語も表示できるだろう。

まとめ

だれかが二度同じ罠にはまらないことを、せつに願う。

23
16
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
23
16