返回

wkhtmltopdf 跨平台处理中文、日文等非英文字符文件名

php

wkhtmltopdf 处理跨平台非英文字符文件名问题

在使用 wkhtmltopdf 进行 PDF 生成时,如果传入的参数包含非英文字符(比如中文、日文)文件名,可能会遇到一些问题。我最近就碰到了:在 Ubuntu 服务器上,传入名为 "Japan日本.xml" 的文件,输出的文件却变成了 "Japan.xml",非英文字符部分丢失了。但在我的 Windows 客户端上就没问题。虽然服务器已经安装了必要的字体,并且 locale 设置也支持 UTF-8,问题还是存在。

问题原因分析

这类问题通常由以下几个方面引起:

  1. 操作系统和文件系统的差异: Windows 和 Linux 在处理文件名编码上可能存在差异。Windows 默认可能使用 UTF-16 或本地编码,而 Linux 通常使用 UTF-8。
  2. wkhtmltopdf 的内部处理: wkhtmltopdf 在接收和处理文件名参数时,可能没有正确处理不同编码。
  3. Shell 环境: Shell 在传递参数给 wkhtmltopdf 时,可能进行了编码转换或字符过滤,导致非英文字符丢失。
  4. PHP 的 escapeshellargshell_exec 函数: escapeshellarg 函数可能无法完美处理某些特殊字符或编码。 shell_exec 在执行系统命令时,也依赖于系统的 locale 设置。
  5. 命令行参数空格 : 有时会因为疏忽忘记在参数与其对应值之间加入空格,这会导致命令运行不起来。

解决方案

针对上述可能的原因,可以尝试以下解决方案:

1. 统一编码为 UTF-8

确保所有环节都使用 UTF-8 编码,这是最通用的做法。

  • PHP 文件: 确保 PHP 文件本身以 UTF-8 无 BOM 格式保存。

  • HTML/XML 输入文件: 确保输入文件(比如你的 "Japan日本.xml")也是 UTF-8 编码。

  • 服务器 Locale 设置:

    # 查看当前 locale 设置
    locale
    
    # 设置 locale (如果需要)
    sudo locale-gen zh_CN.UTF-8
    sudo update-locale LANG=zh_CN.UTF-8
    sudo dpkg-reconfigure locales  # 如果上面的方法无效,可以试试这个
    
    # 之后需要重启服务或重新登录让设置生效。
    

    zh_CN.UTF-8替换为你需要的语言环境,比如日语 ja_JP.UTF-8

  • 检查php.ini设置。 确认default_charset = "UTF-8"

  • 数据库:如果内容有来自数据库,要保证数据库使用UTF-8。

2. 使用 escapeshellarg 的替代方案

escapeshellarg 有时不能很好地处理多字节字符。可以尝试直接拼接字符串,但要非常小心防止 shell 注入攻击。更推荐下面的方法:

2.1. 使用 Symfony Process 组件 (推荐)

Symfony Process 组件提供了更安全、更强大的方式来执行外部命令,能更好地处理各种情况。

<?php
require_once 'vendor/autoload.php'; // 如果使用 Composer

use Symfony\Component\Process\Process;

$WKHTMLTOPDF = $wkhtmltopdf_path;

$inputFilePath = '/usr/local/apache2/htdocs/Japan日本.xml';
$outputFilePath = '/path/to/output.pdf';

$process = new Process([
    $WKHTMLTOPDF,
    '--dump-outline',
     $inputFilePath, //Symfony Process 组件可以自动进行正确的参数转义,因此无需再对文件名使用 escapeshellarg 函数。
     $inputFilePath, //这里将输入文件的路径 $inputFilePath 直接传递给了 Process 组件
    $outputFilePath
]);

$process->run();

if (!$process->isSuccessful()) {
    echo 'Error: ' . $process->getErrorOutput();
} else {
    echo $process->getOutput();
}

  • 安装: composer require symfony/process
  • 原理: Process 组件会自动处理参数的转义和引号,避免了 escapeshellarg 的潜在问题。

3. 显式指定 wkhtmltopdf 的编码

尝试使用 --input-encoding--output-encoding 选项明确指定输入和输出编码。

