现象

公司阿里云服务器部署Rails程序时,发现Gem simple_captcha2生成的验证码图片,和以前的不太一样。图片中的数字会比正常情况下靠上很多, 影响用户体验。

正常情况下的图片:
fonts_normal.jpeg

有问题的图片:
crooked_image.jpeg

原因

Aliyun Linux 2(也包含最新的CentOS 7)中升级了ImageMagick的版本到ImageMagick 6.9.10-68, 字体配置文件/etc/ImageMagick-6/type.xml中默认改用了urw-base35-fonts,而不是之前的ghostscript-fonts

解决方法

安装并修改ImageMagick使用旧字体。

1
2
3
4
5
6
7
8
9
# 安装urw-base35-fonts-legacy, fonts在/usr/share/X11/fonts/urw-fonts目录下
yum install urw-base35-fonts-legacy
# 修改/etc/ImageMagick-6/type.xml配置文件为使用type-ghostscript.xml
sed -i.bak 's,type-urw-base35.xml,type-ghostscript.xml,g' /etc/ImageMagick-6/type.xml
# 修改 /etc/ImageMagick-6/type-ghostscript.xml为/usr/share/X11/fonts/urw-fonts/
sed -i.bak 's,metrics=",metrics="/usr/share/X11/fonts/urw-fonts/,g' /etc/ImageMagick-6/type-ghostscript.xml
sed -i 's,glyphs=",glyphs="/usr/share/X11/fonts/urw-fonts/,g' /etc/ImageMagick-6/type-ghostscript.xml

调查过程

查找生成图片的命令

首先翻simple_captcha2的源码, 看Gem是怎么生成图片的。后来定位到是lib/simple_captcha/image.rb文件中的generate_simple_captcha_image方法调用了ImageMagick的convert命令来生成图片。
命令形式类似如下:

1
convert -background '#F5F7FA' -fill '#409EFF' -border 0 -bordercolor 'none' -size 80x30 -wave 1x89 -gravity Center -pointsize 22 -implode 0.2 label:1441 jpeg:-

在Aliyun Linux 2上,简单试验了下,使用yum源安装的ImageMagick,使用如下官方Label的最简单的用法来生成图片。

1
convert -background lightblue -fill blue -pointsize 72 label:Anthony label.gif

可生成的图片的高度size只有1, 明显存在问题。
label.gif

编译源码版本

看了下Aliyun Linux 2中通过yum安装的ImageMagick版本是ImageMagick 6.9.10-68 Q16 x86_64, 和经常使用的版本ImageMagick 6.7.8-9 2016-03-31不相同。
猜想可能是ImageMagick版本变化导致了图片的位置不对。

尝试使用源码来进行安装6.7.8版本, 在帖子Old releases source code中找到了官方的源码目录,但已经没有6.7.8-9的源码提供了,因此下载了6.7.8-10的源码, 按照官网Install from Unix Source的说明进行编译安装。

1
2
3
4
5
6
wget 'https://www.imagemagick.org/download/releases/ImageMagick-6.7.8-10.tar.xz'
tar xJvf ImageMagick-6.7.8-10.tar.xz
cd ImageMagick-6.7.8-10
./configure
make
make install

尝试使用该版本的convert生成图片。

