背景

有时候我们做推广、宣传之类的需要动态生成一些比较炫丽的图片,或者生成一个PDF文件(如:订单、捡货单等)。这些需求对界面及排版格式等要求比较高,如果采取代码直接绘图的方式可以实现起来成本比较高,且后期改动不方便,因此考虑使用HTML页面转换的方式实现。

实现方案

安装 wkhtmltox

wkhtmltox 包括两部分 wkhtmltopdfwkhtmltoimage 分别用于生成PDF文档和图片,安装比较简单,如下:

# 进入用户安装目录
cd /usr/local

# 下载
sudo wget https://github.com/wkhtmltopdf/wkhtmltopdf/releases/download/0.12.4/wkhtmltox-0.12.4_linux-generic-amd64.tar.xz

# 解压
sudo tar -xvf  wkhtmltox-0.12.4_linux-generic-amd64.tar.xz

# 进入bin目录建立软链接
cd /usr/local/bin
ln -s /usr/local/wkhtmltox/bin/wkhtmltoimage
ln -s /usr/local/wkhtmltox/binwkhtmltopdf

使用 wkhtmltox

wkhtmltoimage --help 查看使用说明,更详细的说明可以看这里,比如基于网页生成一张图片,只需要在命令行执行如下操作

sudo wkhtmltoimage --width 1146 /tmp/demo.html /tmp/demo.png

说明:

  • width - 指定图片生成的宽度,长度可以不指定,会自动处理
  • /tmp/demo.html - 网页绝对路径
  • /tmp/demo.png - 生成图片的绝对路径

注意:

  • 使用时如果不能运行该命令可能是缺少依赖 libxrender1,需要自行安装
  • 切记需要把exec命令允许在服务器执行中
  • 如果服务器没有中文字体时,用到中文时可能会出现乱码的情况
  • 如果想提高图片的质量,可以将页面尺寸等比例放大,然后再生成

PHP代码示例

下面以生成日签图如示例,讲解在项目中的使用。

/**
 * 生成日签
 *
 * @param string $title     标题
 * @param string $dailyDate 日期
 * @param string $content   内容
 * @param string $coverUrl  封面图
 * @return string
 */
protected function generateDaily($title, $dailyDate, $content, $coverUrl)
{
    $filePath = base_path('util/temp/daily-generate.html');
    if (! is_file($filePath)) {
        throw new Exception\RuntimeException('日签图模板不存在,请联系管理员');
    }

    // 获取日签图模版
    $dailyTemp = file_get_contents($filePath);

    // 替换模版数据
    $tempData = [
        'var_title'     => $title,
        'var_dailyDate' => $dailyDate,
        'var_content'   => $content,
        'var_coverUrl'  => $coverUrl,
    ];
    $html = str_replace(array_keys($tempData), array_values($tempData), $dailyTemp);

    // 生成临时文件
    $tmpFile = self::TMP_DIR . '/' . str_random() . '.html';
    file_put_contents($tmpFile, $html);

    // 生成日签图
    $dailyFilename = date('Ymd', strtotime($dailyDate)) . time() . '.jpg';
    $daily = storage_path('app/public/daily/' . $dailyFilename);
    exec("/usr/local/bin/wkhtmltoimage --width 1146 {$tmpFile} {$daily}", $output, $return);
    if ($return !== 0 || ! is_array($output)) {
        throw new Exception\InvalidArgumentException('日签图片生成失败');
    }

    // 判断图片是有效
    if (! is_file($daily) || filesize($daily) <= 0) {
        throw new Exception\InvalidArgumentException('日签图片未生成或者无效');
    }

    // 删除临时文件
    unlink($tmpFile);

    return asset("/storage/daily/{$dailyFilename}");
}

效果图如下

拓展知识

安全性的考虑

上面的方式是利于PHPexec来执行服务器端脚本生成的,如果觉得不安全的话,还可以采用编译PHP扩展的方式,具体实现参考PHP扩展文档 wkhtmltox

中文字体支持

# 进入系统字体目录并自定义文件夹
cd /usr/share/fonts
sudo mkdir -p chinese/truetype
cd chinese/truetype

# 将下载好的字体上传到该目录(如:微软雅黑 msyh.ttf)
sudo rz msyh.ttf

# 扫描字体目录并生成字体信息的缓存
sudo fc-cache -fv

# 查看安装是否成功
sudo fc-list

目前同类功能的项目

目前能达到此效果的同类项目有很多,有大型也有小型的,整理如下,感兴趣的话可以探索一下:

  • TCPDF - 可以生成PDF和图片
  • PhantomJs - 多用于爬虫,也可以用于生成网页截图、自动化测试等
  • CasperJs - 与phantomjs类似,是在它基于上做了进一步的封装,使用起来更加顺手
  • Puppeteer - 谷歌主刀的开源爬虫项目,功能更大强大,以至于当此项目出来后PhantomJs项目作者宣布不再维护