<?php
$WKHTMLTOPDF = $wkhtmltopdf_path;
$_options = [
	'--input-encoding', 'UTF-8',
	'--output-encoding', 'UTF-8',
    '--dump-outline ' . escapeshellarg('/usr/local/apache2/htdocs/Japan日本.xml'),
    $input_file_path,
    $output_file_path
];

$options_string = implode(' ', $_options);
$output = shell_exec('"' . $WKHTMLTOPDF . '" ' . $options_string);

?>

不过这个例子仍然使用了escapeshellarg, 如果结合Symfony Process, 可靠性更高:

<?php
require_once 'vendor/autoload.php';

use Symfony\Component\Process\Process;

$WKHTMLTOPDF = $wkhtmltopdf_path;
$inputFilePath = '/usr/local/apache2/htdocs/Japan日本.xml';
$outputFilePath = '/path/to/output.pdf';

$process = new Process([
    $WKHTMLTOPDF,
    '--input-encoding', 'UTF-8',
	'--output-encoding', 'UTF-8',
    '--dump-outline',
     $inputFilePath,
     $inputFilePath,
    $outputFilePath
]);

$process->run();

if (!$process->isSuccessful()) {
    echo 'Error: ' . $process->getErrorOutput();
}

4. 检查字体安装

虽然你已经确认安装了字体,但可以再次确认 wkhtmltopdf 能否找到它们。有时字体路径配置不正确,会导致 wkhtmltopdf 使用默认字体,而默认字体可能不支持非英文字符。

  • 使用 fc-list 命令列出已安装字体:

    fc-list
    

    查看输出中是否包含你需要的支持中文、日文的字体。

  • 如果字体未正确安装,重新安装或配置字体:

    可以尝试在html里面显式使用font-family 确保使用了支持日语的字体。例如:

     <style>
       body {
         font-family: 'MS Mincho', 'MS Gothic', sans-serif; /* 或其他日语字体 */
       }
     </style>
    

5. 命令行参数空格问题处理方法

养成良好的习惯:参数与值之间永远有空格!

// 不推荐
$options_string = '--output'.$output_file_path;

// 推荐
$options_string = '--output ' . $output_file_path;

或者,在创建包含选项的数组时就包含这些空格:

$_options = [
    '--dump-outline ', // 注意这里最后的空格
    '/usr/local/apache2/htdocs/Japan日本.xml',
    $input_file_path,
    $output_file_path
];

再次推荐使用 Symfony Process组件, 因为Process会自动帮你添加空格:

$process = new Process([
    $WKHTMLTOPDF,
    '--dump-outline', // 这里无需添加额外的空格
     $inputFilePath,
     $inputFilePath,
    $outputFilePath
]);

6. (进阶) Docker 化

如果上述方法仍然无法解决,或者为了获得更一致的运行环境,可以考虑将 wkhtmltopdf 和你的 PHP 应用一起打包到 Docker 容器中。

  • 优势: Docker 可以确保在任何环境中(开发、测试、生产)都使用相同的依赖和配置,避免因环境差异导致的问题。

  • Dockerfile 示例:

    FROM php:7.4-apache # 或者其他你需要的 PHP 版本
    
    # 安装 wkhtmltopdf
    RUN apt-get update && apt-get install -y --no-install-recommends \
        wkhtmltopdf \
        && rm -rf /var/lib/apt/lists/*
    
    # 安装中文字体 (示例,根据需要修改)
    RUN apt-get update && apt-get install -y --no-install-recommends \
        fonts-wqy-zenhei \
        && rm -rf /var/lib/apt/lists/*
    
    # 安装日语字体(示例)
    RUN apt-get update && apt-get install -y --no-install-recommends \
         fonts-takao \
         && rm -rf /var/lib/apt/lists/*
    
    # 设置 locale (示例)
    ENV LANG ja_JP.UTF-8
    ENV LANGUAGE ja_JP:ja
    ENV LC_ALL ja_JP.UTF-8
    
    # 其他配置 (如复制 PHP 代码、安装 Composer 依赖等)
    # ...
    

构建Docker镜像,运行容器,将你的应用运行起来.

通过这些方案的逐一尝试和组合,应该能够解决 wkhtmltopdf 在处理非英文字符文件名时遇到的问题,并提高命令执行的可靠性。使用Symfony Process, 避免使用escapeshellarg 加上对shell_exec更好的错误控制可以大幅度增加脚本的稳定性. Docker 可以消除跨平台的坑。