1
2
3
4
5
6
7
8
9
10
11
$ /usr/local/bin/convert --version
Version: ImageMagick 6.7.8-10 2020-05-06 Q16 http://www.imagemagick.org
Copyright: Copyright (C) 1999-2012 ImageMagick Studio LLC
Features: OpenMP
$
$ /usr/local/bin/convert -background '#F5F7FA' -fill '#409EFF' -border 0 -bordercolor 'none' -size 80x30 -wave 1x89 -gravity Center -pointsize 22 -implode 0.2 label:1441 jpeg:- > compiled.jpeg
convert: unable to read font `/usr/share/ghostscript/fonts/n019003l.pfb' @ error/annotate.c/RenderFreetype/1124.
convert: unable to read font `/usr/share/ghostscript/fonts/n019003l.pfb' @ error/annotate.c/RenderFreetype/1124.
convert: unable to read font `/usr/share/ghostscript/fonts/n019003l.pfb' @ error/annotate.c/RenderFreetype/1124.
$

会有找不到font的报错, 但是能生成图片,并且图片中的数字没有出现上移情况。
compiled_6.7.9_10.jpeg

寻找fonts问题

根据font的报错信息,调查了ImageMagick的fonts配置。
yum安装的ImageMagick的配置文件目录为/etc/ImageMagick-6/, 自编译的ImageMagick的配置文件目录为/usr/local/etc/ImageMagick/
尝试着让自编译版本的ImageMagick使用yum安装的字体配置type-urw-base35.xml。

我把yum安装的ImageMagick的字体配置type-urw-base35.xml拷贝到了自编译的配置目录下/usr/local/etc/ImageMagick/, 再修改type.xml为使用type-urw-base35.xml

1
2
cp /etc/ImageMagick-6/type-urw-base35.xml /usr/local/etc/ImageMagick/
sed -i.bak 's,type-ghostscript.xml,type-urw-base35.xml,g' /usr/local/etc/ImageMagick/type.xml

然后尝试执行自编译的6.7.8_10版本的convert来生成验证码图片。

1
/usr/local/bin/convert -background '#F5F7FA' -fill '#409EFF' -border 0 -bordercolor 'none' -size 80x30 -wave 1x89 -gravity Center -pointsize 22 -implode 0.2 label:1441 jpeg:- > fonts_compiled_with_urw-base35.jpeg

本以为这事就可以这么搞定了。结果却发现,命令的错误信息是没了,但是生成的图片上的数字居然又是偏上的。
compiled_with_urw-base35

这说明有问题的可能不是ImageMagick命令,而是字体问题。

验证fonts问题

为了验证是不是font问题,我先是尝试修改yum的ImageMagick使用自带的type-ghostscript.xml配置文件。

1
sed -i.bak 's,type-urw-base35.xml,type-ghostscript.xml,g' /etc/ImageMagick-6/type.xml

可运行时就直接报错了。

1
2
3
4
$ /usr/bin/convert -background '#F5F7FA' -fill '#409EFF' -border 0 -bordercolor 'none' -size 80x30 -wave 1x89 -gravity Center -pointsize 22 -implode 0.2 label:1441 jpeg:- > yum_with_type-ghostscript.jpeg
convert: unable to read font `helvetica' @ error/annotate.c/RenderFreetype/1336.
convert: no images defined `jpeg:-' @ error/convert.c/ConvertImageCommand/3235.
$

失败后,再尝试让yum的ImageMagick使用编译版本的type-ghostscript.xml配置文件,

1
cp /usr/local/etc/ImageMagick/type-ghostscript.xml /etc/ImageMagick-6/

可运行后还是报错。

1
2
3
4
$ /usr/bin/convert -background '#F5F7FA' -fill '#409EFF' -border 0 -bordercolor 'none' -size 80x30 -wave 1x89 -gravity Center -pointsize 22 -implode 0.2 label:1441 jpeg:- > yum_with_compiled_type-ghostscript.jpeg
convert: unable to read font `helvetica' @ error/annotate.c/RenderFreetype/1336.
convert: no images defined `jpeg:-' @ error/convert.c/ConvertImageCommand/3235.
$

一通操作后,得了一堆的错,不得不来挖一下ImageMagick是怎么使用系统的fonts了。

深挖fonts问题

在RHEL 7.7之后,使用了urw-base35-fonts代替了原先默认的urw-fonts, 期间导致了一些软件出现了些问题。

在7.7之后,默认的yum源中有三组相关的font

  • urw-fonts
  • urw-base35-fonts
  • urw-base35-fonts-legacy

其中urw-fontsurw-base35-fonts废弃替换了。但可以通过手动下载手动安装的方式来进行安装。

1
rpm -ivh --nodeps urw-fonts-2.4-16.1.al7.noarch.rpm

安装后,三个的目录分别为:

  • /usr/share/fonts/default/Type1
  • /usr/share/fonts/urw-base35
  • /usr/share/X11/fonts/urw-fonts

yum源中的ImageMagick后续应该是解决了使用urw-base35-fonts的问题, 因为字体配置文件/etc/ImageMagick-6/type.xml中默认是使用了type-urw-base35.xml。但不知为啥,ImageMagick使用字体urw-base35-fonts,用label生成图片的时候,存在着文字上移的问题。

试验下来,最终解决方案如下:

  1. 使用yum install ImageMagick安装ImageMagick
  2. 再安装urw-base35-fonts-legacy字体,
  3. 最后修改ImageMagick的配置文件来使用legacy的字体,最终解决了该问题。
1
2
3
4
5
6
7
8
9
# 安装urw-base35-fonts-legacy, fonts在/usr/share/X11/fonts/urw-fonts目录下
yum install urw-base35-fonts-legacy
# 修改/etc/ImageMagick-6/type.xml配置文件为使用type-ghostscript.xml
sed -i.bak 's,type-urw-base35.xml,type-ghostscript.xml,g' /etc/ImageMagick-6/type.xml
# 修改 /etc/ImageMagick-6/type-ghostscript.xml为/usr/share/X11/fonts/urw-fonts/
sed -i.bak 's,metrics=",metrics="/usr/share/X11/fonts/urw-fonts/,g' /etc/ImageMagick-6/type-ghostscript.xml
sed -i 's,glyphs=",glyphs="/usr/share/X11/fonts/urw-fonts/,g' /etc/ImageMagick-6/type-ghostscript.xml

额外延伸

ImageMagick的font配置文件

ImageMagick使用配置目录下的type.xml来控制使用哪个fonts

  • yum原生安装的ImageMagick,配置文件目录:/etc/ImageMagick-6/type.xml/etc/ImageMagick/type.xml
  • 源码编译的ImageMagick, 默认配置文件目录: /usr/local/etc/ImageMagick/type.xml

可使用如下命令列出配置中支持的fonts。

1
2
convert -list type # for IM older than v6.3.5-7
convert -list font # for newer versions

ImageMagick使用urw-fonts字体

下载yum中的rpm包

1
2
3
4
5
6
7
8
# yum install yum-utils
# yumdownloader --urls urw-fonts
Loaded plugins: fastestmirror
Loading mirror speeds from cached hostfile
http://mirrors.cloud.aliyuncs.com/alinux/2.1903/os/x86_64/Packages/urw-fonts-2.4-16.1.al7.noarch.rpm
#
# wget 'http://mirrors.cloud.aliyuncs.com/alinux/2.1903/os/x86_64/Packages/urw-fonts-2.4-16.1.al7.noarch.rpm'

使用--nodeps安装rpm包

1
# rpm -ivh --nodeps urw-fonts-2.4-16.1.al7.noarch.rpm

删除rpm包命令: rpm -e urw-fonts-2.4-16.1.al7.noarch

配置ImageMagick使用urw-fonts字体

1
2
3
4
5
6
修改 /etc/ImageMagick-6/type.xml
sed -i.bak 's,type-urw-base35.xml,type-ghostscript.xml,g' /etc/ImageMagick-6/type.xml
修改 /etc/ImageMagick-6/type-ghostscript.xml
sed -i.bak 's,metrics=",metrics="/usr/share/fonts/default/Type1/,g' /etc/ImageMagick-6/type-ghostscript.xml
sed -i 's,glyphs=",glyphs="/usr/share/fonts/default/Type1/,g' /etc/ImageMagick-6/type-ghostscript.xml

Reference

留言