前言

在上一篇水文中,我提到了需要实现“docx转pdf”;

经过一番折腾,最终得出了一个结论:PHP可真不愧是一两面撬刀的🔨。

在尝试了使用“phpoffice/phpword”+“dompdf/mpdf”等主流PDF转换库对docx进行转换后,最后得到的效果实在是不尽人意。(错位+乱码)

虽然在Windows平台可通过“.net COM组件”这种外挂方式实现转换,但这种方法不具备跨平台特性,不方便部署,同时也不方便在日后的项目复用。

最后得到的解决方案则是通过PHP调用Python unoconv工具调用LibreOffice实现。(究极套娃)

调用unoconv

实现调用unoconv进行转换只需要简单两行代码。

/**
 * @param string $unoconv unoconv执行文件位置
 * @param string $uno_path LibreOffice执行文件所在目录
 * @param string $src 待转换的源文件路径
 * @param string|null $dest 转换文件的路径
 * @param string $format 转换的文件格式,需要LibreOffice支持
 */
function unoconv($unoconv, $uno_path, $src, $dest = null, $format = 'pdf')
{
    putenv('UNO_PATH=' . $uno_path);
    shell_exec(sprintf('%s -f %s "%s" %s', $unoconv, $format, $src, empty($dest) ? '' : '-o ' . $dest));
}

以下是通过“phpoffice/phpword”实现数据注入模版并转换为pdf的业务代码。(截选部分)

header('Content-Type: application/pdf');
header(sprintf('Content-Disposition: attachment;filename="%s"', $cert_name));
header('Cache-Control: max-age=0');

$cert_path = WEB_ROOT . 'upload/' . $cert_path;

$template = new TemplateProcessor($cert_path);
$template->setValues($data);

$src = $template->save();
$ext = @pathinfo($src)['extension'];
if (!empty($ext))
    $dest = str_replace('.' . $ext, '.pdf', $src);
else
    $dest = $src . '.pdf';
unoconv($setting['unoconv'], $setting['uno_path'], $src, $dest);

$io = fopen("php://output", "w");
fwrite($io, readfile($dest));
fclose($io);

部署unoconv环境

  • 安装最新版本LibreOffice

  • 安装最新版本Python(3.+)。

  • 使用pip安装unoconv。

pip install unoconv
  • 配置“UNO_PATH”环境变量
    • /Applications/LibreOffice.app/Contents/MacOS
    • C:\Program Files\LibreOffice\program

这时,一般情况下只要在终端输入unoconv,就可以看到相关帮助信息。

  • 但注意,Windows除外,在Windows里,unoconv并不是以可执行文件的形式存在(因为没有后缀)。

    那么,在Windows环境下,则需要输入python C:\Users\HsOjo\AppData\Local\Programs\Python\Python37\Scripts\unoconv运行unoconv。

到这里,unoconv环境的部署就完成了。

PHP调用前注意

在不同的运行环境下,情况可能有所不同。

通常情况下,PHP无法获取到完整的系统环境变量。(有些环境变量配置是仅限于Shell的)

对于前面方法中的“unoconv执行文件位置”参数,建议提供绝对路径。

后记

发现PHP版unoconv(dbq,是我🔨了)

ThinkPHP5 实现

/**
 * @param string $unoconv unoconv执行文件位置
 * @param string $uno_path LibreOffice执行文件所在目录
 * @param string $src 待转换的源文件路径
 * @param string|null $dest 转换文件的路径
 * @param string $format 转换的文件格式,需要LibreOffice支持
 * @param int $timeout 超时时间
 */
function unoconv($unoconv, $uno_path, $src, $dest = null, $format = 'pdf', $timeout = 10)
{
    $builder = new \think\process\Builder();
    $builder->setEnv('UNO_PATH', $uno_path);
    $builder->setTimeout($timeout);
    $args = [$unoconv, '-f', $format, $src];
    if (!empty($dest)) {
        array_push($args, '-o');
        array_push($args, $dest);
    }
    $builder->setArguments($args);
    $builder->getProcess()->run();
}